From 67c177ecb50e4db98417d3976c94aaf21474454f Mon Sep 17 00:00:00 2001 From: Carson Fleming Date: Wed, 15 Oct 2025 23:23:08 -0400 Subject: more compelling magic system --- .../java/net/cflems/mc/sandbox/CommandUtils.java | 38 ++++++++++++++ .../net/cflems/mc/sandbox/DelegableCommand.java | 24 +++++++++ .../cflems/mc/sandbox/PlayerTargetedCommand.java | 16 +++--- src/main/java/net/cflems/mc/sandbox/Sandbox.java | 12 +++-- .../net/cflems/mc/sandbox/commands/Fireball.java | 17 ++++--- .../java/net/cflems/mc/sandbox/commands/PTSU.java | 59 ++++++++++++++++++++++ .../net/cflems/mc/sandbox/commands/PowerTool.java | 20 +++++--- .../mc/sandbox/listeners/PowerToolListener.java | 7 ++- .../sandbox/storage/DeserializationException.java | 7 +++ .../mc/sandbox/storage/PowerToolBinding.java | 32 ++++++++++++ .../cflems/mc/sandbox/storage/PowerToolStore.java | 37 +++++++++++--- src/main/resources/plugin.yml | 10 +++- 12 files changed, 241 insertions(+), 38 deletions(-) create mode 100644 src/main/java/net/cflems/mc/sandbox/DelegableCommand.java create mode 100644 src/main/java/net/cflems/mc/sandbox/commands/PTSU.java create mode 100644 src/main/java/net/cflems/mc/sandbox/storage/DeserializationException.java create mode 100644 src/main/java/net/cflems/mc/sandbox/storage/PowerToolBinding.java (limited to 'src') diff --git a/src/main/java/net/cflems/mc/sandbox/CommandUtils.java b/src/main/java/net/cflems/mc/sandbox/CommandUtils.java index a954c5b..781c332 100644 --- a/src/main/java/net/cflems/mc/sandbox/CommandUtils.java +++ b/src/main/java/net/cflems/mc/sandbox/CommandUtils.java @@ -1,9 +1,16 @@ package net.cflems.mc.sandbox; +import java.util.Arrays; +import java.util.UUID; +import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; public final class CommandUtils { + public static final UUID UUID_ZERO = new UUID(0, 0); private static final String RESPONSE_PREFIX = ChatColor.GOLD + "[Sandbox] " + ChatColor.RESET; private CommandUtils() {} @@ -11,4 +18,35 @@ public final class CommandUtils { public static void respond(CommandSender sender, String message) { sender.sendMessage(RESPONSE_PREFIX + message); } + + public static UUID getUniqueId(CommandSender sender) { + return sender instanceof Player player ? player.getUniqueId() : UUID_ZERO; + } + + public static void delegateCommand(Player player, UUID effectivePlayerId, String command) { + CommandSender effectiveSender = + effectivePlayerId.equals(UUID_ZERO) + ? Bukkit.getConsoleSender() + : Bukkit.getPlayer(effectivePlayerId); + if (effectiveSender == null) { + player.performCommand(command); + return; + } + + String[] args = command.split(" "); + PluginCommand pluginCommand = Bukkit.getPluginCommand(args[0]); + if (pluginCommand == null) { + player.performCommand(command); + return; + } + + CommandExecutor commandExecutor = pluginCommand.getExecutor(); + if (!(commandExecutor instanceof DelegableCommand delegableCommand)) { + player.performCommand(command); + return; + } + + delegableCommand.executeAs( + player, effectiveSender, pluginCommand, args[0], Arrays.copyOfRange(args, 1, args.length)); + } } diff --git a/src/main/java/net/cflems/mc/sandbox/DelegableCommand.java b/src/main/java/net/cflems/mc/sandbox/DelegableCommand.java new file mode 100644 index 0000000..073a427 --- /dev/null +++ b/src/main/java/net/cflems/mc/sandbox/DelegableCommand.java @@ -0,0 +1,24 @@ +package net.cflems.mc.sandbox; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public abstract class DelegableCommand implements CommandExecutor { + public abstract boolean executeAs( + CommandSender sender, + CommandSender effectiveSender, + Command command, + String label, + String[] args); + + @Override + public boolean onCommand( + @NotNull CommandSender sender, + @NotNull Command command, + @NotNull String label, + @NotNull String[] args) { + return executeAs(sender, sender, command, label, args); + } +} diff --git a/src/main/java/net/cflems/mc/sandbox/PlayerTargetedCommand.java b/src/main/java/net/cflems/mc/sandbox/PlayerTargetedCommand.java index 8a553bc..85a335d 100644 --- a/src/main/java/net/cflems/mc/sandbox/PlayerTargetedCommand.java +++ b/src/main/java/net/cflems/mc/sandbox/PlayerTargetedCommand.java @@ -3,11 +3,10 @@ package net.cflems.mc.sandbox; import com.google.errorprone.annotations.CompileTimeConstant; import java.util.Arrays; import java.util.function.Consumer; -import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -public abstract class PlayerTargetedCommand implements CommandExecutor { +public abstract class PlayerTargetedCommand extends DelegableCommand { private final int minArgs; private final int maxArgs; private final String permission; @@ -45,11 +44,14 @@ public abstract class PlayerTargetedCommand implements CommandExecutor { this.othersPermissionMessage = othersPermissionMessage; } - @SuppressWarnings("NullableProblems") @Override - public final boolean onCommand( - CommandSender sender, org.bukkit.command.Command _command, String label, String[] args) { - if (!sender.hasPermission(permission)) { + public final boolean executeAs( + CommandSender sender, + CommandSender effectiveSender, + org.bukkit.command.Command _command, + String label, + String[] args) { + if (!effectiveSender.hasPermission(permission)) { CommandUtils.respond(sender, permissionMessage); return true; } @@ -66,7 +68,7 @@ public abstract class PlayerTargetedCommand implements CommandExecutor { sender, "Specified player not found, make sure to refer to them by name."); return true; } - if (player != sender && !sender.hasPermission(othersPermission)) { + if (player != sender && !effectiveSender.hasPermission(othersPermission)) { CommandUtils.respond(sender, othersPermissionMessage); return true; } diff --git a/src/main/java/net/cflems/mc/sandbox/Sandbox.java b/src/main/java/net/cflems/mc/sandbox/Sandbox.java index 34127f1..63439ce 100644 --- a/src/main/java/net/cflems/mc/sandbox/Sandbox.java +++ b/src/main/java/net/cflems/mc/sandbox/Sandbox.java @@ -1,10 +1,6 @@ package net.cflems.mc.sandbox; -import net.cflems.mc.sandbox.commands.Enchant; -import net.cflems.mc.sandbox.commands.Fireball; -import net.cflems.mc.sandbox.commands.Fly; -import net.cflems.mc.sandbox.commands.PowerTool; -import net.cflems.mc.sandbox.commands.Repair; +import net.cflems.mc.sandbox.commands.*; import net.cflems.mc.sandbox.listeners.PowerToolListener; import net.cflems.mc.sandbox.storage.PowerToolStore; import org.bukkit.plugin.java.JavaPlugin; @@ -12,14 +8,20 @@ import org.bukkit.plugin.java.JavaPlugin; public class Sandbox extends JavaPlugin { public static final String PERMISSION_NAMESPACE = "sandbox"; + @SuppressWarnings("DataFlowIssue") @Override public void onEnable() { + PowerToolStore.registerSerializables(); PowerToolStore powerToolStore = new PowerToolStore(getDataFolder()); getServer().getPluginManager().registerEvents(new PowerToolListener(powerToolStore), this); + this.getCommand("enchant").setExecutor(new Enchant()); this.getCommand("fly").setExecutor(new Fly()); this.getCommand("repair").setExecutor(new Repair()); this.getCommand("fireball").setExecutor(new Fireball()); + + PowerTool powerTool = new PowerTool(powerToolStore); this.getCommand("powertool").setExecutor(new PowerTool(powerToolStore)); + this.getCommand("ptsu").setExecutor(new PTSU(powerTool)); } } diff --git a/src/main/java/net/cflems/mc/sandbox/commands/Fireball.java b/src/main/java/net/cflems/mc/sandbox/commands/Fireball.java index 049c554..447d9dd 100644 --- a/src/main/java/net/cflems/mc/sandbox/commands/Fireball.java +++ b/src/main/java/net/cflems/mc/sandbox/commands/Fireball.java @@ -1,33 +1,36 @@ package net.cflems.mc.sandbox.commands; import net.cflems.mc.sandbox.CommandUtils; +import net.cflems.mc.sandbox.DelegableCommand; import net.cflems.mc.sandbox.Sandbox; -import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -public final class Fireball implements CommandExecutor { +public final class Fireball extends DelegableCommand { private static final String PERMISSION = Sandbox.PERMISSION_NAMESPACE + ".fireball"; private static final String CUSTOM_PERMISSION = PERMISSION + ".custom"; private static final float DEFAULT_POWER = 5.0f; - @SuppressWarnings("NullableProblems") @Override - public boolean onCommand( - CommandSender sender, org.bukkit.command.Command _command, String _label, String[] args) { + public boolean executeAs( + CommandSender sender, + CommandSender effectiveSender, + org.bukkit.command.Command _command, + String _label, + String[] args) { if (args.length > 1) return false; if (!(sender instanceof Player player)) { CommandUtils.respond(sender, "Only players can use fireballs."); return true; } - if (!sender.hasPermission(PERMISSION)) { + if (!effectiveSender.hasPermission(PERMISSION)) { CommandUtils.respond(sender, "You do not have permission to use fireballs."); return true; } float yield = DEFAULT_POWER; if (args.length > 0) { - if (!sender.hasPermission(CUSTOM_PERMISSION)) { + if (!effectiveSender.hasPermission(CUSTOM_PERMISSION)) { CommandUtils.respond( sender, "You do not have permission to use fireballs of custom power."); return true; diff --git a/src/main/java/net/cflems/mc/sandbox/commands/PTSU.java b/src/main/java/net/cflems/mc/sandbox/commands/PTSU.java new file mode 100644 index 0000000..6d28ba3 --- /dev/null +++ b/src/main/java/net/cflems/mc/sandbox/commands/PTSU.java @@ -0,0 +1,59 @@ +package net.cflems.mc.sandbox.commands; + +import java.util.Arrays; +import java.util.UUID; +import net.cflems.mc.sandbox.CommandUtils; +import net.cflems.mc.sandbox.Sandbox; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class PTSU implements CommandExecutor { + private static final String PERMISSION = Sandbox.PERMISSION_NAMESPACE + ".ptsu"; + + private final PowerTool powerToolExecutor; + + public PTSU(PowerTool powerToolExecutor) { + this.powerToolExecutor = powerToolExecutor; + } + + @Override + public boolean onCommand( + @NotNull CommandSender sender, + @NotNull Command command, + @NotNull String label, + @NotNull String[] args) { + if (args.length < 1) return false; + + if (!sender.hasPermission(PERMISSION)) { + CommandUtils.respond( + sender, "You do not have permission to use power tools as other players."); + return true; + } + + CommandSender effectiveSender; + try { + UUID playerId = UUID.fromString(args[0]); + if (playerId.equals(CommandUtils.UUID_ZERO)) { + effectiveSender = Bukkit.getConsoleSender(); + } else { + effectiveSender = Bukkit.getPlayer(playerId); + } + } catch (IllegalArgumentException _e) { + if (args[0].equalsIgnoreCase("console")) { + effectiveSender = Bukkit.getConsoleSender(); + } else { + effectiveSender = Bukkit.getPlayer(args[0]); + } + } + + if (effectiveSender == null) { + CommandUtils.respond(sender, "That player does not exist or is not online."); + } + + return powerToolExecutor.executeAs( + sender, effectiveSender, command, label, Arrays.copyOfRange(args, 1, args.length)); + } +} diff --git a/src/main/java/net/cflems/mc/sandbox/commands/PowerTool.java b/src/main/java/net/cflems/mc/sandbox/commands/PowerTool.java index e4683f6..063f5b3 100644 --- a/src/main/java/net/cflems/mc/sandbox/commands/PowerTool.java +++ b/src/main/java/net/cflems/mc/sandbox/commands/PowerTool.java @@ -1,16 +1,16 @@ package net.cflems.mc.sandbox.commands; import net.cflems.mc.sandbox.CommandUtils; +import net.cflems.mc.sandbox.DelegableCommand; import net.cflems.mc.sandbox.Sandbox; import net.cflems.mc.sandbox.storage.NoMetaException; import net.cflems.mc.sandbox.storage.PowerToolStore; import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -public final class PowerTool implements CommandExecutor { +public final class PowerTool extends DelegableCommand { private static final String PERMISSION = Sandbox.PERMISSION_NAMESPACE + ".powertool"; private static final String RELOAD_PERMISSION = PERMISSION + ".reload"; @@ -20,11 +20,15 @@ public final class PowerTool implements CommandExecutor { this.store = store; } - @SuppressWarnings("NullableProblems") @Override - public boolean onCommand(CommandSender sender, Command _command, String _label, String[] args) { + public boolean executeAs( + CommandSender sender, + CommandSender effectiveSender, + Command _command, + String _label, + String[] args) { if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { - if (!sender.hasPermission(RELOAD_PERMISSION)) + if (!effectiveSender.hasPermission(RELOAD_PERMISSION)) CommandUtils.respond(sender, "You do not have permission to reload power tool bindings."); else if (store.reload()) CommandUtils.respond(sender, "Power tool bindings reloaded successfully."); @@ -39,7 +43,7 @@ public final class PowerTool implements CommandExecutor { CommandUtils.respond(sender, "Only players can make power tools."); return true; } - if (!sender.hasPermission(PERMISSION)) { + if (!effectiveSender.hasPermission(PERMISSION)) { CommandUtils.respond(sender, "You do not have permission to make power tools."); return true; } @@ -49,13 +53,13 @@ public final class PowerTool implements CommandExecutor { if (args.length > 0) { String command = String.join(" ", args); try { - store.add(mhItem, command); + store.add(mhItem, command, CommandUtils.getUniqueId(effectiveSender)); CommandUtils.respond(sender, "Power tool created successfully."); } catch (NoMetaException e) { CommandUtils.respond(sender, "Power tools must be item types that can hold metadata."); } } else { - store.clear(mhItem); + store.clear(mhItem, CommandUtils.getUniqueId(effectiveSender)); CommandUtils.respond(sender, "Power tool bindings cleared for this item."); } return true; diff --git a/src/main/java/net/cflems/mc/sandbox/listeners/PowerToolListener.java b/src/main/java/net/cflems/mc/sandbox/listeners/PowerToolListener.java index 25949d2..8f4211a 100644 --- a/src/main/java/net/cflems/mc/sandbox/listeners/PowerToolListener.java +++ b/src/main/java/net/cflems/mc/sandbox/listeners/PowerToolListener.java @@ -2,6 +2,7 @@ package net.cflems.mc.sandbox.listeners; import java.util.List; import net.cflems.mc.sandbox.CommandUtils; +import net.cflems.mc.sandbox.storage.PowerToolBinding; import net.cflems.mc.sandbox.storage.PowerToolStore; import org.bukkit.entity.Player; import org.bukkit.event.Event; @@ -27,7 +28,7 @@ public final class PowerToolListener implements Listener { || (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK)) return; - List commands = store.get(event.getItem()); + List commands = store.get(event.getItem()); if (commands.isEmpty()) return; Player player = event.getPlayer(); @@ -36,7 +37,9 @@ public final class PowerToolListener implements Listener { return; } - commands.forEach(player::performCommand); + commands.forEach( + binding -> + CommandUtils.delegateCommand(player, binding.effectivePlayerId(), binding.command())); event.setCancelled(true); } } diff --git a/src/main/java/net/cflems/mc/sandbox/storage/DeserializationException.java b/src/main/java/net/cflems/mc/sandbox/storage/DeserializationException.java new file mode 100644 index 0000000..f2bb0db --- /dev/null +++ b/src/main/java/net/cflems/mc/sandbox/storage/DeserializationException.java @@ -0,0 +1,7 @@ +package net.cflems.mc.sandbox.storage; + +public class DeserializationException extends RuntimeException { + public DeserializationException(String message) { + super(message); + } +} diff --git a/src/main/java/net/cflems/mc/sandbox/storage/PowerToolBinding.java b/src/main/java/net/cflems/mc/sandbox/storage/PowerToolBinding.java new file mode 100644 index 0000000..ea35a0a --- /dev/null +++ b/src/main/java/net/cflems/mc/sandbox/storage/PowerToolBinding.java @@ -0,0 +1,32 @@ +package net.cflems.mc.sandbox.storage; + +import java.util.Map; +import java.util.UUID; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.jetbrains.annotations.NotNull; + +public record PowerToolBinding(String command, UUID effectivePlayerId) + implements ConfigurationSerializable { + private static final String COMMAND_KEY = "command"; + private static final String EFFECTIVE_PLAYER_ID_KEY = "effectivePlayerId"; + + @NotNull + @Override + public Map serialize() { + return Map.of(COMMAND_KEY, command, EFFECTIVE_PLAYER_ID_KEY, effectivePlayerId.toString()); + } + + public static PowerToolBinding deserialize(Map serialized) { + Object rawCommand = serialized.get(COMMAND_KEY); + if (!(rawCommand instanceof String command)) + throw new DeserializationException(COMMAND_KEY + " must be a string."); + + Object rawEffectivePlayerId = serialized.get(EFFECTIVE_PLAYER_ID_KEY); + if (!(rawEffectivePlayerId instanceof String effectivePlayerIdString)) + throw new DeserializationException(EFFECTIVE_PLAYER_ID_KEY + " must be a UUID."); + + UUID effectivePlayerId = UUID.fromString(effectivePlayerIdString); + + return new PowerToolBinding(command, effectivePlayerId); + } +} diff --git a/src/main/java/net/cflems/mc/sandbox/storage/PowerToolStore.java b/src/main/java/net/cflems/mc/sandbox/storage/PowerToolStore.java index bbbd2a5..5214761 100644 --- a/src/main/java/net/cflems/mc/sandbox/storage/PowerToolStore.java +++ b/src/main/java/net/cflems/mc/sandbox/storage/PowerToolStore.java @@ -3,6 +3,7 @@ package net.cflems.mc.sandbox.storage; import com.google.common.base.Preconditions; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -12,6 +13,7 @@ import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataType; @@ -31,23 +33,34 @@ public final class PowerToolStore { this.config = YamlConfiguration.loadConfiguration(configFile); } - public synchronized List get(@Nullable ItemStack item) { + public static void registerSerializables() { + ConfigurationSerialization.registerClass(PowerToolBinding.class, "PowerToolBinding"); + } + + public synchronized List get(@Nullable ItemStack item) { if (item == null) return List.of(); - return getId(item).map(id -> config.getStringList(id.toString())).orElseGet(List::of); + return getId(item).map(id -> getFromConfig(id.toString())).orElseGet(List::of); } - public synchronized void add(ItemStack item, String command) { + public synchronized void add(ItemStack item, String command, UUID effectivePlayerId) { String path = getId(item).orElseGet(() -> createId(item)).toString(); - List bindings = config.getStringList(path); - bindings.add(command); + List bindings = getFromConfig(path); + bindings.add(new PowerToolBinding(command, effectivePlayerId)); config.set(path, bindings); saveConfig(); } - public synchronized void clear(ItemStack item) { + public synchronized void clear(ItemStack item, UUID effectivePlayerId) { Optional id = getId(item); if (id.isEmpty()) return; - config.set(id.get().toString(), null); + String path = id.get().toString(); + + List bindings = getFromConfig(path); + List purgedBindings = + bindings.stream() + .filter(binding -> !binding.effectivePlayerId().equals(effectivePlayerId)) + .toList(); + config.set(id.get().toString(), purgedBindings.isEmpty() ? null : purgedBindings); saveConfig(); } @@ -80,6 +93,16 @@ public final class PowerToolStore { return id; } + private List getFromConfig(String path) { + List rawList = config.getList(path, List.of()); + List convertedList = new ArrayList<>(); + rawList.stream() + .filter(item -> item instanceof PowerToolBinding) + .map(item -> (PowerToolBinding) item) + .forEach(convertedList::add); + return convertedList; + } + private void saveConfig() { try { config.save(configFile); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3011fe1..b4db8ad 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,11 +1,11 @@ name: Sandbox website: https://mc.cflems.net author: cflems -version: 0.1.0 +version: 0.2.0 description: Make your Minecraft server into your personal sandbox. main: net.cflems.mc.sandbox.Sandbox load: POSTWORLD -api-version: '1.21.6' +api-version: '1.21.7' depend: [ ] commands: enchant: @@ -24,6 +24,9 @@ commands: aliases: [ "pt" ] usage: /powertool [command...] description: Binds a command to the item in your main hand, or clears bound commands if none specified. + ptsu: + usage: /ptsu [command...] + description: Executes the powertool command as the specified player. permissions: sandbox.*: default: op @@ -40,6 +43,7 @@ permissions: sandbox.powertool: true sandbox.powertool.use: true sandbox.powertool.reload: true + sandbox.powertool.su: true sandbox.enchant: description: Permission to enchant your items. sandbox.enchant.others: @@ -63,3 +67,5 @@ permissions: description: Permission to use power tools. sandbox.powertool.reload: description: Permission to reload power tool bindings from disk. + sandbox.powertool.su: + description: Permission to use power tools as others. -- cgit v1.2.3