Merge branch 'development'

This commit is contained in:
J0nasL 2023-07-19 22:44:19 -04:00
commit 0c5272e679
17 changed files with 1335 additions and 575 deletions

View file

@ -1,42 +1,48 @@
# Egg Hunt
**A plugin for hunting down the dragon egg and stealing it from other players.**
<img src="https://github.com/HyperSMP/EggHuntPlugin/assets/51202569/c0c860ee-bfa1-480f-baad-12874467416c" width="200" height="200">
**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.

View file

@ -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<Player> 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);
}
}

View file

@ -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 <x>"
if (type == Egg_Storage_Type.DNE) {
//if the egg does not exist
sender.sendMessage(msgStart + "does not exist");
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 {
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));
}
} 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;
}

View file

@ -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
}
}

View file

@ -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<BlockState> 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,40 +134,143 @@ 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<BlockState> 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);
handleBlockStateReplace(blocks);
}
} 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
}
/**
* 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<Block> 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<Block> 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()) {
//if dragon is deleting blocks, check if any are the egg
handleBlockReplace(event.blockList());
if(event.blockList().isEmpty()){
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();
}
}
private ArrayList<Block> findEggs(List<Block> blocks){
ArrayList<Block> 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<Block> blocks) {
ArrayList<Block> 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<Block> getBlockListFromBlockStateList(List<BlockState> blockStates) {
ArrayList<Block> blocks = new ArrayList<>();
for (BlockState blockState : blockStates) {
blocks.add(blockState.getBlock());
}
return blocks;
}
private void handleBlockStateReplace(List<BlockState> blockStates) {
ArrayList<Block> blocks = getBlockListFromBlockStateList(blockStates);
ArrayList<Block> 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);
}
}
}
@ -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);
}
}

View file

@ -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;
@ -43,11 +42,12 @@ public class EventScheduler extends BukkitRunnable {
logger.warning("Resetting egg location to prevent repeated warnings");
data.resetEggLocation();
return;
} else if (isUnderWorld(data.getEggEntity())) {
} 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());
if (config.getEggInvulnerable()) {
Egg.removeEgg(data.getEggEntity());
if (config.getEggInvulnerable()) {
// get coords for egg to spawn at
//get highest block
Block highestBlock = respawnLoc.getWorld().getHighestBlockAt(respawnLoc);
@ -58,51 +58,16 @@ 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);
}
data.resetEggOwner(true);
//alert and respawn if applicable
EggController.eggDestroyed(config, data, logger);
}
}
}
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) {

View file

@ -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)) {
@ -108,14 +110,14 @@ public class InventoryListener implements Listener {
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));
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();
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)) {
if ((Egg.hasEgg(cursor) && clicked.getType() == Material.BUNDLE )
|| (Egg.hasEgg(clicked) && cursor.getType() == Material.BUNDLE)) {
if (!config.getCanPackageEgg()) {
event.setCancelled(true);
console_log(String.format("Stopped %s from bundling the egg",
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 (!config.getCanPackageEgg()) {
if (event.getDestination().getType().equals(InventoryType.SHULKER_BOX)) {
if (Egg.isEgg(event.getItem())) {
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);
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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,
@ -38,8 +39,10 @@ public class Data {
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?
public void resetEggOwner(boolean announce, Configuration config) {
ScoreboardController.saveData(logger);
if (owner != null) {
Announcement.announce(String.format("%s no longer owns the dragon egg", Bukkit.getOfflinePlayer(owner).getName()), logger);
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);
}
}
logger.info("Egg owner has been reset");
owner = null;
log("Egg owner reset");
owner = null;
if (config != null) {
EggController.updateOwnerTag(oldOwner, this, config); //update tag after setting owner to null
}
}
}
public void resetEggLocation() {
log("Egg no longer exists");
log("Egg location reset");
block = null;
entity = null;
storedAs = Egg_Storage_Type.DNE;
@ -208,20 +301,11 @@ public class Data {
approxLocation = block.getLocation();
this.block = block;
entity = null;
storedAs=Egg_Storage_Type.BLOCK;
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;
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();
@ -229,47 +313,17 @@ public class Data {
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 <x>" 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);
}

View file

@ -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;
}
/**
* Spawns the dragon egg in the end world specified by the given config.
* @return The new egg block.
* Check if the given material is any color of shulker box
*/
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 isShulker(Material material) {
if (material == null) {
return false;
}
return Tag.SHULKER_BOXES.getValues().contains(material);
}
/**
* Drop the egg out of the given player's inventory
* 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 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 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 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 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 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 isEgg(Material material) {
if (material == null) {
return false;
}
return drop;
return material.equals(egg);
}
/**
* 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 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;
}
/**
* 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 boolean hasEgg(ItemStack[] stacks) {
if (stacks == null) {
return false;
}
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);
}
}

View file

@ -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);
}
}

View file

@ -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!"));
}
}

View file

@ -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);
}
}

View file

@ -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 <playerName> add <tagName>`)
#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=<tagName>] <message>`
#If changing this while the egg is claimed, remove the old tag by running `/tag <playerName> remove <oldTagName>`
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

View file

@ -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