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