commit 5133972c44688f2df05cdeae035577881f6b5d80 Author: hyperdefined Date: Fri Jan 28 13:22:05 2022 -0500 added base files (NOT DONE) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4788b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# Common working directory +run/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c1e12e --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# ToolStats + +A simple plugin to track cool stats for your tools and armor! + +## Todo +* Track trident throw kills. +* Track armor damage. +* Track fish caught. +* Track sheep sheared. +* Properly handle combing of tools. Stats should combine together. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..baf7aba --- /dev/null +++ b/pom.xml @@ -0,0 +1,108 @@ + + + 4.0.0 + + lol.hyper + toolstats + 1.0-SNAPSHOT + jar + + ToolStats + + + 1.8 + UTF-8 + + + + + + maven-clean-plugin + 3.1.0 + + + auto-clean + initialize + + clean + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + + org.bstats + lol.hyper.toolstats.bstats + + + + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + spigotmc-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + + + + org.spigotmc + spigot-api + 1.18.1-R0.1-SNAPSHOT + provided + + + org.jetbrains + annotations + 23.0.0 + compile + + + org.bstats + bstats-bukkit + 3.0.0 + compile + + + lol.hyper + github-release-api + 1.0.1 + + + diff --git a/src/main/java/lol/hyper/toolstats/ToolStats.java b/src/main/java/lol/hyper/toolstats/ToolStats.java new file mode 100644 index 0000000..8deaff7 --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/ToolStats.java @@ -0,0 +1,80 @@ +package lol.hyper.toolstats; + +import lol.hyper.githubreleaseapi.GitHubRelease; +import lol.hyper.githubreleaseapi.GitHubReleaseAPI; +import lol.hyper.toolstats.events.BlocksMined; +import lol.hyper.toolstats.events.CraftItem; +import lol.hyper.toolstats.events.EntityDeath; +import lol.hyper.toolstats.events.MobKill; +import org.bstats.bukkit.Metrics; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.IOException; +import java.util.logging.Logger; + +public final class ToolStats extends JavaPlugin { + + // stores who crafted an item + public final NamespacedKey craftedOwner = new NamespacedKey(this, "owner"); + // stores when an item was crafted + public final NamespacedKey craftedTime = new NamespacedKey(this, "time-created"); + // stores how many player kills by sword + public final NamespacedKey swordPlayerKills = new NamespacedKey(this, "player-kills"); + // stores how many mob kills by sword + public final NamespacedKey swordMobKills = new NamespacedKey(this, "mob-kills"); + // stores how blocks mined (used for all tools) + public final NamespacedKey genericMined = new NamespacedKey(this, "generic-mined"); + // stores how many fish were caught + public final NamespacedKey fishingRodCaught = new NamespacedKey(this, "fish-caught"); + // stores how many times sheep were sheared + public final NamespacedKey shearsSheared = new NamespacedKey(this, "sheared"); + + public BlocksMined blocksMined; + public CraftItem craftItem; + public EntityDeath entityDeath; + public MobKill mobKill; + + public Logger logger = this.getLogger(); + + @Override + public void onEnable() { + blocksMined = new BlocksMined(this); + craftItem = new CraftItem(this); + entityDeath = new EntityDeath(this); + mobKill = new MobKill(this); + + Bukkit.getServer().getPluginManager().registerEvents(blocksMined, this); + Bukkit.getServer().getPluginManager().registerEvents(craftItem, this); + Bukkit.getServer().getPluginManager().registerEvents(entityDeath, this); + Bukkit.getServer().getPluginManager().registerEvents(mobKill, this); + + new Metrics(this, 14110); + + Bukkit.getScheduler().runTaskAsynchronously(this, this::checkForUpdates); + } + + public void checkForUpdates() { + GitHubReleaseAPI api; + try { + api = new GitHubReleaseAPI("ToolStats", "hyperdefined"); + } catch (IOException e) { + logger.warning("Unable to check updates!"); + e.printStackTrace(); + return; + } + GitHubRelease current = api.getReleaseByTag(this.getDescription().getVersion()); + GitHubRelease latest = api.getLatestVersion(); + if (current == null) { + logger.warning("You are running a version that does not exist on GitHub. If you are in a dev environment, you can ignore this. Otherwise, this is a bug!"); + return; + } + int buildsBehind = api.getBuildsBehind(current); + if (buildsBehind == 0) { + logger.info("You are running the latest version."); + } else { + logger.warning("A new version is available (" + latest.getTagVersion() + ")! You are running version " + current.getTagVersion() + ". You are " + buildsBehind + " version(s) behind."); + } + } +} diff --git a/src/main/java/lol/hyper/toolstats/UUIDDataType.java b/src/main/java/lol/hyper/toolstats/UUIDDataType.java new file mode 100644 index 0000000..5e5959a --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/UUIDDataType.java @@ -0,0 +1,37 @@ +package lol.hyper.toolstats; + +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.nio.ByteBuffer; +import java.util.UUID; + +public class UUIDDataType implements PersistentDataType { + + @Override + public @NotNull Class getPrimitiveType() { + return byte[].class; + } + + @Override + public @NotNull Class getComplexType() { + return UUID.class; + } + + @Override + public byte @NotNull [] toPrimitive(UUID complex, @NotNull PersistentDataAdapterContext context) { + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb.putLong(complex.getMostSignificantBits()); + bb.putLong(complex.getLeastSignificantBits()); + return bb.array(); + } + + @Override + public @NotNull UUID fromPrimitive(byte @NotNull [] primitive, @NotNull PersistentDataAdapterContext context) { + ByteBuffer bb = ByteBuffer.wrap(primitive); + long firstLong = bb.getLong(); + long secondLong = bb.getLong(); + return new UUID(firstLong, secondLong); + } +} diff --git a/src/main/java/lol/hyper/toolstats/events/BlocksMined.java b/src/main/java/lol/hyper/toolstats/events/BlocksMined.java new file mode 100644 index 0000000..c663196 --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/events/BlocksMined.java @@ -0,0 +1,90 @@ +package lol.hyper.toolstats.events; + +import lol.hyper.toolstats.ToolStats; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +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; + +public class BlocksMined implements Listener { + + private final ToolStats toolStats; + private final String[] validTools = {"pickaxe", "axe", "hoe", "shovel"}; + private final String blocksMinedLore = ChatColor.GRAY + "Blocks mined: " + ChatColor.DARK_GRAY + "X"; + + public BlocksMined(ToolStats toolStats) { + this.toolStats = toolStats; + } + + @EventHandler + public void onBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + if (player.getGameMode() != GameMode.SURVIVAL) { + return; + } + ItemStack heldItem = player.getInventory().getItem(player.getInventory().getHeldItemSlot()); + if (heldItem == null || heldItem.getType() == Material.AIR) { + return; + } + String itemName = heldItem.getType().toString().toLowerCase(); + if (Arrays.stream(validTools).noneMatch(itemName::contains)) { + return; + } + updateBlocksMined(heldItem); + } + + private void updateBlocksMined(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + if (meta == null) { + return; + } + Integer blocksMined = 0; + PersistentDataContainer container = meta.getPersistentDataContainer(); + if (container.has(toolStats.genericMined, PersistentDataType.INTEGER)) { + blocksMined = container.get(toolStats.genericMined, PersistentDataType.INTEGER); + } + if (blocksMined == null) { + return; + } else { + blocksMined++; + } + container.set(toolStats.genericMined, PersistentDataType.INTEGER, blocksMined); + + List lore; + if (meta.hasLore()) { + lore = meta.getLore(); + assert lore != null; + boolean hasLore = false; + // we do a for loop like this, we can keep track of index + // this doesn't mess the lore up of existing items + for (int x = 0; x < lore.size(); x++) { + if (lore.get(x).contains("Blocks mined")) { + hasLore = true; + lore.set(x, blocksMinedLore.replace("X", Integer.toString(blocksMined))); + break; + } + } + // if the item has lore but doesn't have the tag, add it + if (!hasLore) { + lore.add(blocksMinedLore.replace("X", Integer.toString(blocksMined))); + } + } else { + // if the item has no lore, create a new list and add the string + lore = new ArrayList<>(); + lore.add(blocksMinedLore.replace("X", Integer.toString(blocksMined))); + } + meta.setLore(lore); + itemStack.setItemMeta(meta); + } +} diff --git a/src/main/java/lol/hyper/toolstats/events/CraftItem.java b/src/main/java/lol/hyper/toolstats/events/CraftItem.java new file mode 100644 index 0000000..c4db96a --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/events/CraftItem.java @@ -0,0 +1,72 @@ +package lol.hyper.toolstats.events; + +import lol.hyper.toolstats.ToolStats; +import lol.hyper.toolstats.UUIDDataType; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.text.SimpleDateFormat; +import java.util.*; + +public class CraftItem implements Listener { + + private final ToolStats toolStats; + public final String[] validItems = { + "pickaxe", "sword", "shovel", "axe", "hoe", "bow", "helmet", "chestplate", "leggings", "boots", "fishing" + }; + private final String timeCreatedLore = ChatColor.GRAY + "Crafted on: " + ChatColor.DARK_GRAY + "X"; + private final String ownerLore = ChatColor.GRAY + "Crafted by: " + ChatColor.DARK_GRAY + "X"; + private final SimpleDateFormat format = new SimpleDateFormat("M/dd/yyyy", Locale.ENGLISH); + + public CraftItem(ToolStats toolStats) { + this.toolStats = toolStats; + } + + @EventHandler + public void onCraft(CraftItemEvent event) { + Player player = (Player) event.getWhoClicked(); + ItemStack itemStack = event.getCurrentItem(); + if (itemStack == null || itemStack.getType() == Material.AIR) { + return; + } + String name = itemStack.getType().toString().toLowerCase(Locale.ROOT); + for (String x : validItems) { + if (name.contains(x)) { + event.setCurrentItem(addLore(itemStack, player)); + } + } + } + + private ItemStack addLore(ItemStack itemStack, Player owner) { + ItemStack newItem = itemStack.clone(); + ItemMeta meta = newItem.getItemMeta(); + if (meta == null) { + return null; + } + long timeCreated = System.currentTimeMillis(); + Date finalDate = new Date(timeCreated); + PersistentDataContainer container = meta.getPersistentDataContainer(); + container.set(toolStats.craftedTime, PersistentDataType.LONG, timeCreated); + container.set(toolStats.craftedOwner, new UUIDDataType(), owner.getUniqueId()); + List lore; + if (meta.hasLore()) { + lore = meta.getLore(); + assert lore != null; + } else { + lore = new ArrayList<>(); + } + lore.add(timeCreatedLore.replace("X", format.format(finalDate))); + lore.add(ownerLore.replace("X", owner.getName())); + meta.setLore(lore); + newItem.setItemMeta(meta); + return newItem; + } +} diff --git a/src/main/java/lol/hyper/toolstats/events/EntityDeath.java b/src/main/java/lol/hyper/toolstats/events/EntityDeath.java new file mode 100644 index 0000000..69ae7d3 --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/events/EntityDeath.java @@ -0,0 +1,57 @@ +package lol.hyper.toolstats.events; + +import lol.hyper.toolstats.ToolStats; +import org.bukkit.ChatColor; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.*; + +public class EntityDeath implements Listener { + + private final ToolStats toolStats; + private final String droppedLore = ChatColor.GRAY + "Dropped by: " + ChatColor.DARK_GRAY + "X"; + + public EntityDeath(ToolStats toolStats) { + this.toolStats = toolStats; + } + + @EventHandler + public void onDeath(EntityDeathEvent event) { + LivingEntity livingEntity = event.getEntity(); + UUID livingEntityUUID = event.getEntity().getUniqueId(); + if (toolStats.mobKill.trackedMobs.contains(livingEntityUUID)) { + for (ItemStack current : event.getDrops()) { + String name = current.getType().toString().toLowerCase(Locale.ROOT); + for (String item : toolStats.craftItem.validItems) { + if (name.contains(item)) { + addLore(current, livingEntity.getName()); + } + } + } + toolStats.mobKill.trackedMobs.remove(livingEntityUUID); + } + } + + private void addLore(ItemStack itemStack, String mob) { + ItemMeta meta = itemStack.getItemMeta(); + if (meta == null) { + return; + } + List lore; + if (meta.hasLore()) { + lore = meta.getLore(); + assert lore != null; + } else { + // if the item has no lore, create a new list and add the string + lore = new ArrayList<>(); + } + lore.add(droppedLore.replace("X", mob)); + meta.setLore(lore); + itemStack.setItemMeta(meta); + } +} diff --git a/src/main/java/lol/hyper/toolstats/events/MobKill.java b/src/main/java/lol/hyper/toolstats/events/MobKill.java new file mode 100644 index 0000000..c08db1a --- /dev/null +++ b/src/main/java/lol/hyper/toolstats/events/MobKill.java @@ -0,0 +1,149 @@ +package lol.hyper.toolstats.events; + +import lol.hyper.toolstats.ToolStats; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; +import org.bukkit.Material; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import java.util.*; + +public class MobKill implements Listener { + + private final ToolStats toolStats; + private final String[] validTools = {"sword", "trident", "axe"}; + private final String playerKillsLore = ChatColor.GRAY + "Player kills: " + ChatColor.DARK_GRAY + "X"; + private final String mobKillsLore = ChatColor.GRAY + "Mob kills: " + ChatColor.DARK_GRAY + "X"; + public Set trackedMobs = new HashSet<>(); + + public MobKill(ToolStats toolStats) { + this.toolStats = toolStats; + } + + @EventHandler + public void onDamage(EntityDamageByEntityEvent event) { + LivingEntity livingEntity = (LivingEntity) event.getEntity(); + // mob is going to die + if (livingEntity.getHealth() - event.getFinalDamage() <= 0) { + // a player is killing something + if (event.getDamager() instanceof Player) { + Player player = (Player) event.getDamager(); + if (player.getGameMode() != GameMode.SURVIVAL) { + return; + } + ItemStack heldItem = player.getInventory().getItem(player.getInventory().getHeldItemSlot()); + if (heldItem == null || heldItem.getType() == Material.AIR) { + return; + } + String itemName = heldItem.getType().toString().toLowerCase(); + if (Arrays.stream(validTools).noneMatch(itemName::contains)) { + return; + } + // a player is killing another player + if (livingEntity instanceof Player) { + updatePlayerKills(heldItem); + return; + } + // player is killing regular mob + updateMobKills(heldItem); + trackedMobs.add(livingEntity.getUniqueId()); + } + } + } + + private void updatePlayerKills(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + if (meta == null) { + return; + } + Integer playerKills = 0; + PersistentDataContainer container = meta.getPersistentDataContainer(); + if (container.has(toolStats.swordPlayerKills, PersistentDataType.INTEGER)) { + playerKills = container.get(toolStats.swordPlayerKills, PersistentDataType.INTEGER); + } + if (playerKills == null) { + return; + } else { + playerKills++; + } + container.set(toolStats.swordPlayerKills, PersistentDataType.INTEGER, playerKills); + + List lore; + if (meta.hasLore()) { + lore = meta.getLore(); + assert lore != null; + boolean hasLore = false; + // we do a for loop like this, we can keep track of index + // this doesn't mess the lore up of existing items + for (int x = 0; x < lore.size(); x++) { + if (lore.get(x).contains("Mob kills")) { + hasLore = true; + lore.set(x, playerKillsLore.replace("X", Integer.toString(playerKills))); + break; + } + } + // if the item has lore but doesn't have the tag, add it + if (!hasLore) { + lore.add(playerKillsLore.replace("X", Integer.toString(playerKills))); + } + } else { + // if the item has no lore, create a new list and add the string + lore = new ArrayList<>(); + lore.add(playerKillsLore.replace("X", Integer.toString(playerKills))); + } + meta.setLore(lore); + itemStack.setItemMeta(meta); + } + + private void updateMobKills(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + if (meta == null) { + 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) { + return; + } else { + mobKills++; + } + container.set(toolStats.swordMobKills, PersistentDataType.INTEGER, mobKills); + + List lore; + if (meta.hasLore()) { + lore = meta.getLore(); + assert lore != null; + boolean hasLore = false; + // we do a for loop like this, we can keep track of index + // this doesn't mess the lore up of existing items + for (int x = 0; x < lore.size(); x++) { + if (lore.get(x).contains("Mob kills")) { + hasLore = true; + lore.set(x, mobKillsLore.replace("X", Integer.toString(mobKills))); + break; + } + } + // if the item has lore but doesn't have the tag, add it + if (!hasLore) { + lore.add(mobKillsLore.replace("X", Integer.toString(mobKills))); + } + } else { + // if the item has no lore, create a new list and add the string + lore = new ArrayList<>(); + lore.add(mobKillsLore.replace("X", Integer.toString(mobKills))); + } + meta.setLore(lore); + itemStack.setItemMeta(meta); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..c4c64e3 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,4 @@ +name: ToolStats +version: '${project.version}' +main: lol.hyper.toolstats.ToolStats +api-version: 1.18