diff --git a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties index 702e340e..0581eb0f 100644 --- a/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties +++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem.properties @@ -66,15 +66,33 @@ PARTICLE_LOCKED = §c§lLocked PARTICLE_DEACTIVATED = §cThere are too many players online, particles are deactivated PARTICLE_BACK = §7Back +PARTICLE_SLOT_OPEN = §eClick to open +PARTICLE_SLOT_NAME = §f{0} §8(§e{1}§8/§7{2}§8) +PARTICLE_SLOT_TITLE = §0{0} §8({1}/{2}) +PARTICLE_SLOT_USE = §aUse this slot +PARTICLE_SLOT_STOP_USING = §cStop using this slot +PARTICLE_SHIFT_TO_DELETE = §7Shift click to delete +PARTICLE_SECTION_DESIGN = §fDesign +PARTICLE_SECTION_PARTICLE = §fParticle +PARTICLE_SECTION_SHAPE = §fShape +PARTICLE_SECTION_TIMING = §fTiming and position +PARTICLE_SECTION_CONDITIONS = §fConditions +PARTICLE_FIXED_LOOK = §7This design always looks the same +PARTICLE_VALUE_CLICK = §eClick to change +PARTICLE_VALUE_TOGGLE = §eClick to toggle +PARTICLE_VALUE_SELECTED = §aSelected +PARTICLE_VALUE_RESET = §eReset to {0} +PARTICLE_NUMBER_RANGE = §7Range§8: §e{0} §8- §e{1} +PARTICLE_NUMBER_TYPE = §eClick to type a value +PARTICLE_COLOR_EDITOR = §0Colours +PARTICLE_COLOR_REMOVE = §eClick to remove +PARTICLE_COLOR_ADD = §aAdd colour +PARTICLE_COLOR_RANDOM = §eUse random colours 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_VIEW = §eClick to view 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 @@ -83,30 +101,20 @@ 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_EDITOR = §0{0} 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_ENTRY_EDIT = §eClick to edit +PARTICLE_ENTRY_DELETE = §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 diff --git a/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties b/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties index 59647e67..947ac020 100644 --- a/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties +++ b/LobbySystem/src/de/steamwar/lobby/LobbySystem_de.properties @@ -66,15 +66,33 @@ PARTICLE_LOCKED = §c§lGesperrt PARTICLE_DEACTIVATED = §cEs sind zu viele Spieler online! Partikel sind deaktiviert. PARTICLE_BACK = §7Zurück +PARTICLE_SLOT_OPEN = §eZum Öffnen klicken +PARTICLE_SLOT_NAME = §f{0} §8(§e{1}§8/§7{2}§8) +PARTICLE_SLOT_TITLE = §0{0} §8({1}/{2}) +PARTICLE_SLOT_USE = §aDiesen Slot verwenden +PARTICLE_SLOT_STOP_USING = §cDiesen Slot nicht mehr verwenden +PARTICLE_SHIFT_TO_DELETE = §7Zum Löschen Shift-Klick +PARTICLE_SECTION_DESIGN = §fDesign +PARTICLE_SECTION_PARTICLE = §fPartikel +PARTICLE_SECTION_SHAPE = §fForm +PARTICLE_SECTION_TIMING = §fZeit und Position +PARTICLE_SECTION_CONDITIONS = §fBedingungen +PARTICLE_FIXED_LOOK = §7Dieses Design sieht immer gleich aus +PARTICLE_VALUE_CLICK = §eZum Ändern klicken +PARTICLE_VALUE_TOGGLE = §eZum Umschalten klicken +PARTICLE_VALUE_SELECTED = §aAusgewählt +PARTICLE_VALUE_RESET = §eAuf {0} zurücksetzen +PARTICLE_NUMBER_RANGE = §7Bereich§8: §e{0} §8- §e{1} +PARTICLE_NUMBER_TYPE = §eZum Eingeben klicken +PARTICLE_COLOR_EDITOR = §0Farben +PARTICLE_COLOR_REMOVE = §eZum Entfernen klicken +PARTICLE_COLOR_ADD = §aFarbe hinzufügen +PARTICLE_COLOR_RANDOM = §eZufällige Farben verwenden 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_VIEW = §eZum Ansehen klicken 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 @@ -83,30 +101,20 @@ 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_EDITOR = §0{0} 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_ENTRY_EDIT = §eZum Bearbeiten klicken +PARTICLE_ENTRY_DELETE = §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 diff --git a/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java b/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java index 1f9229a0..1b06daea 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/gui/EntryEditor.java @@ -19,7 +19,6 @@ 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; @@ -32,32 +31,53 @@ 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 java.util.Map; 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 +// Settings of one entry in labelled rows; a click on a setting toggles it or opens its own screen @UtilityClass class EntryEditor { - private final int DESIGN = 4; - private final int FIRST_PARAMETER = 9; + private final int ROW_DESIGN = 0; + private final int ROW_PARTICLE = 1; + private final int ROW_SHAPE = 2; + private final int ROW_TIMING = 3; + private final int ROW_CONDITIONS = 4; 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 final Map, Material> ICONS = Map.ofEntries( + Map.entry(COUNT, Material.GLOWSTONE_DUST), + Map.entry(SPEED, Material.FEATHER), + Map.entry(SPREAD_X, Material.GUNPOWDER), + Map.entry(SPREAD_Y, Material.GUNPOWDER), + Map.entry(SPREAD_Z, Material.GUNPOWDER), + Map.entry(OFFSET_X, Material.SPECTRAL_ARROW), + Map.entry(OFFSET_Y, Material.SPECTRAL_ARROW), + Map.entry(OFFSET_Z, Material.SPECTRAL_ARROW), + Map.entry(INTERVAL, Material.CLOCK), + Map.entry(TICK_MODE, Material.REPEATER), + Map.entry(MOVEMENT, Material.LEATHER_BOOTS), + Map.entry(GLIDING, Material.ELYTRA), + Map.entry(GROUND, Material.GRASS_BLOCK), + Map.entry(SNEAK, Material.LEATHER_LEGGINGS), + Map.entry(VISIBILITY, Material.SPYGLASS), + Map.entry(Designs.CIRCLE_RADIUS, Material.COMPASS), + Map.entry(Designs.CIRCLE_SPEED, Material.RECOVERY_COMPASS), + Map.entry(Designs.CIRCLE_PHASE, Material.TARGET), + Map.entry(Designs.CIRCLE_RISE, Material.LADDER), + Map.entry(Designs.CIRCLE_RISE_SPEED, Material.RABBIT_FOOT), + Map.entry(Designs.WING_IMAGE, Material.PHANTOM_MEMBRANE), + Map.entry(Designs.WING_SIZE, Material.SCAFFOLDING) + ); private record Context(Player player, SteamwarUser user, int index, int entryIndex, ParticleSlot slot, ParticleEntry entry) { void reopen() { @@ -75,159 +95,113 @@ class EntryEditor { } Context context = new Context(player, user, index, entryIndex, slot, slot.getEntries().get(entryIndex)); ParticleEntry entry = context.entry(); + ParticleDesign design = entry.getDesign(); - 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))); + SWInventory inventory = new SWInventory(player, 54, msg("PARTICLE_ENTRY_EDITOR", player, designName(design, player))); + header(inventory, ROW_DESIGN, "PARTICLE_SECTION_DESIGN", player); + header(inventory, ROW_PARTICLE, "PARTICLE_SECTION_PARTICLE", player); + header(inventory, ROW_SHAPE, "PARTICLE_SECTION_SHAPE", player); + header(inventory, ROW_TIMING, "PARTICLE_SECTION_TIMING", player); + header(inventory, ROW_CONDITIONS, "PARTICLE_SECTION_CONDITIONS", player); - int position = FIRST_PARAMETER; - for (Parameter parameter : visibleParameters(entry)) { - if (position >= BACK) break; - inventory.setItem(position++, parameterItem(context, parameter)); + inventory.setItem(ROW_DESIGN * 9 + 1, new SWItem(design.getIcon(), designName(design, player), List.of(msg("PARTICLE_VALUE_CLICK", player)), false, + clickType -> Pickers.design(player, design, picked -> changeDesign(context, picked), context::reopen))); + if (design.usesEntryParticle()) { + inventory.setItem(ROW_DESIGN * 9 + 2, parameterItem(context, PARTICLE)); + if (entry.getValues().get(PARTICLE).isDust()) { + inventory.setItem(ROW_DESIGN * 9 + 3, parameterItem(context, COLORS)); + } + row(inventory, context, ROW_PARTICLE, List.of(COUNT, SPEED, SPREAD_X, SPREAD_Y, SPREAD_Z)); + } else { + inventory.setItem(ROW_PARTICLE * 9 + 1, new SWItem(Material.STRUCTURE_VOID, msg("PARTICLE_FIXED_LOOK", player), clickType -> { + })); } + row(inventory, context, ROW_SHAPE, design.getParameters()); + row(inventory, context, ROW_TIMING, List.of(TICK_MODE, INTERVAL, OFFSET_X, OFFSET_Y, OFFSET_Z)); + row(inventory, context, ROW_CONDITIONS, List.of(MOVEMENT, GLIDING, GROUND, SNEAK, VISIBILITY)); 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 -> { + inventory.setItem(DELETE, new SWItem(Material.LAVA_BUCKET, msg("PARTICLE_ENTRY_DELETE", player), List.of(msg("PARTICLE_SHIFT_TO_DELETE", player)), false, clickType -> { if (!clickType.isShiftClick()) return; slot.getEntries().remove(entryIndex); SlotEditor.save(player, index, slot); SlotEditor.open(player, index); })); + SlotEditor.fill(inventory, 0, 54); 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); + private void header(SWInventory inventory, int row, String key, Player player) { + inventory.setItem(row * 9, new SWItem(Material.LIGHT_GRAY_STAINED_GLASS_PANE, msg(key, player), clickType -> { + })); + } + + private void row(SWInventory inventory, Context context, int row, List> parameters) { + for (int i = 0; i < parameters.size() && i < 8; i++) { + inventory.setItem(row * 9 + 1 + i, parameterItem(context, parameters.get(i))); } - 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) choice); - throw new IllegalArgumentException("No editor for parameter " + parameter.getKey()); - } - - private List lore(Context context, Parameter parameter, String... help) { + Player player = context.player(); + Object value = context.entry().getValues().get(parameter); List lore = new ArrayList<>(); - lore.add(msg("PARTICLE_VALUE", context.player(), valueText(parameter, context.entry().getValues().get(parameter), context.player()))); + lore.add(msg("PARTICLE_VALUE", player, valueText(parameter, value, player))); lore.add(""); - for (String line : help) lore.add(line); - return lore; - } + lore.add(msg(parameter instanceof BooleanParameter ? "PARTICLE_VALUE_TOGGLE" : "PARTICLE_VALUE_CLICK", player)); - private SWItem doubleItem(Context context, DoubleParameter parameter) { - Player player = context.player(); - double value = context.entry().getValues().get(parameter); - List lore = lore(context, parameter, msg("PARTICLE_VALUE_NUMBER_HELP", player, number(parameter.getStep())), msg("PARTICLE_VALUE_EXACT_HELP", player)); - return new SWItem(Material.REPEATER, name(parameter.getNameKey(), player), lore, false, clickType -> { - if (clickType == ClickType.DROP || clickType == ClickType.CONTROL_DROP) { - askNumber(context, parameter, ParticleGuiText.number(value), input -> change(context, parameter, input)); - return; - } - double step = parameter.getStep() * (clickType.isShiftClick() ? 10 : 1) * (clickType.isRightClick() ? -1 : 1); - double next = Math.round((value + step) / parameter.getStep()) * parameter.getStep(); - change(context, parameter, next); - }); - } + Material icon = ICONS.getOrDefault(parameter, Material.PAPER); + if (parameter instanceof BooleanParameter) icon = (Boolean) value ? Material.LIME_DYE : Material.GRAY_DYE; + if (parameter == PARTICLE) icon = ((ParticleType) value).getIcon(); + if (parameter == COLORS) icon = Material.RED_DYE; - private SWItem intItem(Context context, IntParameter parameter) { - Player player = context.player(); - int value = context.entry().getValues().get(parameter); - List lore = lore(context, parameter, msg("PARTICLE_VALUE_NUMBER_HELP", player, 1), msg("PARTICLE_VALUE_EXACT_HELP", player)); - return new SWItem(Material.COMPARATOR, name(parameter.getNameKey(), player), lore, false, clickType -> { - if (clickType == ClickType.DROP || clickType == ClickType.CONTROL_DROP) { - askNumber(context, parameter, String.valueOf(value), input -> change(context, parameter, (int) Math.round(input))); - return; - } - int step = (clickType.isShiftClick() ? 10 : 1) * (clickType.isRightClick() ? -1 : 1); - change(context, parameter, value + step); - }); - } - - private SWItem booleanItem(Context context, BooleanParameter parameter) { - boolean value = context.entry().getValues().get(parameter); - List lore = lore(context, parameter, msg("PARTICLE_VALUE_TOGGLE_HELP", context.player())); - return new SWItem(value ? Material.LIME_DYE : Material.GRAY_DYE, name(parameter.getNameKey(), context.player()), lore, false, - clickType -> change(context, parameter, !value)); - } - - private SWItem gradientItem(Context context, GradientParameter parameter) { - Player player = context.player(); - List colors = context.entry().getValues().get(parameter); - List lore = lore(context, parameter, msg("PARTICLE_VALUE_COLORS_HELP", player)); - return new SWItem(Material.WHITE_DYE, name(parameter.getNameKey(), player), lore, false, clickType -> { - if (clickType.isRightClick()) { - if (colors.isEmpty()) return; - change(context, parameter, colors.subList(0, colors.size() - 1)); - } else if (colors.size() < parameter.getMaxColors()) { - Pickers.color(player, color -> { - List next = new ArrayList<>(colors); - next.add(color); - change(context, parameter, next); - }, context::reopen); + return new SWItem(icon, name(parameter.getNameKey(), player), lore, false, clickType -> { + if (parameter instanceof BooleanParameter toggle) { + apply(context, toggle, !(Boolean) value); + context.reopen(); + } else if (parameter instanceof DoubleParameter number) { + ValueEditors.editNumber(player, name(number.getNameKey(), player), () -> context.entry().getValues().get(number), + number.getStep(), number.getMin(), number.getMax(), number.getDefaultValue(), false, + next -> apply(context, number, next), context::reopen); + } else if (parameter instanceof IntParameter number) { + ValueEditors.editNumber(player, name(number.getNameKey(), player), () -> (double) context.entry().getValues().get(number), + 1, number.getMin(), number.getMax(), number.getDefaultValue(), true, + next -> apply(context, number, (int) Math.round(next)), context::reopen); + } else if (parameter instanceof GradientParameter gradient) { + ValueEditors.editColors(player, () -> context.entry().getValues().get(gradient), gradient.getMaxColors(), + colors -> apply(context, gradient, colors), context::reopen); + } else if (parameter instanceof ChoiceParameter choice) { + pick(context, (ChoiceParameter) choice, value); } }); } - private SWItem choiceItem(Context context, ChoiceParameter parameter) { - Player player = context.player(); - E value = context.entry().getValues().get(parameter); - List values = parameter.getBuildableValues(); - boolean list = values.size() > MAX_CYCLE_CHOICES; - List lore = lore(context, parameter, msg(list ? "PARTICLE_VALUE_LIST_HELP" : "PARTICLE_VALUE_CHOICE_HELP", player)); - return new SWItem(choiceIcon(value), name(parameter.getNameKey(), player), lore, false, clickType -> { - if (list) { - Pickers.choice(player, parameter, EntryEditor::choiceIcon, picked -> change(context, parameter, picked), context::reopen); - return; - } - int current = values.indexOf(value); - int next = Math.floorMod(current + (clickType.isRightClick() ? -1 : 1), values.size()); - change(context, parameter, values.get(next)); - }); + private void pick(Context context, ChoiceParameter parameter, E current) { + Pickers.choice(context.player(), parameter, current, EntryEditor::choiceIcon, picked -> { + apply(context, parameter, picked); + context.reopen(); + }, context::reopen); } private Material choiceIcon(Object value) { if (value instanceof ParticleType type) return type.getIcon(); - if (value instanceof WingDesign) return Material.ELYTRA; + if (value instanceof WingDesign) return Material.PHANTOM_MEMBRANE; return Material.LEVER; } - private void askNumber(Context context, Parameter parameter, String current, java.util.function.DoubleConsumer onInput) { - Player player = context.player(); - SWAnvilInv anvil = new SWAnvilInv(player, name(parameter.getNameKey(), player), current); - anvil.setItem(Material.PAPER); - anvil.setCallback(input -> Bukkit.getScheduler().runTask(LobbySystem.getInstance(), () -> { - try { - onInput.accept(Double.parseDouble(input.trim().replace(',', '.'))); - } catch (NumberFormatException e) { - context.reopen(); - } - })); - anvil.open(); - } - - // Reverts the change when it would push the slot over its budget - private void change(Context context, Parameter parameter, T value) { + // Saves the change, or reverts it and tells the player when the slot would go over its budget + private boolean apply(Context context, Parameter parameter, T value) { T previous = context.entry().getValues().get(parameter); context.entry().getValues().set(parameter, value); if (!SlotEditor.fits(context.user(), context.slot())) { context.entry().getValues().set(parameter, previous); LobbySystem.getMessage().send("PARTICLE_SLOT_OVER_BUDGET", context.player()); - } else { - SlotEditor.save(context.player(), context.index(), context.slot()); + return false; } - context.reopen(); + SlotEditor.save(context.player(), context.index(), context.slot()); + return true; } // Keeps the particle, offset and conditions; a fixed design brings its own conditions and timing diff --git a/LobbySystem/src/de/steamwar/lobby/particle/gui/ParticleGuiText.java b/LobbySystem/src/de/steamwar/lobby/particle/gui/ParticleGuiText.java index 208aafdb..3ddd4133 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/gui/ParticleGuiText.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/gui/ParticleGuiText.java @@ -25,6 +25,8 @@ import de.steamwar.lobby.particle.UnlockContext; 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.sql.SteamwarUser; import lombok.experimental.UtilityClass; import org.bukkit.entity.Player; @@ -136,6 +138,15 @@ class ParticleGuiText { } } + // The slot's name followed by how many of its allowed particles per tick it uses + String slotName(ParticleSlot slot, SteamwarUser user, Player player) { + return msg("PARTICLE_SLOT_NAME", player, slot.getName(), number(slot.cost()), number(ParticleSlots.maxCost(user))); + } + + String slotTitle(ParticleSlot slot, SteamwarUser user, Player player) { + return msg("PARTICLE_SLOT_TITLE", player, slot.getName(), number(slot.cost()), number(ParticleSlots.maxCost(user))); + } + List slotSummary(ParticleSlot slot, Player player) { List lore = new ArrayList<>(); lore.add(msg("PARTICLE_SLOT_ENTRIES", player, slot.getEntries().size())); diff --git a/LobbySystem/src/de/steamwar/lobby/particle/gui/ParticleMenu.java b/LobbySystem/src/de/steamwar/lobby/particle/gui/ParticleMenu.java index 1127c7b6..7611e036 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/gui/ParticleMenu.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/gui/ParticleMenu.java @@ -34,7 +34,6 @@ import de.steamwar.sql.SteamwarUser; import lombok.experimental.UtilityClass; import org.bukkit.Material; import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; import java.util.ArrayList; import java.util.List; @@ -96,28 +95,22 @@ public class ParticleMenu { List lore = new ArrayList<>(ParticleGuiText.slotSummary(slot, player)); lore.add(""); - boolean active = lobbyPlayer.getActiveSlot() == index; + String name = slotName(slot, context.getUser(), player); if (!unlocked) { lore.add(msg("PARTICLE_LOCKED", player)); lore.addAll(ParticleGuiText.requirement(requirement, player)); lore.add(msg("PARTICLE_SLOT_VIEW", player)); - return new SWItem(slot.getDisplayIcon(), "§f" + slot.getName(), lore, false, clickType -> SlotEditor.view(player, index)); + return new SWItem(slot.getDisplayIcon(), name, lore, false, clickType -> SlotEditor.open(player, index)); } + boolean active = lobbyPlayer.getActiveSlot() == index; if (active) { lore.add(msg("PARTICLE_SLOT_ACTIVE", player)); } - lore.add(msg("PARTICLE_SLOT_SELECT", player)); - lore.add(msg("PARTICLE_SLOT_EDIT", player)); - return new SWItem(slot.getDisplayIcon(), "§f" + slot.getName(), lore, active, clickType -> { - if (clickType == ClickType.RIGHT || clickType == ClickType.SHIFT_RIGHT) { - SlotEditor.open(player, index); - } else if (lobbyPlayer.selectSlot(index)) { - selected(player); - } - }); + lore.add(msg("PARTICLE_SLOT_OPEN", player)); + return new SWItem(slot.getDisplayIcon(), name, lore, active, clickType -> SlotEditor.open(player, index)); } - private void selected(Player player) { + void selected(Player player) { player.closeInventory(); if (ParticleListener.disabled(player)) { LobbySystem.getMessage().send("PARTICLE_DEACTIVATED", player); diff --git a/LobbySystem/src/de/steamwar/lobby/particle/gui/Pickers.java b/LobbySystem/src/de/steamwar/lobby/particle/gui/Pickers.java index 9f26e11f..90a70a01 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/gui/Pickers.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/gui/Pickers.java @@ -41,8 +41,9 @@ class Pickers { private final int BACK = 49; - // Basic designs first, then rewards; locked rewards are shown with their requirement but cannot be picked - void design(Player player, Consumer onPick, Runnable onBack) { + // Basic designs first, then rewards; locked rewards are shown with their requirement but cannot be picked. + // The current design, if any, glows. + void design(Player player, ParticleDesign current, Consumer onPick, Runnable onBack) { UnlockContext context = new UnlockContext(SteamwarUser.get(player.getUniqueId())); List> list = new ArrayList<>(); for (DesignVisibility visibility : List.of(DesignVisibility.BASIC, DesignVisibility.REWARD)) { @@ -51,18 +52,20 @@ class Pickers { List lore = new ArrayList<>(requirement(design.getRequirement(), player)); boolean unlocked = design.getRequirement().test(context); if (!lore.isEmpty()) lore.add(""); - lore.add(msg(unlocked ? "PARTICLE_SELECT" : "PARTICLE_LOCKED", player)); - list.add(new SWListInv.SWListEntry<>(new SWItem(design.getIcon(), designName(design, player), lore, false, clickType -> { + lore.add(msg(design == current ? "PARTICLE_VALUE_SELECTED" : unlocked ? "PARTICLE_SELECT" : "PARTICLE_LOCKED", player)); + list.add(new SWListInv.SWListEntry<>(new SWItem(design.getIcon(), designName(design, player), lore, design == current, clickType -> { }), unlocked ? design : null)); } } open(player, msg("PARTICLE_DESIGN_PICKER", player), list, onPick, onBack); } - void choice(Player player, ChoiceParameter parameter, Function icon, Consumer onPick, Runnable onBack) { + // Lists every option; the current one glows + void choice(Player player, ChoiceParameter parameter, E current, Function icon, Consumer onPick, Runnable onBack) { List> list = new ArrayList<>(); for (E value : parameter.getBuildableValues()) { - list.add(new SWListInv.SWListEntry<>(new SWItem(icon.apply(value), choiceName(parameter, value, player), clickType -> { + List lore = List.of(msg(value == current ? "PARTICLE_VALUE_SELECTED" : "PARTICLE_SELECT", player)); + list.add(new SWListInv.SWListEntry<>(new SWItem(icon.apply(value), choiceName(parameter, value, player), lore, value == current, clickType -> { }), value)); } open(player, msg("PARTICLE_CHOICE_PICKER", player, name(parameter.getNameKey(), player)), list, onPick, onBack); diff --git a/LobbySystem/src/de/steamwar/lobby/particle/gui/SlotEditor.java b/LobbySystem/src/de/steamwar/lobby/particle/gui/SlotEditor.java index 9afa3e01..4b672a88 100644 --- a/LobbySystem/src/de/steamwar/lobby/particle/gui/SlotEditor.java +++ b/LobbySystem/src/de/steamwar/lobby/particle/gui/SlotEditor.java @@ -33,18 +33,18 @@ 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.util.ArrayList; import java.util.List; import static de.steamwar.lobby.particle.gui.ParticleGuiText.*; -// Lists a slot's entries; opened read-only for slots the player has lost access to +// Lists a slot's entries; slots the player has lost access to are shown read-only @UtilityClass public class SlotEditor { private final int BACK = 45; + private final int USE = 46; private final int ADD_ENTRY = 47; private final int RENAME = 48; private final int ICON = 49; @@ -52,14 +52,6 @@ public class SlotEditor { private final int DELETE = 53; public void open(Player player, int index) { - open(player, index, false); - } - - public void view(Player player, int index) { - open(player, index, true); - } - - private void open(Player player, int index, boolean readOnly) { LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player); ParticleSlot slot = lobbyPlayer.getSlot(index); if (slot == null) { @@ -68,11 +60,9 @@ public class SlotEditor { } SteamwarUser user = SteamwarUser.get(player.getUniqueId()); UnlockContext context = new UnlockContext(user); - // Locked slots can only ever be viewed, whatever the caller asked for - boolean locked = !ParticleSlots.requirement(index).test(context); - boolean editable = !readOnly && !locked; + boolean editable = ParticleSlots.requirement(index).test(context); - SWInventory inventory = new SWInventory(player, 54, msg(editable ? "PARTICLE_SLOT_EDITOR" : "PARTICLE_SLOT_VIEWER", player, slot.getName())); + SWInventory inventory = new SWInventory(player, 54, slotTitle(slot, user, player)); List entries = slot.getEntries(); for (int i = 0; i < entries.size(); i++) { ParticleEntry entry = entries.get(i); @@ -80,32 +70,10 @@ public class SlotEditor { if (editable) { lore.add(""); lore.add(msg("PARTICLE_ENTRY_EDIT", player)); - lore.add(msg("PARTICLE_ENTRY_DUPLICATE", player)); - lore.add(msg("PARTICLE_ENTRY_DELETE", player)); } int entryIndex = i; - inventory.setItem(i, new SWItem(icon(entry), designName(entry.getDesign(), player), lore, false, clickType -> { - if (!editable) return; - if (clickType == ClickType.SHIFT_RIGHT) { - entries.remove(entryIndex); - save(player, index, slot); - } else if (clickType == ClickType.RIGHT) { - if (entries.size() >= ParticleSlots.MAX_ENTRIES) { - LobbySystem.getMessage().send("PARTICLE_SLOT_MAX_ENTRIES", player); - return; - } - entries.add(entryIndex + 1, new ParticleEntry(entry.getDesign(), entry.getValues().copy())); - if (!fits(user, slot)) { - entries.remove(entryIndex + 1); - LobbySystem.getMessage().send("PARTICLE_SLOT_OVER_BUDGET", player); - return; - } - save(player, index, slot); - } else { - EntryEditor.open(player, index, entryIndex); - return; - } - open(player, index); + inventory.setItem(i, new SWItem(entry.getDesign().getIcon(), designName(entry.getDesign(), player), lore, false, clickType -> { + if (editable) EntryEditor.open(player, index, entryIndex); })); } @@ -114,16 +82,26 @@ public class SlotEditor { List lore = new ArrayList<>(requirement(ParticleSlots.requirement(index), player)); inventory.setItem(ICON, new SWItem(Material.BARRIER, msg("PARTICLE_LOCKED", player), lore, false, clickType -> { })); + fill(inventory, 45, 54); inventory.open(); return; } + boolean active = lobbyPlayer.getActiveSlot() == index; + inventory.setItem(USE, new SWItem(active ? Material.LIME_DYE : Material.GRAY_DYE, msg(active ? "PARTICLE_SLOT_STOP_USING" : "PARTICLE_SLOT_USE", player), List.of(), active, clickType -> { + if (active) { + lobbyPlayer.deselect(); + open(player, index); + } else if (lobbyPlayer.selectSlot(index)) { + ParticleMenu.selected(player); + } + })); inventory.setItem(ADD_ENTRY, new SWItem(Material.NETHER_STAR, msg("PARTICLE_SLOT_ADD_ENTRY", player), clickType -> { if (entries.size() >= ParticleSlots.MAX_ENTRIES) { LobbySystem.getMessage().send("PARTICLE_SLOT_MAX_ENTRIES", player); return; } - Pickers.design(player, design -> { + Pickers.design(player, null, design -> { entries.add(new ParticleEntry(design)); if (!fits(user, slot)) { entries.remove(entries.size() - 1); @@ -152,7 +130,7 @@ public class SlotEditor { open(player, index); }, () -> open(player, index)))); inventory.setItem(COST, costItem(player, user, slot)); - inventory.setItem(DELETE, new SWItem(Material.LAVA_BUCKET, msg("PARTICLE_SLOT_DELETE", player), List.of(msg("PARTICLE_SLOT_DELETE_CONFIRM", player)), false, clickType -> { + inventory.setItem(DELETE, new SWItem(Material.LAVA_BUCKET, msg("PARTICLE_SLOT_DELETE", player), List.of(msg("PARTICLE_SHIFT_TO_DELETE", player)), false, clickType -> { if (!clickType.isShiftClick()) return; if (lobbyPlayer.getActiveSlot() == index) { lobbyPlayer.deselect(); @@ -160,6 +138,7 @@ public class SlotEditor { lobbyPlayer.setSlot(index, null); ParticleMenu.open(player); })); + fill(inventory, 45, 54); inventory.open(); } @@ -176,7 +155,13 @@ public class SlotEditor { LobbyPlayer.getLobbyPlayer(player).setSlot(index, slot); } - Material icon(ParticleEntry entry) { - return entry.getDesign().getIcon(); + // Puts filler panes on the empty positions in the given range + void fill(SWInventory inventory, int from, int to) { + for (int position = from; position < to; position++) { + if (!inventory.hasItem(position)) { + inventory.setItem(position, new SWItem(Material.BLACK_STAINED_GLASS_PANE, " ", clickType -> { + })); + } + } } } diff --git a/LobbySystem/src/de/steamwar/lobby/particle/gui/ValueEditors.java b/LobbySystem/src/de/steamwar/lobby/particle/gui/ValueEditors.java new file mode 100644 index 00000000..b248e501 --- /dev/null +++ b/LobbySystem/src/de/steamwar/lobby/particle/gui/ValueEditors.java @@ -0,0 +1,144 @@ +/* + * 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 lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; +import org.bukkit.DyeColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import static de.steamwar.lobby.particle.gui.ParticleGuiText.*; + +// Screens for settings that need more than a click: numbers and colour lists +@UtilityClass +class ValueEditors { + + private final int BACK = 18; + + // Buttons step the value down or up; clicking the value itself lets the player type one + void editNumber(Player player, String title, Supplier current, double step, double min, double max, double defaultValue, boolean integer, + Predicate apply, Runnable back) { + double value = current.get(); + Runnable reopen = () -> editNumber(player, title, current, step, min, max, defaultValue, integer, apply, back); + + SWInventory inventory = new SWInventory(player, 27, "§0" + strip(title)); + inventory.setItem(10, stepItem(player, Material.RED_CONCRETE, -10 * step, step, value, min, max, integer, apply, reopen)); + inventory.setItem(11, stepItem(player, Material.RED_STAINED_GLASS_PANE, -step, step, value, min, max, integer, apply, reopen)); + inventory.setItem(15, stepItem(player, Material.LIME_STAINED_GLASS_PANE, step, step, value, min, max, integer, apply, reopen)); + inventory.setItem(16, stepItem(player, Material.LIME_CONCRETE, 10 * step, step, value, min, max, integer, apply, reopen)); + + List lore = List.of(msg("PARTICLE_NUMBER_RANGE", player, number(min), number(max)), "", msg("PARTICLE_NUMBER_TYPE", player)); + inventory.setItem(13, new SWItem(Material.PAPER, "§e" + number(value), lore, false, clickType -> { + SWAnvilInv anvil = new SWAnvilInv(player, strip(title), number(value)); + anvil.setItem(Material.PAPER); + anvil.setCallback(input -> Bukkit.getScheduler().runTask(LobbySystem.getInstance(), () -> { + try { + double typed = Double.parseDouble(input.trim().replace(',', '.')); + apply.test(integer ? Math.round(typed) : typed); + } catch (NumberFormatException e) { + // Keep the old value + } + reopen.run(); + })); + anvil.open(); + })); + + inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> back.run())); + inventory.setItem(22, new SWItem(Material.STRUCTURE_VOID, msg("PARTICLE_VALUE_RESET", player, number(defaultValue)), clickType -> { + apply.test(defaultValue); + reopen.run(); + })); + SlotEditor.fill(inventory, 0, 27); + inventory.open(); + } + + private SWItem stepItem(Player player, Material material, double delta, double step, double value, double min, double max, boolean integer, Predicate apply, Runnable reopen) { + String label = (delta < 0 ? "§c-" : "§a+") + number(Math.abs(delta)); + return new SWItem(material, label, clickType -> { + // Snap to the step grid and drop floating point noise, so repeated steps do not drift + double next = integer ? Math.round(value + delta) : Math.round(Math.round((value + delta) / step) * step * 1e6) / 1e6; + apply.test(Math.max(min, Math.min(max, next))); + reopen.run(); + }); + } + + // Shows the colours in order; a click removes one, the buttons below add one or switch to random colours + void editColors(Player player, Supplier> current, int maxColors, Predicate> apply, Runnable back) { + List colors = current.get(); + Runnable reopen = () -> editColors(player, current, maxColors, apply, back); + + SWInventory inventory = new SWInventory(player, 27, msg("PARTICLE_COLOR_EDITOR", player)); + for (int i = 0; i < colors.size() && i < 9; i++) { + int position = i; + Color color = colors.get(i); + inventory.setItem(i, new SWItem(dye(color), net.md_5.bungee.api.ChatColor.of(color) + "█ #" + String.format("%06X", color.getRGB() & 0xFFFFFF), + List.of(msg("PARTICLE_COLOR_REMOVE", player)), false, clickType -> { + List next = new ArrayList<>(colors); + next.remove(position); + apply.test(next); + reopen.run(); + })); + } + if (colors.isEmpty()) { + inventory.setItem(4, new SWItem(Material.WHITE_DYE, msg("PARTICLE_VALUE_RANDOM", player), clickType -> { + })); + } + + inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> back.run())); + if (colors.size() < maxColors) { + inventory.setItem(21, new SWItem(Material.NETHER_STAR, msg("PARTICLE_COLOR_ADD", player), clickType -> Pickers.color(player, color -> { + List next = new ArrayList<>(colors); + next.add(color); + apply.test(next); + reopen.run(); + }, reopen))); + } + inventory.setItem(23, new SWItem(Material.WHITE_DYE, msg("PARTICLE_COLOR_RANDOM", player), clickType -> { + apply.test(List.of()); + reopen.run(); + })); + SlotEditor.fill(inventory, 0, 27); + inventory.open(); + } + + private Material dye(Color color) { + for (DyeColor dye : DyeColor.values()) { + if ((dye.getColor().asRGB() & 0xFFFFFF) == (color.getRGB() & 0xFFFFFF)) { + return Material.valueOf(dye.name() + "_DYE"); + } + } + return Material.WHITE_DYE; + } + + private String strip(String text) { + return text.replaceAll("§.", ""); + } +}