diff --git a/README.md b/README.md index e638720..102eca5 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,48 @@ -# Egg Hunt -**A plugin for hunting down the dragon egg and stealing it from other players.** - -**With this plugin:** +# Egg Hunt +A plugin for hunting down the dragon egg and stealing it from other players. -- The dragon egg cannot be stored in an ender chest, bundle, or shulker box. +## Plugin Summary + +- The dragon egg cannot be stored in an ender chest. - The egg item will drop on the ground if you leave the game with it in your inventory. - The egg item cannot despawn. -- The plugin has an owner system. Taking the egg will give you ownership. +- Ownership is tracked through a last-held system- picking up the egg will give you ownership of it. - Dying with the egg, having it stolen, or losing it will change ownership accordingly. -- The egg can be located/tracked regardless of whether it's a block, an item, or in an inventory, and in any dimension. +- The egg can be located/tracked regardless of whether it's a block, an item, or in an inventory, and across any dimension. -**Commands:** +### Commands - `/eggowner` - displays the name of the player who currently owns the egg - `/locateegg` - displays the current coordinates of the egg, as well as how it's being stored - `/trackegg` - points the compass in your hand towards the egg -**Customization:** +## Customization This plugin's configuration file (`config.yml`) allows for several gameplay customizations to accomodate different types of servers. -- The egg can be made invulnerable, or can be set to respawn in the end when it gets destroyed +- The egg can be made invulnerable, or can be set to respawn in The End when it is destroyed - If set to respawn, the egg can either respawn immediately, or only after the dragon is beaten again -- When the egg teleports, it will become "lost", so it won't have an owner until someone finds it again -- Dragon eggs that are already stored in a player's ender chest can be excluded from the hunt. +- When the egg teleports, it can become "lost", so it will no longer have an owner until a player finds it +- Dragon eggs that are already stored in a player's ender chest can be excluded from the hunt +- The egg owner is tracked with an entity tag, so you target them with the selector `@a[tag=eggOwner]` in any commands. +- Scoreboards track the amount of time each player has owned the dragon egg, and can be configured to track named entities as if those entities actually owned the egg. -**Installation** -- Save the provided .jar file to your server's `/plugins` directory. Alternatively, you can compile the .jar file yourself by cloning this repository to a local directory and running the maven command `mvn package`. The packaged .jar will appear in `/EggHuntPlugin/target/` +## Installation +- Save the provided .jar file to your server's `/plugins` directory. Alternatively, you can compile the .jar file yourself by cloning this repository to a local directory and running the maven command `mvn package`. The packaged .jar will appear in `/EggHuntPlugin/target/`. - Restart (or `/reload`) the server to generate the `/plugins/EggHunt` directory, which will contain the plugin's config file. - Open `config.yml` in any text editor to change the settings, then restart (or reload) the server to apply the changes. -If the dragon egg has already been found, update its location in the plugin by picking up the egg item or by breaking and re-placing the egg block. +- If the dragon egg has already been found, update the plugin's record of the egg's location by picking up the egg item or by breaking and re-placing the egg block. -**Notes for Server Owners:** -- Best paired with an anticheat to prevent duplication (unless you are using paperspigot, which disables falling block dupes by default) -- The egg location does not update when taken out of an inventory (eg chest) until the inventory is closed, so you should also enable [inventory close](https://github.com/NoCheatPlus/Docs/wiki/%5BInventory%5D-Open) on your anticheat to prevent the player from teleporting away with their inventory open +## Notes for Server Owners +- Best paired with an anticheat to prevent duplication. (Unless you are using Paper or Spigot, which disable falling block dupes by default) +- The egg location does not update when taken out of an inventory (eg chest) until the inventory is closed, so you should also enable [inventory close](https://github.com/NoCheatPlus/Docs/wiki/%5BInventory%5D-Open) on your anticheat to prevent the player from teleporting away with their inventory open. - Only players in creative mode can move the egg into an ender chest. -- You can allow players to have dragon eggs in their ender chests that are not tracked. This is enabled by default in the config.yml under "ignore_echest_egg" -- Most settings you might need can be changed via the plugin's config file (/EggHunt/config.yml), open an issue if you don't see a setting you want -- You can disallow players from using certain commands in this plugin if you are also using a permissions plugin -- It is safe to use the Bukkit `/reload` command while this plugin is running +- You can allow players to have dragon eggs in their ender chests that are not tracked. This is enabled by default in the config file under "ignore_echest_egg". +- Most settings you might need can be changed via the plugin's config file (`/EggHunt/config.yml`). Open an issue on GitHub if you don't see a setting you want. +- You can disallow players from using certain commands in this plugin. See the `plugin.yml` file for a list of permissions. +- It is safe to use the Bukkit `/reload` command while this plugin is running. - When resetting the world, also delete the `data.yml` file. + +# Reporting Bugs +Make sure you are running the latest version of this plugin and read that version's Known Issues list to make sure you aren't reporting a known issue. +To report a bug, please open a new [Issue](https://github.com/HyperSMP/EggHuntPlugin/issues) in this repository and fill out the bug report template. \ No newline at end of file diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/Announcement.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/Announcement.java index 4763494..c807252 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/Announcement.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/Announcement.java @@ -2,29 +2,92 @@ package io.github.J0hnL0cke.egghunt.Controller; import java.util.ArrayList; import java.util.List; -import java.util.logging.Logger; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.World; import org.bukkit.entity.Player; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; + /** * Misc helper methods relating to player notification */ public class Announcement { - public static void announce(String message, Logger logger) { + + private static final String PREFIX_FORMAT_CODE = ChatColor.COLOR_CHAR+"l"; //bold + private static final ChatColor PREFIX_COLOR = ChatColor.DARK_PURPLE; + private static final ChatColor LOCATION_COLOR = ChatColor.GREEN; + private static final ChatColor CORRECT_COLOR = ChatColor.DARK_GREEN; + private static final ChatColor INCORRECT_COLOR = ChatColor.RED; + private static final ChatColor RESET_CODE = ChatColor.RESET; + private static final String RAW_MESSAGE_PREFIX = "[Egg Hunt] "; + + public static void sendMessage(Player p, String message) { + p.sendMessage(formatMessage(message)); + } + + public static String formatLocation(Location destination, Location origin) { + //stringify the egg's location + int x = destination.getBlockX(); + int y = destination.getBlockY(); + int z = destination.getBlockZ(); + World world = destination.getWorld(); + + //whether the two locations are in the same world, or false if origin is null + boolean sameWorld = origin != null && origin.getWorld().equals(destination.getWorld()); + + String worldName = formatWorld(world, sameWorld); + + String distanceStr = ""; + if (sameWorld) { + double distance = destination.distance(origin); + if (!Double.isNaN(distance)) { + distanceStr = String.format(" (%d blocks away)", (int)distance); + } + } + + String loc = String.format("[%d, %d, %d]", x, y, z); + + return String.format("%s%s%s in %s%s", LOCATION_COLOR, loc, RESET_CODE, worldName, distanceStr); + } + + public static String formatWorld(World world, boolean correctWorld) { + String worldName; + switch (world.getEnvironment()) { + case NORMAL: + worldName = "The Overworld"; + break; + case THE_END: + worldName = "The End"; + break; + case NETHER: + worldName = "The Nether"; + break; + case CUSTOM: + default: + worldName = world.getName(); + } + ChatColor color = correctWorld ? CORRECT_COLOR : INCORRECT_COLOR; + + return String.format("%s%s%s", color, worldName, RESET_CODE); + } + + public static void announce(String message, LogHandler logger) { List players = new ArrayList<>(Bukkit.getOnlinePlayers()); int playersNotified = 0; for (Player player : players) if (player.hasPermission("egghunt.notify")) { playersNotified += 1; - player.sendMessage(message); + sendMessage(player, message); } - logger.info(String.format("Told %d player(s) \"%s\"", playersNotified, message)); + logger.log(String.format("Told %d player(s) \"%s\"", playersNotified, message)); } /** @@ -38,9 +101,12 @@ public class Announcement { } public static void ShowEggEffects(Player p) { - p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 10, 0); + p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, SoundCategory.MASTER, 10, 0); //min pitch is 0.5 ShowEggEffects(p.getLocation()); } - + + private static String formatMessage(String message) { + return String.format("%s%s%s%s%s", PREFIX_FORMAT_CODE, PREFIX_COLOR, RAW_MESSAGE_PREFIX, RESET_CODE, message); + } } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/CommandHandler.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/CommandHandler.java index f8f7622..e09c1f2 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/CommandHandler.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/CommandHandler.java @@ -24,81 +24,42 @@ public class CommandHandler { this.data = data; } + /** + * Sends a message to the given CommandSender + */ + private void sendMessage(CommandSender sender, String message) { + //make the message formatted + if (sender instanceof Player) { + Announcement.sendMessage((Player) sender, message); + } else { + sender.sendMessage(message); + } + } + private boolean locateEgg(CommandSender sender, Command cmd, String label, String[] args) { if (sender.hasPermission("egghunt.locateegg")) { - final String msgStart = "The dragon egg "; + final String msgStart = "The dragon egg"; - Data.Egg_Storage_Type type = data.getEggType(); + String storageMsg = data.getEggHolderString(); //"is in " - if (type == Egg_Storage_Type.DNE) { - //if the egg does not exist - sender.sendMessage(msgStart + "does not exist"); - - } else { - String storageMsg; - - //figure out how the egg is contained - if (type == Egg_Storage_Type.BLOCK) { - Block eggBlock = data.getEggBlock(); - - if (Egg.isEgg(eggBlock)) { - storageMsg = "has been placed"; - - } else { - //egg is inside a container, provide the name of the container - storageMsg = String.format(" is inside of a(n) %s", eggBlock.getType().toString()); - } - - } else { - Entity eggEntity = data.getEggEntity(); - - switch (eggEntity.getType()) { - case DROPPED_ITEM: - storageMsg = "is a dropped item"; - break; - case ITEM_FRAME: - case GLOW_ITEM_FRAME: - storageMsg = "is in an item frame"; - break; - - case FALLING_BLOCK: - storageMsg = "is a falling block entity"; - break; - - case PLAYER: - storageMsg = String.format("is in the inventory of %s", eggEntity.getName()); - break; - - default: - storageMsg = String.format("is held by a(n) %s", eggEntity.getType().toString()); - if (eggEntity.getCustomName() != null) { - storageMsg += String.format(" named %s", eggEntity.getCustomName()); - } - - } - } - - //stringify the egg's location - Location locMsg = data.getEggLocation(); - int x = locMsg.getBlockX(); - int y = locMsg.getBlockY(); - int z = locMsg.getBlockZ(); - String world = locMsg.getWorld().getName(); - String locStr = String.format("at %d/%d/%d in %s", x, y, z, world); - - sender.sendMessage(String.format("The dragon egg %s %s.", storageMsg, locStr)); + Location origin = null; //get location of player that sent the command for distance calculation + if(sender instanceof Player){ + origin = ((Player) sender).getLocation(); } - + + String locStr = Announcement.formatLocation(data.getEggLocation(), origin); + sendMessage(sender, String.format("%s %s at %s.", msgStart, storageMsg, locStr)); + } else { - sender.sendMessage(NOT_PERMITTED_MSG); + sendMessage(sender, NOT_PERMITTED_MSG); } return true; } private boolean trackEgg(CommandSender sender, Command cmd, String label, String[] args) { if (!(sender instanceof Player)) { - sender.sendMessage("This command can only be run by a player, use /locateegg instead."); + sendMessage(sender, "This command can only be run by a player, use /locateegg instead."); } else { if (sender.hasPermission("egghunt.trackegg")) { Player player = (Player) sender; @@ -114,19 +75,18 @@ public class CommandHandler { compassMeta.setLodestoneTracked(false); compassMeta.setLodestone(eggLoc); heldItem.setItemMeta(compassMeta); - sender.sendMessage("Tracking last known dragon egg position."); + sendMessage(sender, "Tracking last known dragon egg position."); } else { - sender.sendMessage("Not in the same dimension as the egg."); - sender.sendMessage(String.format("The egg is in %s.", eggLoc.getWorld().getName())); + sendMessage(sender, String.format("Not in the same dimension as the egg. The egg is in %s.", Announcement.formatWorld(eggLoc.getWorld(), false))); } } else { - sender.sendMessage("The dragon egg does not exist."); + sendMessage(sender, "The dragon egg does not exist."); } } else { - sender.sendMessage("You must be holding a compass to use this command, use /locateegg instead."); + sendMessage(sender, "You must be holding a compass to use this command, use /locateegg instead."); } } else { - sender.sendMessage(NOT_PERMITTED_MSG); + sendMessage(sender, NOT_PERMITTED_MSG); } } return true; @@ -135,12 +95,12 @@ public class CommandHandler { private boolean getOwner(CommandSender sender, Command cmd, String label, String[] args) { if (sender.hasPermission("egghunt.eggowner")) { if (data.getEggOwner() == null) { - sender.sendMessage("The dragon egg has not been claimed."); + sendMessage(sender, "The dragon egg has not been claimed."); } else { - sender.sendMessage(String.format("The dragon egg belongs to %s.", data.getEggOwner().getName())); + sendMessage(sender, String.format("The dragon egg belongs to %s.", data.getEggOwner().getName())); } } else { - sender.sendMessage(NOT_PERMITTED_MSG); + sendMessage(sender, NOT_PERMITTED_MSG); } return true; } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EggController.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EggController.java new file mode 100644 index 0000000..4fc4dac --- /dev/null +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EggController.java @@ -0,0 +1,138 @@ +package io.github.J0hnL0cke.egghunt.Controller; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; + +import io.github.J0hnL0cke.egghunt.Model.Configuration; +import io.github.J0hnL0cke.egghunt.Model.Data; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; + +/** + * Provides functionality relating to the dragon egg, but does not register any event handlers. + * This is a pure fabrication class to simplify functionality used by multiple controllers. + */ +public class EggController { + + /** + * Makes the given entity invulnerable if enabled in the config + */ + public static void makeEggInvulnerable(Entity entity, Configuration config) { + if (config.getEggInvulnerable()) { + entity.setInvulnerable(true); + } + } + + /** + * Alerts when the egg is destroyed and respawns it if needed + */ + public static void eggDestroyed(Configuration config, Data data, LogHandler logger) { + logger.log("Egg was destroyed"); + OfflinePlayer oldOwner = data.getEggOwner(); + data.resetEggOwner(false, config); + data.resetEggLocation(); + String msg; + + if (config.getRespawnEgg()) { + if (config.getRespawnImmediately()) { + logger.log("Immediate respawn enabled- respawning egg"); + respawnEgg(config, data, logger); + msg = "The dragon egg was destroyed and has respawned in The End!"; + if (oldOwner != null) { //prevent spamming of egg destruction + } + } else { + logger.log("Immediate respawn disabled- egg will respawn after next dragon fight"); + msg = "The dragon egg has been destroyed! It will respawn the next time the Ender Dragon is defeated."; + } + } else { + logger.log("Egg respawn is disabled"); + msg = "The dragon egg has been destroyed!"; + + } + + Announcement.announce(msg, logger); + + } + + /** + * Returns the location above the end fountain where the dragon will respawn + */ + public static Location getEggRespawnLocation(Configuration config) { + //the block above the bedrock fountain where the egg spawns + return config.getEndWorld().getEnderDragonBattle().getEndPortalLocation().add(0, 4, 0); + } + + /** + * Drop the egg out of the given player's inventory + */ + public static void dropEgg(Player player, Data data, Configuration config) { + // Check if the player has a dragon egg + if (player.getInventory().contains(Material.DRAGON_EGG)) { + + // Set owner and remove + data.setEggOwner(player, config); //TODO is this necessary? player will likely already be owner + player.getInventory().remove(Material.DRAGON_EGG); + + // Drop it on the floor and set its location + //TODO use drop egg method in EggRespawn + Item eggDrop = player.getWorld().dropItem(player.getLocation(), + new ItemStack(Material.DRAGON_EGG)); + eggDrop.setVelocity(new Vector(0, 0, 0)); + data.updateEggLocation(eggDrop); + } + } + + /** + * Updates the egg ownership scoreboard tag for the given player if tagging is enabled + * Adds the tag if the player is the owner, otherwise removes it + */ + public static void updateOwnerTag(Player player, Data data, Configuration config) { + if (player != null) { + if (config.getTagOwner()) { + OfflinePlayer owner = data.getEggOwner(); + //if the given player owns the egg + if (owner != null && owner.getUniqueId().equals(player.getUniqueId())) { + player.addScoreboardTag(config.getOwnerTagName()); + } else { + player.removeScoreboardTag(config.getOwnerTagName()); + } + } + } + } + + /** + * Spawns a new egg item at the given location, sets it to invincible if enabled in the given config. + * @return the egg item that was spawned + */ + public static void spawnEggItem(Location loc, Configuration config, Data data) { + ItemStack egg = new ItemStack(Material.DRAGON_EGG); + egg.setAmount(1); + Item drop = loc.getWorld().dropItem(loc, egg); + drop.setGravity(false); + drop.setGlowing(true); + drop.setVelocity(new Vector().setX(0).setY(0).setZ(0)); + if (config.getEggInvulnerable()) { + drop.setInvulnerable(true); + } + data.updateEggLocation(drop); + } + + /** + * Respawns the dragon egg in the end + */ + public static void respawnEgg(Configuration config, Data data, LogHandler logger) { + logger.log("Respawning egg"); + Block eggBlock = getEggRespawnLocation(config).getBlock(); + eggBlock.setType(Material.DRAGON_EGG); + data.updateEggLocation(eggBlock); + Announcement.ShowEggEffects(eggBlock.getLocation().add(0.5, 0, 0.5)); //target the center bottom of the block + } + + +} diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EggDestroyListener.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EggDestroyListener.java index 13fc287..e255ecd 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EggDestroyListener.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EggDestroyListener.java @@ -1,51 +1,48 @@ package io.github.J0hnL0cke.egghunt.Controller; +import java.util.ArrayList; import java.util.List; -import java.util.logging.Logger; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; import org.bukkit.TreeType; -import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockState; -import org.bukkit.block.Container; +import org.bukkit.entity.ComplexEntityPart; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.Item; import org.bukkit.entity.ItemFrame; -import org.bukkit.entity.Player; -import org.bukkit.entity.EnderDragon.Phase; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.entity.EnderDragonChangePhaseEvent; -import org.bukkit.event.entity.EntityCreatePortalEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.ItemDespawnEvent; import org.bukkit.event.hanging.HangingBreakEvent; import org.bukkit.event.hanging.HangingBreakEvent.RemoveCause; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.world.PortalCreateEvent; import org.bukkit.event.world.StructureGrowEvent; +import org.bukkit.event.world.PortalCreateEvent.CreateReason; import org.bukkit.inventory.ItemStack; import io.github.J0hnL0cke.egghunt.Model.Configuration; import io.github.J0hnL0cke.egghunt.Model.Data; import io.github.J0hnL0cke.egghunt.Model.Data.Egg_Storage_Type; import io.github.J0hnL0cke.egghunt.Model.Egg; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; /** * Listens for Bukkit events related to destruction of the dragon egg * Prevents destruction or respawns the egg, depending on config settings */ public class EggDestroyListener implements Listener { - private Logger logger; + private LogHandler logger; private Configuration config; private Data data; - public EggDestroyListener(Logger logger, Configuration config, Data data) { + public EggDestroyListener(LogHandler logger, Configuration config, Data data) { this.logger = logger; this.config = config; this.data = data; @@ -59,10 +56,10 @@ public class EggDestroyListener implements Listener { Entity entity = event.getEntity(); if (entity.getType().equals(EntityType.DROPPED_ITEM)) { ItemStack item = ((Item)entity).getItemStack(); - if (Egg.isEgg(item)) { + if (Egg.hasEgg(item)) { //make sure item is destroyed to prevent dupes event.getEntity().remove(); - eggDestroyed(); + EggController.eggDestroyed(config, data, logger); } } } @@ -76,12 +73,12 @@ public class EggDestroyListener implements Listener { if (event.getEntity().getType().equals(EntityType.ITEM_FRAME) || event.getEntity().getType().equals(EntityType.GLOW_ITEM_FRAME)) { ItemFrame frame = (ItemFrame) event.getEntity(); - if (Egg.isEgg(frame.getItem())) { + if (Egg.hasEgg(frame.getItem())) { if (config.getEggInvulnerable()) { - logToConsole("canceled explosion of egg item frame"); + log("canceled explosion of egg item frame"); event.setCancelled(true); } else { - eggDestroyed(); + EggController.eggDestroyed(config, data, logger); frame.setItem(null); //make sure item is removed } } @@ -97,10 +94,10 @@ public class EggDestroyListener implements Listener { public void onEntityDeath(EntityDeathEvent event) { //if the egg item dies, notify that it has been destroyed if (event.getEntityType().equals(EntityType.DROPPED_ITEM)) { - if (Egg.isEgg((ItemStack) event.getEntity())) { + if (Egg.hasEgg((ItemStack) event.getEntity())) { //remove just in case to prevent dupes event.getEntity().remove(); - eggDestroyed(); + EggController.eggDestroyed(config, data, logger); } } @@ -111,63 +108,24 @@ public class EggDestroyListener implements Listener { //if the egg does not exist if (config.getEndWorld().getEnderDragonBattle().hasBeenPreviouslyKilled()) { //if the dragon has already been beaten - respawnEgg(); + EggController.respawnEgg(config, data, logger); + Announcement.announce("The dragon egg has spawned in the end!", logger); } } } } } - /** - * Stop portals from removing the egg - */ - @EventHandler(priority = EventPriority.MONITOR) - public void onCreatePortal(EntityCreatePortalEvent event) { - //not specific to any particular creation reason because multiple events could cause the egg to be removed (obby platform regen, gateway portal spawning, stronghold portal activation) - boolean egg_affected = false; - Location l = null; - List blocks = event.getBlocks(); - logToConsole(String.format("blocks: %s", blocks.toString())); - for (BlockState blockState : blocks) { - logToConsole(String.format("Block update at %s: from %s to %s", blockState.getLocation(), - blockState.getBlock().getType(), blockState.getType())); - if (Egg.isEgg(blockState.getBlock())) { - egg_affected = true; - l = blockState.getLocation(); - //extra check to make sure egg is removed - blockState.getBlock().setType(Material.AIR); - - } - } - - if (egg_affected) { - String entityName = "Unknown entity"; - if (event.getEntity() != null) { - if (event.getEntity().getCustomName() != null) { - entityName = event.getEntity().getCustomName(); - } - } - logToConsole(String.format("%s tried to overwrite egg with a portal", entityName)); - data.resetEggOwner(false); - if (l != null) { - data.updateEggLocation(Egg.spawnEggItem(l, config, data)); - } else { - logToConsole("Could not spawn egg item! Invalid block location."); - } - } - - } - /** * Prevent the egg from despawning */ @EventHandler(priority = EventPriority.HIGHEST) public void onDespawn(ItemDespawnEvent event) { - if (Egg.isEgg(event.getEntity().getItemStack())) { + if (Egg.hasEgg(event.getEntity().getItemStack())) { event.setCancelled(true); //set the age back to 1 so it doesn't try to despawn every tick event.getEntity().setTicksLived(1); - logToConsole("Canceled egg despawn"); + log("Canceled egg despawn"); } } @@ -176,43 +134,146 @@ public class EggDestroyListener implements Listener { */ @EventHandler(priority = EventPriority.HIGHEST) public void onGrow(StructureGrowEvent event) { - boolean destroy = false; if (event.getSpecies().equals(TreeType.RED_MUSHROOM)) { + logger.log("Red mushroom grown"); List blocks = event.getBlocks(); - for (BlockState blockState : blocks) { - Block block = blockState.getBlock(); - if (Egg.isEgg(block)) { - destroy = true; - if (!config.getEggInvulnerable()) { //if egg will be replaced, make sure it gets removed - block.setType(Material.AIR); - } - } else { - if (blockState instanceof Container) { //TODO exclude item frames, which aren't destroyed by this - Container cont = (Container) blockState; - if (cont.getInventory().contains(Material.DRAGON_EGG)) { - destroy = true; - cont.getInventory().remove(Material.DRAGON_EGG); //make sure egg is removed from container + handleBlockStateReplace(blocks); + } + } + + /** + * Prevent end platform regeneration from overwriting the egg + */ + @EventHandler(priority = EventPriority.MONITOR) + public void onPortalCreate(PortalCreateEvent event) { + logger.log("Portal created"); + if (event.getReason().equals(CreateReason.END_PLATFORM)) { + + ArrayList eggs = findEggs(getBlockListFromBlockStateList(event.getBlocks())); + + //for some reason PortalCreateEvent's block list is not a shallow copy, unlike EntityExplode and StructureGrow Events + //that means editing the list does not change which blocks are actually affected by the event + //as a workaround, allow egg blocks to be destroyed, but respawn the egg as an item + + for (Block egg : eggs) { + EggController.spawnEggItem(egg.getLocation(), config, data); + } + logger.log("Egg overwritten by the end spawn platform, spawning egg item at block location"); + + } + } + + /** + * Listens for the dragon destroying the egg by flying into it + */ + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onDragonDeleteBlock(final EntityExplodeEvent event) { + //check if this event is dealing with the dragon, since the event is very generic + Entity e = event.getEntity(); + if (e instanceof ComplexEntityPart) { + //if a part of a multi-part entity like the dragon, get the whole dragon + e = ((ComplexEntityPart) e).getParent(); + } + + //preserve the egg on the fountain if it's respawned there + //this is an edge case, since the dragon will perch above the egg unless the egg is placed mid-perch or the dragon is pushed into it + if (e.getType().equals(EntityType.ENDER_DRAGON)) { + if (!config.getEggInvulnerable() && config.getRespawnEgg()) { + + ArrayList eggs = findEggs(event.blockList()); + + for (Block egg : eggs) { + if (Egg.isOnlyEgg(egg)) { + if (EggController.getEggRespawnLocation(config).getBlock().equals(egg)) { + //if the egg is set to respawn, and is overlapped by the dragon at the respawn point, + //preserve it rather than letting it be deleted + data.resetEggOwner(false, config); + event.blockList().remove(egg); + //log("prevented dragon destroying respawned egg"); + //continue on to check if other blocks are the egg } } } } - if (destroy) { - if (config.getEggInvulnerable()) { - event.setCancelled(true); - Player p = event.getPlayer(); - if (p != null) { - p.playSound(p.getLocation(), Sound.BLOCK_ANVIL_LAND, 1, 1); - p.sendMessage("Cannot grow mushroom: obstructed by dragon egg"); - p.setCooldown(Material.BONE_MEAL, 100); - logToConsole(String.format("%s tried to mushroom the dragon egg", p.getName())); - } - } else { - eggDestroyed(); - } + + //if dragon is deleting blocks, check if any are the egg + handleBlockReplace(event.blockList()); + + if(event.blockList().isEmpty()){ + event.setCancelled(true); } } } + private ArrayList findEggs(List blocks){ + ArrayList eggs = new ArrayList<>(); + + for (Block block : blocks) { + if (Egg.hasEgg(block)) { + eggs.add(block); + } + } + return eggs; + } + + /** + * Looks through the provided blocks to see if the dragon egg is affected + * Notifies that the egg is destroyed or prevents its destruction + * @param blocks + */ + private void handleBlockReplace(List blocks) { + ArrayList eggs = findEggs(blocks); + + if (!eggs.isEmpty()) { + if (config.getEggInvulnerable()) { //prevent egg destruction + for (Block b : eggs) { + blocks.remove(b); //remove egg from list of blocks to delete + } + //log("Prevented egg from being replaced"); + + } else { //if egg will be replaced, make sure it gets removed + for (Block b : eggs) { + Egg.removeEgg(b); //delete egg + } + log("Dragon egg was replaced"); + EggController.eggDestroyed(config, data, logger); + } + } + } + + private ArrayList getBlockListFromBlockStateList(List blockStates) { + ArrayList blocks = new ArrayList<>(); + + for (BlockState blockState : blockStates) { + blocks.add(blockState.getBlock()); + } + + return blocks; + } + + private void handleBlockStateReplace(List blockStates) { + ArrayList blocks = getBlockListFromBlockStateList(blockStates); + + ArrayList eggs = findEggs(blocks); + + if (!eggs.isEmpty()) { + if (config.getEggInvulnerable()) { //prevent egg destruction + for (int i = eggs.size()-1; i >= 0; i--) { //iterate over every egg found backwards + int index = blocks.indexOf(eggs.get(i)); //get where the egg is in block list + blockStates.remove(index); //remove that item from the blockstate list + } + log("Prevented egg from being replaced"); + + } else { //if egg will be replaced, make sure it gets removed + for (Block b : eggs) { + Egg.removeEgg(b); //delete egg + } + log("Dragon egg was replaced"); + EggController.eggDestroyed(config, data, logger); + } + } + } + /** * Cancel events that damage the egg item if enabled in config * Note: cannot cancel damage to item frames, since damage is used to remove an item but is not called when burned/exploded @@ -223,94 +284,15 @@ public class EggDestroyListener implements Listener { if (config.getEggInvulnerable()) { Entity entity = event.getEntity(); if (entity.getType().equals(EntityType.DROPPED_ITEM)) { - if (Egg.isEgg((Item) entity)) { + if (Egg.hasEgg((Item) entity)) { event.setCancelled(true); } } } } - - /** - * When a portal is updated, check if the egg will be replaced - */ - private void checkPortalBlocks() { - //TODO there has to be a better way to do this - //Check end platform - Location l1 = new Location(config.getEndWorld(), 102, 48, 2); - Location l2 = new Location(config.getEndWorld(), 98, 51, -2); - Location res = checkBlockMaterialFromLocation(config.getEndWorld(), Material.DRAGON_EGG, l1, l2); - if (res != null) { - portalOverwriteEgg(res); - } - } - - /** - * Update data and spawn a new egg when the egg is overwritten with a portal - */ - private void portalOverwriteEgg(Location res) { - res.getBlock().setType(Material.AIR); - logToConsole("Egg was overwritten with a portal"); - if (config.getEggInvulnerable()) { - logToConsole("Spawning new egg"); - data.updateEggLocation(Egg.spawnEggItem(res, config, data)); - } else { - eggDestroyed(); - } - } - - /** - * Checks blocks within the cube created by the given locations. - * Returns the first location corresponding to the block found with the given material. - * Returns null if no matching block is found or locations are in different worlds. - */ - private static Location checkBlockMaterialFromLocation(World w, Material m, Location l1, Location l2) { - if (l1.getWorld().equals(l2.getWorld())) { - for (int x = l1.getBlockX(); x < l2.getBlockX(); x++) { - for (int y = l1.getBlockY(); y < l2.getBlockY(); y++) { - for (int z = l1.getBlockZ(); z < l2.getBlockZ(); z++) { - if (w.getBlockAt(x, y, z).getType().equals(m)) { - return new Location(w, x, y, z); - } - } - } - } - } - return null; - } - /** - * Alerts when the egg is destroyed and respawns it if needed - */ - public void eggDestroyed() { - announce("The dragon egg has been destroyed!"); - data.resetEggOwner(false); - - if (config.getRespawnEgg()) { - if (config.getRespawnImmediately()) { - respawnEgg(); - } else { - data.resetEggLocation(); - announce("It will respawn the next time the dragon is defeated"); - } - } - } - - /** - * Respawns the dragon egg in the end - */ - private void respawnEgg() { - Block eggBlock = Egg.respawnEgg(config); - data.updateEggLocation(eggBlock); - Announcement.ShowEggEffects(eggBlock.getLocation()); - announce("The dragon egg has spawned in the end!"); - } - - private void announce(String msg) { - Announcement.announce(msg, logger); - } - - private void logToConsole(String message) { - logger.info(message); + private void log(String message) { + logger.log(message); } } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EventScheduler.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EventScheduler.java index 6817a95..b846165 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EventScheduler.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/EventScheduler.java @@ -1,7 +1,5 @@ package io.github.J0hnL0cke.egghunt.Controller; -import java.util.logging.Logger; - import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; @@ -16,6 +14,7 @@ import org.bukkit.scheduler.BukkitRunnable; import io.github.J0hnL0cke.egghunt.Model.Configuration; import io.github.J0hnL0cke.egghunt.Model.Data; import io.github.J0hnL0cke.egghunt.Model.Egg; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; public class EventScheduler extends BukkitRunnable { @@ -24,9 +23,9 @@ public class EventScheduler extends BukkitRunnable { private Data data; private Configuration config; - private Logger logger; + private LogHandler logger; - public EventScheduler(Configuration config, Data data, Logger logger) { + public EventScheduler(Configuration config, Data data, LogHandler logger) { this.data = data; this.config = config; this.logger = logger; @@ -39,15 +38,16 @@ public class EventScheduler extends BukkitRunnable { //TODO first check if chunk is loaded if (data.getEggType() == Data.Egg_Storage_Type.ENTITY) { if (data.getEggEntity() == null) { - logger.warning("Lost track of the dragon egg entity!"); - logger.warning("Resetting egg location to prevent repeated warnings"); - data.resetEggLocation(); - return; - } else if (isUnderWorld(data.getEggEntity())) { + logger.warning("Lost track of the dragon egg entity!"); + logger.warning("Resetting egg location to prevent repeated warnings"); + data.resetEggLocation(); + return; + } else if (isUnderWorld(data.getEggEntity()) && !(data.getEggEntity() instanceof Player)) { + logger.log("Egg entity is under the word. Removing egg from entity"); Location respawnLoc = data.getEggEntity().getLocation(); - removeMaterialFromEntity(Material.DRAGON_EGG, data.getEggEntity()); + Egg.removeEgg(data.getEggEntity()); + if (config.getEggInvulnerable()) { - // get coords for egg to spawn at //get highest block Block highestBlock = respawnLoc.getWorld().getHighestBlockAt(respawnLoc); @@ -58,53 +58,18 @@ public class EventScheduler extends BukkitRunnable { yPos = respawnLoc.getWorld().getSeaLevel(); } respawnLoc.setY(yPos); - Egg.spawnEggItem(respawnLoc, config, data); //do not need to update data with this location since item spawn event will be called + EggController.spawnEggItem(respawnLoc, config, data); //do not need to update data with this location since item spawn event will be called + data.resetEggOwner(true, config); } else { - eggDestroyed(); - respawnEgg(respawnLoc); + //alert and respawn if applicable + EggController.eggDestroyed(config, data, logger); } - data.resetEggOwner(true); - } - - } - } - - private void eggDestroyed() { - Announcement.announce("The dragon egg has been destroyed!", logger); - data.resetEggOwner(false); - } - - private void respawnEgg(Location respawnLoc) { - if (config.getRespawnEgg()) { - if (config.getRespawnImmediately()) { - } else { - data.resetEggLocation(); - Announcement.announce("It will respawn the next time the dragon is defeated", logger); } + } } - - public boolean hasEgg(Entity entity) { - if (entity instanceof Player) { - return ((Player) entity).getInventory().contains(Material.DRAGON_EGG); - } else if (entity instanceof LivingEntity) { - LivingEntity mob = (((LivingEntity) entity)); - EntityEquipment inv = mob.getEquipment(); - if (Egg.isEgg(inv.getItemInMainHand())) { - return true; - } - if (Egg.isEgg(inv.getItemInOffHand())) { - return true; - } - } else if (entity instanceof FallingBlock) { - return Egg.isEgg(((FallingBlock) entity)); - } else if (entity instanceof Item) { - return Egg.isEgg((Item) entity); - } - return false; - } - + public boolean isUnderWorld(Entity entity) { return entity.getLocation().getY() < UNDER_WORLD_HEIGHT; } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/InventoryListener.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/InventoryListener.java index b9ca979..cf555c0 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/InventoryListener.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/InventoryListener.java @@ -1,11 +1,12 @@ package io.github.J0hnL0cke.egghunt.Controller; -import java.util.logging.Logger; import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Container; +import org.bukkit.block.DoubleChest; +import org.bukkit.entity.Entity; import org.bukkit.entity.Item; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -20,25 +21,26 @@ import org.bukkit.event.inventory.InventoryMoveItemEvent; import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.player.PlayerDropItemEvent; -import org.bukkit.inventory.AbstractHorseInventory; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Merchant; import io.github.J0hnL0cke.egghunt.Model.Configuration; import io.github.J0hnL0cke.egghunt.Model.Data; import io.github.J0hnL0cke.egghunt.Model.Egg; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; /** * Listens for Bukkit events related to inventories */ public class InventoryListener implements Listener { - private Logger logger; + private LogHandler logger; private Configuration config; private Data data; - public InventoryListener(Logger logger, Configuration config, Data data) { + public InventoryListener(LogHandler logger, Configuration config, Data data) { this.logger = logger; this.config = config; this.data = data; @@ -55,10 +57,10 @@ public class InventoryListener implements Listener { ItemStack stack = item.getItemStack(); // Check if the dropped item is the egg - if (Egg.isEgg(stack)) { - data.setEggOwner(event.getPlayer()); + if (Egg.hasEgg(stack)) { + data.setEggOwner(event.getPlayer(), config); data.updateEggLocation(item); - Egg.makeEggInvulnerable(event.getItemDrop(), config, logger); + EggController.makeEggInvulnerable(event.getItemDrop(), config); } } @@ -68,10 +70,10 @@ public class InventoryListener implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPickupItem(EntityPickupItemEvent event) { // Check if item picked up is an egg - if (Egg.isEgg(event.getItem())) { + if (Egg.hasEgg(event.getItem())) { data.updateEggLocation(event.getEntity()); if (event.getEntity() instanceof Player) { - data.setEggOwner((Player) event.getEntity()); + data.setEggOwner((Player) event.getEntity(), config); } } } @@ -85,7 +87,7 @@ public class InventoryListener implements Listener { // Check if the dragon egg was picked up ItemStack item = event.getItem().getItemStack(); - if (Egg.isEgg(item)) { + if (Egg.hasEgg(item)) { //check if the inventory has open space //TODO test this with edge cases for hopper pull/push if (event.getInventory().firstEmpty() != -1 || event.getInventory().contains(Material.DRAGON_EGG)) { @@ -107,15 +109,15 @@ public class InventoryListener implements Listener { //force an egg in the ender chest to be dropped if enabled in config and if the player is not in creative if (otherInv.getType().equals(InventoryType.ENDER_CHEST)) { if (config.getDropEnderchestedEgg() && player.getGameMode() != GameMode.CREATIVE) { - if (otherInv.contains(Material.DRAGON_EGG)) { - ItemStack egg = otherInv.getItem(otherInv.first(Material.DRAGON_EGG)); + if (otherInv.contains(Material.DRAGON_EGG)) { + ItemStack egg = otherInv.getItem(otherInv.first(Material.DRAGON_EGG)); //TODO make this work with bundles/shulkers Location playerLoc = player.getLocation(); otherInv.remove(egg); Item i = playerLoc.getWorld().dropItem(playerLoc, egg); //TODO use drop item function - console_log(String.format( + log(String.format( "Dropped the dragon egg on the ground since %s had it in their ender chest.", player.getName())); - console_log( + log( "Set ignore_echest_egg to \"true\" in the config file to disable this feature."); data.updateEggLocation(i); } @@ -126,24 +128,40 @@ public class InventoryListener implements Listener { return; } - if (player.getInventory().contains(Material.DRAGON_EGG)) { + if (Egg.hasEgg(player.getInventory())) { //if the player has the egg in their inventory, it will stay there data.updateEggLocation(player); - data.setEggOwner(player); + data.setEggOwner(player, config); - } else if (otherInv.contains(Material.DRAGON_EGG)) { + } else if (Egg.hasEgg(otherInv)) { if (otherInv.getType() != InventoryType.PLAYER) { //TODO check how this affects player viewing own inventory (egg on head/offhand?) - if (!(otherInv.getHolder() instanceof Merchant)) { //chest boat/llama but not villager/wandering trader - //this is a container (chest, furnace, hopper minecart, etc), so the egg will remain here when the inventory is closed - data.updateEggLocation(otherInv); + InventoryHolder holder = otherInv.getHolder(); - } else { - //this is not a container (anvil, crafting table, villager, etc), so it will move back to the player's inventory - //note- if the player's inventory is full, it will instead drop as an item, which will trigger the item drop event - data.updateEggLocation(player); + if (holder instanceof Entity) { + if (holder instanceof Merchant) { //villager/wandering trader + //items will not be stored in this inventory, and will instead revert to the player + logger.log("inventory close- inside merchant entity (reverts to player)"); + data.updateEggLocation(player); + } else { + //this is an entity that stores items (hopper minecart, chest boat, llama, etc), so the egg will remain here when the inventory is closed + logger.log("inventory close- inside non-merchant entity"); + data.updateEggLocation(otherInv); + } + + } else { //this inventory holder is a block + if (holder instanceof Container || holder instanceof DoubleChest) { //DoubleChest implements InventoryHolder directly instead of implementing Container + //this is a container (furnace, chest boat, etc), so the egg will stay here + logger.log("inventory close- inside container block"); + data.updateEggLocation(otherInv); + } else { + //this is not a container (anvil, crafting table, villager, etc), so it will move back to the player's inventory + //note- if the player's inventory is full, it will instead drop as an item, which will trigger the item drop event + logger.log("inventory close- inside non-container block (reverts to player)"); + data.updateEggLocation(player); + } } } } @@ -155,8 +173,8 @@ public class InventoryListener implements Listener { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onInventoryMove(InventoryMoveItemEvent event) { //check if the item being moved is the egg - if (Egg.isEgg(event.getItem())) { - if (event.getDestination().firstEmpty() != -1 || event.getDestination().contains(Material.DRAGON_EGG)) { + if (Egg.hasEgg(event.getItem())) { + if (event.getDestination().firstEmpty() != -1 || Egg.hasEgg(event.getDestination())) { data.updateEggLocation(event.getDestination()); } } @@ -164,7 +182,7 @@ public class InventoryListener implements Listener { /** - * Stop players from holding the egg in their cursor then dragging to drop it into an ender chest or shulker box. + * Stop players from holding the egg in their cursor then dragging to drop it into an ender chest (or shulker box). * * Note: Due to limitations with the API, this prevents dragging the egg when viewing an ender chest or shulker box, * even if the egg is only dragged over slots in the player's inventory. @@ -173,11 +191,11 @@ public class InventoryListener implements Listener { public void onInventoryDragConsider(InventoryDragEvent event) { InventoryType inv = event.getInventory().getType(); //this only gets the currently viewed ("top") inventory, which is not necessairly where the items are dragged in/over if (event.getWhoClicked().getGameMode() != GameMode.CREATIVE) { - if (inv.equals(InventoryType.ENDER_CHEST) || inv.equals(InventoryType.SHULKER_BOX)) { - boolean holdEgg = Egg.isEgg(event.getOldCursor()); + if (inv.equals(InventoryType.ENDER_CHEST) || (inv.equals(InventoryType.SHULKER_BOX) && !config.getCanPackageEgg())) { + boolean holdEgg = Egg.hasEgg(event.getOldCursor()); if (holdEgg) { event.setCancelled(true); - console_log(String.format("Stopped %s from dragging egg while viewing shulker/ender chest", + log(String.format("Stopped %s from dragging egg while viewing shulker/ender chest", event.getWhoClicked().getName())); } } @@ -185,17 +203,17 @@ public class InventoryListener implements Listener { } /** - * Stop players from storing the egg in an ender chest or shulker box. - * Also stops the player from putting the egg into a bundle regardless of open inventory. + * Stop players from storing the egg in an ender chest (or shulker box). + * Also stops the player from putting the egg into a bundle if configured regardless of open inventory. * TODO refactor into multiple smaller listeners */ @EventHandler(priority = EventPriority.HIGHEST) public void onInventoryMoveConsider(InventoryClickEvent event) { InventoryType inv = event.getInventory().getType(); if (event.getWhoClicked().getGameMode() != GameMode.CREATIVE) { - if (inv.equals(InventoryType.ENDER_CHEST) || inv.equals(InventoryType.SHULKER_BOX)) { - boolean hoverEgg = Egg.isEgg(event.getCurrentItem()); - boolean holdEgg = Egg.isEgg(event.getCursor()); + if (inv.equals(InventoryType.ENDER_CHEST) || (inv.equals(InventoryType.SHULKER_BOX) && !config.getCanPackageEgg())) { + boolean hoverEgg = Egg.hasEgg(event.getCurrentItem()); + boolean holdEgg = Egg.hasEgg(event.getCursor()); Boolean clickedContainer = null; if (event.getClickedInventory() != null) { clickedContainer = event.getClickedInventory().equals(event.getInventory()); @@ -207,7 +225,7 @@ public class InventoryListener implements Listener { boolean cancel = false; if (clickedContainer == null){ - logger.info(event.getAction().toString()); + log(event.getAction().toString()); } else if (clickedContainer) { //player clicked the container switch (event.getAction()) { @@ -247,7 +265,7 @@ public class InventoryListener implements Listener { if (cancel) { //if the item clicked was the egg event.setCancelled(true); - console_log(String.format("Stopped %s from moving egg to shulker/ender chest", + log(String.format("Stopped %s from moving egg to shulker/ender chest", event.getWhoClicked().getName())); } } @@ -263,9 +281,9 @@ public class InventoryListener implements Listener { if (event.getClickedInventory().equals(event.getInventory())) { //make sure the destination is the ender chest //this allows hotkeying the egg around in the player's inventory - if (Egg.isEgg(item)) { + if (Egg.hasEgg(item)) { event.setCancelled(true); - console_log(String.format("Stopped %s from hotkeying egg to shulker/ender chest", + log(String.format("Stopped %s from hotkeying egg to shulker/ender chest", event.getWhoClicked().getName())); } } @@ -273,20 +291,21 @@ public class InventoryListener implements Listener { } } - //prevent using bundles on the egg in *any* inventory + //prevent using bundles on the egg in any inventory if(event.getCurrentItem() != null){ if(event.getCursor()!=null){ ItemStack clicked=event.getCurrentItem(); ItemStack cursor=event.getCursor(); - if ((Egg.isEgg(cursor) && clicked.getType() == Material.BUNDLE) - || (Egg.isEgg(clicked) && cursor.getType() == Material.BUNDLE)) { - event.setCancelled(true); - console_log(String.format("Stopped %s from bundling the egg", + if ((Egg.hasEgg(cursor) && clicked.getType() == Material.BUNDLE ) + || (Egg.hasEgg(clicked) && cursor.getType() == Material.BUNDLE)) { + if (!config.getCanPackageEgg()) { + event.setCancelled(true); + log(String.format("Stopped %s from bundling the egg", event.getWhoClicked().getName())); + } } - } } } @@ -294,19 +313,22 @@ public class InventoryListener implements Listener { /** * Stop players from pushing the egg into a shulker using a hopper/hopper minecart + * Only works if this setting is enabled in the config */ @EventHandler(priority = EventPriority.HIGHEST) public void onPushConsider(InventoryMoveItemEvent event) { //nice try, but it won't work - if (event.getDestination().getType().equals(InventoryType.SHULKER_BOX)) { - if (Egg.isEgg(event.getItem())) { - event.setCancelled(true); + if (!config.getCanPackageEgg()) { + if (event.getDestination().getType().equals(InventoryType.SHULKER_BOX)) { + if (Egg.hasEgg(event.getItem())) { + event.setCancelled(true); + } } } } - private void console_log(String message) { - logger.info(message); + private void log(String message) { + logger.log(message); } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/MiscListener.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/MiscListener.java index 77af6bb..2291128 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/MiscListener.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/MiscListener.java @@ -5,10 +5,12 @@ import org.bukkit.entity.*; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockDispenseEvent; import org.bukkit.event.block.BlockFromToEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.*; import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.world.WorldSaveEvent; import org.bukkit.inventory.ItemStack; @@ -17,21 +19,19 @@ import org.bukkit.inventory.PlayerInventory; import io.github.J0hnL0cke.egghunt.Model.Configuration; import io.github.J0hnL0cke.egghunt.Model.Data; import io.github.J0hnL0cke.egghunt.Model.Egg; -import io.github.J0hnL0cke.egghunt.Model.Data.Egg_Storage_Type; - -import java.util.logging.Logger; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; /** * Listens to miscellaneous Bukkit events */ public class MiscListener implements Listener { - private Logger logger; + private LogHandler logger; private Configuration config; private Data data; - public MiscListener(Logger logger, Configuration config, Data data) { + public MiscListener(LogHandler logger, Configuration config, Data data) { this.logger = logger; this.config = config; this.data = data; @@ -48,18 +48,25 @@ public class MiscListener implements Listener { data.saveData(); } + /** + * Update the player's entity tag when they join + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + EggController.updateOwnerTag(player, data, config); + } + /** * Drop egg if a player logs off with it in their inventory. * TODO check if item spawn and item drop events overlap, and check which can be replaced */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onLogoff(PlayerQuitEvent event) { + public void onQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); - Egg.dropEgg(player, data); + EggController.dropEgg(player, data, config); } - - /** * Track the egg droping as an item. * Note: No handler to check if the falling egg entity drops an item, so we have to check every item spawn @@ -69,9 +76,10 @@ public class MiscListener implements Listener { // Check if the spawned item is the egg Item item = event.getEntity(); - if (Egg.isEgg(item)) { + if (Egg.hasEgg(item)) { data.updateEggLocation(item); - Egg.makeEggInvulnerable(event.getEntity(), config, logger); + EggController.makeEggInvulnerable(event.getEntity(), config); + log("made entity invulnerable"); } } @@ -79,10 +87,29 @@ public class MiscListener implements Listener { * Handle the egg being placed as a block */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onBlockPlace (BlockPlaceEvent event) { - if (Egg.isEgg(event.getBlock())) { + public void onBlockPlace(BlockPlaceEvent event) { + if (Egg.hasEgg(event.getBlock())) { data.updateEggLocation(event.getBlock()); - data.setEggOwner(event.getPlayer()); + data.setEggOwner(event.getPlayer(), config); + } + } + + /** + * Prevent the egg being dispensed when inside a shulker box + * TODO allow this to happen and just update the position of the egg + */ + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockDispense(BlockDispenseEvent event) { + + if (event.getBlock().getType().equals(Material.DISPENSER)) { + if (Egg.hasEgg(event.getItem())) { + //if egg or egg container will be dispensed by a dispenser + if (Egg.isShulker(event.getItem().getType())) { + //if the item is a shulker box holding the egg (will be dispensed as a block) + event.setCancelled(true); + //log("Prevented shulker containing the egg from being dispensed"); + } + } } } @@ -98,11 +125,11 @@ public class MiscListener implements Listener { Allay allay = (Allay) event.getRightClicked(); if (!(allay.getEquipment().getItemInMainHand().getType().equals(Material.AIR))) { //if allay has an item - if (Egg.isEgg(allay.getEquipment().getItemInMainHand()) + if (Egg.hasEgg(allay.getEquipment().getItemInMainHand()) && playerInv.getItemInMainHand().getType().equals(Material.AIR)) { //interaction with empty main hand will remove the egg from the allay and put it in the player's inventory data.updateEggLocation(event.getPlayer()); - data.setEggOwner(event.getPlayer()); + data.setEggOwner(event.getPlayer(), config); } break; //allay has an item, giving it the egg is not possible @@ -118,9 +145,9 @@ public class MiscListener implements Listener { } if (!heldItem.getType().equals(Material.AIR)) { //if item in either hand - if (Egg.isEgg(heldItem)) { + if (Egg.hasEgg(heldItem)) { data.updateEggLocation(event.getRightClicked()); - data.setEggOwner(event.getPlayer()); + data.setEggOwner(event.getPlayer(), config); } } @@ -134,20 +161,20 @@ public class MiscListener implements Listener { */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onSpread(BlockFromToEvent event) { - if (Egg.isEgg(event.getBlock())) { + if (Egg.hasEgg(event.getBlock())) { data.updateEggLocation(event.getToBlock()); if (!config.getAccurateLocation()) { //TODO disable accuracy here - console_log("The egg teleported, showing players the egg's location before teleport"); + //log("The egg teleported, showing players the egg's location before teleport"); } else { - console_log("The egg teleported, showing players the egg's up-to-date location"); + //log("The egg teleported, showing players the egg's up-to-date location"); } if (config.resetOwnerOnTeleport()) { if (data.getEggOwner() != null) { announce(String.format("The dragon egg has teleported. %s is no longer the owner.", data.getEggOwner().getName())); - data.resetEggOwner(false); + data.resetEggOwner(false, config); } } } @@ -161,15 +188,15 @@ public class MiscListener implements Listener { //check if this is dealing with a falling block, since EntityChangeBlockEvent is generic if (event.getEntityType() == EntityType.FALLING_BLOCK) { //if the falling block is the egg - if (Egg.isEgg((FallingBlock)event.getEntity())) { + if (Egg.hasEgg((FallingBlock)event.getEntity())) { - console_log("Gravity event involving dragon egg occured"); + log("Gravity event involving dragon egg occured"); if (event.getBlock().getType() == Material.AIR) { //egg lands data.updateEggLocation(event.getBlock()); - } else if (Egg.isEgg(event.getBlock())) { + } else if (Egg.hasEgg(event.getBlock())) { //egg begins falling data.updateEggLocation(event.getEntity()); } @@ -184,8 +211,8 @@ public class MiscListener implements Listener { */ @EventHandler(priority = EventPriority.HIGHEST) public void onPlayerDeath(PlayerDeathEvent event) { - if (!event.getKeepInventory() && event.getEntity().getInventory().contains(Material.DRAGON_EGG)) { - data.resetEggOwner(false); + if (!event.getKeepInventory() && Egg.hasEgg(event.getEntity().getInventory())) { + data.resetEggOwner(false, config); //change the death message String deathmsg = event.getDeathMessage(); @@ -207,8 +234,8 @@ public class MiscListener implements Listener { Announcement.announce(msg, logger); } - private void console_log(String message) { - logger.info(message); + private void log(String message) { + logger.log(message); } } \ No newline at end of file diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/ScoreboardController.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/ScoreboardController.java new file mode 100644 index 0000000..ac4c589 --- /dev/null +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/ScoreboardController.java @@ -0,0 +1,151 @@ +package io.github.J0hnL0cke.egghunt.Controller; + +import java.time.temporal.ChronoUnit; +import java.time.Instant; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Entity; +import org.bukkit.scoreboard.Criteria; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.RenderType; +import org.bukkit.scoreboard.Score; +import org.bukkit.scoreboard.Scoreboard; +import org.bukkit.scoreboard.ScoreboardManager; + +import io.github.J0hnL0cke.egghunt.Model.Configuration; +import io.github.J0hnL0cke.egghunt.Model.Data; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; + +/** + * Handles interactions with the scoreboard api + */ +public class ScoreboardController { + + private static ScoreboardController thisHandler; + + private Data data; + private Configuration config; + private LogHandler logger; + private ScoreboardManager manager; + private Scoreboard board; + private Objective eggMinutes; + private Objective eggSeconds; + private Instant lastUpdate; + private static final String EGG_MINUTES_KEY = "eggOwnedMinutes"; + private static final String EGG_SECONDS_KEY = "eggOwnedSeconds"; + + public static ScoreboardController getScoreboardHandler(Data data, Configuration configuration, LogHandler logger) { + if (thisHandler == null) { + thisHandler = new ScoreboardController(data, configuration, logger); + } + return thisHandler; + } + + private ScoreboardController(Data data, Configuration config, LogHandler logger) { + this.data = data; + this.config = config; + this.logger = logger; + + if (config.getKeepScore()) { + logger.log("Scorekeeping is enabled, loading scoreboard..."); + loadData(); + } + } + + public static void saveData(LogHandler logger) { + if (thisHandler == null) { + logger.warning("ScoreboardController has not been initalized before attempting to save!"); + } else { + thisHandler.updateScores(); + } + } + + private void loadData() { + manager = Bukkit.getScoreboardManager(); + board = manager.getMainScoreboard(); //possibly switch to new board? would need to save/load independently of server + + eggSeconds = getOrMakeObjective(EGG_SECONDS_KEY, "Seconds owning egg"); + eggMinutes = getOrMakeObjective(EGG_MINUTES_KEY, "Minutes owning egg"); + + lastUpdate = Instant.now(); + } + + + /** + * Updates the score for the current egg owner + * + * For accurate scoring, this should be updated at least every time egg ownership changes, and + * every time the plugin is enabled/disabled + * @param data + */ + public void updateScores() { + if (config.getKeepScore() && data.getEggType()!= Data.Egg_Storage_Type.DNE) { + logger.log("Updating scoreboard"); + + if (data.getEggType() == Data.Egg_Storage_Type.ENTITY) { + Entity eggEntity = data.getEggEntity(); + if (config.getNamedEntitiesGetScore()) { + if (eggEntity.getCustomName() != null) { + incrementScoring(eggEntity.getCustomName()); //prioritize the named entity for scoring + return; + } + } + } + + OfflinePlayer owner = data.getEggOwner(); + if (owner != null) { + incrementScoring(owner.getName()); + } + } + } + + private void incrementScoring(String entityName) { + //YOU WILL REMEMBER ME! REMEMBER ME FOR + //ChronoUnit.CENTURIES + + Instant newUpdate = Instant.now(); //when writing tests, switch this to using an injected Clock instance + + long seconds = ChronoUnit.SECONDS.between(lastUpdate, newUpdate); //this is the floored duration in minutes + int convertedSeconds = Long.valueOf(seconds).intValue(); + + addScore(entityName, eggSeconds, convertedSeconds); //add to eggSeconds + + int mins = Math.floorDiv(getScore(entityName, eggSeconds), 60); //convert seconds into minutes + setScore(entityName, eggMinutes, mins); //update eggMinutes + + lastUpdate = newUpdate; //update lastUpdate time + } + + public Objective getOrMakeObjective(String key, String displayName) { + Objective obj = board.getObjective(key); + if (obj == null) { + logger.info(String.format("No scoreboard objective \"%s\" found, creating new objective", key)); + obj = board.registerNewObjective(key, Criteria.DUMMY, displayName, RenderType.INTEGER); + } + + if (!obj.isModifiable()) { + String msg = "Could not modify objective \"%s\"! Make sure objective criteria is set to dummy!"; + logger.warning(String.format(msg, obj.getName())); + } + + return obj; + } + + private void addScore(String name, Objective obj, int score) { + if (score != 0) { //avoid adding the entity to the scoreboard if adding 0 + Score s = obj.getScore(name); + s.setScore(s.getScore() + score); + } + } + + private void setScore(String name, Objective obj, int score) { + Score s = obj.getScore(name); + s.setScore(score); + } + + private int getScore(String name, Objective obj) { + Score s = obj.getScore(name); + return s.getScore(); + } + +} diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Configuration.java b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Configuration.java index 358e13e..cf9b463 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Configuration.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Configuration.java @@ -9,15 +9,21 @@ import io.github.J0hnL0cke.egghunt.Persistence.ConfigFileDAO; * Retrieve settings for this plugin */ public class Configuration { - boolean eggInvulnerable; /** whether to make the egg immune to damage */ - boolean respawnEgg; /** whether the egg should respawn when destroyed */ - boolean respawnImmediately; /** if respawning the egg, whether it should immediately respawn in-place or whether it should generate after next dragon fight */ - boolean resetOwnerOnTeleport; /** whether the egg should become "lost" when it is teleported */ - boolean accurateLocation; - boolean dropEnderchestedEgg; /** whether an existing egg already in an ender chest should be forced out or stuck there */ - World endWorld; /** the name of the world that counts as the end on this server */ + private boolean debug; /** whether to show debug information in console log */ + private boolean eggInvulnerable; /** whether to make the egg immune to damage */ + private boolean respawnEgg; /** whether the egg should respawn when destroyed */ + private boolean respawnImmediately; /** if respawning the egg, whether it should immediately respawn in-place or whether it should generate after next dragon fight */ + private boolean resetOwnerOnTeleport; /** whether the egg should become "lost" when it is teleported */ + private boolean accurateLocation; + private boolean dropEnderchestedEgg; /** whether an existing egg already in an ender chest should be forced out or stuck there */ + private boolean canPackageEgg; /** whether the egg can be stored in a shulker box or bundle */ + private boolean tagOwner; /** whether to apply a tag to the owner of the egg */ + private boolean keepScore; /** whether to create and increment a scoreboard for time holding the egg */ + private boolean namedEntitiesGetScore; /** whether entities with custom names are counted on the scoreboard when holding the egg */ + private World endWorld; /** the name of the world that counts as the end on this server */ + private String ownerTagName; /** the name of the tag to apply to the owner, if enabled */ - public static String defaultEnd = "world_end"; /** default end world name for most spigot servers */ + public static final String DEFAULT_END = "world_end"; /** default end world name for most spigot servers */ ConfigFileDAO fileDao; @@ -29,16 +35,26 @@ public class Configuration { private void loadData() { //load config settings + debug = fileDao.readBool("debug"); eggInvulnerable = fileDao.readBool("egg_inv"); respawnEgg = fileDao.readBool("resp_egg"); respawnImmediately = fileDao.readBool("resp_imm"); resetOwnerOnTeleport = fileDao.readBool("reset_owner"); accurateLocation = fileDao.readBool("accurate_loc"); dropEnderchestedEgg = fileDao.readBool("drop_enderchested_egg"); + canPackageEgg = fileDao.readBool("can_package_egg"); + tagOwner = fileDao.readBool("tag_owner"); + keepScore = fileDao.readBool("keep_score"); + namedEntitiesGetScore = fileDao.readBool("named_entities_keep_score"); + + ownerTagName = fileDao.read("owner_tag_name", null); + endWorld = Bukkit.getServer().getWorld(fileDao.read("end", DEFAULT_END)); - endWorld = Bukkit.getServer().getWorld(fileDao.read("end", defaultEnd)); } + public boolean getDebugEnabled() { + return debug; + } public boolean getEggInvulnerable() { return eggInvulnerable; @@ -63,9 +79,30 @@ public class Configuration { return dropEnderchestedEgg; } + public boolean getCanPackageEgg() { + return false; //canPackageEgg; TODO implement checks for this + } + + public boolean getTagOwner() { + return tagOwner; + } + + public boolean getKeepScore() { + return keepScore; + } + + public boolean getNamedEntitiesGetScore() { + return namedEntitiesGetScore; + } + + public String getOwnerTagName(){ + return ownerTagName; + } + public World getEndWorld() { return endWorld; } + } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Data.java b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Data.java index c61da31..a9dd425 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Data.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Data.java @@ -2,7 +2,6 @@ package io.github.J0hnL0cke.egghunt.Model; import java.time.LocalDateTime; import java.util.UUID; -import java.util.logging.Logger; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -14,6 +13,8 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import io.github.J0hnL0cke.egghunt.Controller.Announcement; +import io.github.J0hnL0cke.egghunt.Controller.EggController; +import io.github.J0hnL0cke.egghunt.Controller.ScoreboardController; import io.github.J0hnL0cke.egghunt.Persistence.DataFileDAO; /** @@ -22,7 +23,7 @@ import io.github.J0hnL0cke.egghunt.Persistence.DataFileDAO; public class Data { private DataFileDAO dataDao; - private Logger logger; + private LogHandler logger; public enum Egg_Storage_Type { ENTITY, @@ -37,9 +38,11 @@ public class Data { private Block block; private Entity entity; private UUID entityFallback; + + private static final String BUG_STR = "THIS MAY BE A BUG! Please report it at https://github.com/HyperSMP/EggHuntPlugin/issues"; - public Data(DataFileDAO dataDao, Logger logger) { + public Data(DataFileDAO dataDao, LogHandler logger) { this.dataDao = dataDao; this.logger = logger; loadData(); @@ -56,14 +59,38 @@ public class Data { return null; } + /** + * Returns the current storage type for the dragon egg + * When getting the corresponding entity or block, that object is guaranteed to be non-null + */ public Egg_Storage_Type getEggType() { + if ((storedAs == Egg_Storage_Type.BLOCK && block == null) + || (storedAs == Egg_Storage_Type.ENTITY && entity == null)) { + //make sure object is non-null + String msg = String.format( + "Unable to find egg! storedAs = %s but corresponding object is null!\nResetting storedAs to prevent further errors", + storedAs.toString()); + resetEggLocation(); + logger.warning(msg); + } + return storedAs; } + /** + * Returns the entity currently associated with the dragon egg + * This should only be used after checking the egg is stored as an entity + * The provided entity should (but is not guaranteed to) contain the egg + */ public Entity getEggEntity() { return entity; } + /** + * Returns the block currently associated with the dragon egg + * This should only be used after checking the egg is stored as a block + * The provided block should (but is not guaranteed to) contain the egg + */ public Block getEggBlock() { return block; } @@ -121,6 +148,59 @@ public class Data { return UUID.fromString(uuid); } + private void validateData() { + logger.log("Data loaded. Validating loaded data..."); + //make sure data was properly loaded + + switch (storedAs) { + case BLOCK: + if (block == null) { + logger.warning("Could not locate egg block!"); + resetEggLocation(); + } else if (!Egg.hasEgg(block)) { + logger.warning("Saved egg block does not actually contain the egg! Was the egg moved while the plugin was disabled?"); + logger.warning(BUG_STR); + } else { + log("Successfully found dragon egg block."); + } + break; + case ENTITY: + if (entity == null) { + logger.warning("Could not locate the egg entity!"); + resetEggLocation(); + } else if (!Egg.hasEgg(entity)) { + logger.warning("Saved egg entity does not actually contain the egg! Was the egg moved while the plugin was disabled?"); + logger.warning(BUG_STR); + } else { + log("Successfully found dragon egg entity."); + } + break; + case DNE: + log("Dragon egg has not been claimed."); + } + + if (owner != null) { + OfflinePlayer p = Bukkit.getOfflinePlayer(owner); + if (p == null) { + logger.warning("Could not find player with the saved UUID! Resetting egg owner to prevent errors."); + logger.warning(BUG_STR); + resetEggOwner(false, null); + } + } + + if (entity == null && storedAs.equals(Egg_Storage_Type.ENTITY)) { + logger.warning("Could not locate the egg entity!"); + resetEggLocation(); + } else if (block == null && storedAs.equals(Egg_Storage_Type.BLOCK)) { + logger.warning("Could not locate egg block!"); + resetEggLocation(); + } else if (storedAs.equals(Egg_Storage_Type.DNE)) { + log("Dragon egg has not been claimed."); + } else { + log("Successfully found dragon egg."); + } + } + public void loadData() { owner = dataDao.readUUID("owner", null); block = deserializeBlock(dataDao.readLocation("block", null)); @@ -131,28 +211,19 @@ public class Data { if (storageString == null) { logger.warning("Could not correctly load egg location data! Was this plugin's data folder deleted?\n" + - "If this is the first time this plugin has run, it is safe to ignore this error."); + "If this is the first time this plugin has run, it is safe to ignore this warning."); storedAs = Egg_Storage_Type.DNE; saveData(); } else { storedAs = Egg_Storage_Type.valueOf(storageString); - //make sure data was properly loaded - if (entity == null && storedAs.equals(Egg_Storage_Type.ENTITY)) { - logger.warning("Could not locate the egg entity!"); - resetEggLocation(); - } else if (block == null && storedAs.equals(Egg_Storage_Type.BLOCK)) { - logger.warning("Could not locate egg block!"); - resetEggLocation(); - } else if (storedAs.equals(Egg_Storage_Type.DNE)) { - logger.info("Dragon egg has not been claimed."); - } else { - logger.info("Successfully found dragon egg."); - } + validateData(); } } public void saveData() { + ScoreboardController.saveData(logger); + dataDao.writeUUID("owner", owner); dataDao.writeLocation("block", serializeBlock(block)); dataDao.writeUUID("entity", serializeEntity(entity)); @@ -170,35 +241,57 @@ public class Data { dataDao.save(); } - public void setEggOwner(Player player) { - setEggOwner(player.getUniqueId()); + public void setEggOwner(Player player, Configuration config) { + setEggOwner(player.getUniqueId(), config); } - private void setEggOwner(UUID playerUUID) { - if (!playerUUID.equals(owner)) { //only announce if the egg has actually changed posession - owner = playerUUID; - Announcement.announce( - String.format("%s has claimed the dragon egg!", Bukkit.getOfflinePlayer(owner).getName()), logger); + private void setEggOwner(UUID playerUUID, Configuration config) { + if (!playerUUID.equals(owner)) { //only update if the egg has actually changed posession + ScoreboardController.saveData(logger); + UUID oldOwner = owner; + owner = playerUUID; //set new owner + String ownerName = Bukkit.getOfflinePlayer(owner).getName(); + String msg; + + //if the old owner is online, remove their scoreboard tag + if (oldOwner != null) { + EggController.updateOwnerTag(Bukkit.getPlayer(oldOwner), this, config); + String oldOwnerName = Bukkit.getOfflinePlayer(oldOwner).getName(); + msg = String.format("%s has stolen the dragon egg from %s!", ownerName, oldOwnerName); + + } else { + msg = String.format("%s has claimed the dragon egg!", ownerName); + } + + Announcement.announce(msg, logger); + Player p = Bukkit.getPlayer(playerUUID); if (p != null) { //make sure player is online Announcement.ShowEggEffects(p); - } + EggController.updateOwnerTag(p, this, config); //update the scoreboard tag of the new owner + } } } - public void resetEggOwner(boolean announce) { - if (announce) { //TODO move announcements somewhere else? - if (owner != null) { - Announcement.announce(String.format("%s no longer owns the dragon egg", Bukkit.getOfflinePlayer(owner).getName()), logger); + public void resetEggOwner(boolean announce, Configuration config) { + ScoreboardController.saveData(logger); + if (owner != null) { + Player oldOwner = Bukkit.getPlayer(owner); + if (announce) { //TODO move announcements somewhere else? + String ownerName = Bukkit.getOfflinePlayer(owner).getName(); + Announcement.announce(String.format("%s no longer owns the dragon egg", ownerName), logger); + } + + log("Egg owner reset"); + owner = null; + if (config != null) { + EggController.updateOwnerTag(oldOwner, this, config); //update tag after setting owner to null } } - logger.info("Egg owner has been reset"); - owner = null; - } public void resetEggLocation() { - log("Egg no longer exists"); + log("Egg location reset"); block = null; entity = null; storedAs = Egg_Storage_Type.DNE; @@ -208,68 +301,29 @@ public class Data { approxLocation = block.getLocation(); this.block = block; entity = null; - storedAs=Egg_Storage_Type.BLOCK; - - switch (block.getType()) { - case DRAGON_EGG: - //egg is stored as a block - log(String.format("The egg is placed as a block")); - break; + storedAs = Egg_Storage_Type.BLOCK; - default: - //egg is stored within the inventory of a tile entity (chest, hopper, furnace, etc) - log(String.format("The egg is in a(n) %s", block.getType())); - } - + log("The egg is "+getEggHolderString()); } + public void updateEggLocation(Entity holderEntity) { approxLocation = holderEntity.getLocation(); entity = holderEntity; block = null; storedAs = Egg_Storage_Type.ENTITY; - switch (holderEntity.getType()) { - case PLAYER: - //TODO Switch posession here - log(String.format("The egg is in the inventory of the player \"%s\"", entity.getName())); - break; - - case FALLING_BLOCK: - log(String.format("The egg is a falling block")); - break; - - case DROPPED_ITEM: - log(String.format("The egg is a dropped item")); - break; - - case ITEM_FRAME: - case GLOW_ITEM_FRAME: - log(String.format("The egg is an item frame")); - break; - - case ARMOR_STAND: - log(String.format("The egg is held by an armor stand")); - break; - - default: - //stored in the inventory of a non-player entity (zombie, hopper minecart, etc) - if (entity.getCustomName() != null) { - log(String.format("The egg is held by a(n) %s named \"%s\"", entity.getType().toString(), - entity.getName())); - } else { - log(String.format("The egg is held by a(n) %s", entity.getType().toString())); - } - - } + log("The egg is "+getEggHolderString()); } + /** + * likely to be called when a generic inventory is the most info given for what picked up an item, like on hopper collect event + * if more info is available (such as Entity or Block instance), better to pass that instead, although this should still work + */ public void updateEggLocation(Inventory inv) { - //likely to be called when a generic inventory is the most info given for what picked up an item, like on hopper collect event - //if more info is available (such as Entity or Block instance), better to pass that instead, although this should still work if (inv.getHolder() instanceof Entity){ // Hopper minecart, llama, or some other entity has the egg - for (Entity e : inv.getLocation().getWorld().getEntities()) { + for (Entity e : inv.getLocation().getChunk().getEntities()) { //search all entities in the world if (e instanceof InventoryHolder) { if (inv.getHolder().equals((InventoryHolder) e)) { @@ -279,16 +333,83 @@ public class Data { } } } - log("could not find correct inventoryHolder entity!"); + logger.warning("Could not find the correct InventoryHolder entity for this Inventory! Are you using custom inventories?"); + logger.warning(BUG_STR); } else { - // Hopper picked up the egg - updateEggLocation(inv.getLocation().getBlock()); //TODO check if this cast works or if .getLocation().getBlock() is needed + // Block contains the egg + updateEggLocation(inv.getLocation().getBlock()); } } - + + /** + * Gives a human-readable string describing where the dragon egg is + * Will always start with "is " unless the message is "does not exist" + */ + public String getEggHolderString() { + Egg_Storage_Type type = getEggType(); + + String storageMsg; + + if (type == Egg_Storage_Type.DNE) { //egg does not exist + storageMsg = "does not exist"; + + } else { //egg exists + + if (type == Egg_Storage_Type.BLOCK) { + Block eggBlock = getEggBlock(); + + if (Egg.isOnlyEgg(eggBlock)) { //egg is an egg block + storageMsg = "is a block"; + + } else { //egg is in a block + //inside a container, so provide the name of the container + storageMsg = String.format("is inside of a(n) %s", eggBlock.getType().toString()); + } + + } else { //egg is in an entity + + Entity eggEntity = getEggEntity(); + + switch (eggEntity.getType()) { + case PLAYER: + storageMsg = String.format("is in the inventory of %s", entity.getName()); + break; + + case FALLING_BLOCK: + storageMsg = String.format("is a falling block"); + break; + + case DROPPED_ITEM: + storageMsg = String.format("is a dropped item"); + break; + + case ITEM_FRAME: + case GLOW_ITEM_FRAME: + storageMsg = String.format("is an item frame"); + break; + + case ARMOR_STAND: + storageMsg = String.format("is held by an armor stand"); + break; + + default: + //stored in the inventory of a non-player entity (zombie, hopper minecart, etc) + if (entity.getCustomName() != null) { + storageMsg = String.format("is held by a(n) %s named \"%s\"", + entity.getType().toString(), + entity.getName()); + } else { + storageMsg = String.format("is held by a(n) %s", entity.getType().toString()); + } + } + } + } + return storageMsg; + } + private void log(String msg) { - logger.info(msg); + logger.log(msg); } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Egg.java b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Egg.java index 3e9632f..829841c 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Egg.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Egg.java @@ -1,79 +1,280 @@ package io.github.J0hnL0cke.egghunt.Model; -import java.util.logging.Logger; - -import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Container; +import org.bukkit.block.ShulkerBox; import org.bukkit.entity.Entity; import org.bukkit.entity.FallingBlock; import org.bukkit.entity.Item; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; -import org.bukkit.util.Vector; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.bukkit.inventory.meta.BundleMeta; /** - * Provides methods related to the dragon egg + * Provides methods related to checking for or removing the dragon egg */ public class Egg { private static Material egg = Material.DRAGON_EGG; /** - * Makes the given entity invulnerable if enabled in the config + * Checks if the given ItemStack is a container that is holding the dragon egg. Also returns false if the provided stack is null. + * @param stack ItemStack to check + * @return True if the stack is a dragon egg or holding the egg, otherwise false */ - public static void makeEggInvulnerable(Entity entity, Configuration config, Logger logger) { - if (config.getEggInvulnerable()) { - entity.setInvulnerable(true); - logger.info("made drop invulnerable"); + public static boolean containsEgg(ItemStack stack) { + if (stack == null) { + return false; } + if (isShulker(stack.getType())) { //check if shulker + BlockStateMeta meta = (BlockStateMeta) stack.getItemMeta(); + ShulkerBox box = (ShulkerBox) meta.getBlockState(); + return hasEgg(box.getInventory()); + + } else if (stack.getType().equals(Material.BUNDLE)) { //check if bundle + BundleMeta bundle = (BundleMeta) stack.getItemMeta(); + return hasEgg(bundle); + } + return false; + } + + /** + * Check if the given material is any color of shulker box + */ + public static boolean isShulker(Material material) { + if (material == null) { + return false; + } + return Tag.SHULKER_BOXES.getValues().contains(material); + } + + /** + * Checks if the given Block is a container that is holding the dragon egg. Also returns false if the provided block is null. + * @param block Block to check + * @return True if the block is a dragon egg or holding the egg, otherwise false + */ + public static boolean containsEgg(Block block) { + if (block == null) { + return false; + } + BlockState state = block.getState(); + if (state instanceof Container) { + Container cont = (Container) state; + return hasEgg(cont.getInventory()); + } + return false; + } + + public static boolean removeEgg(Entity entity) { + //TODO update for containers + if (entity instanceof Player) { + if (hasEgg(((Player) entity).getInventory())) { + Player player = (Player)entity; + player.getInventory().remove(egg); + } + } else if (entity instanceof LivingEntity) { + LivingEntity mob = (((LivingEntity) entity)); + EntityEquipment inv = mob.getEquipment(); + if (hasEgg(inv.getItemInMainHand())) { + inv.setItemInMainHand(null); + return true; + } else if (hasEgg(inv.getItemInOffHand())) { + inv.setItemInOffHand(null); + return true; + } + } else if (entity instanceof FallingBlock) { + FallingBlock block = (FallingBlock) entity; + if (hasEgg(block)) { + block.remove(); + return true; + } + } else if (entity instanceof Item) { + Item item = (Item) entity; + if (hasEgg(item)) { + item.remove(); + return true; + } + } + return false; + } + + public static boolean removeEgg(Block block) { + if (hasEgg(block)) { + block.setType(Material.AIR); + return true; + } + return false; } /** - * Spawns the dragon egg in the end world specified by the given config. - * @return The new egg block. + * Check if the given Material is the dragon egg. Also returns false if the provided material is null. + * @param material Material to check + * @return True if the stack material is a dragon egg, otherwise false */ - public static Block respawnEgg(Configuration config) { - Block newEggLoc = config.getEndWorld().getEnderDragonBattle().getEndPortalLocation().add(0, 4, 0).getBlock(); - newEggLoc.setType(Material.DRAGON_EGG); - return newEggLoc; + public static boolean isEgg(Material material) { + if (material == null) { + return false; + } + return material.equals(egg); } /** - * Drop the egg out of the given player's inventory + * Checks if the given Entity is holding the dragon egg. Also returns false if the provided entity is null. + * @param entity + * @return True if the entity is holding the dragon egg or a container that is holding the egg, otherwise false */ - public static void dropEgg(Player player, Data data) { - // Check if the player has a dragon egg - if (player.getInventory().contains(Material.DRAGON_EGG)) { - - // Set owner and remove - data.setEggOwner(player); //TODO is this necessary? player will likely already be owner - player.getInventory().remove(Material.DRAGON_EGG); - - // Drop it on the floor and set its location - //TODO use drop egg method in EggRespawn - Item egg_drop = player.getWorld().dropItem(player.getLocation(), - new ItemStack(Material.DRAGON_EGG)); - data.updateEggLocation(egg_drop); + public static boolean hasEgg(Entity entity) { + if (entity instanceof Player) { + return Egg.hasEgg(((Player) entity).getInventory()); //Handles entire player check, including inventory, equipment, and custom slots } + + //Some entities can be instances of both LivingEntity and InventoryHolder (villager, allay, etc) + //therefore, check both inventory and equipment separately for the egg + if (entity instanceof LivingEntity) { + LivingEntity mob = (((LivingEntity) entity)); + EntityEquipment inv = mob.getEquipment(); + if (Egg.hasEgg(inv.getArmorContents())) { + return true; + } else if (Egg.hasEgg(inv.getItemInMainHand()) || Egg.hasEgg(inv.getItemInOffHand())) { + return true; + } + } + + if (entity instanceof InventoryHolder) { + if (Egg.hasEgg(((InventoryHolder) entity).getInventory())) { + return true; + } + } + + if (entity instanceof FallingBlock) { + return Egg.hasEgg(((FallingBlock) entity)); + } else if (entity instanceof Item) { + return Egg.hasEgg(((Item) entity)); + } else if (entity instanceof ItemFrame) { + return Egg.hasEgg(((ItemFrame) entity).getItem()); + } + return false; } /** - * Spawns a new egg item at the given location, sets it to invincible if enabled in the given config. - * This should trigger the item drop event, so it should not be necessary to immediately update the data file with the returned item. - * @return the egg item that was spawned + * Check if any of the given ItemStacks are the dragon egg or are holding the egg. Also returns false if the provided list is null. + * @param stack list of ItemStacks to check + * @return True if any of the stacks' material is a dragon egg or holding the egg, otherwise false */ - public static Item spawnEggItem(Location loc, Configuration config, Data data){ - ItemStack egg=new ItemStack(Material.DRAGON_EGG); - egg.setAmount(1); - Item drop=loc.getWorld().dropItem(loc, egg); - drop.setGravity(false); - drop.setGlowing(true); - drop.setVelocity(new Vector().setX(0).setY(0).setZ(0)); - if (config.getEggInvulnerable()) { - drop.setInvulnerable(true); + public static boolean hasEgg(ItemStack[] stacks) { + if (stacks == null) { + return false; } - return drop; + + for (ItemStack item : stacks) { + if (Egg.hasEgg(item)) { + return true; + } + } + + return false; + } + + /** + * Check if the given ItemStack is the dragon egg or is holding the egg. Also returns false if the provided stack is null. + * @param stack ItemStack to check + * @return True if the stack material is a dragon egg or holding the egg, otherwise false + */ + public static boolean hasEgg(ItemStack stack) { + if (stack == null) { + return false; + } + return isEgg(stack.getType()) || containsEgg(stack); + } + + /** + * Check if the given Inventory contains the dragon egg or a container holding the egg. Also returns false if the provided inventory is null. + * @param inventory Inventory to check + * @return True if the inventory contains the a dragon egg item or an item holding the egg, otherwise false + */ + public static boolean hasEgg(Inventory inventory) { + if (inventory == null) { + return false; + } + return hasEgg(inventory.getContents()); + } + + /** + * Check if the given PlayerInventory contains the dragon egg or a container holding the egg. Also returns false if the provided inventory is null. + * Note: Player inventories can contain extra content slots on top of normal inventory contents + * @param inventory PlayerInventory to check + * @return True if the inventory contains the a dragon egg item or an item holding the egg, otherwise false + */ + public static boolean hasEgg(PlayerInventory inventory) { + if (inventory == null) { + return false; + } + for (ItemStack stack : inventory.getExtraContents()) { + if (hasEgg(stack)){ + return true; + } + } + return hasEgg((Inventory)inventory); + } + + /** + * Check if the given Item is the dragon egg or is holding the egg. Also returns false if the provided item is null. + * @param item Item to check + * @return True if the item material is a dragon egg or holding the egg, otherwise false + */ + public static boolean hasEgg(Item item) { + if (item == null) { + return false; + } + return isEgg(item.getItemStack().getType()) || containsEgg(item.getItemStack()); + } + + /** + * Check if the given Block is the dragon egg or is holding the egg. Also returns false if the provided block is null. + * @param block Block to check + * @return True if the block material is a dragon egg or holding the egg, otherwise false + */ + public static boolean hasEgg(Block block) { + if (block == null) { + return false; + } + return isEgg(block.getType()) || containsEgg(block); + } + + /** + * Check if the given FallingBlock is the dragon egg or is holding the egg. Also returns false if the provided block is null. + * @param block FallingBlock to check + * @return True if the block material is a dragon egg or holding the egg, otherwise false + */ + public static boolean hasEgg(FallingBlock block) { + if (block == null) { + return false; + } + return isEgg(block.getBlockData().getMaterial()); //don't check containsEgg because shulker can't be falling block + } + + /** + * Check if the given BundleMeta contains the dragon egg or a container holding the egg. Also returns false if the provided bundle is null. + * @param bundle Bundle to check + * @return True if the bundle contains either the dragon egg or a container (another bundle) holding the egg, otherwise false + */ + public static boolean hasEgg(BundleMeta bundle) { + for (ItemStack stack : bundle.getItems()) { + if (hasEgg(stack)) { + return true; + } + } + return false; } /** @@ -81,7 +282,7 @@ public class Egg { * @param stack ItemStack to check * @return True if the stack material is a dragon egg, otherwise false */ - public static boolean isEgg(ItemStack stack) { + public static boolean isOnlyEgg(ItemStack stack) { if (stack == null) { return false; } @@ -93,7 +294,7 @@ public class Egg { * @param item Item to check * @return True if the item material is a dragon egg, otherwise false */ - public static boolean isEgg(Item item) { + public static boolean isOnlyEgg(Item item) { if (item == null) { return false; } @@ -105,7 +306,7 @@ public class Egg { * @param block Block to check * @return True if the block material is a dragon egg, otherwise false */ - public static boolean isEgg(Block block) { + public static boolean isOnlyEgg(Block block) { if (block == null) { return false; } @@ -117,11 +318,24 @@ public class Egg { * @param block FallingBlock to check * @return True if the block material is a dragon egg, otherwise false */ - public static boolean isEgg(FallingBlock block) { + public static boolean isOnlyEgg(FallingBlock block) { if (block == null) { return false; } return block.getBlockData().getMaterial().equals(egg); } + /** + * Check if the given Inventory contains the dragon egg. Also returns false if the provided inventory is null. + * @param inventory Inventory to check + * @return True if the inventorry contains a dragon egg, otherwise false + */ + public static boolean hasOnlyEgg(Inventory inventory) { + if (inventory == null) { + return false; + } + return inventory.contains(Material.DRAGON_EGG); + } + + } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Model/LogHandler.java b/src/main/java/io/github/J0hnL0cke/egghunt/Model/LogHandler.java new file mode 100644 index 0000000..e867c33 --- /dev/null +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Model/LogHandler.java @@ -0,0 +1,47 @@ +package io.github.J0hnL0cke.egghunt.Model; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class LogHandler { + private Logger logger; + private boolean debug = false; + + /** + * Provides loggging functionality + * Debugging requires increasing the logging level, since the console never listens to logs below INFO + * due to how parent loggers are hardcoded + */ + public LogHandler(Logger logger) { + this.logger = logger; + + log("Logging ready"); + } + + public void setDebug(boolean debug) { + this.debug = debug; + if (debug) { + log("Debugging is ON"); + } + } + + public void log(String msg, Level loggingLevel) { + logger.log(loggingLevel, msg); + } + + public void log(String msg) { + log(msg, debug ? Level.INFO : Level.CONFIG); + } + + public void info(String msg) { + log(msg, Level.INFO); + } + + public void warning(String msg) { + log(msg, Level.WARNING); + } + + public void severe(String msg) { + log(msg, Level.SEVERE); + } +} diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Persistence/DataFileDAO.java b/src/main/java/io/github/J0hnL0cke/egghunt/Persistence/DataFileDAO.java index e542ba1..292fc10 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Persistence/DataFileDAO.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Persistence/DataFileDAO.java @@ -1,9 +1,6 @@ package io.github.J0hnL0cke.egghunt.Persistence; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; import java.util.UUID; import java.io.File; @@ -15,6 +12,8 @@ import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; + public class DataFileDAO { public static final String DATA_FILE = "data.yml"; @@ -22,17 +21,16 @@ public class DataFileDAO { private static DataFileDAO thisDao; private FileConfiguration fileConfig; private File file; + private LogHandler logger; - JavaPlugin plugin; - - private DataFileDAO(JavaPlugin plugin){ - this.plugin = plugin; + private DataFileDAO(JavaPlugin plugin, LogHandler logger) { + this.logger = logger; loadData(plugin, DATA_FILE); } - public static DataFileDAO getDataDAO(JavaPlugin plugin){ + public static DataFileDAO getDataDAO(JavaPlugin plugin, LogHandler logger){ if(thisDao == null){ - thisDao = new DataFileDAO(plugin); + thisDao = new DataFileDAO(plugin, logger); } return thisDao; } @@ -58,7 +56,7 @@ public class DataFileDAO { try { fileConfig.save(file); } catch (IOException e) { - Bukkit.getServer().getLogger().severe(String.format("Could not save data file!")); + logger.severe(String.format("Could not save data file!")); } } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/egghunt.java b/src/main/java/io/github/J0hnL0cke/egghunt/egghunt.java index e49ca2e..ab9ebed 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/egghunt.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/egghunt.java @@ -1,7 +1,5 @@ package io.github.J0hnL0cke.egghunt; -import java.util.logging.Logger; - import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Entity; @@ -10,14 +8,17 @@ import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitTask; +import io.github.J0hnL0cke.egghunt.Controller.Announcement; import io.github.J0hnL0cke.egghunt.Controller.CommandHandler; +import io.github.J0hnL0cke.egghunt.Controller.EggController; import io.github.J0hnL0cke.egghunt.Controller.EggDestroyListener; import io.github.J0hnL0cke.egghunt.Controller.MiscListener; +import io.github.J0hnL0cke.egghunt.Controller.ScoreboardController; import io.github.J0hnL0cke.egghunt.Controller.EventScheduler; import io.github.J0hnL0cke.egghunt.Controller.InventoryListener; import io.github.J0hnL0cke.egghunt.Model.Configuration; import io.github.J0hnL0cke.egghunt.Model.Data; -import io.github.J0hnL0cke.egghunt.Model.Egg; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; import io.github.J0hnL0cke.egghunt.Persistence.ConfigFileDAO; import io.github.J0hnL0cke.egghunt.Persistence.DataFileDAO; @@ -28,27 +29,32 @@ public final class egghunt extends JavaPlugin { CommandHandler commandHandler; BukkitTask belowWorldTask; - Logger logger; + LogHandler logger; @Override public void onEnable() { - logger = getLogger(); - log("Enabling EggHunt..."); + //create logger instance + logger = new LogHandler(getLogger()); + + logger.info("Enabling EggHunt..."); //server already provides an enable message saveDefaultConfig(); //create model instances using dependency injection config = new Configuration(new ConfigFileDAO(this)); - data = new Data(DataFileDAO.getDataDAO(this), getLogger()); + data = new Data(DataFileDAO.getDataDAO(this, logger), logger); + + logger.setDebug(config.getDebugEnabled()); //create controller instances - MiscListener miscListener = new MiscListener(getLogger(), config, data); + ScoreboardController scoreboardController = ScoreboardController.getScoreboardHandler(data, config, logger); + MiscListener miscListener = new MiscListener(logger, config, data); InventoryListener inventoryListener = new InventoryListener(logger, config, data); EggDestroyListener destroyListener = new EggDestroyListener(logger, config, data); EventScheduler scheduler = new EventScheduler(config, data, logger); commandHandler = new CommandHandler(data); //register event handlers - log("Registering event listeners..."); + logger.log("Registering event listeners..."); PluginManager manager = getServer().getPluginManager(); manager.registerEvents(miscListener, this); manager.registerEvents(inventoryListener, this); @@ -56,35 +62,36 @@ public final class egghunt extends JavaPlugin { //schedule tasks //TODO: disable task when not in use - log("Scheduling below world task..."); + logger.log("Scheduling task..."); belowWorldTask = scheduler.runTaskTimer(this, 20, 20); - log("Done!"); + logger.info("EggHunt enabled."); } @Override public void onDisable() { - log("onDisable has been invoked, disabling eggHunt."); + logger.log("Preparing to disable EggHunt..."); //server already provides a disable message if (data != null) { //if a player has the egg in their inventory, //drop it on the ground in case the server is closing Entity eggHolder = data.getEggEntity(); if (eggHolder != null) { if (eggHolder instanceof Player) { - log("egg is held by a player, dropping egg..."); + logger.log("Egg is held by a player, dropping egg..."); Player p = (Player) eggHolder; - Egg.dropEgg(p, data); + EggController.dropEgg(p, data, config); //in case the server isn't restarting, let the player know what happend - p.sendMessage("The dragon egg was dropped from your inventory"); + Announcement.sendMessage(p, "The dragon egg was dropped from your inventory"); } } - log("saving data..."); + logger.log("Saving data..."); data.saveData(); } + logger.log("Disabling task..."); if (belowWorldTask!=null) { belowWorldTask.cancel(); } - log("Plugin disabled."); + logger.info("EggHunt disabled."); } @Override @@ -92,8 +99,6 @@ public final class egghunt extends JavaPlugin { return commandHandler.onCommand(sender, cmd, label, args); } - private void log(String msg) { - logger.info(msg); - } + } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2b0ceb6..64ffb93 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,11 +1,11 @@ +#If enabled, logs extra debugging information to the console. This information will appear in logs regardless of debug setting. +debug: true + #Name of your end world, usually to "world_the_end" on spigot end: world_the_end #Whether the egg teleporting causes the owner to no longer count as the owner of the egg reset_owner: true -#Whether to tell players the new location of the egg (true) or the previous location of the egg (false) when it teleports -#!!! not yet implemented !!! -#accurate_loc: false #Whether the egg should be invulnerable to damage egg_inv: false @@ -13,6 +13,21 @@ egg_inv: false resp_egg: true #Whether the egg respawns immediately (true) or after a new dragon is spawned and killed (false) after the egg is destroyed resp_imm: false + #Whether to drop any eggs that players have in their ender chest onto the ground. #Setting this to false will prevent the player from removing that egg from their ender chest. drop_enderchested_egg: false + +#Whether to apply an entity tag to the egg owner (works like `/tag add `) +#Tag is updated when egg ownership changes and on player login +tag_owner: true +#The name of the tag to apply. Selected like: `/tell @a[tag=] ` +#If changing this while the egg is claimed, remove the old tag by running `/tag remove ` +owner_tag_name: eggOwner + +#Whether to create and update scoreboards for time holding the dragon egg +#Scoreboards are updated when the egg changes ownership and when the world saves +#When editing the scores, modify the board tracking seconds. Other times are calculated based on that time +keep_score: true +#Whether entities with custom names are counted on the scoreboard instead of the owner when holding the egg +named_entities_keep_score: false \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index b5f440a..a25e059 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,5 +1,6 @@ name: EggHunt main: io.github.J0hnL0cke.egghunt.egghunt +website: https://github.com/HyperSMP/EggHuntPlugin version: 1.20.1 api-version: '1.20' @@ -34,13 +35,18 @@ permissions: children: egghunt.locateegg: true egghunt.trackegg: true + egghunt.eggowner: true egghunt.notify: true + default: true egghunt.locateegg: description: Allows you to locate the dragon egg using /locateegg default: true egghunt.trackegg: description: Allows you to track the egg with a compass using /trackegg default: true + egghunt.eggowner: + description: Allows you to query the egg's current owner + default: true egghunt.notify: description: Receive a chat message when the egg is claimed, stolen, lost, or destroyed default: true