diff --git a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties
index a699b80f..2d80d7f9 100644
--- a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties
+++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties
@@ -85,6 +85,105 @@ PARTICLE_SELECT = §eClick to select
PARTICLE_LOCKED = §c§lLocked
PARTICLE_DEACTIVATED = §cThere are too many players online, particles are deactivated
+PARTICLE_BACK = §7Back
+PARTICLE_SLOT_EMPTY = §7Empty slot
+PARTICLE_SLOT_CREATE = §eClick to create
+PARTICLE_SLOT_ENTRIES = §7{0} entries
+PARTICLE_SLOT_ACTIVE = §aActive
+PARTICLE_SLOT_SELECT = §eLeft click§8: §7select
+PARTICLE_SLOT_EDIT = §eRight click§8: §7edit
+PARTICLE_SLOT_VIEW = §eClick§8: §7view
+PARTICLE_SLOT_EDITOR = §0Edit {0}
+PARTICLE_SLOT_VIEWER = §0{0}
+PARTICLE_SLOT_ADD_ENTRY = §aAdd entry
+PARTICLE_SLOT_MAX_ENTRIES = §cThis slot is full
+PARTICLE_SLOT_OVER_BUDGET = §cThat would spawn too many particles for this slot
+PARTICLE_SLOT_RENAME = §eRename
+PARTICLE_SLOT_RENAME_TITLE = Slot name
+PARTICLE_SLOT_ICON = §eChange icon
+PARTICLE_SLOT_ICON_TITLE = §0Slot icon
+PARTICLE_SLOT_DELETE = §cDelete slot
+PARTICLE_SLOT_DELETE_CONFIRM = §7Shift click to delete
+PARTICLE_SLOT_COST = §7Particles per tick§8: §e{0}§8/§7{1}
+
+PARTICLE_ENTRY_EDITOR = §0Edit entry
+PARTICLE_ENTRY_DESIGN = §7Design§8: {0}
+PARTICLE_ENTRY_PARTICLE = §7Particle§8: {0}
+PARTICLE_ENTRY_LOCKED = §cDesign locked, not shown
+PARTICLE_ENTRY_EDIT = §eLeft click§8: §7edit
+PARTICLE_ENTRY_DUPLICATE = §eRight click§8: §7duplicate
+PARTICLE_ENTRY_DELETE = §eShift right click§8: §7delete
+PARTICLE_ENTRY_CHANGE_DESIGN = §eClick to change the design
+PARTICLE_ENTRY_DELETE_ITEM = §cDelete entry
+
+PARTICLE_DESIGN_PICKER = §0Choose design
+PARTICLE_CHOICE_PICKER = §0{0}
+PARTICLE_COLOR_PICKER = §0Choose colour
+
+PARTICLE_VALUE = §7Value§8: {0}
+PARTICLE_VALUE_NUMBER_HELP = §eLeft§8/§eright click§8: §7+/- {0}, §eshift§8: §7x10
+PARTICLE_VALUE_EXACT_HELP = §eQ§8: §7enter value
+PARTICLE_VALUE_TOGGLE_HELP = §eClick§8: §7toggle
+PARTICLE_VALUE_CHOICE_HELP = §eLeft§8/§eright click§8: §7next/previous
+PARTICLE_VALUE_LIST_HELP = §eClick§8: §7choose
+PARTICLE_VALUE_COLORS_HELP = §eLeft click§8: §7add colour, §eright click§8: §7remove last
+PARTICLE_VALUE_RANDOM = §7Random colours
+PARTICLE_VALUE_YES = §aYes
+PARTICLE_VALUE_NO = §cNo
+
+PARTICLE_DESIGN_SIMPLE = §fSimple
+PARTICLE_DESIGN_CIRCLE = §fCircle
+PARTICLE_DESIGN_WING = §fWings
+PARTICLE_DESIGN_CLOUD = §fCloud
+
+PARTICLE_PARAM_PARTICLE = §fParticle
+PARTICLE_PARAM_COUNT = §fAmount
+PARTICLE_PARAM_SPREAD_X = §fSpread X
+PARTICLE_PARAM_SPREAD_Y = §fSpread Y
+PARTICLE_PARAM_SPREAD_Z = §fSpread Z
+PARTICLE_PARAM_SPEED = §fSpeed
+PARTICLE_PARAM_COLORS = §fColours
+PARTICLE_PARAM_OFFSET_X = §fOffset X
+PARTICLE_PARAM_OFFSET_Y = §fOffset Y
+PARTICLE_PARAM_OFFSET_Z = §fOffset Z
+PARTICLE_PARAM_INTERVAL = §fEvery n ticks
+PARTICLE_PARAM_TICK_MODE = §fShown
+PARTICLE_PARAM_MOVEMENT = §fMovement
+PARTICLE_PARAM_GLIDING = §fElytra
+PARTICLE_PARAM_GROUND = §fGround
+PARTICLE_PARAM_SNEAK = §fSneaking
+PARTICLE_PARAM_VISIBILITY = §fVisible to
+PARTICLE_PARAM_RADIUS = §fRadius
+PARTICLE_PARAM_CIRCLE_SPEED = §fRotation speed
+PARTICLE_PARAM_PHASE = §fStart angle
+PARTICLE_PARAM_RISE = §fUp and down height
+PARTICLE_PARAM_RISE_SPEED = §fUp and down speed
+PARTICLE_PARAM_BOUNCE = §fBounce back
+PARTICLE_PARAM_INVERTED = §fStart at the top
+PARTICLE_PARAM_WING_IMAGE = §fWing shape
+PARTICLE_PARAM_WING_SIZE = §fWing size
+
+PARTICLE_CHOICE_TICKMODE_MOVE = §fWhile moving
+PARTICLE_CHOICE_TICKMODE_ALWAYS = §fAlways
+PARTICLE_CHOICE_MOVEMENT_ANY = §fAny movement
+PARTICLE_CHOICE_MOVEMENT_MOVING = §fOnly while moving
+PARTICLE_CHOICE_MOVEMENT_STANDING_STILL = §fOnly while standing still
+PARTICLE_CHOICE_GLIDING_ANY = §fWith or without elytra
+PARTICLE_CHOICE_GLIDING_GLIDING = §fOnly while gliding
+PARTICLE_CHOICE_GLIDING_NOT_GLIDING = §fNot while gliding
+PARTICLE_CHOICE_GROUND_ANY = §fOn ground and in air
+PARTICLE_CHOICE_GROUND_ON_GROUND = §fOnly on ground
+PARTICLE_CHOICE_GROUND_IN_AIR = §fOnly in air
+PARTICLE_CHOICE_SNEAK_ANY = §fSneaking or not
+PARTICLE_CHOICE_SNEAK_SNEAKING = §fOnly while sneaking
+PARTICLE_CHOICE_SNEAK_NOT_SNEAKING = §fNot while sneaking
+PARTICLE_CHOICE_VISIBILITY_EVERYONE = §fEveryone
+PARTICLE_CHOICE_VISIBILITY_ONLY_SELF = §fOnly me
+PARTICLE_CHOICE_VISIBILITY_ONLY_OTHERS = §fOnly others
+PARTICLE_CHOICE_IMAGE_SIMPLE = §fSimple
+PARTICLE_CHOICE_IMAGE_COMPLEX = §fComplex
+PARTICLE_CHOICE_IMAGE_SWORD = §fSword
+
PARTICLE_SNEEZE = §aSneeze
PARTICLE_SMOKE = §7Smoke
PARTICLE_FIRE = §cFire
diff --git a/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties b/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties
index 3931fc26..6c01a373 100644
--- a/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties
+++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties
@@ -84,6 +84,105 @@ PARTICLE_SELECT = §eZum Auswählen klicken
PARTICLE_LOCKED = §c§lGesperrt
PARTICLE_DEACTIVATED = §cEs sind zu viele Spieler online! Partikel sind deaktiviert.
+PARTICLE_BACK = §7Zurück
+PARTICLE_SLOT_EMPTY = §7Leerer Slot
+PARTICLE_SLOT_CREATE = §eZum Erstellen klicken
+PARTICLE_SLOT_ENTRIES = §7{0} Einträge
+PARTICLE_SLOT_ACTIVE = §aAktiv
+PARTICLE_SLOT_SELECT = §eLinksklick§8: §7auswählen
+PARTICLE_SLOT_EDIT = §eRechtsklick§8: §7bearbeiten
+PARTICLE_SLOT_VIEW = §eKlick§8: §7ansehen
+PARTICLE_SLOT_EDITOR = §0{0} bearbeiten
+PARTICLE_SLOT_VIEWER = §0{0}
+PARTICLE_SLOT_ADD_ENTRY = §aEintrag hinzufügen
+PARTICLE_SLOT_MAX_ENTRIES = §cDieser Slot ist voll
+PARTICLE_SLOT_OVER_BUDGET = §cDamit würde dieser Slot zu viele Partikel erzeugen
+PARTICLE_SLOT_RENAME = §eUmbenennen
+PARTICLE_SLOT_RENAME_TITLE = Slotname
+PARTICLE_SLOT_ICON = §eSymbol ändern
+PARTICLE_SLOT_ICON_TITLE = §0Slotsymbol
+PARTICLE_SLOT_DELETE = §cSlot löschen
+PARTICLE_SLOT_DELETE_CONFIRM = §7Zum Löschen Shift-Klick
+PARTICLE_SLOT_COST = §7Partikel pro Tick§8: §e{0}§8/§7{1}
+
+PARTICLE_ENTRY_EDITOR = §0Eintrag bearbeiten
+PARTICLE_ENTRY_DESIGN = §7Design§8: {0}
+PARTICLE_ENTRY_PARTICLE = §7Partikel§8: {0}
+PARTICLE_ENTRY_LOCKED = §cDesign gesperrt, wird nicht angezeigt
+PARTICLE_ENTRY_EDIT = §eLinksklick§8: §7bearbeiten
+PARTICLE_ENTRY_DUPLICATE = §eRechtsklick§8: §7duplizieren
+PARTICLE_ENTRY_DELETE = §eShift-Rechtsklick§8: §7löschen
+PARTICLE_ENTRY_CHANGE_DESIGN = §eZum Ändern des Designs klicken
+PARTICLE_ENTRY_DELETE_ITEM = §cEintrag löschen
+
+PARTICLE_DESIGN_PICKER = §0Design wählen
+PARTICLE_CHOICE_PICKER = §0{0}
+PARTICLE_COLOR_PICKER = §0Farbe wählen
+
+PARTICLE_VALUE = §7Wert§8: {0}
+PARTICLE_VALUE_NUMBER_HELP = §eLinks§8/§erechtsklick§8: §7+/- {0}, §eShift§8: §7x10
+PARTICLE_VALUE_EXACT_HELP = §eQ§8: §7Wert eingeben
+PARTICLE_VALUE_TOGGLE_HELP = §eKlick§8: §7umschalten
+PARTICLE_VALUE_CHOICE_HELP = §eLinks§8/§erechtsklick§8: §7weiter/zurück
+PARTICLE_VALUE_LIST_HELP = §eKlick§8: §7auswählen
+PARTICLE_VALUE_COLORS_HELP = §eLinksklick§8: §7Farbe hinzufügen, §eRechtsklick§8: §7letzte entfernen
+PARTICLE_VALUE_RANDOM = §7Zufällige Farben
+PARTICLE_VALUE_YES = §aJa
+PARTICLE_VALUE_NO = §cNein
+
+PARTICLE_DESIGN_SIMPLE = §fEinfach
+PARTICLE_DESIGN_CIRCLE = §fKreis
+PARTICLE_DESIGN_WING = §fFlügel
+PARTICLE_DESIGN_CLOUD = §fWolke
+
+PARTICLE_PARAM_PARTICLE = §fPartikel
+PARTICLE_PARAM_COUNT = §fAnzahl
+PARTICLE_PARAM_SPREAD_X = §fStreuung X
+PARTICLE_PARAM_SPREAD_Y = §fStreuung Y
+PARTICLE_PARAM_SPREAD_Z = §fStreuung Z
+PARTICLE_PARAM_SPEED = §fGeschwindigkeit
+PARTICLE_PARAM_COLORS = §fFarben
+PARTICLE_PARAM_OFFSET_X = §fVersatz X
+PARTICLE_PARAM_OFFSET_Y = §fVersatz Y
+PARTICLE_PARAM_OFFSET_Z = §fVersatz Z
+PARTICLE_PARAM_INTERVAL = §fAlle n Ticks
+PARTICLE_PARAM_TICK_MODE = §fAnzeige
+PARTICLE_PARAM_MOVEMENT = §fBewegung
+PARTICLE_PARAM_GLIDING = §fElytra
+PARTICLE_PARAM_GROUND = §fBoden
+PARTICLE_PARAM_SNEAK = §fDucken
+PARTICLE_PARAM_VISIBILITY = §fSichtbar für
+PARTICLE_PARAM_RADIUS = §fRadius
+PARTICLE_PARAM_CIRCLE_SPEED = §fDrehgeschwindigkeit
+PARTICLE_PARAM_PHASE = §fStartwinkel
+PARTICLE_PARAM_RISE = §fAuf und ab Höhe
+PARTICLE_PARAM_RISE_SPEED = §fAuf und ab Geschwindigkeit
+PARTICLE_PARAM_BOUNCE = §fZurückspringen
+PARTICLE_PARAM_INVERTED = §fOben beginnen
+PARTICLE_PARAM_WING_IMAGE = §fFlügelform
+PARTICLE_PARAM_WING_SIZE = §fFlügelgröße
+
+PARTICLE_CHOICE_TICKMODE_MOVE = §fBeim Bewegen
+PARTICLE_CHOICE_TICKMODE_ALWAYS = §fImmer
+PARTICLE_CHOICE_MOVEMENT_ANY = §fJede Bewegung
+PARTICLE_CHOICE_MOVEMENT_MOVING = §fNur beim Bewegen
+PARTICLE_CHOICE_MOVEMENT_STANDING_STILL = §fNur beim Stehen
+PARTICLE_CHOICE_GLIDING_ANY = §fMit oder ohne Elytra
+PARTICLE_CHOICE_GLIDING_GLIDING = §fNur beim Gleiten
+PARTICLE_CHOICE_GLIDING_NOT_GLIDING = §fNicht beim Gleiten
+PARTICLE_CHOICE_GROUND_ANY = §fAm Boden und in der Luft
+PARTICLE_CHOICE_GROUND_ON_GROUND = §fNur am Boden
+PARTICLE_CHOICE_GROUND_IN_AIR = §fNur in der Luft
+PARTICLE_CHOICE_SNEAK_ANY = §fDucken egal
+PARTICLE_CHOICE_SNEAK_SNEAKING = §fNur beim Ducken
+PARTICLE_CHOICE_SNEAK_NOT_SNEAKING = §fNicht beim Ducken
+PARTICLE_CHOICE_VISIBILITY_EVERYONE = §fAlle
+PARTICLE_CHOICE_VISIBILITY_ONLY_SELF = §fNur ich
+PARTICLE_CHOICE_VISIBILITY_ONLY_OTHERS = §fNur andere
+PARTICLE_CHOICE_IMAGE_SIMPLE = §fEinfach
+PARTICLE_CHOICE_IMAGE_COMPLEX = §fKomplex
+PARTICLE_CHOICE_IMAGE_SWORD = §fSchwert
+
PARTICLE_SNEEZE = §aSneeze
PARTICLE_SMOKE = §7Rauch
PARTICLE_FIRE = §cFeuer
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/ParticleInventory.java b/LobbySystem/src/de/steamwar/lobby/particle/ParticleInventory.java
deleted file mode 100644
index 0ebe3b41..00000000
--- a/LobbySystem/src/de/steamwar/lobby/particle/ParticleInventory.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * This file is a part of the SteamWar software.
- *
- * Copyright (C) 2025 SteamWar.de-Serverteam
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package de.steamwar.lobby.particle;
-
-import de.steamwar.inventory.SWItem;
-import de.steamwar.inventory.SWListInv;
-import de.steamwar.lobby.LobbySystem;
-import de.steamwar.lobby.particle.slot.ParticlePreset;
-import de.steamwar.lobby.particle.slot.Presets;
-import de.steamwar.lobby.util.LobbyPlayer;
-import de.steamwar.sql.SteamwarUser;
-import lombok.experimental.UtilityClass;
-import org.bukkit.Material;
-import org.bukkit.entity.Player;
-
-import java.util.ArrayList;
-import java.util.List;
-
-// Lists the presets a player has unlocked; the slot menus replace this
-@UtilityClass
-public class ParticleInventory {
-
- public void openInventory(Player player) {
- LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player);
- UnlockContext context = new UnlockContext(SteamwarUser.get(player.getUniqueId()));
-
- List> presetList = new ArrayList<>();
- for (ParticlePreset preset : Presets.all()) {
- if (!preset.getRequirement().test(context)) continue;
- List lore = new ArrayList<>();
- lore.add("");
- lore.add(LobbySystem.getMessage().parse("PARTICLE_SELECT", player));
- presetList.add(new SWListInv.SWListEntry<>(new SWItem(preset.getIcon(), translate(preset.getNameKey(), player), lore, preset == lobbyPlayer.getActivePreset(), clickType -> {
- }), preset));
- }
-
- SWListInv inventory = new SWListInv<>(player, LobbySystem.getMessage().parse("PARTICLE_INVENTORY", player), false, presetList, (clickType, preset) -> {
- if (preset == null) return;
- lobbyPlayer.selectPreset(preset);
- player.closeInventory();
- if (ParticleListener.disabled(player)) {
- LobbySystem.getMessage().send("PARTICLE_DEACTIVATED", player);
- }
- });
- inventory.setItem(49, Material.BARRIER, LobbySystem.getMessage().parse("PARTICLE_DESELECT", player), new ArrayList<>(), false, clickType -> {
- lobbyPlayer.deselect();
- player.closeInventory();
- });
- inventory.open();
- }
-
- private String translate(String key, Player player) {
- try {
- return LobbySystem.getMessage().parse(key, player);
- } catch (Exception e) {
- return key;
- }
- }
-}
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java b/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java
index 5b19c9e0..9d569ebb 100644
--- a/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java
+++ b/LobbySystem/src/de/steamwar/lobby/particle/ParticleListener.java
@@ -23,6 +23,7 @@ import de.steamwar.linkage.Linked;
import de.steamwar.lobby.LobbySystem;
import de.steamwar.lobby.jumpandrun.JumpAndRun;
import de.steamwar.lobby.listener.PlayerSpawn;
+import de.steamwar.lobby.particle.gui.ParticleMenu;
import de.steamwar.lobby.util.LobbyPlayer;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.UserPerm;
@@ -76,7 +77,7 @@ public class ParticleListener implements Listener {
if (!PlayerSpawn.PARTICLE.equals(event.getItem())) return;
event.setCancelled(true);
- ParticleInventory.openInventory(player);
+ ParticleMenu.open(player);
}
@EventHandler
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/design/Designs.java b/LobbySystem/src/de/steamwar/lobby/particle/design/Designs.java
index 7b6d3590..cfa4a419 100644
--- a/LobbySystem/src/de/steamwar/lobby/particle/design/Designs.java
+++ b/LobbySystem/src/de/steamwar/lobby/particle/design/Designs.java
@@ -93,6 +93,16 @@ public class Designs {
public int points(ParameterValues values) {
return values.get(WING_IMAGE).getVectors().length;
}
+
+ // Wings spawn a particle on every pixel of the image, so they start sparse
+ @Override
+ public void applyDefaults(ParameterValues values) {
+ values.set(EntryParameters.COUNT, 1)
+ .set(EntryParameters.SPEED, 0.0)
+ .set(EntryParameters.TICK_MODE, EntryParameters.TickMode.ALWAYS)
+ .set(EntryParameters.INTERVAL, 20)
+ .set(EntryParameters.GLIDING, EntryParameters.Gliding.NOT_GLIDING);
+ }
};
public final ParticleDesign CLOUD = new ParticleDesign("cloud", Material.WHITE_WOOL, "PARTICLE_DESIGN_CLOUD") {
diff --git a/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java b/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java
new file mode 100644
index 00000000..1f9229a0
--- /dev/null
+++ b/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java
@@ -0,0 +1,257 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2026 SteamWar.de-Serverteam
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package de.steamwar.lobby.particle.gui;
+
+import de.steamwar.inventory.SWAnvilInv;
+import de.steamwar.inventory.SWInventory;
+import de.steamwar.inventory.SWItem;
+import de.steamwar.lobby.LobbySystem;
+import de.steamwar.lobby.particle.UnlockContext;
+import de.steamwar.lobby.particle.WingDesign;
+import de.steamwar.lobby.particle.design.*;
+import de.steamwar.lobby.particle.slot.ParticleEntry;
+import de.steamwar.lobby.particle.slot.ParticleSlot;
+import de.steamwar.lobby.particle.slot.ParticleSlots;
+import de.steamwar.lobby.util.LobbyPlayer;
+import de.steamwar.sql.SteamwarUser;
+import lombok.experimental.UtilityClass;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import static de.steamwar.lobby.particle.design.EntryParameters.*;
+import static de.steamwar.lobby.particle.gui.ParticleGuiText.*;
+
+// One item per parameter of an entry; every change is saved and shown right away
+@UtilityClass
+class EntryEditor {
+
+ private final int DESIGN = 4;
+ private final int FIRST_PARAMETER = 9;
+ private final int BACK = 45;
+ private final int COST = 49;
+ private final int DELETE = 53;
+ // Choices with more options than this open a list instead of cycling on click
+ private final int MAX_CYCLE_CHOICES = 6;
+
+ private final Set> PARTICLE_PARAMETERS = Set.of(PARTICLE, COUNT, SPREAD_X, SPREAD_Y, SPREAD_Z, SPEED, COLORS);
+
+ private record Context(Player player, SteamwarUser user, int index, int entryIndex, ParticleSlot slot, ParticleEntry entry) {
+ void reopen() {
+ open(player, index, entryIndex);
+ }
+ }
+
+ void open(Player player, int index, int entryIndex) {
+ LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player);
+ ParticleSlot slot = lobbyPlayer.getSlot(index);
+ SteamwarUser user = SteamwarUser.get(player.getUniqueId());
+ if (slot == null || entryIndex >= slot.getEntries().size() || !ParticleSlots.requirement(index).test(new UnlockContext(user))) {
+ SlotEditor.open(player, index);
+ return;
+ }
+ Context context = new Context(player, user, index, entryIndex, slot, slot.getEntries().get(entryIndex));
+ ParticleEntry entry = context.entry();
+
+ SWInventory inventory = new SWInventory(player, 54, msg("PARTICLE_ENTRY_EDITOR", player));
+ inventory.setItem(DESIGN, new SWItem(entry.getDesign().getIcon(), designName(entry.getDesign(), player), List.of(msg("PARTICLE_ENTRY_CHANGE_DESIGN", player)), false,
+ clickType -> Pickers.design(player, design -> changeDesign(context, design), context::reopen)));
+
+ int position = FIRST_PARAMETER;
+ for (Parameter> parameter : visibleParameters(entry)) {
+ if (position >= BACK) break;
+ inventory.setItem(position++, parameterItem(context, parameter));
+ }
+
+ inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> SlotEditor.open(player, index)));
+ inventory.setItem(COST, SlotEditor.costItem(player, user, slot));
+ inventory.setItem(DELETE, new SWItem(Material.LAVA_BUCKET, msg("PARTICLE_ENTRY_DELETE_ITEM", player), List.of(msg("PARTICLE_SLOT_DELETE_CONFIRM", player)), false, clickType -> {
+ if (!clickType.isShiftClick()) return;
+ slot.getEntries().remove(entryIndex);
+ SlotEditor.save(player, index, slot);
+ SlotEditor.open(player, index);
+ }));
+ inventory.open();
+ }
+
+ private List> visibleParameters(ParticleEntry entry) {
+ List> parameters = new ArrayList<>();
+ boolean dust = entry.getValues().get(PARTICLE).isDust();
+ for (Parameter> parameter : entry.getParameters()) {
+ if (!entry.getDesign().usesEntryParticle() && PARTICLE_PARAMETERS.contains(parameter)) continue;
+ if (parameter == COLORS && !dust) continue;
+ parameters.add(parameter);
+ }
+ return parameters;
+ }
+
+ @SuppressWarnings("unchecked")
+ private SWItem parameterItem(Context context, Parameter> parameter) {
+ if (parameter instanceof DoubleParameter number) return doubleItem(context, number);
+ if (parameter instanceof IntParameter number) return intItem(context, number);
+ if (parameter instanceof BooleanParameter toggle) return booleanItem(context, toggle);
+ if (parameter instanceof GradientParameter gradient) return gradientItem(context, gradient);
+ if (parameter instanceof ChoiceParameter> choice) return choiceItem(context, (ChoiceParameter