/* * 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 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 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, 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)); 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(RESET, new SWItem(Material.STRUCTURE_VOID, msg("PARTICLE_VALUE_RESET", player, number(defaultValue)), clickType -> { apply.test(defaultValue); reopen.run(); })); BottomBar.fillUnused(inventory, SIZE, false); SlotEditor.fill(inventory, 0, SIZE); 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)); // Snap to the step grid and drop floating point noise, so repeated steps do not drift double unclamped = integer ? Math.round(value + delta) : Math.round(Math.round((value + delta) / step) * step * 1e6) / 1e6; double next = Math.max(min, Math.min(max, unclamped)); String limit = next == max && unclamped >= max ? "PARTICLE_NUMBER_AT_MAX" : next == min && unclamped <= min ? "PARTICLE_NUMBER_AT_MIN" : "PARTICLE_NUMBER_RESULT"; return new SWItem(material, label, List.of(msg(limit, player, number(next))), false, clickType -> { apply.test(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, SIZE, 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(ADD, 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))); // Same position as typing a value on the number screen inventory.setItem(13, new SWItem(Material.NAME_TAG, msg("PARTICLE_COLOR_HEX", player), List.of(msg("PARTICLE_COLOR_HEX_EXAMPLE", player)), false, clickType -> { SWAnvilInv anvil = new SWAnvilInv(player, msg("PARTICLE_COLOR_HEX_TITLE", player), "#"); anvil.setItem(Material.NAME_TAG); anvil.setCallback(input -> Bukkit.getScheduler().runTask(LobbySystem.getInstance(), () -> { Color color = parseHex(input); if (color == null) { LobbySystem.getMessage().send("PARTICLE_COLOR_HEX_INVALID", player); } else { List next = new ArrayList<>(colors); next.add(color); apply.test(next); } reopen.run(); })); anvil.open(); })); } inventory.setItem(RESET, new SWItem(Material.WHITE_DYE, msg("PARTICLE_COLOR_RANDOM", player), clickType -> { apply.test(List.of()); reopen.run(); })); BottomBar.fillUnused(inventory, SIZE, false); SlotEditor.fill(inventory, 0, SIZE); inventory.open(); } // Accepts #RRGGBB and #RGB, with or without the #; null for anything else Color parseHex(String input) { String hex = input.trim(); if (hex.startsWith("#")) hex = hex.substring(1); if (hex.length() == 3) { hex = "" + hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2); } if (!hex.matches("[0-9a-fA-F]{6}")) { return null; } return new Color(Integer.parseInt(hex, 16)); } 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("§.", ""); } }