diff --git a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java index fd6fb815..bc1fbb7f 100644 --- a/plugin/src/main/java/org/battleplugins/arena/BattleArena.java +++ b/plugin/src/main/java/org/battleplugins/arena/BattleArena.java @@ -14,7 +14,6 @@ import org.battleplugins.arena.competition.map.MapType; import org.battleplugins.arena.config.ArenaConfigParser; import org.battleplugins.arena.config.ParseException; -import org.battleplugins.arena.event.BattleArenaPostInitializeEvent; import org.battleplugins.arena.event.BattleArenaPreInitializeEvent; import org.battleplugins.arena.event.BattleArenaReloadEvent; import org.battleplugins.arena.event.BattleArenaReloadedEvent; @@ -34,9 +33,6 @@ import org.bukkit.configuration.Configuration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.server.ServerLoadEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; @@ -63,7 +59,7 @@ /** * The main class for BattleArena. */ -public class BattleArena extends JavaPlugin implements Listener, LoggerHolder { +public class BattleArena extends JavaPlugin implements LoggerHolder { private static final int PLUGIN_ID = 4597; private static BattleArena instance; @@ -111,7 +107,7 @@ public void onLoad() { @Override public void onEnable() { - Bukkit.getPluginManager().registerEvents(this, this); + Bukkit.getPluginManager().registerEvents(new BattleArenaListener(this), this); // Register default arenas this.registerArena(this, "Arena", Arena.class); @@ -197,19 +193,7 @@ private void disable() { this.teams = null; } - @EventHandler - public void onServerLoad(ServerLoadEvent event) { - // There is logic called later, however by this point all plugins - // using the BattleArena API should have been loaded. As modules will - // listen for this event to register their behavior, we need to ensure - // they are fully initialized so any references to said modules in - // arena config files will be valid. - new BattleArenaPostInitializeEvent(this).callEvent(); - - this.postInitialize(); - } - - private void postInitialize() { + void postInitialize() { // Load messages MessageLoader.load(this.getDataFolder().toPath().resolve("messages.yml")); diff --git a/plugin/src/main/java/org/battleplugins/arena/BattleArenaListener.java b/plugin/src/main/java/org/battleplugins/arena/BattleArenaListener.java new file mode 100644 index 00000000..f776b5a4 --- /dev/null +++ b/plugin/src/main/java/org/battleplugins/arena/BattleArenaListener.java @@ -0,0 +1,42 @@ +package org.battleplugins.arena; + +import io.papermc.paper.event.player.AsyncChatEvent; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.battleplugins.arena.editor.ArenaEditorWizard; +import org.battleplugins.arena.event.BattleArenaPostInitializeEvent; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.server.ServerLoadEvent; + +class BattleArenaListener implements Listener { + private final BattleArena plugin; + + public BattleArenaListener(BattleArena plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onServerLoad(ServerLoadEvent event) { + // There is logic called later, however by this point all plugins + // using the BattleArena API should have been loaded. As modules will + // listen for this event to register their behavior, we need to ensure + // they are fully initialized so any references to said modules in + // arena config files will be valid. + new BattleArenaPostInitializeEvent(this.plugin).callEvent(); + + this.plugin.postInitialize(); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onChat(AsyncChatEvent event) { + String message = PlainTextComponentSerializer.plainText().serialize(event.originalMessage()); + if (message.equalsIgnoreCase("cancel")) { + ArenaEditorWizard.wizardContext(event.getPlayer()).ifPresent(ctx -> { + event.setCancelled(true); + + ctx.getWizard().onCancel(ctx); + }); + } + } +} diff --git a/plugin/src/main/java/org/battleplugins/arena/editor/ArenaEditorWizard.java b/plugin/src/main/java/org/battleplugins/arena/editor/ArenaEditorWizard.java index 0b261997..4de84784 100644 --- a/plugin/src/main/java/org/battleplugins/arena/editor/ArenaEditorWizard.java +++ b/plugin/src/main/java/org/battleplugins/arena/editor/ArenaEditorWizard.java @@ -5,17 +5,17 @@ import org.battleplugins.arena.editor.type.EditorKey; import org.battleplugins.arena.messages.Messages; import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.UUID; +import java.util.Optional; import java.util.function.Consumer; public class ArenaEditorWizard> { + private static final String EDITOR_META_KEY = "editor"; private final BattleArena plugin; private final ContextFactory contextFactory; @@ -26,8 +26,6 @@ public class ArenaEditorWizard> { private Consumer onCreationComplete; private Consumer onCancel; - private final List players = new ArrayList<>(); - public ArenaEditorWizard(BattleArena plugin, ContextFactory contextFactory) { this.plugin = plugin; this.contextFactory = contextFactory; @@ -42,6 +40,10 @@ public ArenaEditorWizard addStage(EditorKey key, WizardStage stage) { return this; } + Map> getStages() { + return this.stages; + } + public ArenaEditorWizard onEditComplete(Consumer onEditComplete) { this.onEditComplete = onEditComplete; return this; @@ -62,7 +64,9 @@ public void onCancel(E context) { this.onCancel.accept(context); } - this.players.remove(context.getPlayer().getUniqueId()); + context.cancel(); + context.getPlayer().removeMetadata(EDITOR_META_KEY, this.plugin); + Messages.WIZARD_CLOSED.send(context.getPlayer()); } @@ -71,7 +75,7 @@ public void openWizard(Player player, Arena arena) { } public void openWizard(Player player, Arena arena, @Nullable Consumer contextConsumer) { - if (this.players.contains(player.getUniqueId())) { + if (player.hasMetadata(EDITOR_META_KEY)) { Messages.ERROR_ALREADY_IN_EDITOR.send(player); return; } @@ -97,7 +101,7 @@ public void openWizard(Player player, Arena arena, @Nullable Consumer context this.onCreationComplete.accept(context); } - this.players.remove(player.getUniqueId()); + player.removeMetadata(EDITOR_META_KEY, this.plugin); } else { WizardStage stage = iterator.next().getValue(); stage.enter(context); @@ -108,14 +112,16 @@ public void openWizard(Player player, Arena arena, @Nullable Consumer context contextConsumer.accept(context); } + Messages.ENTERED_WIZARD.send(player); + WizardStage initialStage = iterator.next().getValue(); initialStage.enter(context); - this.players.add(player.getUniqueId()); + player.setMetadata(EDITOR_META_KEY, new FixedMetadataValue(this.plugin, context)); } public void openSingleWizardStage(Player player, Arena arena, WizardStage stage, Consumer contextConsumer) { - if (this.players.contains(player.getUniqueId())) { + if (player.hasMetadata(EDITOR_META_KEY)) { Messages.ERROR_ALREADY_IN_EDITOR.send(player); return; } @@ -133,7 +139,7 @@ public void openSingleWizardStage(Player player, Arena arena, WizardStage sta this.onEditComplete.accept(context); } - this.players.remove(player.getUniqueId()); + player.removeMetadata(EDITOR_META_KEY, this.plugin); }); stage.enter(context); @@ -149,6 +155,19 @@ public WizardStage getStage(EditorKey key) { return this.stages.get(key.getKey()); } + public static boolean inWizard(Player player) { + return player.hasMetadata(EDITOR_META_KEY); + } + + public static > Optional wizardContext(Player player) { + return Optional.ofNullable(getWizardContext(player)); + } + + @SuppressWarnings("unchecked") + public static > E getWizardContext(Player player) { + return player.hasMetadata(EDITOR_META_KEY) ? (E) player.getMetadata(EDITOR_META_KEY).get(0).value() : null; + } + public interface ContextFactory> { E create(ArenaEditorWizard wizard, Arena arena, Player player); diff --git a/plugin/src/main/java/org/battleplugins/arena/editor/BaseEditorContext.java b/plugin/src/main/java/org/battleplugins/arena/editor/BaseEditorContext.java deleted file mode 100644 index 3cd45189..00000000 --- a/plugin/src/main/java/org/battleplugins/arena/editor/BaseEditorContext.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.battleplugins.arena.editor; - -import org.battleplugins.arena.Arena; -import org.bukkit.entity.Player; - -public class BaseEditorContext extends EditorContext { - public BaseEditorContext(ArenaEditorWizard wizard, Arena arena, Player player) { - super(wizard, arena, player); - } - - @Override - public boolean isComplete() { - return true; - } -} \ No newline at end of file diff --git a/plugin/src/main/java/org/battleplugins/arena/editor/EditorContext.java b/plugin/src/main/java/org/battleplugins/arena/editor/EditorContext.java index eacc8819..2afac118 100644 --- a/plugin/src/main/java/org/battleplugins/arena/editor/EditorContext.java +++ b/plugin/src/main/java/org/battleplugins/arena/editor/EditorContext.java @@ -1,8 +1,16 @@ package org.battleplugins.arena.editor; +import net.kyori.adventure.text.Component; import org.battleplugins.arena.Arena; import org.battleplugins.arena.BattleArena; +import org.battleplugins.arena.messages.Message; +import org.battleplugins.arena.messages.Messages; import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +import java.util.ArrayList; +import java.util.List; public abstract class EditorContext> { @@ -10,6 +18,9 @@ public abstract class EditorContext> { protected final Arena arena; protected final Player player; private Runnable advanceListener; + private int position; + + private final List boundListeners = new ArrayList<>(); protected boolean reconstructed; @@ -44,10 +55,36 @@ void setAdvanceListener(Runnable listener) { } public void advanceStage() { + this.position++; if (this.advanceListener != null) { this.advanceListener.run(); } } + public void bind(Listener listener) { + this.boundListeners.add(listener); + } + + public void unbind(Listener listener) { + this.boundListeners.remove(listener); + } + + public void cancel() { + this.boundListeners.forEach(HandlerList::unregisterAll); + this.boundListeners.clear(); + } + + public void inform(Message message) { + if (this.reconstructed) { + message.send(this.player); + return; + } + + Component positionMessage = Component.text("(" + (this.position + 1) + "/" + this.wizard.getStages().size() + ") ", Messages.SECONDARY_COLOR) + .append(message.toComponent()); + + this.player.sendMessage(positionMessage); + } + public abstract boolean isComplete(); } diff --git a/plugin/src/main/java/org/battleplugins/arena/editor/stage/PositionInputStage.java b/plugin/src/main/java/org/battleplugins/arena/editor/stage/PositionInputStage.java index 5bc7504c..4763bb30 100644 --- a/plugin/src/main/java/org/battleplugins/arena/editor/stage/PositionInputStage.java +++ b/plugin/src/main/java/org/battleplugins/arena/editor/stage/PositionInputStage.java @@ -24,7 +24,7 @@ public PositionInputStage(Message chatMessage, Function> i @Override public void enter(E context) { if (this.chatMessage != null) { - this.chatMessage.send(context.getPlayer()); + context.inform(this.chatMessage); } new InteractionInputs.PositionInput(context.getPlayer()) { @@ -34,6 +34,6 @@ public void onPositionInteract(Location position) { inputConsumer.apply(context).accept(position); context.advanceStage(); } - }; + }.bind(context); } } \ No newline at end of file diff --git a/plugin/src/main/java/org/battleplugins/arena/editor/stage/SpawnInputStage.java b/plugin/src/main/java/org/battleplugins/arena/editor/stage/SpawnInputStage.java index f376edf7..82c2ad78 100644 --- a/plugin/src/main/java/org/battleplugins/arena/editor/stage/SpawnInputStage.java +++ b/plugin/src/main/java/org/battleplugins/arena/editor/stage/SpawnInputStage.java @@ -23,7 +23,7 @@ public SpawnInputStage(Message chatMessage, String input, Function doneC @Override public void enter(E context) { if (this.chatMessage != null) { - this.chatMessage.send(context.getPlayer()); + context.inform(this.chatMessage); } this.enterWizardConversation(context); @@ -62,11 +60,6 @@ public void onChatInput(String input) { return; } - if ("cancel".equalsIgnoreCase(input)) { - context.getWizard().onCancel(context); - return; - } - boolean clear = "clear".equalsIgnoreCase(input); // We have the location, now we need to get the team name @@ -96,11 +89,6 @@ public void onChatInput(String input) { @Override public void onChatInput(String input) { - if ("cancel".equalsIgnoreCase(input)) { - context.getWizard().onCancel(context); - return; - } - if (clear) { clearConsumer.apply(context).accept(input); @@ -117,15 +105,15 @@ public void onChatInput(String input) { @Override public boolean isValidChatInput(String input) { - return TextInputStage.isCancel(input) || (!input.startsWith("/") && teamNames.contains(input)); + return !input.startsWith("/") && teamNames.contains(input); } - }; + }.bind(context); } @Override public boolean isValidChatInput(String input) { - return input.equalsIgnoreCase("clear") || TextInputStage.isCancelOrDone(input) || (!input.startsWith("/") && TeamSpawnInputStage.this.input.equalsIgnoreCase(input)); + return input.equalsIgnoreCase("clear") || input.equalsIgnoreCase("done") || (!input.startsWith("/") && TeamSpawnInputStage.this.input.equalsIgnoreCase(input)); } - }; + }.bind(context); } } \ No newline at end of file diff --git a/plugin/src/main/java/org/battleplugins/arena/editor/stage/TextInputStage.java b/plugin/src/main/java/org/battleplugins/arena/editor/stage/TextInputStage.java index bd7d3f4c..1c35e2e6 100644 --- a/plugin/src/main/java/org/battleplugins/arena/editor/stage/TextInputStage.java +++ b/plugin/src/main/java/org/battleplugins/arena/editor/stage/TextInputStage.java @@ -29,34 +29,21 @@ public TextInputStage(Message chatMessage, Message invalidInputMessage, BiFuncti @Override public void enter(E context) { if (this.chatMessage != null) { - this.chatMessage.send(context.getPlayer()); + context.inform(this.chatMessage); } new InteractionInputs.ChatInput(context.getPlayer(), this.invalidInputMessage) { @Override public void onChatInput(String input) { - if ("cancel".equalsIgnoreCase(input)) { - context.getWizard().onCancel(context); - return; - } - inputConsumer.apply(context).accept(input); context.advanceStage(); } @Override public boolean isValidChatInput(String input) { - return isCancel(input) || (!input.startsWith("/") && (validContentFunction == null || validContentFunction.apply(context, input))); + return (!input.startsWith("/") && (validContentFunction == null || validContentFunction.apply(context, input))); } - }; - } - - static boolean isCancel(String input) { - return "cancel".equalsIgnoreCase(input); - } - - static boolean isCancelOrDone(String input) { - return "cancel".equalsIgnoreCase(input) || "done".equalsIgnoreCase(input); + }.bind(context); } } \ No newline at end of file diff --git a/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java b/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java index 7547f078..f982c8e2 100644 --- a/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java +++ b/plugin/src/main/java/org/battleplugins/arena/messages/Messages.java @@ -89,8 +89,15 @@ public final class Messages { public static final Message FIGHT = message("arena-fight", " O---[{==========> Fight <==========}]---O "); // Editor wizard messages + public static final Message ENTERED_WIZARD = info("editor-entered", """ + + You have just entered a wizard editor. Follow the steps that are prompted to advance through the wizard. + + At any point, you can type "cancel" to exit. + """ + ); public static final Message ERROR_OCCURRED_APPLYING_CHANGES = error("editor-error-occurred-applying-changes", "An error occurred while applying changes. Please see the console for more information!"); - public static final Message ERROR_ALREADY_IN_EDITOR = error("editor-error-already-in-editor", "You are already in an editor!"); + public static final Message ERROR_ALREADY_IN_EDITOR = error("editor-error-already-in-editor", "You are already in an editor wizard!"); public static final Message MAP_CREATE_NAME = info("editor-map-create-name", "Enter a name for the map! Type \"cancel\" to cancel."); public static final Message MAP_EXISTS = error("editor-map-exists", "A map by that name already exists!"); diff --git a/plugin/src/main/java/org/battleplugins/arena/util/InteractionInputs.java b/plugin/src/main/java/org/battleplugins/arena/util/InteractionInputs.java index 36adf8d8..054b31ec 100644 --- a/plugin/src/main/java/org/battleplugins/arena/util/InteractionInputs.java +++ b/plugin/src/main/java/org/battleplugins/arena/util/InteractionInputs.java @@ -3,6 +3,7 @@ import io.papermc.paper.event.player.AsyncChatEvent; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.battleplugins.arena.BattleArena; +import org.battleplugins.arena.editor.EditorContext; import org.battleplugins.arena.messages.Message; import org.battleplugins.arena.messages.Messages; import org.bukkit.Bukkit; @@ -17,13 +18,16 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; public class InteractionInputs { - public static abstract class ChatInput { + public static abstract class ChatInput extends InputListener { + private final Message invalidInput; /** * Constructs a new ChatInput instance @@ -31,9 +35,16 @@ public static abstract class ChatInput { * @param player the player to receive the chat input from */ public ChatInput(Player player, Message invalidInput) { - Listener listener = new Listener() { + super(player); - @EventHandler + this.invalidInput = invalidInput; + } + + @Override + Listener createListener(Player player) { + return new Listener() { + + @EventHandler(ignoreCancelled = true) public void onChat(AsyncChatEvent event) { if (!player.equals(event.getPlayer())) { return; @@ -55,10 +66,9 @@ public void onChat(AsyncChatEvent event) { }); HandlerList.unregisterAll(this); + unbind(); } }; - - Bukkit.getPluginManager().registerEvents(listener, BattleArena.getInstance()); } /** @@ -79,7 +89,7 @@ public boolean isValidChatInput(String input) { } } - public static abstract class InventoryInput { + public static abstract class InventoryInput extends InputListener { /** * Constructs a new InventoryInput instance @@ -87,7 +97,12 @@ public static abstract class InventoryInput { * @param player the player to receive the inventory input from */ public InventoryInput(Player player) { - Listener listener = new Listener() { + super(player); + } + + @Override + Listener createListener(Player player) { + return new Listener() { @EventHandler public void onInventoryClick(InventoryClickEvent event) { @@ -98,6 +113,7 @@ public void onInventoryClick(InventoryClickEvent event) { if (!player.getInventory().equals(event.getClickedInventory())) { Messages.INVALID_INVENTORY_CANCELLING.send(player); HandlerList.unregisterAll(this); + unbind(); return; } @@ -119,10 +135,9 @@ public void onInventoryClick(InventoryClickEvent event) { player.updateInventory(); HandlerList.unregisterAll(this); + unbind(); } }; - - Bukkit.getPluginManager().registerEvents(listener, BattleArena.getInstance()); } /** @@ -134,7 +149,7 @@ public void onInventoryClick(InventoryClickEvent event) { public abstract void onInventoryInteract(ItemStack item); } - public static abstract class PositionInput { + public static abstract class PositionInput extends InputListener { private static final Map LAST_INPUT = new HashMap<>(); /** @@ -143,7 +158,12 @@ public static abstract class PositionInput { * @param player the player to receive the position input from */ public PositionInput(Player player) { - Listener listener = new Listener() { + super(player); + } + + @Override + Listener createListener(Player player) { + return new Listener() { @EventHandler public void onInteract(PlayerInteractEvent event) { @@ -169,10 +189,9 @@ public void onInteract(PlayerInteractEvent event) { event.setCancelled(true); HandlerList.unregisterAll(this); + unbind(); } }; - - Bukkit.getPluginManager().registerEvents(listener, BattleArena.getInstance()); } /** @@ -182,4 +201,28 @@ public void onInteract(PlayerInteractEvent event) { */ public abstract void onPositionInteract(Location position); } + + public abstract static class InputListener { + private final Listener listener; + private final List unregisterHandlers = new ArrayList<>(); + + public InputListener(Player player) { + this.listener = this.createListener(player); + + Bukkit.getPluginManager().registerEvents(this.listener, BattleArena.getInstance()); + } + + abstract Listener createListener(Player player); + + public > void bind(EditorContext context) { + context.bind(this.listener); + + this.unregisterHandlers.add(() -> context.unbind(this.listener)); + } + + public void unbind() { + this.unregisterHandlers.forEach(Runnable::run); + this.unregisterHandlers.clear(); + } + } } \ No newline at end of file