diff --git a/src/main/java/lol/hyper/toolstats/ToolStats.java b/src/main/java/lol/hyper/toolstats/ToolStats.java index a3e554e..3b079a8 100644 --- a/src/main/java/lol/hyper/toolstats/ToolStats.java +++ b/src/main/java/lol/hyper/toolstats/ToolStats.java @@ -21,16 +21,15 @@ import lol.hyper.githubreleaseapi.GitHubRelease; import lol.hyper.githubreleaseapi.GitHubReleaseAPI; import lol.hyper.toolstats.commands.CommandToolStats; import lol.hyper.toolstats.events.*; -import lol.hyper.toolstats.tools.HashMaker; -import lol.hyper.toolstats.tools.ItemChecker; -import lol.hyper.toolstats.tools.ItemLore; -import lol.hyper.toolstats.tools.NumberFormat; +import lol.hyper.toolstats.tools.*; import lol.hyper.toolstats.tools.config.ConfigTools; import lol.hyper.toolstats.tools.config.ConfigUpdater; +import lol.hyper.toolstats.tools.config.TokenItems; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ShapedRecipe; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; @@ -91,6 +90,14 @@ public final class ToolStats extends JavaPlugin { * Key for tracking flight time. */ public final NamespacedKey flightTime = new NamespacedKey(this, "flightTime"); + /** + * Key for token type. This is for the token itself. + */ + public final NamespacedKey tokenType = new NamespacedKey(this, "token-type"); + /** + * Key for applied token. This is what goes onto the tool/armor to record the type. + */ + public final NamespacedKey tokenApplied = new NamespacedKey(this, "token-applied"); /** * Stores how an item was created. * 0 = crafted. @@ -103,9 +110,10 @@ public final class ToolStats extends JavaPlugin { */ public final NamespacedKey originType = new NamespacedKey(this, "origin"); - public final int CONFIG_VERSION = 8; + public final int CONFIG_VERSION = 9; public final Logger logger = this.getLogger(); public final File configFile = new File(this.getDataFolder(), "config.yml"); + public boolean tokens = false; public BlocksMined blocksMined; public ChunkPopulate chunkPopulate; @@ -130,6 +138,10 @@ public final class ToolStats extends JavaPlugin { public ItemChecker itemChecker; public ShootBow shootBow; public ConfigTools configTools; + public TokenItems tokenItems; + public TokenCrafting tokenCrafting; + public AnvilEvent anvilEvent; + public PrepareCraft prepareCraft; @Override public void onEnable() { @@ -137,7 +149,17 @@ public final class ToolStats extends JavaPlugin { this.saveResource("config.yml", true); logger.info("Copying default config!"); } + loadConfig(); + configTools = new ConfigTools(this); + tokenItems = new TokenItems(this); + tokenCrafting = new TokenCrafting(this); + tokenCrafting.setup(); + for (ShapedRecipe recipe : tokenCrafting.getRecipes()) { + if (tokens && config.getBoolean("tokens.craft-tokens")) { + Bukkit.addRecipe(recipe); + } + } hashMaker = new HashMaker(this); blocksMined = new BlocksMined(this); craftItem = new CraftItem(this); @@ -156,9 +178,11 @@ public final class ToolStats extends JavaPlugin { playerJoin = new PlayerJoin(this); creativeEvent = new CreativeEvent(this); playerMove = new PlayerMove(this); - itemChecker = new ItemChecker(); + itemChecker = new ItemChecker(this); + itemChecker.setup(); shootBow = new ShootBow(this); - configTools = new ConfigTools(this); + anvilEvent = new AnvilEvent(this); + prepareCraft = new PrepareCraft(this); Bukkit.getServer().getPluginManager().registerEvents(blocksMined, this); Bukkit.getServer().getPluginManager().registerEvents(chunkPopulate, this); @@ -176,6 +200,8 @@ public final class ToolStats extends JavaPlugin { Bukkit.getServer().getPluginManager().registerEvents(creativeEvent, this); Bukkit.getServer().getPluginManager().registerEvents(shootBow, this); Bukkit.getServer().getPluginManager().registerEvents(playerMove, this); + Bukkit.getServer().getPluginManager().registerEvents(anvilEvent, this); + Bukkit.getServer().getPluginManager().registerEvents(prepareCraft, this); this.getCommand("toolstats").setExecutor(commandToolStats); @@ -191,6 +217,13 @@ public final class ToolStats extends JavaPlugin { configUpdater.updateConfig(); } + if (config.getBoolean("tokens.enabled")) { + logger.info("Tokens are enabled! All stat tracking (besides origins) is forced disabled."); + logger.info("If you want to track stats on items, add the correct token to it!"); + } + + tokens = config.getBoolean("tokens.enabled"); + numberFormat = new NumberFormat(this); } diff --git a/src/main/java/lol/hyper/toolstats/commands/CommandToolStats.java b/src/main/java/lol/hyper/toolstats/commands/CommandToolStats.java index 1ea728f..162c3be 100644 --- a/src/main/java/lol/hyper/toolstats/commands/CommandToolStats.java +++ b/src/main/java/lol/hyper/toolstats/commands/CommandToolStats.java @@ -30,6 +30,7 @@ import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; @@ -59,7 +60,27 @@ public class CommandToolStats implements TabExecutor { switch (args[0]) { case "reload": { if (sender.hasPermission("toolstats.reload")) { + boolean oldTokensStatus = toolStats.tokens; toolStats.loadConfig(); + toolStats.tokenCrafting.getRecipes().clear(); + toolStats.tokenCrafting.setup(); + // if the server went from tokens off -> on, add the recipes + // if the server went from tokens on -> off, remove the recipes + if (toolStats.tokens != oldTokensStatus) { + // tokens are now enabled + if (toolStats.tokens) { + if (toolStats.config.getBoolean("tokens.craft-token")) { + for (ShapedRecipe recipe : toolStats.tokenCrafting.getRecipes()) { + Bukkit.addRecipe(recipe); + } + } + } else { + // tokens are now disabled + for (ShapedRecipe recipe : toolStats.tokenCrafting.getRecipes()) { + Bukkit.removeRecipe(recipe.getKey()); + } + } + } sender.sendMessage(Component.text("Configuration reloaded!", NamedTextColor.GREEN)); } else { sender.sendMessage(Component.text("You do not have permission for this command.", NamedTextColor.RED)); @@ -96,6 +117,46 @@ public class CommandToolStats implements TabExecutor { sender.sendMessage(Component.text("Type /toolstats reset confirm to confirm this.", NamedTextColor.GREEN)); return true; } + case "givetokens": { + if (!sender.hasPermission("toolstats.givetokens")) { + sender.sendMessage(Component.text("You do not have permission for this command.", NamedTextColor.RED)); + return true; + } + // Make sure /toolstats givetoken is present + if (args.length < 3) { + sender.sendMessage(Component.text("Invalid syntax. Usage: /toolstats givetokens ", NamedTextColor.RED)); + return true; + } + Player target = Bukkit.getPlayerExact(args[1]); + if (target == null) { + sender.sendMessage(Component.text("Player not found.", NamedTextColor.RED)); + return true; + } + String tokenType = args[2]; + if (!toolStats.tokenCrafting.getTokenTypes().contains(tokenType)) { + sender.sendMessage(Component.text("Invalid token type.", NamedTextColor.RED)); + return true; + } + // if the user does not send in a number, default to 1 + int amount = 1; + if (args.length >= 4) { + try { + amount = Integer.parseInt(args[3]); + if (amount <= 0) { // Optional: Prevent negative or zero values + sender.sendMessage(Component.text("Token quantity must be above or 1.", NamedTextColor.RED)); + return true; + } + } catch (NumberFormatException exception) { + sender.sendMessage(Component.text("Invalid token quantity.", NamedTextColor.RED)); + return true; + } + } + giveToken(target, tokenType, amount); + if (sender instanceof Player) { + sender.sendMessage(Component.text("Gave " + target.getName() + " " + amount + " " + tokenType + " tokens.", NamedTextColor.GREEN)); + } + return true; + } default: { sender.sendMessage(Component.text("Invalid sub-command.", NamedTextColor.RED)); } @@ -312,24 +373,95 @@ public class CommandToolStats implements TabExecutor { player.getInventory().setItem(slot, finalItem); } + /** + * Gives a player a token. + * + * @param target The player. + * @param tokenType The token type. + */ + private void giveToken(Player target, String tokenType, int amount) { + switch (tokenType) { + case "crops-mined": { + ItemStack itemStack = toolStats.tokenItems.cropsMined(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + break; + } + case "blocks-mined": { + ItemStack itemStack = toolStats.tokenItems.blocksMined(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + break; + } + case "damage-taken": { + ItemStack itemStack = toolStats.tokenItems.damageTaken(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + break; + } + case "mob-kills": { + ItemStack itemStack = toolStats.tokenItems.mobKills(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + break; + } + case "player-kills": { + ItemStack itemStack = toolStats.tokenItems.playerKills(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + break; + } + case "arrows-shot": { + ItemStack itemStack = toolStats.tokenItems.arrowsShot(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + break; + } + case "sheep-sheared": { + ItemStack itemStack = toolStats.tokenItems.sheepSheared(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + break; + } + case "flight-time": { + ItemStack itemStack = toolStats.tokenItems.flightTime(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + break; + } + case "fish-caught": { + ItemStack itemStack = toolStats.tokenItems.fishCaught(); + itemStack.setAmount(amount); + target.getInventory().addItem(itemStack); + break; + } + } + } + @Nullable @Override public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { if (args.length == 1) { + List suggestions = new ArrayList<>(); if (sender.hasPermission("toolstats.reload")) { - return Arrays.asList("reset", "reload"); + suggestions.add("reload"); } if (sender.hasPermission("toolstats.reset")) { - return Collections.singletonList("reset"); + suggestions.add("reset"); } - } - if (args.length == 2) { - if (args[0].equalsIgnoreCase("reset")) { - if (sender.hasPermission("toolstats.reset.confirm")) { - return Collections.singletonList("confirm"); - } + if (sender.hasPermission("toolstats.givetokens")) { + suggestions.add("givetokens"); } + return suggestions.isEmpty() ? null : suggestions; } + + if (args.length == 2 && args[0].equalsIgnoreCase("reset") && sender.hasPermission("toolstats.reset.confirm")) { + return Collections.singletonList("confirm"); + } + if (args.length == 3 && args[0].equalsIgnoreCase("givetokens") && sender.hasPermission("toolstats.givetokens")) { + return toolStats.tokenCrafting.getTokenTypes(); + } + return null; } } diff --git a/src/main/java/lol/hyper/toolstats/events/AnvilEvent.java b/src/main/java/lol/hyper/toolstats/events/AnvilEvent.java new file mode 100644 index 0000000..e7c592e --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/events/AnvilEvent.java @@ -0,0 +1,195 @@ +/* + * This file is part of ToolStats. + * + * ToolStats is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ToolStats is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ToolStats. If not, see . + */ + +package lol.hyper.toolstats.events; + +import lol.hyper.toolstats.ToolStats; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.util.Locale; + +public class AnvilEvent implements Listener { + + private final ToolStats toolStats; + + public AnvilEvent(ToolStats toolStats) { + this.toolStats = toolStats; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onAnvilEvent(PrepareAnvilEvent event) { + AnvilInventory inventory = event.getInventory(); + + ItemStack firstSlot = inventory.getItem(0); + ItemStack secondSlot = inventory.getItem(1); + + // make sure both slots have items + if (firstSlot == null || secondSlot == null) { + return; + } + + Material firstSlotMaterial = firstSlot.getType(); + Material secondSlotMaterial = secondSlot.getType(); + + // make sure the first item is a valid item + if (!toolStats.itemChecker.isValidItem(firstSlotMaterial)) { + return; + } + + PersistentDataContainer firstSlotContainer = firstSlot.getItemMeta().getPersistentDataContainer(); + PersistentDataContainer secondSlotContainer = secondSlot.getItemMeta().getPersistentDataContainer(); + + // make sure the 2nd item is one of ours + if (secondSlotMaterial != Material.PAPER || !secondSlotContainer.has(toolStats.tokenType, PersistentDataType.STRING)) { + return; + } + + //get the type from the token + String tokenType = secondSlotContainer.get(toolStats.tokenType, PersistentDataType.STRING); + if (tokenType == null) { + return; + } + + // clone the item + ItemStack clone = firstSlot.clone(); + + // if the item is a mining tool + if (toolStats.itemChecker.isMineTool(firstSlotMaterial)) { + if (firstSlotMaterial.toString().toLowerCase(Locale.ROOT).contains("hoe")) { + // the item is a hoe + addToken(event, tokenType, "crops-mined", clone); + } else { + // since shears will fall under here, check if the token is for sheep sheared + if (firstSlotMaterial == Material.SHEARS && tokenType.equals("sheep-sheared")) { + addToken(event, tokenType, "sheep-sheared", clone); + return; + } + addToken(event, tokenType, "blocks-mined", clone); + } + return; + } + if (toolStats.itemChecker.isArmor(firstSlotMaterial)) { + addToken(event, tokenType, "damage-taken", clone); + return; + } + if (toolStats.itemChecker.isMeleeWeapon(firstSlotMaterial)) { + if (tokenType.equalsIgnoreCase("player-kills")) { + addToken(event, tokenType, "player-kills", clone); + return; + } + if (tokenType.equalsIgnoreCase("mobs-kills")) { + addToken(event, tokenType, "mobs-kills", clone); + return; + } + return; + } + if (firstSlotMaterial == Material.BOW || firstSlotMaterial == Material.CROSSBOW) { + if (tokenType.equalsIgnoreCase("player-kills")) { + addToken(event, tokenType, "player-kills", clone); + return; + } + if (tokenType.equalsIgnoreCase("mobs-kills")) { + addToken(event, tokenType, "mobs-kills", clone); + return; + } + if (tokenType.equalsIgnoreCase("arrows-shot")) { + addToken(event, tokenType, "arrows-shot", clone); + return; + } + return; + } + if (firstSlotMaterial == Material.ELYTRA) { + addToken(event, tokenType, "flight-time", clone); + return; + } + if (firstSlotMaterial == Material.FISHING_ROD) { + addToken(event, tokenType, "fish-caught", clone); + } + } + + /** + * Add token to an item. + * + * @param event The anvil event. + * @param attemptToken The token in the 2nd slot of the anvil. The one the player wants to add. + * @param targetToken The token we are checking for. This should match the tool. + * @param firstSlotItem The item in the first slot. + */ + private void addToken(PrepareAnvilEvent event, String attemptToken, String targetToken, ItemStack firstSlotItem) { + // make sure the token we are using is for this tool + if (!attemptToken.equalsIgnoreCase(targetToken)) { + event.setResult(null); + return; + } + + // if the item already has the token, ignore + if (toolStats.itemChecker.checkTokens(firstSlotItem.getItemMeta().getPersistentDataContainer(), targetToken)) { + event.setResult(null); + return; + } + + // apply the token and set the result + ItemStack newItem = toolStats.itemChecker.addToken(firstSlotItem, targetToken); + switch (targetToken) { + case "crops-mined": { + event.setResult(toolStats.itemLore.updateCropsMined(newItem, 0)); + break; + } + case "blocks-mined": { + event.setResult(toolStats.itemLore.updateBlocksMined(newItem, 0)); + break; + } + case "damage-taken": { + event.setResult(toolStats.itemLore.updateDamage(newItem, 0.0)); + break; + } + case "mob-kills": { + event.setResult(toolStats.itemLore.updateMobKills(newItem, 0)); + break; + } + case "player-kills": { + event.setResult(toolStats.itemLore.updatePlayerKills(newItem, 0)); + break; + } + case "arrows-shot": { + event.setResult(toolStats.itemLore.updateArrowsShot(newItem, 0)); + break; + } + case "sheep-sheared": { + event.setResult(toolStats.itemLore.updateSheepSheared(newItem, 0)); + break; + } + case "flight-time": { + event.setResult(toolStats.itemLore.updateFlightTime(newItem, 0)); + break; + } + case "fish-caught": { + event.setResult(toolStats.itemLore.updateFishCaught(newItem, 0)); + break; + } + } + event.getView().setRepairCost(toolStats.itemChecker.getCost(targetToken)); + } +} diff --git a/src/main/java/lol/hyper/toolstats/events/BlocksMined.java b/src/main/java/lol/hyper/toolstats/events/BlocksMined.java index 7edbc7f..69e334c 100644 --- a/src/main/java/lol/hyper/toolstats/events/BlocksMined.java +++ b/src/main/java/lol/hyper/toolstats/events/BlocksMined.java @@ -18,7 +18,6 @@ package lol.hyper.toolstats.events; import lol.hyper.toolstats.ToolStats; -import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Material; @@ -31,11 +30,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; -import java.util.List; import java.util.Locale; public class BlocksMined implements Listener { @@ -69,91 +64,29 @@ public class BlocksMined implements Listener { return; } + // if the item is a hoe if (heldItem.getType().toString().toLowerCase(Locale.ROOT).contains("hoe")) { // player is breaking crops with a hoe - if (block.getBlockData() instanceof Ageable) { - updateCropsMined(heldItem, (Ageable) block.getBlockData()); + if (block.getBlockData() instanceof Ageable ageable) { + // ignore crops that are not fully grown + if (ageable.getAge() != ageable.getMaximumAge()) { + return; + } + ItemStack newItem = toolStats.itemLore.updateCropsMined(heldItem, 1); + if (newItem != null) { + // replace item in main hand + inventory.setItemInMainHand(newItem); + } } } else { + // item is not a hoe // update the blocks mined - updateBlocksMined(heldItem); - } - } - - private void updateBlocksMined(ItemStack playerTool) { - ItemMeta meta = playerTool.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(playerTool + " does NOT have any meta! Unable to update stats."); - return; - } - // read the current stats from the item - // if they don't exist, then start from 0 - Integer blocksMined = 0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.genericMined, PersistentDataType.INTEGER)) { - blocksMined = container.get(toolStats.genericMined, PersistentDataType.INTEGER); - } - - if (blocksMined == null) { - blocksMined = 0; - toolStats.logger.warning(playerTool + " does not have valid generic-mined set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.genericMined, PersistentDataType.INTEGER, blocksMined + 1); - - // do we add the lore based on the config? - if (toolStats.configTools.checkConfig(playerTool.getType(), "blocks-mined")) { - String oldBlocksMinedFormatted = toolStats.numberFormat.formatInt(blocksMined); - String newBlocksMinedFormatted = toolStats.numberFormat.formatInt(blocksMined + 1); - Component oldLine = toolStats.configTools.formatLore("blocks-mined", "{blocks}", oldBlocksMinedFormatted); - Component newLine = toolStats.configTools.formatLore("blocks-mined", "{blocks}", newBlocksMinedFormatted); - if (oldLine == null || newLine == null) { - return; + ItemStack newItem = toolStats.itemLore.updateBlocksMined(heldItem, 1); + if (newItem != null) { + toolStats.logger.info(newItem.toString()); + // replace item in main hand + inventory.setItemInMainHand(newItem); } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); } - playerTool.setItemMeta(meta); - } - - private void updateCropsMined(ItemStack playerTool, Ageable block) { - // ignore crops that are not fully grown - if (block.getAge() != block.getMaximumAge()) { - return; - } - - ItemMeta meta = playerTool.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(playerTool + " does NOT have any meta! Unable to update stats."); - return; - } - // read the current stats from the item - // if they don't exist, then start from 0 - Integer cropsMined = 0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.cropsHarvested, PersistentDataType.INTEGER)) { - cropsMined = container.get(toolStats.cropsHarvested, PersistentDataType.INTEGER); - } - - if (cropsMined == null) { - cropsMined = 0; - toolStats.logger.warning(playerTool + " does not have valid crops-mined set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.cropsHarvested, PersistentDataType.INTEGER, cropsMined + 1); - - // do we add the lore based on the config? - if (toolStats.configTools.checkConfig(playerTool.getType(), "blocks-mined")) { - String oldCropsMinedFormatted = toolStats.numberFormat.formatInt(cropsMined); - String newCropsMinedFormatted = toolStats.numberFormat.formatInt(cropsMined + 1); - Component oldLine = toolStats.configTools.formatLore("crops-harvested", "{crops}", oldCropsMinedFormatted); - Component newLine = toolStats.configTools.formatLore("crops-harvested", "{crops}", newCropsMinedFormatted); - if (oldLine == null || newLine == null) { - return; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - playerTool.setItemMeta(meta); } } diff --git a/src/main/java/lol/hyper/toolstats/events/CraftItem.java b/src/main/java/lol/hyper/toolstats/events/CraftItem.java index 472aaf5..0fc03a0 100644 --- a/src/main/java/lol/hyper/toolstats/events/CraftItem.java +++ b/src/main/java/lol/hyper/toolstats/events/CraftItem.java @@ -17,11 +17,9 @@ package lol.hyper.toolstats.events; -import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import lol.hyper.toolstats.ToolStats; import lol.hyper.toolstats.tools.UUIDDataType; import net.kyori.adventure.text.Component; -import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -29,9 +27,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.inventory.CraftItemEvent; -import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; @@ -39,7 +35,6 @@ import org.bukkit.persistence.PersistentDataType; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.function.Consumer; public class CraftItem implements Listener { diff --git a/src/main/java/lol/hyper/toolstats/events/EntityDamage.java b/src/main/java/lol/hyper/toolstats/events/EntityDamage.java index abfd66a..dcf2f46 100644 --- a/src/main/java/lol/hyper/toolstats/events/EntityDamage.java +++ b/src/main/java/lol/hyper/toolstats/events/EntityDamage.java @@ -18,10 +18,12 @@ package lol.hyper.toolstats.events; import lol.hyper.toolstats.ToolStats; -import net.kyori.adventure.text.Component; import org.bukkit.GameMode; import org.bukkit.Material; -import org.bukkit.entity.*; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Trident; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -30,9 +32,6 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; import java.util.*; @@ -77,11 +76,17 @@ public class EntityDamage implements Listener { } // a player is killing another player if (mobBeingAttacked instanceof Player) { - updatePlayerKills(heldItem); + ItemStack newItem = toolStats.itemLore.updatePlayerKills(heldItem, 1); + if (newItem != null) { + attackingPlayerInventory.setItemInMainHand(newItem); + } return; } // player is killing regular mob - updateMobKills(heldItem); + ItemStack newItem = toolStats.itemLore.updateMobKills(heldItem, 1); + if (newItem != null) { + attackingPlayerInventory.setItemInMainHand(newItem); + } trackedMobs.add(mobBeingAttacked.getUniqueId()); } // trident is being thrown at something @@ -89,10 +94,10 @@ public class EntityDamage implements Listener { ItemStack newTrident; // trident is killing player if (mobBeingAttacked instanceof Player) { - newTrident = tridentPlayerKills(trident.getItemStack()); + newTrident = toolStats.itemLore.updatePlayerKills(trident.getItemStack(), 1); } else { // trident is killing a mob - newTrident = tridentMobKills(trident.getItemStack()); + newTrident = toolStats.itemLore.updateMobKills(trident.getItemStack(), 1); trackedMobs.add(mobBeingAttacked.getUniqueId()); } if (newTrident != null) { @@ -106,9 +111,9 @@ public class EntityDamage implements Listener { if (shootingPlayer.getGameMode() == GameMode.CREATIVE || shootingPlayer.getGameMode() == GameMode.SPECTATOR) { return; } - PlayerInventory inventory = shootingPlayer.getInventory(); - ItemStack main = inventory.getItemInMainHand(); - ItemStack offHand = inventory.getItemInOffHand(); + PlayerInventory shootingPlayerInventory = shootingPlayer.getInventory(); + ItemStack main = shootingPlayerInventory.getItemInMainHand(); + ItemStack offHand = shootingPlayerInventory.getItemInOffHand(); boolean isMain = main.getType() == Material.BOW || main.getType() == Material.CROSSBOW; boolean isOffHand = offHand.getType() == Material.BOW || offHand.getType() == Material.CROSSBOW; ItemStack heldBow = null; @@ -126,9 +131,16 @@ public class EntityDamage implements Listener { // player is shooting another player if (mobBeingAttacked instanceof Player) { - updatePlayerKills(heldBow); + ItemStack newItem = toolStats.itemLore.updatePlayerKills(heldBow, 1); + if (newItem != null) { + shootingPlayerInventory.setItemInMainHand(newItem); + } } else { - updateMobKills(heldBow); + // player is shooting a mob + ItemStack newItem = toolStats.itemLore.updateMobKills(heldBow, 1); + if (newItem != null) { + shootingPlayerInventory.setItemInMainHand(newItem); + } trackedMobs.add(mobBeingAttacked.getUniqueId()); } } @@ -140,13 +152,21 @@ public class EntityDamage implements Listener { return; } PlayerInventory playerInventory = playerTakingDamage.getInventory(); - for (ItemStack armorPiece : playerInventory.getArmorContents()) { + ItemStack[] armorContents = playerInventory.getArmorContents(); + for (int i = 0; i < armorContents.length; i++) { + ItemStack armorPiece = armorContents[i]; if (armorPiece != null) { if (toolStats.itemChecker.isArmor(armorPiece.getType())) { - updateDamage(armorPiece, event.getFinalDamage()); + ItemStack newItem = toolStats.itemLore.updateDamage(armorPiece, event.getFinalDamage()); + if (newItem != null) { + armorContents[i] = newItem; + } } } } + + // apply the new armor + playerInventory.setArmorContents(armorContents); } } @@ -168,13 +188,21 @@ public class EntityDamage implements Listener { return; } PlayerInventory playerInventory = playerTakingDamage.getInventory(); - for (ItemStack armorPiece : playerInventory.getArmorContents()) { + ItemStack[] armorContents = playerInventory.getArmorContents(); + for (int i = 0; i < armorContents.length; i++) { + ItemStack armorPiece = armorContents[i]; if (armorPiece != null) { if (toolStats.itemChecker.isArmor(armorPiece.getType())) { - updateDamage(armorPiece, event.getFinalDamage()); + ItemStack newItem = toolStats.itemLore.updateDamage(armorPiece, event.getFinalDamage()); + if (newItem != null) { + armorContents[i] = newItem; + } } } } + + // apply the new armor + playerInventory.setArmorContents(armorContents); } } @@ -196,216 +224,21 @@ public class EntityDamage implements Listener { return; } PlayerInventory playerInventory = playerTakingDamage.getInventory(); - for (ItemStack armorPiece : playerInventory.getArmorContents()) { + ItemStack[] armorContents = playerInventory.getArmorContents(); + for (int i = 0; i < armorContents.length; i++) { + ItemStack armorPiece = armorContents[i]; if (armorPiece != null) { if (toolStats.itemChecker.isArmor(armorPiece.getType())) { - updateDamage(armorPiece, event.getFinalDamage()); + ItemStack newItem = toolStats.itemLore.updateDamage(armorPiece, event.getFinalDamage()); + if (newItem != null) { + armorContents[i] = newItem; + } } } } - } - } - /** - * Updates a weapon's player kills. - * - * @param itemStack The item to update. - */ - private void updatePlayerKills(ItemStack itemStack) { - ItemMeta meta = itemStack.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(itemStack + " does NOT have any meta! Unable to update stats."); - return; + // apply the new armor + playerInventory.setArmorContents(armorContents); } - Integer playerKills = 0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.swordPlayerKills, PersistentDataType.INTEGER)) { - playerKills = container.get(toolStats.swordPlayerKills, PersistentDataType.INTEGER); - } - - if (playerKills == null) { - playerKills = 0; - toolStats.logger.warning(itemStack + " does not have valid player-kills set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.swordPlayerKills, PersistentDataType.INTEGER, playerKills + 1); - - // do we add the lore based on the config? - if (toolStats.configTools.checkConfig(itemStack.getType(), "player-kills")) { - String oldPlayerKillsFormatted = toolStats.numberFormat.formatInt(playerKills); - String newPlayerKillsFormatted = toolStats.numberFormat.formatInt(playerKills + 1); - Component oldLine = toolStats.configTools.formatLore("kills.player", "{kills}", oldPlayerKillsFormatted); - Component newLine = toolStats.configTools.formatLore("kills.player", "{kills}", newPlayerKillsFormatted); - if (oldLine == null || newLine == null) { - return; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - itemStack.setItemMeta(meta); - } - - /** - * Updates a weapon's mob kills. - * - * @param itemStack The item to update. - */ - private void updateMobKills(ItemStack itemStack) { - ItemMeta meta = itemStack.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(itemStack + " does NOT have any meta! Unable to update stats."); - return; - } - Integer mobKills = 0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.swordMobKills, PersistentDataType.INTEGER)) { - mobKills = container.get(toolStats.swordMobKills, PersistentDataType.INTEGER); - } - - if (mobKills == null) { - mobKills = 0; - toolStats.logger.warning(itemStack + " does not have valid mob-kills set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.swordMobKills, PersistentDataType.INTEGER, mobKills + 1); - - // do we add the lore based on the config? - if (toolStats.configTools.checkConfig(itemStack.getType(), "mob-kills")) { - String oldMobKillsFormatted = toolStats.numberFormat.formatInt(mobKills); - String newMobKillsFormatted = toolStats.numberFormat.formatInt(mobKills + 1); - Component oldLine = toolStats.configTools.formatLore("kills.mob", "{kills}", oldMobKillsFormatted); - Component newLine = toolStats.configTools.formatLore("kills.mob", "{kills}", newMobKillsFormatted); - if (oldLine == null || newLine == null) { - return; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - itemStack.setItemMeta(meta); - } - - /** - * Updates a player's armor damage stats. - * - * @param itemStack The armor piece. - * @param damage How much damage is being added. - */ - private void updateDamage(ItemStack itemStack, double damage) { - // ignore if the damage is zero or negative - if (damage < 0) { - return; - } - ItemMeta meta = itemStack.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(itemStack + " does NOT have any meta! Unable to update stats."); - return; - } - Double damageTaken = 0.0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.armorDamage, PersistentDataType.DOUBLE)) { - damageTaken = container.get(toolStats.armorDamage, PersistentDataType.DOUBLE); - } - - if (damageTaken == null) { - damageTaken = 0.0; - toolStats.logger.warning(itemStack + " does not have valid damage-taken set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.armorDamage, PersistentDataType.DOUBLE, damageTaken + damage); - - if (toolStats.config.getBoolean("enabled.armor-damage")) { - String oldDamageFormatted = toolStats.numberFormat.formatDouble(damageTaken); - String newDamageFormatted = toolStats.numberFormat.formatDouble(damageTaken + damage); - Component oldLine = toolStats.configTools.formatLore("damage-taken", "{damage}", oldDamageFormatted); - Component newLine = toolStats.configTools.formatLore("damage-taken", "{damage}", newDamageFormatted); - if (oldLine == null || newLine == null) { - return; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - itemStack.setItemMeta(meta); - } - - /** - * Updates a trident's mob kills. - * - * @param trident The item to update. - */ - private ItemStack tridentMobKills(ItemStack trident) { - ItemStack newTrident = trident.clone(); - ItemMeta meta = newTrident.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(newTrident + " does NOT have any meta! Unable to update stats."); - return null; - } - Integer mobKills = 0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.swordMobKills, PersistentDataType.INTEGER)) { - mobKills = container.get(toolStats.swordMobKills, PersistentDataType.INTEGER); - } - - if (mobKills == null) { - mobKills = 0; - toolStats.logger.warning(newTrident + " does not have valid mob-kills set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.swordMobKills, PersistentDataType.INTEGER, mobKills + 1); - - // do we add the lore based on the config? - if (toolStats.configTools.checkConfig(newTrident.getType(), "mob-kills")) { - String oldMobKillsFormatted = toolStats.numberFormat.formatInt(mobKills); - String newMobKillsFormatted = toolStats.numberFormat.formatInt(mobKills + 1); - Component oldLine = toolStats.configTools.formatLore("kills.mob", "{kills}", oldMobKillsFormatted); - Component newLine = toolStats.configTools.formatLore("kills.mob", "{kills}", newMobKillsFormatted); - if (oldLine == null || newLine == null) { - return null; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - newTrident.setItemMeta(meta); - return newTrident; - } - - /** - * Updates a trident's player kills. - * - * @param trident The item to update. - */ - private ItemStack tridentPlayerKills(ItemStack trident) { - ItemStack newTrident = trident.clone(); - ItemMeta meta = newTrident.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(newTrident + " does NOT have any meta! Unable to update stats."); - return null; - } - Integer playerKills = 0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.swordPlayerKills, PersistentDataType.INTEGER)) { - playerKills = container.get(toolStats.swordPlayerKills, PersistentDataType.INTEGER); - } - - if (playerKills == null) { - playerKills = 0; - toolStats.logger.warning(newTrident + " does not have valid player-kills set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.swordPlayerKills, PersistentDataType.INTEGER, playerKills + 1); - - // do we add the lore based on the config? - if (toolStats.configTools.checkConfig(newTrident.getType(), "player-kills")) { - String oldPlayerKillsFormatted = toolStats.numberFormat.formatInt(playerKills); - String newPlayerKillsFormatted = toolStats.numberFormat.formatInt(playerKills + 1); - Component oldLine = toolStats.configTools.formatLore("kills.player", "{kills}", oldPlayerKillsFormatted); - Component newLine = toolStats.configTools.formatLore("kills.player", "{kills}", newPlayerKillsFormatted); - if (oldLine == null || newLine == null) { - return null; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - newTrident.setItemMeta(meta); - return newTrident; } } diff --git a/src/main/java/lol/hyper/toolstats/events/PlayerFish.java b/src/main/java/lol/hyper/toolstats/events/PlayerFish.java index 9e1b724..007710b 100644 --- a/src/main/java/lol/hyper/toolstats/events/PlayerFish.java +++ b/src/main/java/lol/hyper/toolstats/events/PlayerFish.java @@ -61,14 +61,25 @@ public class PlayerFish implements Listener { return; } - ItemStack fishingRod = getItemStack(player); + ItemStack fishingRod = getFishingRod(player.getInventory()); // player swapped items? if (fishingRod == null) { return; } // update the fishing rod! - updateFishCount(fishingRod); + ItemStack newFishingRod = toolStats.itemLore.updateFishCaught(fishingRod, 1); + if (newFishingRod != null) { + PlayerInventory inventory = player.getInventory(); + boolean isMain = inventory.getItemInMainHand().getType() == Material.FISHING_ROD; + boolean isOffHand = inventory.getItemInOffHand().getType() == Material.FISHING_ROD; + if (isMain) { + inventory.setItemInMainHand(newFishingRod); + } + if (isOffHand) { + inventory.setItemInOffHand(newFishingRod); + } + } // check if the player caught an item if (event.getCaught() == null) { @@ -84,62 +95,30 @@ public class PlayerFish implements Listener { } } - private static @Nullable ItemStack getItemStack(Player player) { - PlayerInventory inventory = player.getInventory(); - boolean isMainHand = inventory.getItemInMainHand().getType() == Material.FISHING_ROD; - boolean isOffHand = inventory.getItemInOffHand().getType() == Material.FISHING_ROD; - ItemStack fishingRod = null; - if (isMainHand) { - fishingRod = inventory.getItemInMainHand(); + /** + * Get the player's fishing rod. + * + * @param inventory Their inventory. + * @return Their fishing rod, either main or offhand. + */ + private static @Nullable ItemStack getFishingRod(PlayerInventory inventory) { + ItemStack main = inventory.getItemInMainHand(); + ItemStack offHand = inventory.getItemInOffHand(); + + boolean isMain = main.getType() == Material.FISHING_ROD; + boolean isOffHand = offHand.getType() == Material.FISHING_ROD; + + // if the player is holding a fishing rod in their main hand, use that one + // if the fishing rod is in their offhand instead, use that one after checking main hand + // Minecraft prioritizes main hand if the player holds in both hands + if (isMain) { + return main; } if (isOffHand) { - fishingRod = inventory.getItemInOffHand(); + return offHand; } - // if the player is hold fishing rods in both hands - // default to main hand since that takes priority - if (isMainHand && isOffHand) { - fishingRod = inventory.getItemInMainHand(); - } - return fishingRod; - } - - /** - * Update a fishing rod's fish count. - * - * @param fishingRod The fishing rod to update. - */ - private void updateFishCount(ItemStack fishingRod) { - ItemMeta meta = fishingRod.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(fishingRod + " does NOT have any meta! Unable to update stats."); - return; - } - Integer fishCaught = 0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.fishingRodCaught, PersistentDataType.INTEGER)) { - fishCaught = container.get(toolStats.fishingRodCaught, PersistentDataType.INTEGER); - } - - if (fishCaught == null) { - fishCaught = 0; - toolStats.logger.warning(fishingRod + " does not have valid fish-caught set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.fishingRodCaught, PersistentDataType.INTEGER, fishCaught + 1); - - if (toolStats.config.getBoolean("enabled.fish-caught")) { - String oldFishFormatted = toolStats.numberFormat.formatInt(fishCaught); - String newFishFormatted = toolStats.numberFormat.formatInt(fishCaught + 1); - Component oldLine = toolStats.configTools.formatLore("fished.fish-caught", "{fish}", oldFishFormatted); - Component newLine = toolStats.configTools.formatLore("fished.fish-caught", "{fish}", newFishFormatted); - if (oldLine == null || newLine == null) { - return; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - fishingRod.setItemMeta(meta); + return null; } /** diff --git a/src/main/java/lol/hyper/toolstats/events/PlayerMove.java b/src/main/java/lol/hyper/toolstats/events/PlayerMove.java index 11e010a..7ad605f 100644 --- a/src/main/java/lol/hyper/toolstats/events/PlayerMove.java +++ b/src/main/java/lol/hyper/toolstats/events/PlayerMove.java @@ -18,7 +18,6 @@ package lol.hyper.toolstats.events; import lol.hyper.toolstats.ToolStats; -import net.kyori.adventure.text.Component; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -26,12 +25,9 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; +import org.bukkit.inventory.PlayerInventory; import java.util.HashMap; -import java.util.List; import java.util.Map; public class PlayerMove implements Listener { @@ -55,54 +51,18 @@ public class PlayerMove implements Listener { } else { // player is not flying if (playerStartFlight.containsKey(player)) { - trackFlight(player, playerStartFlight.get(player)); + PlayerInventory inventory = player.getInventory(); + ItemStack chest = inventory.getChestplate(); + // make sure the player is wearing an elytra + if (chest != null && chest.getType() == Material.ELYTRA) { + long duration = (System.currentTimeMillis() - playerStartFlight.get(player)); + ItemStack newItem = toolStats.itemLore.updateFlightTime(chest, duration); + if (newItem != null) { + inventory.setChestplate(newItem); + } + } playerStartFlight.remove(player); } } } - - private void trackFlight(Player player, long startTime) { - ItemStack chest = player.getInventory().getChestplate(); - // make sure their chest piece is an elytra - if (chest == null || chest.getType() != Material.ELYTRA) { - return; - } - ItemMeta meta = chest.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(chest + " does NOT have any meta! Unable to update stats."); - return; - } - - // read the current stats from the item - // if they don't exist, then start from 0 - Long flightTime = 0L; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.flightTime, PersistentDataType.LONG)) { - flightTime = container.get(toolStats.flightTime, PersistentDataType.LONG); - } - - if (flightTime == null) { - flightTime = 0L; - toolStats.logger.warning(flightTime + " does not have valid flight-time set! Resting to zero. This should NEVER happen."); - } - - // get the duration of the flight - long duration = (System.currentTimeMillis() - startTime); - double newDuration = flightTime + duration; - container.set(toolStats.flightTime, PersistentDataType.LONG, flightTime + duration); - - // do we add the lore based on the config? - if (toolStats.config.getBoolean("enabled.flight-time")) { - String oldFlightFormatted = toolStats.numberFormat.formatDouble((double) flightTime / 1000); - String newFlightFormatted = toolStats.numberFormat.formatDouble(newDuration / 1000); - Component oldLine = toolStats.configTools.formatLore("flight-time", "{time}", oldFlightFormatted); - Component newLine = toolStats.configTools.formatLore("flight-time", "{time}", newFlightFormatted); - if (oldLine == null || newLine == null) { - return; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - chest.setItemMeta(meta); - } } diff --git a/src/main/java/lol/hyper/toolstats/events/PrepareCraft.java b/src/main/java/lol/hyper/toolstats/events/PrepareCraft.java new file mode 100644 index 0000000..10b9437 --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/events/PrepareCraft.java @@ -0,0 +1,61 @@ +/* + * This file is part of ToolStats. + * + * ToolStats is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ToolStats is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ToolStats. If not, see . + */ + +package lol.hyper.toolstats.events; + +import lol.hyper.toolstats.ToolStats; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.PrepareItemCraftEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; + +public class PrepareCraft implements Listener { + + private final ToolStats toolStats; + + public PrepareCraft(ToolStats toolStats) { + this.toolStats = toolStats; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onCraft(PrepareItemCraftEvent event) { + // get the items in the crafting grid + ItemStack[] grid = event.getInventory().getMatrix(); + for (ItemStack item : grid) { + if (item == null || item.getType() != Material.PAPER) { + continue; + } + toolStats.logger.info(item.getType().toString()); + ItemMeta meta = item.getItemMeta(); + if (meta == null) { + continue; + } + // if the paper item has our PDC, cancel it + PersistentDataContainer container = meta.getPersistentDataContainer(); + if (container.has(toolStats.tokenType)) { + toolStats.logger.info("has PDC"); + event.getInventory().setResult(null); + } else { + toolStats.logger.info("no PDC"); + } + } + } +} diff --git a/src/main/java/lol/hyper/toolstats/events/SheepShear.java b/src/main/java/lol/hyper/toolstats/events/SheepShear.java index 5c43e66..7146f7f 100644 --- a/src/main/java/lol/hyper/toolstats/events/SheepShear.java +++ b/src/main/java/lol/hyper/toolstats/events/SheepShear.java @@ -18,7 +18,6 @@ package lol.hyper.toolstats.events; import lol.hyper.toolstats.ToolStats; -import net.kyori.adventure.text.Component; import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.entity.Entity; @@ -30,13 +29,8 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.persistence.PersistentDataContainer; -import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.Nullable; -import java.util.List; - public class SheepShear implements Listener { private final ToolStats toolStats; @@ -71,9 +65,26 @@ public class SheepShear implements Listener { } // update the stats - addLore(heldShears); + ItemStack newItem = toolStats.itemLore.updateSheepSheared(heldShears, 1); + if (newItem != null) { + PlayerInventory inventory = player.getInventory(); + boolean isMain = inventory.getItemInMainHand().getType() == Material.SHEARS; + boolean isOffHand = inventory.getItemInOffHand().getType() == Material.SHEARS; + if (isMain) { + inventory.setItemInMainHand(newItem); + } + if (isOffHand) { + inventory.setItemInOffHand(newItem); + } + } } + /** + * Get the player's shears. + * + * @param inventory Their inventory. + * @return Their shears, either main or offhand. + */ private static @Nullable ItemStack getShears(PlayerInventory inventory) { ItemStack main = inventory.getItemInMainHand(); ItemStack offHand = inventory.getItemInOffHand(); @@ -93,42 +104,4 @@ public class SheepShear implements Listener { return null; } - - /** - * Adds tags to shears. - * - * @param newShears The shears. - */ - private void addLore(ItemStack newShears) { - ItemMeta meta = newShears.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(newShears + " does NOT have any meta! Unable to update stats."); - return; - } - Integer sheepSheared = 0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.shearsSheared, PersistentDataType.INTEGER)) { - sheepSheared = container.get(toolStats.shearsSheared, PersistentDataType.INTEGER); - } - - if (sheepSheared == null) { - sheepSheared = 0; - toolStats.logger.warning(newShears + " does not have valid sheared set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.shearsSheared, PersistentDataType.INTEGER, sheepSheared + 1); - - if (toolStats.config.getBoolean("enabled.sheep-sheared")) { - String oldSheepFormatted = toolStats.numberFormat.formatInt(sheepSheared); - String newSheepFormatted = toolStats.numberFormat.formatInt(sheepSheared + 1); - Component oldLine = toolStats.configTools.formatLore("sheep-sheared", "{sheep}", oldSheepFormatted); - Component newLine = toolStats.configTools.formatLore("sheep-sheared", "{sheep}", newSheepFormatted); - if (oldLine == null || newLine == null) { - return; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - newShears.setItemMeta(meta); - } } diff --git a/src/main/java/lol/hyper/toolstats/events/ShootBow.java b/src/main/java/lol/hyper/toolstats/events/ShootBow.java index 4afb626..fcd66c4 100644 --- a/src/main/java/lol/hyper/toolstats/events/ShootBow.java +++ b/src/main/java/lol/hyper/toolstats/events/ShootBow.java @@ -62,9 +62,26 @@ public class ShootBow implements Listener { return; } - updateArrowsShot(heldBow); + ItemStack newItem = toolStats.itemLore.updateArrowsShot(heldBow, 1); + if (newItem != null) { + PlayerInventory inventory = player.getInventory(); + boolean isMain = inventory.getItemInMainHand().getType() == Material.BOW || inventory.getItemInMainHand().getType() == Material.CROSSBOW; + boolean isOffHand = inventory.getItemInOffHand().getType() == Material.BOW || inventory.getItemInOffHand().getType() == Material.CROSSBOW; + if (isMain) { + inventory.setItemInMainHand(newItem); + } + if (isOffHand) { + inventory.setItemInOffHand(newItem); + } + } } + /** + * Get the player's bow/crossbow. + * + * @param inventory Their inventory. + * @return Their bow/crossbow, either main or offhand. + */ private static @Nullable ItemStack getBow(PlayerInventory inventory) { ItemStack main = inventory.getItemInMainHand(); ItemStack offHand = inventory.getItemInOffHand(); @@ -84,40 +101,4 @@ public class ShootBow implements Listener { return null; } - - private void updateArrowsShot(ItemStack bow) { - ItemMeta meta = bow.getItemMeta(); - if (meta == null) { - toolStats.logger.warning(bow + " does NOT have any meta! Unable to update stats."); - return; - } - // read the current stats from the item - // if they don't exist, then start from 0 - Integer arrowsShot = 0; - PersistentDataContainer container = meta.getPersistentDataContainer(); - if (container.has(toolStats.arrowsShot, PersistentDataType.INTEGER)) { - arrowsShot = container.get(toolStats.arrowsShot, PersistentDataType.INTEGER); - } - - if (arrowsShot == null) { - arrowsShot = 0; - toolStats.logger.warning(arrowsShot + " does not have valid arrows-shot set! Resting to zero. This should NEVER happen."); - } - - container.set(toolStats.arrowsShot, PersistentDataType.INTEGER, arrowsShot + 1); - - // do we add the lore based on the config? - if (toolStats.config.getBoolean("enabled.arrows-shot")) { - String oldArrowsFormatted = toolStats.numberFormat.formatInt(arrowsShot); - String newArrowsFormatted = toolStats.numberFormat.formatInt(arrowsShot + 1); - Component oldLine = toolStats.configTools.formatLore("arrows-shot", "{arrows}", oldArrowsFormatted); - Component newLine = toolStats.configTools.formatLore("arrows-shot", "{arrows}", newArrowsFormatted); - if (oldLine == null || newLine == null) { - return; - } - List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); - meta.lore(newLore); - } - bow.setItemMeta(meta); - } } diff --git a/src/main/java/lol/hyper/toolstats/tools/ItemChecker.java b/src/main/java/lol/hyper/toolstats/tools/ItemChecker.java index 3d7fae9..6f83e2b 100644 --- a/src/main/java/lol/hyper/toolstats/tools/ItemChecker.java +++ b/src/main/java/lol/hyper/toolstats/tools/ItemChecker.java @@ -17,9 +17,15 @@ package lol.hyper.toolstats.tools; +import lol.hyper.toolstats.ToolStats; import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -29,11 +35,16 @@ public class ItemChecker { private final List armorItems = new ArrayList<>(); private final List meleeItems = new ArrayList<>(); private final List mineItems = new ArrayList<>(); + private final ToolStats toolStats; + + public ItemChecker(ToolStats toolStats) { + this.toolStats = toolStats; + } /** - * Creates an item checker and saves all valid items we want. + * Set up the item checker. */ - public ItemChecker() { + public void setup() { for (Material material : Material.values()) { String lowerCase = material.toString().toLowerCase(Locale.ROOT); if (lowerCase.contains("_pickaxe") || lowerCase.contains("_axe") || lowerCase.contains("_hoe") || lowerCase.contains("_shovel")) { @@ -52,11 +63,12 @@ public class ItemChecker { // hardcode these mineItems.add(Material.SHEARS); meleeItems.add(Material.TRIDENT); + meleeItems.add(Material.MACE); + validItems.add(Material.BOW); - validItems.add(Material.FISHING_ROD); validItems.add(Material.CROSSBOW); + validItems.add(Material.FISHING_ROD); validItems.add(Material.ELYTRA); - validItems.add(Material.MACE); // combine the lists validItems.addAll(armorItems); @@ -103,4 +115,89 @@ public class ItemChecker { public boolean isMineTool(Material itemType) { return mineItems.contains(itemType); } + + /** + * Check a given item for a target token. + * + * @param container The PDC of the item. + * @param targetToken The target to look for. + * @return True if the item has a given token, false if not. + */ + public boolean checkTokens(PersistentDataContainer container, String targetToken) { + // make sure the item has tokens + if (!container.has(toolStats.tokenApplied, PersistentDataType.STRING)) { + return false; + } + + // get the tokens for this item + String tokens = container.get(toolStats.tokenApplied, PersistentDataType.STRING); + if (tokens == null) { + return false; + } + + return tokens.contains(targetToken); + } + + /** + * Get the tokens for a given item. + * + * @param item The item. + * @return An array of the tokens, empty if there are none. + */ + private String[] getTokens(ItemStack item) { + // make sure the item has tokens + ItemMeta meta = item.getItemMeta(); + if (meta == null) { + return new String[0]; + } + PersistentDataContainer container = meta.getPersistentDataContainer(); + if (!container.has(toolStats.tokenApplied, PersistentDataType.STRING)) { + return new String[0]; + } + + // get the tokens for this item + String tokensRaw = container.get(toolStats.tokenApplied, PersistentDataType.STRING); + if (tokensRaw == null) { + return new String[0]; + } + + return tokensRaw.split(","); + } + + /** + * Add a token to an item. + * + * @param item The item. + * @param token The token to add. + * @return The new PDC with the new token. Null if something went wrong. + */ + public ItemStack addToken(ItemStack item, String token) { + ItemMeta meta = item.getItemMeta(); + if (meta == null) { + return null; + } + PersistentDataContainer container = meta.getPersistentDataContainer(); + String[] tokens = getTokens(item); + // there are no tokens + if (tokens.length == 0) { + container.set(toolStats.tokenApplied, PersistentDataType.STRING, token); + } else { + // other tokens exist, so add + String[] newTokens = Arrays.copyOf(tokens, tokens.length + 1); + newTokens[tokens.length] = token; + container.set(toolStats.tokenApplied, PersistentDataType.STRING, String.join(",", newTokens)); + } + item.setItemMeta(meta); + return item; + } + + /** + * Get the XP levels required to use token in anvil. + * + * @param tokenType The token type. + * @return The amount of levels to use. + */ + public int getCost(String tokenType) { + return toolStats.config.getInt("tokens.data." + tokenType + ".levels"); + } } diff --git a/src/main/java/lol/hyper/toolstats/tools/ItemLore.java b/src/main/java/lol/hyper/toolstats/tools/ItemLore.java index b0dff4a..9f3e5da 100644 --- a/src/main/java/lol/hyper/toolstats/tools/ItemLore.java +++ b/src/main/java/lol/hyper/toolstats/tools/ItemLore.java @@ -20,6 +20,7 @@ package lol.hyper.toolstats.tools; import lol.hyper.toolstats.ToolStats; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; @@ -68,6 +69,13 @@ public class ItemLore { return itemLore; } + /** + * Add lore to a given item. + * + * @param itemMeta The item's meta. + * @param newLine The new line to add to the lore. + * @return The new item's lore. + */ public List addItemLore(ItemMeta itemMeta, Component newLine) { List itemLore; if (itemMeta.hasLore()) { @@ -204,4 +212,486 @@ public class ItemLore { newLore.add(itemOwnerLore); return newLore; } + + /** + * Add x to the crops mined stat. + * + * @param playerTool The tool to update. + */ + public ItemStack updateCropsMined(ItemStack playerTool, int add) { + ItemStack clone = playerTool.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) { + toolStats.logger.warning(clone + " does NOT have any meta! Unable to update stats."); + return null; + } + // read the current stats from the item + // if they don't exist, then start from 0 + PersistentDataContainer container = meta.getPersistentDataContainer(); + + // check for tokens + if (toolStats.config.getBoolean("tokens.enabled")) { + // if the item has this token, then continue + // if the item does not, ignore + boolean validTokens = toolStats.itemChecker.checkTokens(container, "crops-mined"); + if (!validTokens) { + return null; + } + } + + Integer cropsMined = 0; + if (container.has(toolStats.cropsHarvested, PersistentDataType.INTEGER)) { + cropsMined = container.get(toolStats.cropsHarvested, PersistentDataType.INTEGER); + } + + if (cropsMined == null) { + cropsMined = 0; + toolStats.logger.warning(clone + " does not have valid crops-mined set! Resting to zero. This should NEVER happen."); + } + + container.set(toolStats.cropsHarvested, PersistentDataType.INTEGER, cropsMined + add); + + // do we add the lore based on the config? + if (toolStats.configTools.checkConfig(clone.getType(), "blocks-mined")) { + String oldCropsMinedFormatted = toolStats.numberFormat.formatInt(cropsMined); + String newCropsMinedFormatted = toolStats.numberFormat.formatInt(cropsMined + add); + Component oldLine = toolStats.configTools.formatLore("crops-harvested", "{crops}", oldCropsMinedFormatted); + Component newLine = toolStats.configTools.formatLore("crops-harvested", "{crops}", newCropsMinedFormatted); + if (oldLine == null || newLine == null) { + return null; + } + List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); + meta.lore(newLore); + } + clone.setItemMeta(meta); + return clone; + } + + /** + * Add x to the blocks mined stat. + * + * @param playerTool The tool to update. + */ + public ItemStack updateBlocksMined(ItemStack playerTool, int add) { + ItemStack clone = playerTool.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) { + toolStats.logger.warning(clone + " does NOT have any meta! Unable to update stats."); + return null; + } + + PersistentDataContainer container = meta.getPersistentDataContainer(); + // check for tokens + if (toolStats.config.getBoolean("tokens.enabled")) { + toolStats.logger.info("tokens are enabled!"); + // if the item has this token, then continue + // if the item does not, ignore + boolean validTokens = toolStats.itemChecker.checkTokens(container, "blocks-mined"); + if (!validTokens) { + return null; + } + } else { + toolStats.logger.info("tokens are disabled!"); + } + + // read the current stats from the item + // if they don't exist, then start from 0 + Integer blocksMined = 0; + if (container.has(toolStats.genericMined, PersistentDataType.INTEGER)) { + blocksMined = container.get(toolStats.genericMined, PersistentDataType.INTEGER); + } + + if (blocksMined == null) { + blocksMined = 0; + toolStats.logger.warning(clone + " does not have valid generic-mined set! Resting to zero. This should NEVER happen."); + } + + container.set(toolStats.genericMined, PersistentDataType.INTEGER, blocksMined + add); + + // do we add the lore based on the config? + if (toolStats.configTools.checkConfig(clone.getType(), "blocks-mined")) { + String oldBlocksMinedFormatted = toolStats.numberFormat.formatInt(blocksMined); + String newBlocksMinedFormatted = toolStats.numberFormat.formatInt(blocksMined + add); + Component oldLine = toolStats.configTools.formatLore("blocks-mined", "{blocks}", oldBlocksMinedFormatted); + Component newLine = toolStats.configTools.formatLore("blocks-mined", "{blocks}", newBlocksMinedFormatted); + if (oldLine == null || newLine == null) { + return null; + } + List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); + meta.lore(newLore); + toolStats.logger.info("adding lore!"); + } + clone.setItemMeta(meta); + toolStats.logger.info("reached end of function!"); + return clone; + } + + /** + * Add +1 to the player kills stat. + * + * @param playerWeapon The tool to update. + */ + public ItemStack updatePlayerKills(ItemStack playerWeapon, int add) { + ItemStack clone = playerWeapon.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) { + toolStats.logger.warning(clone + " does NOT have any meta! Unable to update stats."); + return null; + } + + PersistentDataContainer container = meta.getPersistentDataContainer(); + // check for tokens + if (toolStats.config.getBoolean("tokens.enabled")) { + // if the item has this token, then continue + // if the item does not, ignore + boolean validTokens = toolStats.itemChecker.checkTokens(container, "player-kills"); + if (!validTokens) { + return null; + } + } + + Integer playerKills = 0; + if (container.has(toolStats.swordPlayerKills, PersistentDataType.INTEGER)) { + playerKills = container.get(toolStats.swordPlayerKills, PersistentDataType.INTEGER); + } + + if (playerKills == null) { + playerKills = 0; + toolStats.logger.warning(clone + " does not have valid player-kills set! Resting to zero. This should NEVER happen."); + } + + container.set(toolStats.swordPlayerKills, PersistentDataType.INTEGER, playerKills + add); + + // do we add the lore based on the config? + if (toolStats.configTools.checkConfig(clone.getType(), "player-kills")) { + String oldPlayerKillsFormatted = toolStats.numberFormat.formatInt(playerKills); + String newPlayerKillsFormatted = toolStats.numberFormat.formatInt(playerKills + add); + Component oldLine = toolStats.configTools.formatLore("kills.player", "{kills}", oldPlayerKillsFormatted); + Component newLine = toolStats.configTools.formatLore("kills.player", "{kills}", newPlayerKillsFormatted); + if (oldLine == null || newLine == null) { + return null; + } + List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); + meta.lore(newLore); + } + clone.setItemMeta(meta); + return clone; + } + + /** + * Add x to the mob kills stat. + * + * @param playerWeapon The tool to update. + */ + public ItemStack updateMobKills(ItemStack playerWeapon, int add) { + ItemStack clone = playerWeapon.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) { + toolStats.logger.warning(clone + " does NOT have any meta! Unable to update stats."); + return null; + } + + PersistentDataContainer container = meta.getPersistentDataContainer(); + // check for tokens + if (toolStats.config.getBoolean("tokens.enabled")) { + // if the item has this token, then continue + // if the item does not, ignore + boolean validTokens = toolStats.itemChecker.checkTokens(container, "mob-kills"); + if (!validTokens) { + return null; + } + } + + Integer mobKills = 0; + if (container.has(toolStats.swordMobKills, PersistentDataType.INTEGER)) { + mobKills = container.get(toolStats.swordMobKills, PersistentDataType.INTEGER); + } + + if (mobKills == null) { + mobKills = 0; + toolStats.logger.warning(clone + " does not have valid mob-kills set! Resting to zero. This should NEVER happen."); + } + + container.set(toolStats.swordMobKills, PersistentDataType.INTEGER, mobKills + add); + + // do we add the lore based on the config? + if (toolStats.configTools.checkConfig(clone.getType(), "mob-kills")) { + String oldMobKillsFormatted = toolStats.numberFormat.formatInt(mobKills); + String newMobKillsFormatted = toolStats.numberFormat.formatInt(mobKills + add); + Component oldLine = toolStats.configTools.formatLore("kills.mob", "{kills}", oldMobKillsFormatted); + Component newLine = toolStats.configTools.formatLore("kills.mob", "{kills}", newMobKillsFormatted); + if (oldLine == null || newLine == null) { + return null; + } + List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); + meta.lore(newLore); + } + clone.setItemMeta(meta); + return clone; + } + + /** + * Add damage to an armor piece. + * + * @param armorPiece The armor to update. + */ + public ItemStack updateDamage(ItemStack armorPiece, double damage) { + // ignore if the damage is zero or negative + if (damage < 0) { + return null; + } + ItemStack clone = armorPiece.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) { + toolStats.logger.warning(clone + " does NOT have any meta! Unable to update stats."); + return null; + } + + PersistentDataContainer container = meta.getPersistentDataContainer(); + // check for tokens + if (toolStats.config.getBoolean("tokens.enabled")) { + // if the item has this token, then continue + // if the item does not, ignore + boolean validTokens = toolStats.itemChecker.checkTokens(container, "damage-taken"); + if (!validTokens) { + return null; + } + } + + Double damageTaken = 0.0; + if (container.has(toolStats.armorDamage, PersistentDataType.DOUBLE)) { + damageTaken = container.get(toolStats.armorDamage, PersistentDataType.DOUBLE); + } + + if (damageTaken == null) { + damageTaken = 0.0; + toolStats.logger.warning(clone + " does not have valid damage-taken set! Resting to zero. This should NEVER happen."); + } + + container.set(toolStats.armorDamage, PersistentDataType.DOUBLE, damageTaken + damage); + + if (toolStats.config.getBoolean("enabled.armor-damage")) { + String oldDamageFormatted = toolStats.numberFormat.formatDouble(damageTaken); + String newDamageFormatted = toolStats.numberFormat.formatDouble(damageTaken + damage); + Component oldLine = toolStats.configTools.formatLore("damage-taken", "{damage}", oldDamageFormatted); + Component newLine = toolStats.configTools.formatLore("damage-taken", "{damage}", newDamageFormatted); + if (oldLine == null || newLine == null) { + return null; + } + List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); + meta.lore(newLore); + } + clone.setItemMeta(meta); + return clone; + } + + /** + * Add flight time to an elytra. + * + * @param elytra The player's elytra. + */ + public ItemStack updateFlightTime(ItemStack elytra, long duration) { + ItemStack clone = elytra.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) { + toolStats.logger.warning(clone + " does NOT have any meta! Unable to update stats."); + return null; + } + + PersistentDataContainer container = meta.getPersistentDataContainer(); + // check for tokens + if (toolStats.config.getBoolean("tokens.enabled")) { + // if the item has this token, then continue + // if the item does not, ignore + boolean validTokens = toolStats.itemChecker.checkTokens(container, "flight-time"); + if (!validTokens) { + return null; + } + } + + // read the current stats from the item + // if they don't exist, then start from 0 + Long flightTime = 0L; + if (container.has(toolStats.flightTime, PersistentDataType.LONG)) { + flightTime = container.get(toolStats.flightTime, PersistentDataType.LONG); + } + + if (flightTime == null) { + flightTime = 0L; + toolStats.logger.warning(flightTime + " does not have valid flight-time set! Resting to zero. This should NEVER happen."); + } + + container.set(toolStats.flightTime, PersistentDataType.LONG, flightTime + duration); + + // do we add the lore based on the config? + if (toolStats.config.getBoolean("enabled.flight-time")) { + String oldFlightFormatted = toolStats.numberFormat.formatDouble((double) flightTime / 1000); + String newFlightFormatted = toolStats.numberFormat.formatDouble((double) (flightTime + duration) / 1000); + Component oldLine = toolStats.configTools.formatLore("flight-time", "{time}", oldFlightFormatted); + Component newLine = toolStats.configTools.formatLore("flight-time", "{time}", newFlightFormatted); + if (oldLine == null || newLine == null) { + return null; + } + List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); + meta.lore(newLore); + } + clone.setItemMeta(meta); + return clone; + } + + /** + * Add x to sheep sheared stat. + * + * @param shears The shears. + */ + public ItemStack updateSheepSheared(ItemStack shears, int add) { + ItemStack clone = shears.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) { + toolStats.logger.warning(clone + " does NOT have any meta! Unable to update stats."); + return null; + } + + PersistentDataContainer container = meta.getPersistentDataContainer(); + // check for tokens + if (toolStats.config.getBoolean("tokens.enabled")) { + // if the item has this token, then continue + // if the item does not, ignore + boolean validTokens = toolStats.itemChecker.checkTokens(container, "sheep-sheared"); + if (!validTokens) { + return null; + } + } + + Integer sheepSheared = 0; + if (container.has(toolStats.shearsSheared, PersistentDataType.INTEGER)) { + sheepSheared = container.get(toolStats.shearsSheared, PersistentDataType.INTEGER); + } + + if (sheepSheared == null) { + sheepSheared = 0; + toolStats.logger.warning(clone + " does not have valid sheared set! Resting to zero. This should NEVER happen."); + } + + container.set(toolStats.shearsSheared, PersistentDataType.INTEGER, sheepSheared + add); + + if (toolStats.config.getBoolean("enabled.sheep-sheared")) { + String oldSheepFormatted = toolStats.numberFormat.formatInt(sheepSheared); + String newSheepFormatted = toolStats.numberFormat.formatInt(sheepSheared + add); + Component oldLine = toolStats.configTools.formatLore("sheep-sheared", "{sheep}", oldSheepFormatted); + Component newLine = toolStats.configTools.formatLore("sheep-sheared", "{sheep}", newSheepFormatted); + if (oldLine == null || newLine == null) { + return null; + } + List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); + meta.lore(newLore); + } + clone.setItemMeta(meta); + return clone; + } + + /** + * Add x to arrows shot stat. + * + * @param bow The bow. + */ + public ItemStack updateArrowsShot(ItemStack bow, int add) { + ItemStack clone = bow.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) { + toolStats.logger.warning(clone + " does NOT have any meta! Unable to update stats."); + return null; + } + + PersistentDataContainer container = meta.getPersistentDataContainer(); + // check for tokens + if (toolStats.config.getBoolean("tokens.enabled")) { + // if the item has this token, then continue + // if the item does not, ignore + boolean validTokens = toolStats.itemChecker.checkTokens(container, "arrows-shot"); + if (!validTokens) { + return null; + } + } + + // read the current stats from the item + // if they don't exist, then start from 0 + Integer arrowsShot = 0; + if (container.has(toolStats.arrowsShot, PersistentDataType.INTEGER)) { + arrowsShot = container.get(toolStats.arrowsShot, PersistentDataType.INTEGER); + } + + if (arrowsShot == null) { + arrowsShot = 0; + toolStats.logger.warning(arrowsShot + " does not have valid arrows-shot set! Resting to zero. This should NEVER happen."); + } + + container.set(toolStats.arrowsShot, PersistentDataType.INTEGER, arrowsShot + add); + + // do we add the lore based on the config? + if (toolStats.config.getBoolean("enabled.arrows-shot")) { + String oldArrowsFormatted = toolStats.numberFormat.formatInt(arrowsShot); + String newArrowsFormatted = toolStats.numberFormat.formatInt(arrowsShot + add); + Component oldLine = toolStats.configTools.formatLore("arrows-shot", "{arrows}", oldArrowsFormatted); + Component newLine = toolStats.configTools.formatLore("arrows-shot", "{arrows}", newArrowsFormatted); + if (oldLine == null || newLine == null) { + return null; + } + List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); + meta.lore(newLore); + } + clone.setItemMeta(meta); + return clone; + } + + /** + * Add x to fish caught stat. + * + * @param fishingRod The fishing rod. + */ + public ItemStack updateFishCaught(ItemStack fishingRod, int add) { + ItemStack clone = fishingRod.clone(); + ItemMeta meta = clone.getItemMeta(); + if (meta == null) { + toolStats.logger.warning(clone + " does NOT have any meta! Unable to update stats."); + return null; + } + + PersistentDataContainer container = meta.getPersistentDataContainer(); + // check for tokens + if (toolStats.config.getBoolean("tokens.enabled")) { + // if the item has this token, then continue + // if the item does not, ignore + boolean validTokens = toolStats.itemChecker.checkTokens(container, "fish-caught"); + if (!validTokens) { + return null; + } + } + + Integer fishCaught = 0; + if (container.has(toolStats.fishingRodCaught, PersistentDataType.INTEGER)) { + fishCaught = container.get(toolStats.fishingRodCaught, PersistentDataType.INTEGER); + } + + if (fishCaught == null) { + fishCaught = 0; + toolStats.logger.warning(clone + " does not have valid fish-caught set! Resting to zero. This should NEVER happen."); + } + + container.set(toolStats.fishingRodCaught, PersistentDataType.INTEGER, fishCaught + add); + + if (toolStats.config.getBoolean("enabled.fish-caught")) { + String oldFishFormatted = toolStats.numberFormat.formatInt(fishCaught); + String newFishFormatted = toolStats.numberFormat.formatInt(fishCaught + add); + Component oldLine = toolStats.configTools.formatLore("fished.fish-caught", "{fish}", oldFishFormatted); + Component newLine = toolStats.configTools.formatLore("fished.fish-caught", "{fish}", newFishFormatted); + if (oldLine == null || newLine == null) { + return null; + } + List newLore = toolStats.itemLore.updateItemLore(meta, oldLine, newLine); + meta.lore(newLore); + } + clone.setItemMeta(meta); + return clone; + } } diff --git a/src/main/java/lol/hyper/toolstats/tools/NumberFormat.java b/src/main/java/lol/hyper/toolstats/tools/NumberFormat.java index ac3e004..24aa3ec 100644 --- a/src/main/java/lol/hyper/toolstats/tools/NumberFormat.java +++ b/src/main/java/lol/hyper/toolstats/tools/NumberFormat.java @@ -65,7 +65,7 @@ public class NumberFormat { } if (decimalFormat == null) { - decimalFormat = "#,###.00"; + decimalFormat = "#,##0.00"; toolStats.logger.warning("number-formats.comma-separator is missing! Using default #,###.00 instead."); } diff --git a/src/main/java/lol/hyper/toolstats/tools/TokenCrafting.java b/src/main/java/lol/hyper/toolstats/tools/TokenCrafting.java new file mode 100644 index 0000000..88b730c --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/tools/TokenCrafting.java @@ -0,0 +1,121 @@ +/* + * This file is part of ToolStats. + * + * ToolStats is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ToolStats is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ToolStats. If not, see . + */ + +package lol.hyper.toolstats.tools; + +import lol.hyper.toolstats.ToolStats; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ShapedRecipe; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +public class TokenCrafting { + + private final ToolStats toolStats; + private final Set recipes = new HashSet<>(); + private final ArrayList tokenTypes = new ArrayList<>(); + + public TokenCrafting(ToolStats toolStats) { + this.toolStats = toolStats; + } + + public void setup() { + NamespacedKey playerKillsKey = new NamespacedKey(toolStats, "player-kills-token"); + ShapedRecipe playerKillRecipe = new ShapedRecipe(playerKillsKey, toolStats.tokenItems.playerKills()); + playerKillRecipe.shape(" P ", "PSP", " P "); + playerKillRecipe.setIngredient('P', Material.PAPER); + playerKillRecipe.setIngredient('S', Material.WOODEN_SWORD); + recipes.add(playerKillRecipe); + + NamespacedKey mobKillsKey = new NamespacedKey(toolStats, "mob-kills-token"); + ShapedRecipe mobKillsRecipe = new ShapedRecipe(mobKillsKey, toolStats.tokenItems.mobKills()); + mobKillsRecipe.shape(" P ", "PRP", " P "); + mobKillsRecipe.setIngredient('P', Material.PAPER); + mobKillsRecipe.setIngredient('R', Material.ROTTEN_FLESH); + recipes.add(mobKillsRecipe); + + NamespacedKey blocksMinedKey = new NamespacedKey(toolStats, "blocks-mined-token"); + ShapedRecipe blocksMinedRecipe = new ShapedRecipe(blocksMinedKey, toolStats.tokenItems.blocksMined()); + blocksMinedRecipe.shape(" P ", "PSP", " P "); + blocksMinedRecipe.setIngredient('P', Material.PAPER); + blocksMinedRecipe.setIngredient('S', Material.WOODEN_PICKAXE); + recipes.add(blocksMinedRecipe); + + NamespacedKey cropsMinedKey = new NamespacedKey(toolStats, "crops-mined-token"); + ShapedRecipe cropsMinedRecipe = new ShapedRecipe(cropsMinedKey, toolStats.tokenItems.cropsMined()); + cropsMinedRecipe.shape(" P ", "PHP", " P "); + cropsMinedRecipe.setIngredient('P', Material.PAPER); + cropsMinedRecipe.setIngredient('H', Material.WOODEN_HOE); + recipes.add(cropsMinedRecipe); + + NamespacedKey fishCaughtKey = new NamespacedKey(toolStats, "fish-caught-token"); + ShapedRecipe fishCaughtRecipe = new ShapedRecipe(fishCaughtKey, toolStats.tokenItems.fishCaught()); + fishCaughtRecipe.shape(" P ", "PCP", " P "); + fishCaughtRecipe.setIngredient('P', Material.PAPER); + fishCaughtRecipe.setIngredient('C', Material.COD); + recipes.add(fishCaughtRecipe); + + NamespacedKey sheepShearedKey = new NamespacedKey(toolStats, "sheep-sheared-token"); + ShapedRecipe sheepShearedRecipe = new ShapedRecipe(sheepShearedKey, toolStats.tokenItems.sheepSheared()); + sheepShearedRecipe.shape(" P ", "PWP", " P "); + sheepShearedRecipe.setIngredient('P', Material.PAPER); + sheepShearedRecipe.setIngredient('W', Material.WHITE_WOOL); + recipes.add(sheepShearedRecipe); + + NamespacedKey armorDamageKey = new NamespacedKey(toolStats, "damage-taken-token"); + ShapedRecipe armorDamageRecipe = new ShapedRecipe(armorDamageKey, toolStats.tokenItems.damageTaken()); + armorDamageRecipe.shape(" P ", "PCP", " P "); + armorDamageRecipe.setIngredient('P', Material.PAPER); + armorDamageRecipe.setIngredient('C', Material.LEATHER_CHESTPLATE); + recipes.add(armorDamageRecipe); + + NamespacedKey arrowsShotKey = new NamespacedKey(toolStats, "arrows-shot-token"); + ShapedRecipe arrowsShotRecipe = new ShapedRecipe(arrowsShotKey, toolStats.tokenItems.arrowsShot()); + arrowsShotRecipe.shape(" P ", "PAP", " P "); + arrowsShotRecipe.setIngredient('P', Material.PAPER); + arrowsShotRecipe.setIngredient('A', Material.ARROW); + recipes.add(arrowsShotRecipe); + + NamespacedKey flightTimeKey = new NamespacedKey(toolStats, "flight-time-token"); + ShapedRecipe flightTimeRecipe = new ShapedRecipe(flightTimeKey, toolStats.tokenItems.flightTime()); + flightTimeRecipe.shape(" P ", "PFP", " P "); + flightTimeRecipe.setIngredient('P', Material.PAPER); + flightTimeRecipe.setIngredient('F', Material.FEATHER); + recipes.add(flightTimeRecipe); + + tokenTypes.add("crops-mined"); + tokenTypes.add("blocks-mined"); + tokenTypes.add("damage-taken"); + tokenTypes.add("mob-kills"); + tokenTypes.add("player-kills"); + tokenTypes.add("arrows-shot"); + tokenTypes.add("sheep-sheared"); + tokenTypes.add("flight-time"); + tokenTypes.add("fish-caught"); + } + + public Set getRecipes() { + return recipes; + } + + public ArrayList getTokenTypes() { + return tokenTypes; + } +} diff --git a/src/main/java/lol/hyper/toolstats/tools/config/ConfigTools.java b/src/main/java/lol/hyper/toolstats/tools/config/ConfigTools.java index e1a1548..6cfa495 100644 --- a/src/main/java/lol/hyper/toolstats/tools/config/ConfigTools.java +++ b/src/main/java/lol/hyper/toolstats/tools/config/ConfigTools.java @@ -20,6 +20,7 @@ package lol.hyper.toolstats.tools.config; import lol.hyper.toolstats.ToolStats; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Material; @@ -31,7 +32,6 @@ public class ConfigTools { private final ToolStats toolStats; public static final Pattern COLOR_CODES = Pattern.compile("[&§]([0-9a-fk-or])"); public static final Pattern CONFIG_HEX_PATTERN = Pattern.compile("[&§]#([A-Fa-f0-9]{6})"); - public static final Pattern MINECRAFT_HEX_PATTERN = Pattern.compile("§x(?:§[a-fA-F0-9]){6}|§[a-fA-F0-9]"); public ConfigTools(ToolStats toolStats) { this.toolStats = toolStats; @@ -124,22 +124,42 @@ public class ConfigTools { component = LegacyComponentSerializer.legacyAmpersand().deserialize(lore); } else { // otherwise format them normally - component = Component.text(lore); + component = MiniMessage.miniMessage().deserialize(lore); } return component.decorationIfAbsent(TextDecoration.ITALIC, TextDecoration.State.FALSE); } /** - * Remove all color codes from a message. + * Format a string from the config. * - * @param message The message. - * @return The message without color codes. + * @param configName The config to format. + * @return Formatted string, null if the configName doesn't exist. */ - public String removeColor(String message) { - message = MINECRAFT_HEX_PATTERN.matcher(message).replaceAll(""); - message = COLOR_CODES.matcher(message).replaceAll(""); - message = CONFIG_HEX_PATTERN.matcher(message).replaceAll(""); - return message; + public Component format(String configName) { + String message = toolStats.config.getString(configName); + if (message == null) { + toolStats.logger.warning("Unable to find config message for: " + configName); + return null; + } + + // if the config message is empty, don't send it + if (message.isEmpty()) { + return null; + } + + // the final component for this lore + Component component; + // if we match the old color codes, then format them as so + Matcher hexMatcher = CONFIG_HEX_PATTERN.matcher(message); + Matcher colorMatcher = COLOR_CODES.matcher(message); + if (hexMatcher.find() || colorMatcher.find()) { + component = LegacyComponentSerializer.legacyAmpersand().deserialize(message); + } else { + // otherwise format them normally + component = MiniMessage.miniMessage().deserialize(message); + } + + return component; } } diff --git a/src/main/java/lol/hyper/toolstats/tools/config/ConfigUpdater.java b/src/main/java/lol/hyper/toolstats/tools/config/ConfigUpdater.java index 1d76978..2194c0b 100644 --- a/src/main/java/lol/hyper/toolstats/tools/config/ConfigUpdater.java +++ b/src/main/java/lol/hyper/toolstats/tools/config/ConfigUpdater.java @@ -21,6 +21,7 @@ import lol.hyper.toolstats.ToolStats; import lol.hyper.toolstats.tools.config.versions.Version6; import lol.hyper.toolstats.tools.config.versions.Version7; import lol.hyper.toolstats.tools.config.versions.Version8; +import lol.hyper.toolstats.tools.config.versions.Version9; public class ConfigUpdater { @@ -52,6 +53,12 @@ public class ConfigUpdater { version8.update(); break; } + case 8: { + // Version 8 to 9 + Version9 version9 = new Version9(toolStats); + version9.update(); + break; + } } } } diff --git a/src/main/java/lol/hyper/toolstats/tools/config/TokenItems.java b/src/main/java/lol/hyper/toolstats/tools/config/TokenItems.java new file mode 100644 index 0000000..f3b9842 --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/tools/config/TokenItems.java @@ -0,0 +1,219 @@ +/* + * This file is part of ToolStats. + * + * ToolStats is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ToolStats is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ToolStats. If not, see . + */ + +package lol.hyper.toolstats.tools.config; + +import lol.hyper.toolstats.ToolStats; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.util.ArrayList; +import java.util.List; + +public class TokenItems { + + private final ToolStats toolStats; + + public TokenItems(ToolStats toolStats) { + this.toolStats = toolStats; + } + + public ItemStack playerKills() { + // set up the item + ItemStack token = new ItemStack(Material.PAPER); + ItemMeta tokenMeta = token.getItemMeta(); + PersistentDataContainer tokenData = tokenMeta.getPersistentDataContainer(); + + // set the title and lore + Component title = toolStats.configTools.format("tokens.data.player-kills.title"); + Component lore = toolStats.configTools.format("tokens.data.player-kills.lore"); + tokenMeta.displayName(title); + List newLore = new ArrayList<>(); + newLore.add(lore); + tokenMeta.lore(newLore); + + // set the PDC + tokenData.set(toolStats.tokenType, PersistentDataType.STRING, "player-kills"); + token.setItemMeta(tokenMeta); + return token; + } + + public ItemStack mobKills() { + // set up the item + ItemStack token = new ItemStack(Material.PAPER); + ItemMeta tokenMeta = token.getItemMeta(); + PersistentDataContainer tokenData = tokenMeta.getPersistentDataContainer(); + + // set the title and lore + Component title = toolStats.configTools.format("tokens.data.mob-kills.title"); + Component lore = toolStats.configTools.format("tokens.data.mob-kills.lore"); + tokenMeta.displayName(title); + List newLore = new ArrayList<>(); + newLore.add(lore); + tokenMeta.lore(newLore); + + // set the PDC + tokenData.set(toolStats.tokenType, PersistentDataType.STRING, "mob-kills"); + token.setItemMeta(tokenMeta); + return token; + } + + public ItemStack blocksMined() { + // set up the item + ItemStack token = new ItemStack(Material.PAPER); + ItemMeta tokenMeta = token.getItemMeta(); + PersistentDataContainer tokenData = tokenMeta.getPersistentDataContainer(); + + // set the title and lore + Component title = toolStats.configTools.format("tokens.data.blocks-mined.title"); + Component lore = toolStats.configTools.format("tokens.data.blocks-mined.lore"); + tokenMeta.displayName(title); + List newLore = new ArrayList<>(); + newLore.add(lore); + tokenMeta.lore(newLore); + + // set the PDC + tokenData.set(toolStats.tokenType, PersistentDataType.STRING, "blocks-mined"); + token.setItemMeta(tokenMeta); + return token; + } + + public ItemStack cropsMined() { + // set up the item + ItemStack token = new ItemStack(Material.PAPER); + ItemMeta tokenMeta = token.getItemMeta(); + PersistentDataContainer tokenData = tokenMeta.getPersistentDataContainer(); + + // set the title and lore + Component title = toolStats.configTools.format("tokens.data.crops-mined.title"); + Component lore = toolStats.configTools.format("tokens.data.crops-mined.lore"); + tokenMeta.displayName(title); + List newLore = new ArrayList<>(); + newLore.add(lore); + tokenMeta.lore(newLore); + + // set the PDC + tokenData.set(toolStats.tokenType, PersistentDataType.STRING, "crops-mined"); + token.setItemMeta(tokenMeta); + return token; + } + + public ItemStack fishCaught() { + // set up the item + ItemStack token = new ItemStack(Material.PAPER); + ItemMeta tokenMeta = token.getItemMeta(); + PersistentDataContainer tokenData = tokenMeta.getPersistentDataContainer(); + + // set the title and lore + Component title = toolStats.configTools.format("tokens.data.fish-caught.title"); + Component lore = toolStats.configTools.format("tokens.data.fish-caught.lore"); + tokenMeta.displayName(title); + List newLore = new ArrayList<>(); + newLore.add(lore); + tokenMeta.lore(newLore); + + // set the PDC + tokenData.set(toolStats.tokenType, PersistentDataType.STRING, "fish-caught"); + token.setItemMeta(tokenMeta); + return token; + } + + public ItemStack sheepSheared() { + // set up the item + ItemStack token = new ItemStack(Material.PAPER); + ItemMeta tokenMeta = token.getItemMeta(); + PersistentDataContainer tokenData = tokenMeta.getPersistentDataContainer(); + + // set the title and lore + Component title = toolStats.configTools.format("tokens.data.sheep-sheared.title"); + Component lore = toolStats.configTools.format("tokens.data.sheep-sheared.lore"); + tokenMeta.displayName(title); + List newLore = new ArrayList<>(); + newLore.add(lore); + tokenMeta.lore(newLore); + + // set the PDC + tokenData.set(toolStats.tokenType, PersistentDataType.STRING, "sheep-sheared"); + token.setItemMeta(tokenMeta); + return token; + } + + public ItemStack damageTaken() { + // set up the item + ItemStack token = new ItemStack(Material.PAPER); + ItemMeta tokenMeta = token.getItemMeta(); + PersistentDataContainer tokenData = tokenMeta.getPersistentDataContainer(); + + // set the title and lore + Component title = toolStats.configTools.format("tokens.data.damage-taken.title"); + Component lore = toolStats.configTools.format("tokens.data.damage-taken.lore"); + tokenMeta.displayName(title); + List newLore = new ArrayList<>(); + newLore.add(lore); + tokenMeta.lore(newLore); + + // set the PDC + tokenData.set(toolStats.tokenType, PersistentDataType.STRING, "damage-taken"); + token.setItemMeta(tokenMeta); + return token; + } + + public ItemStack arrowsShot() { + // set up the item + ItemStack token = new ItemStack(Material.PAPER); + ItemMeta tokenMeta = token.getItemMeta(); + PersistentDataContainer tokenData = tokenMeta.getPersistentDataContainer(); + + // set the title and lore + Component title = toolStats.configTools.format("tokens.data.arrows-shot.title"); + Component lore = toolStats.configTools.format("tokens.data.arrows-shot.lore"); + tokenMeta.displayName(title); + List newLore = new ArrayList<>(); + newLore.add(lore); + tokenMeta.lore(newLore); + + // set the PDC + tokenData.set(toolStats.tokenType, PersistentDataType.STRING, "arrows-shot"); + token.setItemMeta(tokenMeta); + return token; + } + + public ItemStack flightTime() { + // set up the item + ItemStack token = new ItemStack(Material.PAPER); + ItemMeta tokenMeta = token.getItemMeta(); + PersistentDataContainer tokenData = tokenMeta.getPersistentDataContainer(); + + // set the title and lore + Component title = toolStats.configTools.format("tokens.data.flight-time.title"); + Component lore = toolStats.configTools.format("tokens.data.flight-time.lore"); + tokenMeta.displayName(title); + List newLore = new ArrayList<>(); + newLore.add(lore); + tokenMeta.lore(newLore); + + // set the PDC + tokenData.set(toolStats.tokenType, PersistentDataType.STRING, "flight-time"); + token.setItemMeta(tokenMeta); + return token; + } +} diff --git a/src/main/java/lol/hyper/toolstats/tools/config/versions/Version9.java b/src/main/java/lol/hyper/toolstats/tools/config/versions/Version9.java new file mode 100644 index 0000000..63d49fd --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/tools/config/versions/Version9.java @@ -0,0 +1,99 @@ +/* + * This file is part of ToolStats. + * + * ToolStats is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ToolStats is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ToolStats. If not, see . + */ + +package lol.hyper.toolstats.tools.config.versions; + +import lol.hyper.toolstats.ToolStats; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class Version9 { + + private final ToolStats toolStats; + + /** + * Used for updating from version 8 to 9. + * + * @param toolStats ToolStats instance. + */ + public Version9(ToolStats toolStats) { + this.toolStats = toolStats; + } + + /** + * Perform the config update. + */ + public void update() { + // save the old config first + try { + toolStats.config.save("plugins" + File.separator + "ToolStats" + File.separator + "config-8.yml"); + } catch (IOException exception) { + toolStats.logger.severe("Unable to save config-8.yml!"); + throw new RuntimeException(exception); + } + + toolStats.logger.info("Updating config.yml to version 9."); + toolStats.config.set("config-version", 9); + + toolStats.logger.info("Adding new tokens configuration!"); + // false by default so it doesn't break servers on updating + toolStats.config.set("tokens.enabled", false); + toolStats.config.set("tokens.craft-tokens", true); + + List tokenComments = new ArrayList<>(); + tokenComments.add("Use token system for tracking stats."); + tokenComments.add("See https://github.com/hyperdefined/ToolStats/wiki/Token-System"); + toolStats.config.setComments("tokens", tokenComments); + + addToken("player-kills", "&7ToolStats: &8Player Kills Token", "&8Combine with a melee or ranged weapon in an anvil to track player kills."); + addToken("mob-kills", "&7ToolStats: &8Mob Kills Token", "&8Combine with a melee or ranged weapon in an anvil to track mob kills."); + addToken("blocks-mined", "&7ToolStats: &8Blocks Mined Token", "&8Combine with a pickaxe, axe, shovel, or shears in an anvil to track blocks mined."); + addToken("crops-mined", "&7ToolStats: &8Crops Mined Token", "&8Combine with a hoe in an anvil to track crops broken."); + addToken("fish-caught", "&7ToolStats: &8Fish Caught Token", "&8Combine with a fishing rod in an anvil to track fish caught."); + addToken("sheep-sheared", "&7ToolStats: &8Sheep Sheared Token", "&8Combine with shears in an anvil to track sheep sheared."); + addToken("damage-taken", "&7ToolStats: &8Damage Taken Token", "&8Combine with an armor piece in an anvil to track damage taken."); + addToken("arrows-shot", "&7ToolStats: &8Arrows Shot Token", "&8Combine with a bow or crossbow in an anvil to track arrows shot."); + addToken("flight-time", "&7ToolStats: &8Flight Time Token", "&8Combine with an elytra in an anvil to track flight time."); + + // save the config and reload it + try { + toolStats.config.save("plugins" + File.separator + "ToolStats" + File.separator + "config.yml"); + } catch (IOException exception) { + toolStats.logger.severe("Unable to save config.yml!"); + throw new RuntimeException(exception); + } + toolStats.loadConfig(); + toolStats.logger.info("Config has been updated to version 9. A copy of version 8 has been saved as config-8.yml"); + } + + /** + * Add a given token to the config. Made this since I was lazy. + * + * @param tokenType The token type to add. + * @param title The title for the item. + * @param lore The lore of the item. + */ + private void addToken(String tokenType, String title, String lore) { + toolStats.logger.info("Adding token type configuration for " + tokenType); + toolStats.config.set("tokens.data." + tokenType + ".title", title); + toolStats.config.set("tokens.data." + tokenType + ".lore", lore); + toolStats.config.set("tokens.data." + tokenType + ".levels", 1); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index d86df86..b9b5421 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,3 +1,46 @@ +# Use token system for tracking stats. +# See https://github.com/hyperdefined/ToolStats/wiki/Token-System +tokens: + enabled: true + craft-tokens: true + data: + player-kills: + title: "&7ToolStats: &8Player Kills Token" + lore: "&8Combine with a melee or ranged weapon in an anvil to track player kills." + levels: 1 + mob-kills: + title: "&7ToolStats: &8Mob Kills Token" + lore: "&8Combine with a melee or ranged weapon in an anvil to track mob kills." + levels: 1 + blocks-mined: + title: "&7ToolStats: &8Blocks Mined Token" + lore: "&8Combine with a pickaxe, axe, shovel, or shears in an anvil to track blocks mined." + levels: 1 + crops-mined: + title: "&7ToolStats: &8Crops Mined Token" + lore: "&8Combine with a hoe in an anvil to track crops broken." + levels: 1 + fish-caught: + title: "&7ToolStats: &8Fish Caught Token" + lore: "&8Combine with a fishing rod in an anvil to track fish caught." + levels: 1 + sheep-sheared: + title: "&7ToolStats: &8Sheep Sheared Token" + lore: "&8Combine with shears in an anvil to track sheep sheared." + levels: 1 + damage-taken: + title: "&7ToolStats: &8Damage Taken Token" + lore: "&8Combine with an armor piece in an anvil to track damage taken." + levels: 1 + arrows-shot: + title: "&7ToolStats: &8Arrows Shot Token" + lore: "&8Combine with a bow or crossbow in an anvil to track arrows shot." + levels: 1 + flight-time: + title: "&7ToolStats: &8Flight Time Token" + lore: "&8Combine with an elytra in an anvil to track flight time." + levels: 1 + enabled: # Will show ownership of items when they are created/found. created-by: @@ -132,11 +175,11 @@ number-formats: comma-separator: "," decimal-separator: "." comma-format: "#,###" - decimal-format: "#,###.00" + decimal-format: "#,##0.00" # When any tool is created, it will generate a hash for the item. # This hash is not on the item lore, only stored in the NBT data. # This has no use currently, but can be used for future features for dupe detection. generate-hash-for-items: true -config-version: 8 \ No newline at end of file +config-version: 9 \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 5ac3ff7..d2cad58 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -22,4 +22,7 @@ permissions: default: true toolstats.reset.confirm: description: Allows the usage of /toolstats reset confirm. - default: true \ No newline at end of file + default: true + toolstats.givetokens: + description: Allows the usage of /toolstats givetoken. + default: op \ No newline at end of file