From 1ef4f86a141a10d4c7b59b0510f330469f60c7d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elias=20M=C3=BCller?= <elias@elias-mueller.com>
Date: Sun, 25 Aug 2024 14:30:18 +0200
Subject: [PATCH] added multi bool setting and switched hotbar replace to it

---
 build.gradle                                  |   7 +
 .../hotbarRefill/ItemRefillListener.java      |  20 ++-
 .../settings/datatypes/MultiBoolSetting.java  | 150 ++++++++++++++++++
 .../settings/datatypes/SelectSetting.java     |   9 +-
 .../settings/HotbarReplaceSetting.java        |  21 ++-
 5 files changed, 194 insertions(+), 13 deletions(-)
 create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/MultiBoolSetting.java

diff --git a/build.gradle b/build.gradle
index 49cb1f6..2e6f0b6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -58,3 +58,10 @@ tasks.register('copyJarToServer', Exec) {
 
     commandLine 'scp', 'build/libs/spawn-1.0-all.jar', 'root@10.20.6.1:/home/minecraft/server/plugins'
 }
+
+tasks.register('copyJarToTestServer', Exec) {
+    dependsOn shadowJar
+    mustRunAfter shadowJar
+
+    commandLine 'cp', 'build/libs/spawn-1.0-all.jar', '/home/elias/Dokumente/mcTestServer/plugins/spawn-1.0-all.jar'
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/ItemRefillListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/ItemRefillListener.java
index 2f733a0..d001598 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/ItemRefillListener.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/hotbarRefill/ItemRefillListener.java
@@ -2,6 +2,8 @@ package eu.mhsl.craftattack.spawn.appliances.hotbarRefill;
 
 import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
 import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
+import eu.mhsl.craftattack.spawn.appliances.settings.settings.HotbarReplaceSetting;
+import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.block.BlockPlaceEvent;
 import org.bukkit.event.player.PlayerItemBreakEvent;
@@ -9,26 +11,34 @@ import org.bukkit.event.player.PlayerItemConsumeEvent;
 import org.bukkit.inventory.ItemStack;
 
 public class ItemRefillListener extends ApplianceListener<HotbarRefill> {
+    private HotbarReplaceSetting.HotbarReplaceConfig getPlayerSetting(Player player) {
+        return Settings.instance().getSetting(
+            player,
+            Settings.Key.HotbarReplacer,
+            HotbarReplaceSetting.HotbarReplaceConfig.class
+        );
+    }
+
     @EventHandler
     public void blockPlace(BlockPlaceEvent event) {
-        if(!Settings.instance().getSetting(event.getPlayer(), Settings.Key.HotbarReplacer, Boolean.class)) return;
-
         ItemStack stackInHand = event.getItemInHand();
+        if(stackInHand.getAmount() != 1) return;
         if(stackInHand.getType().getMaxDurability() > 0) return;
+
+        if(!getPlayerSetting(event.getPlayer()).onBlocks()) return;
         getAppliance().handleHotbarChange(event.getPlayer(), stackInHand);
     }
 
     @EventHandler
     public void onPlayerItemBreak(PlayerItemBreakEvent event) {
-        if(!Settings.instance().getSetting(event.getPlayer(), Settings.Key.HotbarReplacer, Boolean.class)) return;
+        if(!getPlayerSetting(event.getPlayer()).onTools()) return;
 
         getAppliance().handleHotbarChange(event.getPlayer(), event.getBrokenItem());
     }
 
-
     @EventHandler
     public void onPlayerItemConsume(PlayerItemConsumeEvent event) {
-        if(!Settings.instance().getSetting(event.getPlayer(), Settings.Key.HotbarReplacer, Boolean.class)) return;
+        if(!getPlayerSetting(event.getPlayer()).onConsumable()) return;
 
         getAppliance().handleHotbarChange(event.getPlayer(), event.getItem());
     }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/MultiBoolSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/MultiBoolSetting.java
new file mode 100644
index 0000000..fb61063
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/MultiBoolSetting.java
@@ -0,0 +1,150 @@
+package eu.mhsl.craftattack.spawn.appliances.settings.datatypes;
+
+import com.google.gson.Gson;
+import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.RecordComponent;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+public abstract class MultiBoolSetting<T> extends Setting<T> {
+    private String cursorPosition;
+
+    public MultiBoolSetting(Settings.Key key) {
+        super(key);
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface DisplayName {
+        String value();
+    }
+
+    @Override
+    public ItemMeta buildMeta(ItemMeta meta) {
+        record SettingField(String name, String displayName, Boolean value) {}
+
+        meta.displayName(Component.text(title(), NamedTextColor.WHITE));
+        List<Component> lore = new ArrayList<>();
+        lore.add(Component.text("Status: ", NamedTextColor.DARK_GRAY));
+
+        lore.addAll(
+            Arrays.stream(this.state.getClass().getRecordComponents())
+                .map(component -> {
+                    try {
+                        Method method = this.state.getClass().getDeclaredMethod(component.getName());
+                        Boolean value = (Boolean) method.invoke(this.state);
+
+                        DisplayName annotation = component.getAnnotation(DisplayName.class);
+                        String displayName = annotation != null
+                            ? annotation.value()
+                            : component.getName();
+
+                        return new SettingField(component.getName(), displayName, value);
+                    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+                        throw new RuntimeException(e);
+                    }
+                })
+                .map(field -> {
+                    if (cursorPosition == null) cursorPosition = field.name;
+                    boolean isSelected = field.name.equals(this.cursorPosition);
+                    return Component.text()
+                        .append(Component.text(
+                            isSelected ? "> " : "  ",
+                            isSelected ? NamedTextColor.GREEN : NamedTextColor.GRAY)
+                        )
+                        .append(Component.text(
+                            field.displayName + ": ",
+                            isSelected ? NamedTextColor.DARK_GREEN : NamedTextColor.GRAY)
+                        )
+                        .append(Component.text(
+                            field.value ? "Aktiviert" : "Deaktiviert",
+                            field.value ? NamedTextColor.GREEN : NamedTextColor.RED)
+                        )
+                        .build();
+                })
+                .toList()
+        );
+        lore.add(Component.empty());
+        lore.addAll(buildDescription(description()));
+        lore.add(Component.empty());
+        lore.add(Component.text("Linksklick", NamedTextColor.AQUA).append(Component.text(" zum Wählen der Option", NamedTextColor.GRAY)));
+        lore.add(Component.text("Rechtsklick", NamedTextColor.AQUA).append(Component.text(" zum Ändern des Wertes", NamedTextColor.GRAY)));
+        meta.lore(lore);
+        return meta;
+    }
+
+    @Override
+    protected void change(ClickType clickType) {
+        var recordComponents = this.state.getClass().getRecordComponents();
+
+        int currentIndex = IntStream.range(0, recordComponents.length)
+            .filter(i -> recordComponents[i].getName().equals(this.cursorPosition))
+            .findFirst()
+            .orElse(-1);
+
+        if (clickType.equals(ClickType.LEFT)) {
+            currentIndex = (currentIndex + 1) % recordComponents.length;
+            this.cursorPosition = recordComponents[currentIndex].getName();
+        } else if (clickType.equals(ClickType.RIGHT)) {
+            try {
+                Object[] values = Arrays.stream(recordComponents)
+                    .map(rc -> {
+                        try {
+                            return this.state.getClass().getDeclaredMethod(rc.getName()).invoke(this.state);
+                        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+                            throw new RuntimeException(e);
+                        }
+                    })
+                    .toArray();
+
+                Method getter = this.state.getClass().getDeclaredMethod(this.cursorPosition);
+                boolean currentValue = (Boolean) getter.invoke(this.state);
+                values[currentIndex] = !currentValue;
+
+                //noinspection unchecked
+                this.state = (T) this.state.getClass().getConstructor(
+                    Arrays.stream(recordComponents).map(RecordComponent::getType).toArray(Class[]::new)
+                ).newInstance(values);
+            } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Override
+    protected void fromStorage(PersistentDataContainer container) {
+        String data = container.has(getNamespacedKey())
+            ? Objects.requireNonNull(container.get(getNamespacedKey(), PersistentDataType.STRING))
+            : new Gson().toJson(defaultValue());
+
+        try {
+            //noinspection unchecked
+            this.state = (T) new Gson().fromJson(data, dataType());
+        } catch(Exception e) {
+            this.state = defaultValue();
+        }
+    }
+
+    @Override
+    protected void toStorage(PersistentDataContainer container, T value) {
+        container.set(getNamespacedKey(), PersistentDataType.STRING, new Gson().toJson(value));
+    }
+
+    @Override
+    public T state() {
+        return this.state;
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/SelectSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/SelectSetting.java
index 45ed4db..7f56cf6 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/SelectSetting.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/datatypes/SelectSetting.java
@@ -43,7 +43,10 @@ public abstract class SelectSetting extends Setting<SelectSetting.Options.Option
             this.options.options.stream()
                 .map(option -> {
                     boolean isSelected = option.equals(this.state);
-                    return Component.text("> " + option.name, isSelected ? NamedTextColor.GREEN : NamedTextColor.GRAY);
+                    return Component.text()
+                        .append(Component.text(isSelected ? "> " : "  ", isSelected ? NamedTextColor.GREEN : NamedTextColor.GRAY))
+                        .append(Component.text(option.name, isSelected ? NamedTextColor.DARK_GREEN : NamedTextColor.DARK_GRAY))
+                        .build();
                 })
                 .toList()
         );
@@ -62,9 +65,9 @@ public abstract class SelectSetting extends Setting<SelectSetting.Options.Option
             .mapToObj(i -> {
                 int nextIndex = i + optionModifier;
                 if (nextIndex >= options.size()) {
-                    return options.get(0);
+                    return options.getFirst();
                 } else if (nextIndex < 0) {
-                    return options.get(options.size() - 1);
+                    return options.getLast();
                 } else {
                     return options.get(nextIndex);
                 }
diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/HotbarReplaceSetting.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/HotbarReplaceSetting.java
index b2b454a..1331882 100644
--- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/HotbarReplaceSetting.java
+++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/settings/settings/HotbarReplaceSetting.java
@@ -1,10 +1,16 @@
 package eu.mhsl.craftattack.spawn.appliances.settings.settings;
 
 import eu.mhsl.craftattack.spawn.appliances.settings.Settings;
-import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.BoolSetting;
+import eu.mhsl.craftattack.spawn.appliances.settings.datatypes.MultiBoolSetting;
 import org.bukkit.Material;
 
-public class HotbarReplaceSetting extends BoolSetting {
+public class HotbarReplaceSetting extends MultiBoolSetting<HotbarReplaceSetting.HotbarReplaceConfig> {
+    public record HotbarReplaceConfig(
+        @DisplayName("Blöcke") boolean onBlocks,
+        @DisplayName("Werkzeuge") boolean onTools,
+        @DisplayName("Essen") boolean onConsumable
+    ) {}
+
     public HotbarReplaceSetting() {
         super(Settings.Key.HotbarReplacer);
     }
@@ -16,7 +22,7 @@ public class HotbarReplaceSetting extends BoolSetting {
 
     @Override
     protected String description() {
-        return "Verschiebe automatisch Blöcke von deinem Inventar in die Hotbar, wenn diese verbraucht werden";
+        return "Verschiebe Items automatisch von deinem Inventar in die Hotbar, wenn diese verbraucht werden";
     }
 
     @Override
@@ -25,7 +31,12 @@ public class HotbarReplaceSetting extends BoolSetting {
     }
 
     @Override
-    protected Boolean defaultValue() {
-        return false;
+    protected HotbarReplaceConfig defaultValue() {
+        return new HotbarReplaceConfig(false, false, false);
+    }
+
+    @Override
+    public Class<?> dataType() {
+        return HotbarReplaceConfig.class;
     }
 }