Give particle menus a shared bottom bar

Back, use, add, rename, icon, particle count and delete or reset now sit
in the same column of the last row in every particle menu, and columns a
menu does not use show a barrier.
This commit is contained in:
2026-10-26 09:48:28 +01:00
parent 0c7ee052e8
commit e41f76937e
5 changed files with 95 additions and 25 deletions
@@ -0,0 +1,59 @@
/*
* 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.SWInventory;
import de.steamwar.inventory.SWItem;
import lombok.experimental.UtilityClass;
import org.bukkit.Material;
// Every particle menu puts the same button in the same column of its last row
@UtilityClass
class BottomBar {
// Taken by the page arrows of list menus
final int PREVIOUS_PAGE = 0;
final int BACK = 1;
final int USE = 2;
final int ADD = 3;
final int RENAME = 4;
final int ICON = 5;
final int COST = 6;
// Delete in editors, reset in value screens; no menu has both
final int DELETE = 7;
final int RESET = 7;
final int NEXT_PAGE = 8;
int position(int inventorySize, int column) {
return inventorySize - 9 + column;
}
// Marks the columns this menu does not use, so the buttons it does use stay recognisable by position.
// List menus keep the outer columns free for the page arrows they add when opened.
void fillUnused(SWInventory inventory, int inventorySize, boolean listMenu) {
for (int column = listMenu ? 1 : 0; column < (listMenu ? 8 : 9); column++) {
int position = position(inventorySize, column);
if (!inventory.hasItem(position)) {
inventory.setItem(position, new SWItem(Material.BARRIER, "§8-", clickType -> {
}));
}
}
}
}
@@ -50,9 +50,10 @@ class EntryEditor {
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;
private final int SIZE = 54;
private final int BACK = BottomBar.position(SIZE, BottomBar.BACK);
private final int COST = BottomBar.position(SIZE, BottomBar.COST);
private final int DELETE = BottomBar.position(SIZE, BottomBar.DELETE);
private final Map<Parameter<?>, Material> ICONS = Map.ofEntries(
Map.entry(COUNT, Material.GLOWSTONE_DUST),
@@ -97,7 +98,7 @@ class EntryEditor {
ParticleEntry entry = context.entry();
ParticleDesign design = entry.getDesign();
SWInventory inventory = new SWInventory(player, 54, msg("PARTICLE_ENTRY_EDITOR", player, designName(design, player)));
SWInventory inventory = new SWInventory(player, SIZE, msg("PARTICLE_ENTRY_EDITOR", player, designName(design, player)));
header(inventory, ROW_DESIGN, Material.PAINTING, "PARTICLE_SECTION_DESIGN", player);
header(inventory, ROW_PARTICLE, Material.FIREWORK_STAR, "PARTICLE_SECTION_PARTICLE", player);
header(inventory, ROW_SHAPE, Material.ARMOR_STAND, "PARTICLE_SECTION_SHAPE", player);
@@ -128,7 +129,8 @@ class EntryEditor {
SlotEditor.save(player, index, slot);
SlotEditor.open(player, index);
}));
SlotEditor.fill(inventory, 0, 54);
BottomBar.fillUnused(inventory, SIZE, false);
SlotEditor.fill(inventory, 0, SIZE);
inventory.open();
}
@@ -39,7 +39,8 @@ import static de.steamwar.lobby.particle.gui.ParticleGuiText.*;
@UtilityClass
class Pickers {
private final int BACK = 49;
private final int SIZE = 54;
private final int BACK = BottomBar.position(SIZE, BottomBar.BACK);
// Basic designs first, then rewards; locked rewards are shown with their requirement but cannot be picked.
// The current design, if any, glows.
@@ -104,6 +105,7 @@ class Pickers {
if (value != null) onPick.accept(value);
});
inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> onBack.run()));
BottomBar.fillUnused(inventory, SIZE, true);
inventory.open();
}
}
@@ -43,13 +43,14 @@ import static de.steamwar.lobby.particle.gui.ParticleGuiText.*;
@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;
private final int COST = 50;
private final int DELETE = 53;
private final int SIZE = 54;
private final int BACK = BottomBar.position(SIZE, BottomBar.BACK);
private final int USE = BottomBar.position(SIZE, BottomBar.USE);
private final int ADD_ENTRY = BottomBar.position(SIZE, BottomBar.ADD);
private final int RENAME = BottomBar.position(SIZE, BottomBar.RENAME);
private final int ICON = BottomBar.position(SIZE, BottomBar.ICON);
private final int COST = BottomBar.position(SIZE, BottomBar.COST);
private final int DELETE = BottomBar.position(SIZE, BottomBar.DELETE);
public void open(Player player, int index) {
LobbyPlayer lobbyPlayer = LobbyPlayer.getLobbyPlayer(player);
@@ -62,7 +63,7 @@ public class SlotEditor {
UnlockContext context = new UnlockContext(user);
boolean editable = ParticleSlots.requirement(index).test(context);
SWInventory inventory = new SWInventory(player, 54, slotTitle(slot, user, player));
SWInventory inventory = new SWInventory(player, SIZE, slotTitle(slot, user, player));
List<ParticleEntry> entries = slot.getEntries();
for (int i = 0; i < entries.size(); i++) {
ParticleEntry entry = entries.get(i);
@@ -80,9 +81,10 @@ public class SlotEditor {
inventory.setItem(BACK, new SWItem(Material.ARROW, msg("PARTICLE_BACK", player), clickType -> ParticleMenu.open(player)));
if (!editable) {
List<String> lore = new ArrayList<>(requirement(ParticleSlots.requirement(index), player));
inventory.setItem(ICON, new SWItem(Material.BARRIER, msg("PARTICLE_LOCKED", player), lore, false, clickType -> {
inventory.setItem(USE, new SWItem(Material.IRON_BARS, msg("PARTICLE_LOCKED", player), lore, false, clickType -> {
}));
fill(inventory, 45, 54);
inventory.setItem(COST, costItem(player, user, slot));
BottomBar.fillUnused(inventory, SIZE, false);
inventory.open();
return;
}
@@ -138,7 +140,7 @@ public class SlotEditor {
lobbyPlayer.setSlot(index, null);
ParticleMenu.open(player);
}));
fill(inventory, 45, 54);
BottomBar.fillUnused(inventory, SIZE, false);
inventory.open();
}
@@ -41,7 +41,10 @@ import static de.steamwar.lobby.particle.gui.ParticleGuiText.*;
@UtilityClass
class ValueEditors {
private final int BACK = 18;
private final int SIZE = 27;
private final int BACK = BottomBar.position(SIZE, BottomBar.BACK);
private final int ADD = BottomBar.position(SIZE, BottomBar.ADD);
private final int RESET = BottomBar.position(SIZE, BottomBar.RESET);
// 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,
@@ -49,7 +52,7 @@ class ValueEditors {
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));
SWInventory inventory = new SWInventory(player, SIZE, "§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));
@@ -72,11 +75,12 @@ class ValueEditors {
}));
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 -> {
inventory.setItem(RESET, new SWItem(Material.STRUCTURE_VOID, msg("PARTICLE_VALUE_RESET", player, number(defaultValue)), clickType -> {
apply.test(defaultValue);
reopen.run();
}));
SlotEditor.fill(inventory, 0, 27);
BottomBar.fillUnused(inventory, SIZE, false);
SlotEditor.fill(inventory, 0, SIZE);
inventory.open();
}
@@ -95,7 +99,7 @@ class ValueEditors {
List<Color> colors = current.get();
Runnable reopen = () -> editColors(player, current, maxColors, apply, back);
SWInventory inventory = new SWInventory(player, 27, msg("PARTICLE_COLOR_EDITOR", player));
SWInventory inventory = new SWInventory(player, SIZE, msg("PARTICLE_COLOR_EDITOR", player));
for (int i = 0; i < colors.size() && i < 9; i++) {
int position = i;
Color color = colors.get(i);
@@ -114,18 +118,19 @@ class ValueEditors {
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 -> {
inventory.setItem(ADD, 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 -> {
inventory.setItem(RESET, new SWItem(Material.WHITE_DYE, msg("PARTICLE_COLOR_RANDOM", player), clickType -> {
apply.test(List.of());
reopen.run();
}));
SlotEditor.fill(inventory, 0, 27);
BottomBar.fillUnused(inventory, SIZE, false);
SlotEditor.fill(inventory, 0, SIZE);
inventory.open();
}