forked from SteamWar/SteamWar
Simplify lobby particle menus
Every menu item now acts on a plain click: slots open their editor with a use button, entries open their editor, and duplicating is gone. The entry editor groups settings into labelled rows, numbers and colours get their own screens instead of relying on right click or Q, and slot names show the particles per tick used out of the maximum.
This commit is contained in:
@@ -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<Parameter<?>> PARTICLE_PARAMETERS = Set.of(PARTICLE, COUNT, SPREAD_X, SPREAD_Y, SPREAD_Z, SPEED, COLORS);
|
||||
private final Map<Parameter<?>, 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<Parameter<?>> visibleParameters(ParticleEntry entry) {
|
||||
List<Parameter<?>> 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<Parameter<?>> 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<Object>) choice);
|
||||
throw new IllegalArgumentException("No editor for parameter " + parameter.getKey());
|
||||
}
|
||||
|
||||
private List<String> lore(Context context, Parameter<?> parameter, String... help) {
|
||||
Player player = context.player();
|
||||
Object value = context.entry().getValues().get(parameter);
|
||||
List<String> 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<String> 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<String> 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<String> 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<Color> colors = context.entry().getValues().get(parameter);
|
||||
List<String> 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<Color> 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<Object>) choice, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <E> SWItem choiceItem(Context context, ChoiceParameter<E> parameter) {
|
||||
Player player = context.player();
|
||||
E value = context.entry().getValues().get(parameter);
|
||||
List<E> values = parameter.getBuildableValues();
|
||||
boolean list = values.size() > MAX_CYCLE_CHOICES;
|
||||
List<String> 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 <E> void pick(Context context, ChoiceParameter<E> 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 <T> void change(Context context, Parameter<T> parameter, T value) {
|
||||
// Saves the change, or reverts it and tells the player when the slot would go over its budget
|
||||
private <T> boolean apply(Context context, Parameter<T> 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
|
||||
|
||||
Reference in New Issue
Block a user