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:
2026-10-26 09:48:20 +01:00
parent 98f04451a4
commit b60cdfc88d
8 changed files with 347 additions and 221 deletions
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<Double> current, double step, double min, double max, double defaultValue, boolean integer,
Predicate<Double> 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<String> 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<Double> 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<List<Color>> current, int maxColors, Predicate<List<Color>> apply, Runnable back) {
List<Color> 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<Color> 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<Color> 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("§.", "");
}
}