diff --git a/changelog.md b/changelog.md index c8c89e6..2cd0b2c 100644 --- a/changelog.md +++ b/changelog.md @@ -13,6 +13,7 @@ These changes will (most likely) be included in the next version. ## [Unreleased] ### Added - Support for chest references in item syntax. The new `inv` syntax allows for referencing container indices in the config-file. This should help bridge the gap between class chests and various other parts of the config-file, such as rewards and upgrade waves. +- Support for saved items. The new `/ma save-item` command can be used to save the currently held item to disk, which allows it to be used in various places in the config-file. This should help bridge the gap between the config-file and class chests for config-file centric setups. ### Fixed - Explosion damage caused by Exploding Sheep now correctly counts as monster damage. This means that the explosions only affect other mobs if the per-arena setting `monster-infight` is set to `true`. diff --git a/src/main/java/com/garbagemule/MobArena/MobArena.java b/src/main/java/com/garbagemule/MobArena/MobArena.java index 6bd10e4..db4440f 100644 --- a/src/main/java/com/garbagemule/MobArena/MobArena.java +++ b/src/main/java/com/garbagemule/MobArena/MobArena.java @@ -8,6 +8,7 @@ import com.garbagemule.MobArena.formula.FormulaMacros; import com.garbagemule.MobArena.formula.FormulaManager; import com.garbagemule.MobArena.framework.Arena; import com.garbagemule.MobArena.framework.ArenaMaster; +import com.garbagemule.MobArena.items.SavedItemsManager; import com.garbagemule.MobArena.listeners.MAGlobalListener; import com.garbagemule.MobArena.metrics.ArenaCountChart; import com.garbagemule.MobArena.metrics.ClassChestsChart; @@ -67,6 +68,8 @@ public class MobArena extends JavaPlugin private FormulaManager formman; private FormulaMacros macros; + private SavedItemsManager itemman; + private SignListeners signListeners; @Override @@ -106,6 +109,7 @@ public class MobArena extends JavaPlugin try { createDataFolder(); setupFormulaMacros(); + setupSavedItemsManager(); setupArenaMaster(); setupCommandHandler(); @@ -134,6 +138,10 @@ public class MobArena extends JavaPlugin macros = FormulaMacros.create(this); } + private void setupSavedItemsManager() { + itemman = new SavedItemsManager(this); + } + private void setupArenaMaster() { arenaMaster = new ArenaMasterImpl(this); } @@ -190,6 +198,7 @@ public class MobArena extends JavaPlugin reloadConfig(); reloadGlobalMessenger(); reloadFormulaMacros(); + reloadSavedItemsManager(); reloadArenaMaster(); reloadAnnouncementsFile(); reloadSigns(); @@ -226,6 +235,10 @@ public class MobArena extends JavaPlugin } } + private void reloadSavedItemsManager() { + itemman.reload(); + } + private void reloadArenaMaster() { arenaMaster.getArenas().forEach(Arena::forceEnd); arenaMaster.initialize(); @@ -317,4 +330,8 @@ public class MobArena extends JavaPlugin public FormulaMacros getFormulaMacros() { return macros; } + + public SavedItemsManager getSavedItemsManager() { + return itemman; + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/CommandHandler.java b/src/main/java/com/garbagemule/MobArena/commands/CommandHandler.java index 7ff06b4..293cf1a 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/CommandHandler.java +++ b/src/main/java/com/garbagemule/MobArena/commands/CommandHandler.java @@ -15,12 +15,15 @@ import com.garbagemule.MobArena.commands.setup.AutoGenerateCommand; import com.garbagemule.MobArena.commands.setup.CheckDataCommand; import com.garbagemule.MobArena.commands.setup.CheckSpawnsCommand; import com.garbagemule.MobArena.commands.setup.ClassChestCommand; +import com.garbagemule.MobArena.commands.setup.DeleteItemCommand; import com.garbagemule.MobArena.commands.setup.EditArenaCommand; import com.garbagemule.MobArena.commands.setup.ListClassesCommand; +import com.garbagemule.MobArena.commands.setup.LoadItemCommand; import com.garbagemule.MobArena.commands.setup.RemoveArenaCommand; import com.garbagemule.MobArena.commands.setup.RemoveContainerCommand; import com.garbagemule.MobArena.commands.setup.RemoveLeaderboardCommand; import com.garbagemule.MobArena.commands.setup.RemoveSpawnpointCommand; +import com.garbagemule.MobArena.commands.setup.SaveItemCommand; import com.garbagemule.MobArena.commands.setup.SettingCommand; import com.garbagemule.MobArena.commands.setup.SetupCommand; import com.garbagemule.MobArena.commands.user.ArenaListCommand; @@ -340,6 +343,10 @@ public class CommandHandler implements CommandExecutor, TabCompleter register(RemoveLeaderboardCommand.class); register(AutoGenerateCommand.class); + + register(SaveItemCommand.class); + register(DeleteItemCommand.class); + register(LoadItemCommand.class); } /** diff --git a/src/main/java/com/garbagemule/MobArena/commands/setup/DeleteItemCommand.java b/src/main/java/com/garbagemule/MobArena/commands/setup/DeleteItemCommand.java new file mode 100644 index 0000000..934239f --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/commands/setup/DeleteItemCommand.java @@ -0,0 +1,60 @@ +package com.garbagemule.MobArena.commands.setup; + +import com.garbagemule.MobArena.commands.Command; +import com.garbagemule.MobArena.commands.CommandInfo; +import com.garbagemule.MobArena.framework.ArenaMaster; +import com.garbagemule.MobArena.items.SavedItemsManager; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@CommandInfo( + name = "delete-item", + pattern = "delete(-)?item", + usage = "/ma delete-item ", + desc = "delete the item with the given identifier", + permission = "mobarena.setup.deleteitem" +) +public class DeleteItemCommand implements Command { + + @Override + public boolean execute(ArenaMaster am, CommandSender sender, String... args) { + if (args.length != 1) { + return false; + } + + String key = args[0]; + + SavedItemsManager items = am.getPlugin().getSavedItemsManager(); + try { + items.deleteItem(key); + } catch (Exception e) { + am.getGlobalMessenger().tell(sender, "Couldn't delete " + ChatColor.YELLOW + key + ChatColor.RESET + ", because: " + ChatColor.RED + e.getMessage()); + return true; + } + + am.getGlobalMessenger().tell(sender, "Saved item " + ChatColor.YELLOW + key + ChatColor.RESET + " deleted."); + return true; + } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + SavedItemsManager items = am.getPlugin().getSavedItemsManager(); + List keys = items.getKeys(); + + return keys.stream() + .filter(key -> key.startsWith(prefix)) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/commands/setup/LoadItemCommand.java b/src/main/java/com/garbagemule/MobArena/commands/setup/LoadItemCommand.java new file mode 100644 index 0000000..5dbbb41 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/commands/setup/LoadItemCommand.java @@ -0,0 +1,70 @@ +package com.garbagemule.MobArena.commands.setup; + +import com.garbagemule.MobArena.Msg; +import com.garbagemule.MobArena.commands.Command; +import com.garbagemule.MobArena.commands.CommandInfo; +import com.garbagemule.MobArena.commands.Commands; +import com.garbagemule.MobArena.framework.ArenaMaster; +import com.garbagemule.MobArena.items.SavedItemsManager; +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@CommandInfo( + name = "load-item", + pattern = "load(-)?item", + usage = "/ma load-item ", + desc = "load the item saved by the given identifier into your hand", + permission = "mobarena.setup.loaditem" +) +public class LoadItemCommand implements Command { + + @Override + public boolean execute(ArenaMaster am, CommandSender sender, String... args) { + if (!Commands.isPlayer(sender)) { + am.getGlobalMessenger().tell(sender, Msg.MISC_NOT_FROM_CONSOLE); + return true; + } + + if (args.length != 1) { + return false; + } + + String key = args[0]; + + SavedItemsManager items = am.getPlugin().getSavedItemsManager(); + ItemStack stack = items.getItem(key); + if (stack == null) { + am.getGlobalMessenger().tell(sender, "No saved item with identifier " + ChatColor.YELLOW + key + ChatColor.RESET + " found."); + return true; + } + + Player player = Commands.unwrap(sender); + player.getInventory().setItemInMainHand(stack); + + am.getGlobalMessenger().tell(sender, "Saved item " + ChatColor.YELLOW + key + ChatColor.RESET + " loaded."); + return true; + } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + SavedItemsManager items = am.getPlugin().getSavedItemsManager(); + List keys = items.getKeys(); + + return keys.stream() + .filter(key -> key.startsWith(prefix)) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/commands/setup/SaveItemCommand.java b/src/main/java/com/garbagemule/MobArena/commands/setup/SaveItemCommand.java new file mode 100644 index 0000000..2f9d5c4 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/commands/setup/SaveItemCommand.java @@ -0,0 +1,80 @@ +package com.garbagemule.MobArena.commands.setup; + +import com.garbagemule.MobArena.Msg; +import com.garbagemule.MobArena.commands.Command; +import com.garbagemule.MobArena.commands.CommandInfo; +import com.garbagemule.MobArena.commands.Commands; +import com.garbagemule.MobArena.framework.ArenaMaster; +import com.garbagemule.MobArena.items.SavedItemsManager; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@CommandInfo( + name = "save-item", + pattern = "save(-)?item", + usage = "/ma save-item ", + desc = "save the currently held item for use in the config-file", + permission = "mobarena.setup.saveitem" +) +public class SaveItemCommand implements Command { + + @Override + public boolean execute(ArenaMaster am, CommandSender sender, String... args) { + if (!Commands.isPlayer(sender)) { + am.getGlobalMessenger().tell(sender, Msg.MISC_NOT_FROM_CONSOLE); + return true; + } + + if (args.length != 1) { + return false; + } + + String key = args[0]; + if (!key.matches("^[\\p{IsAlphabetic}\\d_]+$")) { + am.getGlobalMessenger().tell(sender, "The identifier must contain only letters, numbers, and underscores"); + return true; + } + + Player player = Commands.unwrap(sender); + ItemStack stack = player.getInventory().getItemInMainHand(); + if (stack.getType() == Material.AIR) { + am.getGlobalMessenger().tell(sender, "You must be holding an item."); + return true; + } + + SavedItemsManager items = am.getPlugin().getSavedItemsManager(); + try { + items.saveItem(key, stack); + } catch (Exception e) { + am.getGlobalMessenger().tell(sender, "Couldn't save " + ChatColor.YELLOW + key + ChatColor.RESET + ", because: " + ChatColor.RED + e.getMessage()); + return true; + } + + am.getGlobalMessenger().tell(sender, "Item saved as " + ChatColor.YELLOW + key + ChatColor.RESET + "."); + return true; + } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + SavedItemsManager items = am.getPlugin().getSavedItemsManager(); + List keys = items.getKeys(); + + return keys.stream() + .filter(key -> key.startsWith(prefix)) + .collect(Collectors.toList()); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/items/SavedItemsManager.java b/src/main/java/com/garbagemule/MobArena/items/SavedItemsManager.java new file mode 100644 index 0000000..48bee29 --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/items/SavedItemsManager.java @@ -0,0 +1,117 @@ +package com.garbagemule.MobArena.items; + +import com.garbagemule.MobArena.MobArena; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +public class SavedItemsManager { + + private static final String FOLDER_NAME = "items"; + private static final String FILE_EXT = ".yml"; + private static final String YAML_KEY = "item"; + + private final MobArena plugin; + private final Path folder; + private final Map items; + + public SavedItemsManager(MobArena plugin) { + this.plugin = plugin; + this.folder = plugin.getDataFolder().toPath().resolve(FOLDER_NAME); + this.items = new HashMap<>(); + } + + public void reload() { + try { + Files.createDirectories(folder); + } catch (Exception e) { + throw new IllegalStateException("Failed to create items folder", e); + } + + loadItems(folder); + } + + private void loadItems(Path folder) { + items.clear(); + + try (Stream candidates = Files.list(folder)) { + candidates.forEach(this::loadItem); + } catch (IOException e) { + throw new IllegalStateException("Failed to load saved items", e); + } + + if (!items.isEmpty()) { + plugin.getLogger().info("Loaded " + items.size() + " saved item(s)."); + } + } + + private void loadItem(Path path) { + if (!Files.isRegularFile(path)) { + return; + } + + String filename = path.getFileName().toString(); + if (!filename.endsWith(FILE_EXT)) { + return; + } + + YamlConfiguration yaml = new YamlConfiguration(); + try { + yaml.load(path.toFile()); + } catch (Exception e) { + throw new IllegalStateException("Failed to load saved item from " + path, e); + } + + ItemStack stack = yaml.getItemStack(YAML_KEY); + if (stack == null) { + throw new IllegalStateException("No item found in saved item file " + path); + } + + String key = filename.substring(0, filename.length() - FILE_EXT.length()); + items.put(key, stack); + } + + public List getKeys() { + return new ArrayList<>(items.keySet()); + } + + public ItemStack getItem(String key) { + ItemStack stack = items.get(key); + if (stack == null) { + return null; + } + return stack.clone(); + } + + public void saveItem(String key, ItemStack stack) throws IOException { + Path file = folder.resolve(key + FILE_EXT); + Files.deleteIfExists(file); + + YamlConfiguration yaml = new YamlConfiguration(); + yaml.set(YAML_KEY, stack); + yaml.save(file.toFile()); + + items.put(key, stack.clone()); + } + + public void deleteItem(String key) throws IOException { + items.remove(key); + + Path file = folder.resolve(key + FILE_EXT); + try { + Files.delete(file); + } catch (NoSuchFileException e) { + throw new IllegalArgumentException("File " + file + " not found"); + } + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/SavedItemParser.java b/src/main/java/com/garbagemule/MobArena/things/SavedItemParser.java new file mode 100644 index 0000000..f945f8e --- /dev/null +++ b/src/main/java/com/garbagemule/MobArena/things/SavedItemParser.java @@ -0,0 +1,60 @@ +package com.garbagemule.MobArena.things; + +import com.garbagemule.MobArena.MobArena; +import com.garbagemule.MobArena.items.SavedItemsManager; +import org.bukkit.inventory.ItemStack; + +class SavedItemParser implements ItemStackParser { + + private final MobArena plugin; + + SavedItemParser(MobArena plugin) { + this.plugin = plugin; + } + + @Override + public ItemStack parse(String s) { + // Note that the saved items manager is not available during + // the load procedure, so we need to defer the call to the + // getter until we actually need it. We don't really have to + // check for null here, but without it, we would need to set + // up a dummy manager for the ThingManager test suite, which + // is a bit of a hassle. + SavedItemsManager items = plugin.getSavedItemsManager(); + if (items == null) { + return null; + } + + if (s.contains(":")) { + return parseWithAmount(s, items); + } else { + return parseSimple(s, items); + } + } + + private ItemStack parseWithAmount(String s, SavedItemsManager items) { + String[] parts = s.split(":"); + if (parts.length > 2) { + return null; + } + + ItemStack stack = parseSimple(parts[0], items); + if (stack == null) { + return null; + } + + try { + int amount = Integer.parseInt(parts[1]); + stack.setAmount(amount); + } catch (NumberFormatException e) { + return null; + } + + return stack; + } + + private ItemStack parseSimple(String s, SavedItemsManager items) { + return items.getItem(s); + } + +} diff --git a/src/main/java/com/garbagemule/MobArena/things/ThingManager.java b/src/main/java/com/garbagemule/MobArena/things/ThingManager.java index 3843c68..f28d6c4 100644 --- a/src/main/java/com/garbagemule/MobArena/things/ThingManager.java +++ b/src/main/java/com/garbagemule/MobArena/things/ThingManager.java @@ -18,6 +18,7 @@ public class ThingManager implements ThingParser { parsers.add(new PotionEffectThingParser()); parsers.add(new InventoryThingParser(plugin.getServer())); items = parser; + items.register(new SavedItemParser(plugin)); } public ThingManager(MobArena plugin) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index d1fef92..ac77e98 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -85,6 +85,9 @@ permissions: mobarena.setup.leaderboards: true mobarena.setup.autogenerate: true mobarena.setup.autodegenerate: true + mobarena.setup.saveitem: true + mobarena.setup.deleteitem: true + mobarena.setup.loaditem: true mobarena.setup.config: description: Save or reload the config-file default: false @@ -130,3 +133,12 @@ permissions: mobarena.setup.autodegenerate: description: Auto-degenerate an arena. default: false + mobarena.setup.saveitem: + description: Store the currently held item as a saved item. + default: false + mobarena.setup.deleteitem: + description: Delete a saved item. + default: false + mobarena.setup.loaditem: + description: Set a saved item as the currently held item. + default: false