From 982fc10e72738660e3a2d15bfcf87f433f4d6ed3 Mon Sep 17 00:00:00 2001 From: PeyaPeyaPeyang Date: Tue, 26 Sep 2023 06:26:22 +0900 Subject: [PATCH] feat(actions): TabComplete action --- .idea/fileTemplates/PlayerAction.java | 1 - .../actions/server/AbstractServerAction.java | 1 + .../actions/server/CommandDispatchAction.java | 15 +- .../actions/server/TabCompleteAction.java | 161 ++++++++++++++++++ .../action/utils/CommandSenders.java | 52 ++++++ .../scenamatica/commons/utils/MapUtils.java | 3 + .../actions/server/TabComplete-2.yml | 28 +++ .../actions/server/TabComplete-3.yml | 28 +++ .../actions/server/TabComplete-4.yml | 27 +++ .../actions/server/TabComplete-5.yml | 24 +++ .../actions/server/TabComplete-6.yml | 29 ++++ .../actions/server/TabComplete-7.yml | 28 +++ .../scenarios/actions/server/TabComplete.yml | 29 ++++ 13 files changed, 415 insertions(+), 11 deletions(-) create mode 100644 ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/TabCompleteAction.java create mode 100644 ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/utils/CommandSenders.java create mode 100644 ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-2.yml create mode 100644 ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-3.yml create mode 100644 ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-4.yml create mode 100644 ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-5.yml create mode 100644 ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-6.yml create mode 100644 ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-7.yml create mode 100644 ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete.yml diff --git a/.idea/fileTemplates/PlayerAction.java b/.idea/fileTemplates/PlayerAction.java index cb6a72aae..e6eb5107f 100644 --- a/.idea/fileTemplates/PlayerAction.java +++ b/.idea/fileTemplates/PlayerAction.java @@ -3,7 +3,6 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.kunlab.scenamatica.action.actions.AbstractAction; -import org.kunlab.scenamatica.interfaces.action.ActionArgument; import org.kunlab.scenamatica.interfaces.scenario.ScenarioEngine; import org.kunlab.scenamatica.interfaces.scenariofile.BeanSerializer; import org.kunlab.scenamatica.interfaces.scenariofile.trigger.TriggerArgument; diff --git a/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/AbstractServerAction.java b/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/AbstractServerAction.java index d84ca0146..73e4d9b83 100644 --- a/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/AbstractServerAction.java +++ b/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/AbstractServerAction.java @@ -20,6 +20,7 @@ public static List> getActions() actions.add(new PluginEnableAction()); actions.add(new BroadcastMessageAction()); actions.add(new CommandDispatchAction()); + actions.add(new TabCompleteAction()); actions.add(new WhitelistToggleAction()); return actions; diff --git a/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/CommandDispatchAction.java b/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/CommandDispatchAction.java index dc9d8d604..5b9158b9e 100644 --- a/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/CommandDispatchAction.java +++ b/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/CommandDispatchAction.java @@ -2,7 +2,6 @@ import lombok.EqualsAndHashCode; import lombok.Value; -import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.event.Event; @@ -11,7 +10,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.kunlab.scenamatica.action.actions.AbstractActionArgument; -import org.kunlab.scenamatica.action.utils.PlayerUtils; +import org.kunlab.scenamatica.action.utils.CommandSenders; import org.kunlab.scenamatica.commons.utils.MapUtils; import org.kunlab.scenamatica.enums.ScenarioType; import org.kunlab.scenamatica.interfaces.action.types.Executable; @@ -43,13 +42,9 @@ public void execute(@NotNull ScenarioEngine engine, @Nullable CommandDispatchAct { argument = this.requireArgsNonNull(argument); - CommandSender sender; - if (argument.sender == null) - sender = Bukkit.getConsoleSender(); - else - sender = PlayerUtils.getPlayerOrThrow(argument.sender); + CommandSender sender = CommandSenders.resolveSenderOrConsoleOrThrow(argument.getSender()); - String command = argument.command; + String command = argument.getCommand(); if (command.startsWith("/")) // シンタックスシュガーのために, / から始まるやつにも対応 command = command.substring(1); @@ -73,13 +68,13 @@ else if (event instanceof PlayerCommandPreprocessEvent) // player if (argument.getCommand() != null) { - Pattern pattern = Pattern.compile(argument.command); + Pattern pattern = Pattern.compile(argument.getCommand()); Matcher matcher = pattern.matcher(command); if (!matcher.matches()) return false; } - return argument.sender == null || sender != null && StringUtils.equalsIgnoreCase(argument.sender, sender.getName()); + return CommandSenders.isSpecifiedSender(sender, argument.getSender()); } @Override diff --git a/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/TabCompleteAction.java b/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/TabCompleteAction.java new file mode 100644 index 000000000..c6258c4eb --- /dev/null +++ b/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/actions/server/TabCompleteAction.java @@ -0,0 +1,161 @@ +package org.kunlab.scenamatica.action.actions.server; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.bukkit.command.CommandSender; +import org.bukkit.event.Event; +import org.bukkit.event.server.TabCompleteEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.kunlab.scenamatica.action.actions.AbstractActionArgument; +import org.kunlab.scenamatica.action.utils.CommandSenders; +import org.kunlab.scenamatica.commons.utils.MapUtils; +import org.kunlab.scenamatica.enums.ScenarioType; +import org.kunlab.scenamatica.interfaces.action.types.Executable; +import org.kunlab.scenamatica.interfaces.action.types.Watchable; +import org.kunlab.scenamatica.interfaces.scenario.ScenarioEngine; +import org.kunlab.scenamatica.interfaces.scenariofile.BeanSerializer; +import org.kunlab.scenamatica.interfaces.scenariofile.trigger.TriggerArgument; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TabCompleteAction extends AbstractServerAction + implements Executable, Watchable +{ + + public static final String KEY_ACTION_NAME = "tab_complete"; + + private static boolean checkCompletions(@Nullable List expected, List actual, boolean strict) + { + if (expected == null) + return true; + + if (strict && expected.size() != actual.size()) + return false; + + for (String expectedCompletion : expected) + { + Pattern pattern = Pattern.compile(expectedCompletion); + if (actual.stream().noneMatch(pattern.asPredicate())) + return false; + } + + return true; + } + + @Override + public String getName() + { + return KEY_ACTION_NAME; + } + + @Override + public void execute(@NotNull ScenarioEngine engine, @Nullable Argument argument) + { + argument = this.requireArgsNonNull(argument); + + CommandSender sender = CommandSenders.resolveSenderOrConsoleOrThrow(argument.getSender()); + String buffer = argument.getBuffer(); + List completions = argument.getCompletions(); + if (completions == null) + completions = new ArrayList<>(); // Collections.emptyList() だと不変なのでプラグインのイベントハンドラがバグるかも。 + + TabCompleteEvent event = new TabCompleteEvent(sender, buffer, completions); + engine.getPlugin().getServer().getPluginManager().callEvent(event); + } + + @Override + public boolean isFired(@NotNull Argument argument, @NotNull ScenarioEngine engine, @NotNull Event event) + { + assert event instanceof TabCompleteEvent; + TabCompleteEvent e = (TabCompleteEvent) event; + + if (argument.getBuffer() != null) + { + String expectedBuffer = argument.getBuffer(); + String actualBuffer = e.getBuffer(); + + Pattern pattern = Pattern.compile(expectedBuffer); + Matcher matcher = pattern.matcher(actualBuffer); + + if (!matcher.matches()) + return false; + } + + + return checkCompletions(argument.getCompletions(), e.getCompletions(), argument.isStrict()) + && CommandSenders.isSpecifiedSender(e.getSender(), argument.getSender()); + } + + @Override + public List> getAttachingEvents() + { + return Collections.singletonList( + TabCompleteEvent.class + ); + } + + @Override + public Argument deserializeArgument(@NotNull Map map, @NotNull BeanSerializer serializer) + { + return new Argument( + (String) map.get(Argument.KEY_SENDER), + (String) map.get(Argument.KEY_BUFFER), + MapUtils.getAsListOrNull(map, Argument.KEY_COMPLETIONS), + MapUtils.getOrDefault(map, Argument.KEY_STRICT, true) + ); + } + + @Value + @EqualsAndHashCode(callSuper = true) + public static class Argument extends AbstractActionArgument + { + public static final String KEY_SENDER = "sender"; + public static final String KEY_BUFFER = "buffer"; + public static final String KEY_COMPLETIONS = "completions"; + public static final String KEY_STRICT = "strict"; + + String sender; + String buffer; + List completions; + boolean strict; + + @Override + public boolean isSame(TriggerArgument argument) + { + if (!(argument instanceof Argument)) + return false; + + Argument arg = (Argument) argument; + + return Objects.equals(this.sender, arg.sender) + && Objects.equals(this.buffer, arg.buffer) + && MapUtils.equals(this.completions, arg.completions + ); + } + + @Override + public void validate(@NotNull ScenarioEngine engine, @NotNull ScenarioType type) + { + if (type == ScenarioType.ACTION_EXECUTE) + throwIfNotPresent(KEY_BUFFER, this.buffer); + } + + @Override + public String getArgumentString() + { + return buildArgumentString( + KEY_SENDER, this.sender, + KEY_BUFFER, this.buffer, + KEY_COMPLETIONS, this.completions, + KEY_STRICT, this.strict + ); + } + } +} diff --git a/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/utils/CommandSenders.java b/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/utils/CommandSenders.java new file mode 100644 index 000000000..c4e63afe5 --- /dev/null +++ b/ScenamaticaActionEngine/src/main/java/org/kunlab/scenamatica/action/utils/CommandSenders.java @@ -0,0 +1,52 @@ +package org.kunlab.scenamatica.action.utils; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class CommandSenders +{ + public static final String CONSOLE_SENDER = ""; + + public static boolean isSpecifiedSender(CommandSender actualSender, String expectedSender) + { + if (expectedSender == null) + return true; + + if (!(actualSender instanceof Player) && expectedSender.equalsIgnoreCase(CONSOLE_SENDER)) + return true; + + return actualSender.getName().equalsIgnoreCase(expectedSender); + } + + public static CommandSender getCommandSenderOrThrow(String specifier) + { + if (specifier == null) + throw new IllegalArgumentException("specifier is null"); + + CommandSender sender = getCommandSenderOrNull(specifier); + if (sender == null) + throw new IllegalArgumentException("specifier is invalid"); + else + return sender; + } + + public static CommandSender getCommandSenderOrNull(String specifier) + { + if (specifier == null) + return null; + + if (specifier.equalsIgnoreCase(CONSOLE_SENDER)) + return Bukkit.getConsoleSender(); + + return PlayerUtils.getPlayerOrNull(specifier); + } + + public static CommandSender resolveSenderOrConsoleOrThrow(String specifier) + { + if (specifier == null) + return Bukkit.getConsoleSender(); + + return getCommandSenderOrThrow(specifier); + } +} diff --git a/ScenamaticaCommons/src/main/java/org/kunlab/scenamatica/commons/utils/MapUtils.java b/ScenamaticaCommons/src/main/java/org/kunlab/scenamatica/commons/utils/MapUtils.java index 807b64eb2..961b61c70 100644 --- a/ScenamaticaCommons/src/main/java/org/kunlab/scenamatica/commons/utils/MapUtils.java +++ b/ScenamaticaCommons/src/main/java/org/kunlab/scenamatica/commons/utils/MapUtils.java @@ -55,6 +55,9 @@ else if (entry.getValue() instanceof List) public static boolean equals(List expected, List actual) { + if (expected == null || actual == null) + return expected == actual; + if (expected.size() != actual.size()) return false; for (int i = 0; i < expected.size(); i++) diff --git a/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-2.yml b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-2.yml new file mode 100644 index 000000000..2aed94a22 --- /dev/null +++ b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-2.yml @@ -0,0 +1,28 @@ +# noinspection YAMLSchemaValidation +scenamatica: ${project.version} + +name: actions_server_tab_complete_2 +description: Testing tab_complete action without sender works or not +on: + - type: on_load + - type: manual_dispatch + +context: + actors: + - name: Actor001 + +scenario: + - type: execute + action: tab_complete + with: + sender: Actor001 + buffer: /say Hello + completions: + - World! + - type: expect + action: tab_complete + with: + buffer: /say Hello + completions: + - World! + diff --git a/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-3.yml b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-3.yml new file mode 100644 index 000000000..ece984778 --- /dev/null +++ b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-3.yml @@ -0,0 +1,28 @@ +# noinspection YAMLSchemaValidation +scenamatica: ${project.version} + +name: actions_server_tab_complete_3 +description: Testing tab_complete action without buffer works or not +on: + - type: on_load + - type: manual_dispatch + +context: + actors: + - name: Actor001 + +scenario: + - type: execute + action: tab_complete + with: + sender: Actor001 + buffer: /say Hello + completions: + - World! + - type: expect + action: tab_complete + with: + sender: Actor001 + completions: + - World! + diff --git a/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-4.yml b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-4.yml new file mode 100644 index 000000000..6d8bef081 --- /dev/null +++ b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-4.yml @@ -0,0 +1,27 @@ +# noinspection YAMLSchemaValidation +scenamatica: ${project.version} + +name: actions_server_tab_complete_4 +description: Testing tab_complete action without completions works or not +on: + - type: on_load + - type: manual_dispatch + +context: + actors: + - name: Actor001 + +scenario: + - type: execute + action: tab_complete + with: + sender: Actor001 + buffer: /say Hello + completions: + - World! + - type: expect + action: tab_complete + with: + sender: Actor001 + buffer: /say Hello + diff --git a/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-5.yml b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-5.yml new file mode 100644 index 000000000..bfcba8a95 --- /dev/null +++ b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-5.yml @@ -0,0 +1,24 @@ +# noinspection YAMLSchemaValidation +scenamatica: ${project.version} + +name: actions_server_tab_complete_5 +description: Testing tab_complete action without argument works or not +on: + - type: on_load + - type: manual_dispatch + +context: + actors: + - name: Actor001 + +scenario: + - type: execute + action: tab_complete + with: + sender: Actor001 + buffer: /say Hello + completions: + - World! + - type: expect + action: tab_complete + diff --git a/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-6.yml b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-6.yml new file mode 100644 index 000000000..9bed75408 --- /dev/null +++ b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-6.yml @@ -0,0 +1,29 @@ +# noinspection YAMLSchemaValidation +scenamatica: ${project.version} + +name: actions_server_tab_complete_6 +description: Testing tab_complete action by console works or not +on: + - type: on_load + - type: manual_dispatch + +context: + actors: + - name: Actor001 + +scenario: + - type: execute + action: tab_complete + with: + sender: + buffer: /say Hello + completions: + - World! + - type: expect + action: tab_complete + with: + sender: + buffer: /say Hello + completions: + - World! + diff --git a/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-7.yml b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-7.yml new file mode 100644 index 000000000..6139023dc --- /dev/null +++ b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete-7.yml @@ -0,0 +1,28 @@ +# noinspection YAMLSchemaValidation +scenamatica: ${project.version} + +name: actions_server_tab_complete_7 +description: Testing tab_complete action by implicit console works or not +on: + - type: on_load + - type: manual_dispatch + +context: + actors: + - name: Actor001 + +scenario: + - type: execute + action: tab_complete + with: + buffer: /say Hello + completions: + - World! + - type: expect + action: tab_complete + with: + sender: + buffer: /say Hello + completions: + - World! + diff --git a/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete.yml b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete.yml new file mode 100644 index 000000000..3a86ac84c --- /dev/null +++ b/ScenamaticaPlugin/src/main/resources/scenarios/actions/server/TabComplete.yml @@ -0,0 +1,29 @@ +# noinspection YAMLSchemaValidation +scenamatica: ${project.version} + +name: actions_server_tab_complete +description: Testing tab_complete action works or not +on: + - type: on_load + - type: manual_dispatch + +context: + actors: + - name: Actor001 + +scenario: + - type: execute + action: tab_complete + with: + sender: Actor001 + buffer: /say Hello + completions: + - World! + - type: expect + action: tab_complete + with: + sender: Actor001 + buffer: /say Hello + completions: + - World! +