diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/MiscListener.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/MiscListener.java index 0b80f45..2291128 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/MiscListener.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/MiscListener.java @@ -29,14 +29,12 @@ public class MiscListener implements Listener { private LogHandler logger; private Configuration config; private Data data; - private ScoreboardHandler scoreboardHandler; - public MiscListener(LogHandler logger, Configuration config, Data data, ScoreboardHandler scoreboardHandler) { + public MiscListener(LogHandler logger, Configuration config, Data data) { this.logger = logger; this.config = config; this.data = data; - this.scoreboardHandler = scoreboardHandler; } /** @@ -47,7 +45,6 @@ public class MiscListener implements Listener { */ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onAutosave(WorldSaveEvent event) { - scoreboardHandler.updateScores(); data.saveData(); } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/ScoreboardController.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/ScoreboardController.java new file mode 100644 index 0000000..ac4c589 --- /dev/null +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/ScoreboardController.java @@ -0,0 +1,151 @@ +package io.github.J0hnL0cke.egghunt.Controller; + +import java.time.temporal.ChronoUnit; +import java.time.Instant; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Entity; +import org.bukkit.scoreboard.Criteria; +import org.bukkit.scoreboard.Objective; +import org.bukkit.scoreboard.RenderType; +import org.bukkit.scoreboard.Score; +import org.bukkit.scoreboard.Scoreboard; +import org.bukkit.scoreboard.ScoreboardManager; + +import io.github.J0hnL0cke.egghunt.Model.Configuration; +import io.github.J0hnL0cke.egghunt.Model.Data; +import io.github.J0hnL0cke.egghunt.Model.LogHandler; + +/** + * Handles interactions with the scoreboard api + */ +public class ScoreboardController { + + private static ScoreboardController thisHandler; + + private Data data; + private Configuration config; + private LogHandler logger; + private ScoreboardManager manager; + private Scoreboard board; + private Objective eggMinutes; + private Objective eggSeconds; + private Instant lastUpdate; + private static final String EGG_MINUTES_KEY = "eggOwnedMinutes"; + private static final String EGG_SECONDS_KEY = "eggOwnedSeconds"; + + public static ScoreboardController getScoreboardHandler(Data data, Configuration configuration, LogHandler logger) { + if (thisHandler == null) { + thisHandler = new ScoreboardController(data, configuration, logger); + } + return thisHandler; + } + + private ScoreboardController(Data data, Configuration config, LogHandler logger) { + this.data = data; + this.config = config; + this.logger = logger; + + if (config.getKeepScore()) { + logger.log("Scorekeeping is enabled, loading scoreboard..."); + loadData(); + } + } + + public static void saveData(LogHandler logger) { + if (thisHandler == null) { + logger.warning("ScoreboardController has not been initalized before attempting to save!"); + } else { + thisHandler.updateScores(); + } + } + + private void loadData() { + manager = Bukkit.getScoreboardManager(); + board = manager.getMainScoreboard(); //possibly switch to new board? would need to save/load independently of server + + eggSeconds = getOrMakeObjective(EGG_SECONDS_KEY, "Seconds owning egg"); + eggMinutes = getOrMakeObjective(EGG_MINUTES_KEY, "Minutes owning egg"); + + lastUpdate = Instant.now(); + } + + + /** + * Updates the score for the current egg owner + * + * For accurate scoring, this should be updated at least every time egg ownership changes, and + * every time the plugin is enabled/disabled + * @param data + */ + public void updateScores() { + if (config.getKeepScore() && data.getEggType()!= Data.Egg_Storage_Type.DNE) { + logger.log("Updating scoreboard"); + + if (data.getEggType() == Data.Egg_Storage_Type.ENTITY) { + Entity eggEntity = data.getEggEntity(); + if (config.getNamedEntitiesGetScore()) { + if (eggEntity.getCustomName() != null) { + incrementScoring(eggEntity.getCustomName()); //prioritize the named entity for scoring + return; + } + } + } + + OfflinePlayer owner = data.getEggOwner(); + if (owner != null) { + incrementScoring(owner.getName()); + } + } + } + + private void incrementScoring(String entityName) { + //YOU WILL REMEMBER ME! REMEMBER ME FOR + //ChronoUnit.CENTURIES + + Instant newUpdate = Instant.now(); //when writing tests, switch this to using an injected Clock instance + + long seconds = ChronoUnit.SECONDS.between(lastUpdate, newUpdate); //this is the floored duration in minutes + int convertedSeconds = Long.valueOf(seconds).intValue(); + + addScore(entityName, eggSeconds, convertedSeconds); //add to eggSeconds + + int mins = Math.floorDiv(getScore(entityName, eggSeconds), 60); //convert seconds into minutes + setScore(entityName, eggMinutes, mins); //update eggMinutes + + lastUpdate = newUpdate; //update lastUpdate time + } + + public Objective getOrMakeObjective(String key, String displayName) { + Objective obj = board.getObjective(key); + if (obj == null) { + logger.info(String.format("No scoreboard objective \"%s\" found, creating new objective", key)); + obj = board.registerNewObjective(key, Criteria.DUMMY, displayName, RenderType.INTEGER); + } + + if (!obj.isModifiable()) { + String msg = "Could not modify objective \"%s\"! Make sure objective criteria is set to dummy!"; + logger.warning(String.format(msg, obj.getName())); + } + + return obj; + } + + private void addScore(String name, Objective obj, int score) { + if (score != 0) { //avoid adding the entity to the scoreboard if adding 0 + Score s = obj.getScore(name); + s.setScore(s.getScore() + score); + } + } + + private void setScore(String name, Objective obj, int score) { + Score s = obj.getScore(name); + s.setScore(score); + } + + private int getScore(String name, Objective obj) { + Score s = obj.getScore(name); + return s.getScore(); + } + +} diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/ScoreboardHandler.java b/src/main/java/io/github/J0hnL0cke/egghunt/Controller/ScoreboardHandler.java deleted file mode 100644 index 140fc97..0000000 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Controller/ScoreboardHandler.java +++ /dev/null @@ -1,135 +0,0 @@ -package io.github.J0hnL0cke.egghunt.Controller; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.time.temporal.TemporalUnit; -import java.time.Instant; -import java.util.Date; -import java.time.temporal.TemporalUnit; - -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.Statistic; -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 - */ -public class ScoreboardHandler { - - private Data data; - private Configuration config; - private LogHandler logger; - private ScoreboardManager manager; - private Scoreboard board; - private Objective eggObjective; - private Instant lastUpdate; - private static final String OBJECTIVE_KEY = "eggy"; - - public ScoreboardHandler(Data data, Configuration config, LogHandler logger) { - this.data = data; - this.config = config; - this.logger = logger; - manager = Bukkit.getScoreboardManager(); - board = manager.getMainScoreboard(); //possibly switch to new board? would need to save/load independently of server - - - eggObjective = board.getObjective(OBJECTIVE_KEY); - if (eggObjective == null) { - //TODO - logger.info("No scoreboard objective for owning egg found, creating new objective"); - logger.log("TODO"); - } - - 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() { - - OfflinePlayer owner = data.getEggOwner(); - - if (owner != null) { - - if (data.getEggEntity() != null) { - //TODO remove later, this is just for testing - addScore(data.getEggEntity(), 0); - } - - Instant newUpdate = Instant.now(); //when writing tests, switch this to using an injected Clock instance - - //YOU WILL REMEMBER ME! REMEMBER ME FOR - //ChronoUnit.CENTURIES - - long mins = ChronoUnit.MINUTES.between(lastUpdate, newUpdate); //this is the floored duration in minutes - - int minInt = Long.valueOf(mins).intValue(); - - addScore(data.getEggOwner(), minInt); - - lastUpdate = newUpdate; - - } - - } - - public void makeScoreboard() { - - - Objective newObj = board.registerNewObjective(OBJECTIVE_KEY, Criteria.DUMMY, "Minutes owning egg", - RenderType.INTEGER); - - //newObj.isModifiable(); - - //setScore(null, 0); - - //manager. - - //Criteria c = Criteria.statistic(Statistic.); - - //Criteria d = Criteria.create("MinsHeldEgg"); - - } - - private void addScore(OfflinePlayer p, int score) { - setScore(p, getScore(p) + score); //maybe do this more efficiently - } - - private void addScore(Entity e, int score) { //TODO testing method, remove - String key = e.getUniqueId().toString(); - if (e.getCustomName() != null) { - key = e.getCustomName(); - } - - Score s = eggObjective.getScore(key); - - s.setScore(s.getScore() + 1); - } - - private void setScore(OfflinePlayer p, int score) { - Score s = eggObjective.getScore(p.getName()); - s.setScore(score); - } - - private int getScore(OfflinePlayer p) { - Score s = eggObjective.getScore(p.getName()); - return s.getScore(); - } - -} diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Configuration.java b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Configuration.java index e3db98a..cf9b463 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Configuration.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Configuration.java @@ -18,6 +18,8 @@ public class Configuration { 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 */ @@ -42,6 +44,8 @@ public class Configuration { 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)); @@ -79,11 +83,18 @@ public class Configuration { 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; } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Data.java b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Data.java index 877c21b..a9dd425 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Data.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Data.java @@ -14,6 +14,7 @@ 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; /** @@ -221,6 +222,8 @@ public class Data { } public void saveData() { + ScoreboardController.saveData(logger); + dataDao.writeUUID("owner", owner); dataDao.writeLocation("block", serializeBlock(block)); dataDao.writeUUID("entity", serializeEntity(entity)); @@ -244,6 +247,7 @@ public class Data { 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(); @@ -270,6 +274,7 @@ public class Data { } public void resetEggOwner(boolean announce, Configuration config) { + ScoreboardController.saveData(logger); if (owner != null) { Player oldOwner = Bukkit.getPlayer(owner); if (announce) { //TODO move announcements somewhere else? diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Egg.java b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Egg.java index 8e09f5b..829841c 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/Model/Egg.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/Model/Egg.java @@ -143,7 +143,9 @@ public class Egg { if (entity instanceof LivingEntity) { LivingEntity mob = (((LivingEntity) entity)); EntityEquipment inv = mob.getEquipment(); - if (Egg.hasEgg(inv.getArmorContents())) { //TODO check if includes main/offhand + if (Egg.hasEgg(inv.getArmorContents())) { + return true; + } else if (Egg.hasEgg(inv.getItemInMainHand()) || Egg.hasEgg(inv.getItemInOffHand())) { return true; } } diff --git a/src/main/java/io/github/J0hnL0cke/egghunt/egghunt.java b/src/main/java/io/github/J0hnL0cke/egghunt/egghunt.java index 01bcac0..ab9ebed 100644 --- a/src/main/java/io/github/J0hnL0cke/egghunt/egghunt.java +++ b/src/main/java/io/github/J0hnL0cke/egghunt/egghunt.java @@ -13,7 +13,7 @@ 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.ScoreboardHandler; +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; @@ -46,8 +46,8 @@ public final class egghunt extends JavaPlugin { logger.setDebug(config.getDebugEnabled()); //create controller instances - ScoreboardHandler scoreboardHandler = new ScoreboardHandler(data, config, logger); - MiscListener miscListener = new MiscListener(logger, config, data, scoreboardHandler); + 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); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 338ba33..c336763 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,5 +1,5 @@ #If enabled, logs extra debugging information to the console. This information will appear in logs regardless of debug setting. -debug: false +debug: true #Name of your end world, usually to "world_the_end" on spigot end: world_the_end @@ -13,16 +13,23 @@ 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 the egg is allowed to be stored in bundles and shulker boxes #WIP -#can_package_egg: true +#can_package_egg: false #Whether to apply an entity tag to the egg owner (works like `/tag add `) +#Tag is updated when egg ownership changes and on player login tag_owner: true -#The name of the tag to apply (selected like: `/tell @a[tag=] `) -#Do not overlap with tag names used by other plugins. +#The name of the tag to apply. Selected like: `/tell @a[tag=] ` #If changing this while the egg is claimed, remove the old tag by running `/tag remove `. -owner_tag_name: eggOwner \ No newline at end of file +owner_tag_name: eggOwner + +#Whether to create and update a scoreboard for time holding the dragon egg +#Scoreboard is updated when the egg changes ownership and when the world saves +keep_score: true +#Whether entities with custom names are counted on the scoreboard instead of the owner when holding the egg +named_entities_keep_score: false \ No newline at end of file