From 95e6769ecd03db5fd841f4e8432f47d216193435 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 4 Oct 2018 21:48:44 -0700 Subject: [PATCH] WIP Does not compile yet. Just wanted to put some code in here. --- pom.xml | 190 +++++++++++++ .../java/bentobox/addon/limits/Limits.java | 63 +++++ .../java/bentobox/addon/limits/Settings.java | 37 +++ .../listeners/EntityLimitsListener.java | 263 ++++++++++++++++++ .../addon/limits/objects/EntityLimitsDO.java | 97 +++++++ src/main/resources/addon.yml | 8 + src/main/resources/config.yml | 67 +++++ src/main/resources/locales/en-US.yml | 32 +++ 8 files changed, 757 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/bentobox/addon/limits/Limits.java create mode 100644 src/main/java/bentobox/addon/limits/Settings.java create mode 100644 src/main/java/bentobox/addon/limits/listeners/EntityLimitsListener.java create mode 100644 src/main/java/bentobox/addon/limits/objects/EntityLimitsDO.java create mode 100755 src/main/resources/addon.yml create mode 100644 src/main/resources/config.yml create mode 100755 src/main/resources/locales/en-US.yml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..97eb5e6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,190 @@ + + 4.0.0 + bentobox.add + addon-limits + 0.0.1-SNAPSHOT + addon-limits + An add-on for BentoBox that limits entities on islands. + https://github.com/BentoBoxWorld/addon-level + 2018 + + + scm:git:https://github.com/BentoBoxWorld/addon-limits.git + scm:git:git@github.com:BentoBoxWorld/addon-limits.git + https://github.com/BentoBoxWorld/addon-limits + + + + GitHub + https://github.com/BentoBoxWorld/addon-limits/issues + + + + UTF-8 + UTF-8 + 1.8 + 1.7.4 + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots + + + codemc + https://repo.codemc.org/repository/maven-snapshots/ + + + + + + org.spigotmc + spigot-api + 1.13.1-R0.1-SNAPSHOT + provided + + + org.mockito + mockito-all + 1.10.19 + test + + + org.powermock + powermock-module-junit4 + ${powermock.version} + test + + + org.powermock + powermock-api-mockito + ${powermock.version} + test + + + world.bentobox + bentobox + 0.10.0-SNAPSHOT + provided + + + + + clean package + + + src/main/resources + true + + + src/main/resources/locales + ./locales + false + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-resources-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.0 + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.1 + + public + false + -Xdoclint:none + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.0.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.1 + + false + + + + org.apache.maven.plugins + maven-install-plugin + 2.5.2 + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + + org.jacoco + jacoco-maven-plugin + 0.8.1 + + true + + + + pre-unit-test + + prepare-agent + + + + post-unit-test + + report + + + + + + + + \ No newline at end of file diff --git a/src/main/java/bentobox/addon/limits/Limits.java b/src/main/java/bentobox/addon/limits/Limits.java new file mode 100644 index 0000000..27f22f1 --- /dev/null +++ b/src/main/java/bentobox/addon/limits/Limits.java @@ -0,0 +1,63 @@ +package bentobox.addon.limits; + +import world.bentobox.bentobox.api.addons.Addon; +import world.bentobox.bentobox.api.commands.CompositeCommand; + + +/** + * Addon to BSkyBlock that enables island level scoring and top ten functionality + * @author tastybento + * + */ +public class Limits extends Addon { + + Settings settings; + + @Override + public void onDisable(){ + } + + @Override + public void onEnable() { + // Load the plugin's config + saveDefaultConfig(); + // Load settings + settings = new Settings(this); + // Register commands + // AcidIsland hook in + this.getPlugin().getAddonsManager().getAddonByName("AcidIsland").ifPresent(a -> { + CompositeCommand acidIslandCmd = getPlugin().getCommandsManager().getCommand(getConfig().getString("acidisland.user-command","ai")); + if (acidIslandCmd != null) { + CompositeCommand acidCmd = getPlugin().getCommandsManager().getCommand(getConfig().getString("acidisland.admin-command","acid")); + } + }); + // BSkyBlock hook in + this.getPlugin().getAddonsManager().getAddonByName("BSkyBlock").ifPresent(a -> { + CompositeCommand bsbIslandCmd = getPlugin().getCommandsManager().getCommand(getConfig().getString("bskyblock.user-command","island")); + if (bsbIslandCmd != null) { + CompositeCommand bsbAdminCmd = getPlugin().getCommandsManager().getCommand(getConfig().getString("bskyblock.admin-command","bsbadmin")); + } + }); + + // Register new island listener + //registerListener(new NewIslandListener(this)); + //registerListener(new JoinLeaveListener(this)); + // Done + + } + + /** + * Save the levels to the database + */ + private void save(){ + } + + /** + * @return the settings + */ + public Settings getSettings() { + return settings; + } + + +} diff --git a/src/main/java/bentobox/addon/limits/Settings.java b/src/main/java/bentobox/addon/limits/Settings.java new file mode 100644 index 0000000..5acaff6 --- /dev/null +++ b/src/main/java/bentobox/addon/limits/Settings.java @@ -0,0 +1,37 @@ +package bentobox.addon.limits; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.EntityType; + +public class Settings { + + private Map limits = new HashMap<>(); + + public Settings(Limits addon) { + ConfigurationSection el = addon.getConfig().getConfigurationSection("entitylimits"); + if (el != null) { + for (String key : el.getKeys(false)) { + EntityType type = getType(key); + if (type != null) { + limits.put(type, el.getInt(key, 0)); + } + } + } + } + + private EntityType getType(String key) { + return Arrays.stream(EntityType.values()).filter(v -> v.name().equalsIgnoreCase(key)).findFirst().orElse(null); + } + + /** + * @return the limits + */ + public Map getLimits() { + return limits; + } + +} diff --git a/src/main/java/bentobox/addon/limits/listeners/EntityLimitsListener.java b/src/main/java/bentobox/addon/limits/listeners/EntityLimitsListener.java new file mode 100644 index 0000000..de79589 --- /dev/null +++ b/src/main/java/bentobox/addon/limits/listeners/EntityLimitsListener.java @@ -0,0 +1,263 @@ +package bentobox.addon.limits.listeners; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.event.vehicle.VehicleCreateEvent; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; + +import bentobox.addon.limits.Limits; +import bentobox.addon.limits.objects.EntityLimitsDO; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.Database; +import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; + +public class EntityLimitsListener implements Listener { + private final Limits addon; + + private Database handler; + + /** + * Handles entity and natural limitations + * @param addon - Limits object + */ + public EntityLimitsListener(Limits addon) { + this.addon = addon; + handler = new Database<>(addon, EntityLimitsDO.class); + } + + /** + * Add meta data to entities in the chunk + * @param e - event + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onChunkLoad(ChunkLoadEvent e) { + // Return if not in a known world + if (!addon.getPlugin().getIWM().inWorld(e.getWorld())) { + return; + } + String uniqueId = e.getWorld().getName() + "." + e.getChunk().getX() + "." + e.getChunk().getZ(); + if (handler.objectExists(uniqueId)) { + EntityLimitsDO eld = handler.loadObject(uniqueId); + if (eld != null) { + for (Entity entity : e.getChunk().getEntities()) { + if (eld.getSpawnLoc().containsKey(entity.getUniqueId())) { + entity.setMetadata("spawnLoc", new FixedMetadataValue(addon.getPlugin(), eld.getSpawnLoc().get(entity.getUniqueId()))); + } + } + } + // Delete chunk + handler.deleteObject(eld); + } + } + + /** + * Save meta data on entities in the chunk + * @param e - event + */ + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onChunkUnload(ChunkUnloadEvent e) { + // Return if not in a known world + if (!addon.getPlugin().getIWM().inWorld(e.getWorld())) { + return; + } + // Save chunk entities spawn loc meta data + EntityLimitsDO eld = new EntityLimitsDO(e.getWorld().getName() + "." + e.getChunk().getX() + "." + e.getChunk().getZ()); + Map spawnLoc = new HashMap<>(); + Arrays.stream(e.getChunk().getEntities()).filter(x -> x.hasMetadata("spawnLoc")).forEach(entity -> { + // Get the meta data + entity.getMetadata("spawnLoc").stream().filter(y -> y.getOwningPlugin().equals(addon)).forEach(v -> { + spawnLoc.put(entity.getUniqueId(), v.asString()); + }); + }); + if (!spawnLoc.isEmpty()) { + eld.setSpawnLoc(spawnLoc); + handler.saveObject(eld); + } + } + + /** + * Save all the entity meta data for world + * @param world - world being saved + */ + public void disable(World world) { + HashMap chunkMeta = new HashMap<>(); + world.getEntities().stream().filter(x -> x.hasMetadata("spawnLoc")).forEach(entity -> { + // Get the meta data + entity.getMetadata("spawnLoc").stream().filter(y -> y.getOwningPlugin().equals(addon.getPlugin())).forEach(v -> { + String uniqueId = entity.getWorld().getName() + "." + entity.getLocation().getChunk().getX() + "." + entity.getLocation().getChunk().getZ(); + chunkMeta.putIfAbsent(uniqueId, new EntityLimitsDO(uniqueId)); + chunkMeta.get(uniqueId).getSpawnLoc().put(entity.getUniqueId(), v.asString()); + }); + }); + // Save all the chunks + chunkMeta.values().forEach(handler::saveObject); + } + + /** + * Handles minecart placing + * @param e - event + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onMinecart(VehicleCreateEvent e) { + // Return if not in a known world + if (!addon.getPlugin().getIWM().inWorld(e.getVehicle().getWorld())) { + return; + } + if (addon.getSettings().getLimits().containsKey(e.getVehicle().getType())) { + // If someone in that area has the bypass permission, allow the spawning + for (Entity entity : e.getVehicle().getLocation().getWorld().getNearbyEntities(e.getVehicle().getLocation(), 5, 5, 5)) { + if (entity instanceof Player) { + Player player = (Player)entity; + Boolean bypass = false; + if (player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getVehicle().getWorld()) + "mod.bypass")) { + bypass = true; + } + // Check island + addon.getIslands().getProtectedIslandAt(e.getVehicle().getLocation()).ifPresent(island -> { + // Ignore spawn + if (island.isSpawn()) { + return; + } + // Check if the player is at the limit + if (atLimit(island, bypass, e.getVehicle())) { + e.setCancelled(true); + for (Entity ent : e.getVehicle().getLocation().getWorld().getNearbyEntities(e.getVehicle().getLocation(), 5, 5, 5)) { + if (ent instanceof Player) { + User.getInstance(ent).sendMessage("entityLimitReached", "[entity]", + Util.prettifyText(e.getVehicle().getType().toString()) + ,"[number]", String.valueOf(addon.getSettings().getLimits().get(e.getVehicle().getType()))); + } + } + } + }); + } + } + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onCreatureSpawn(final CreatureSpawnEvent e) { + // Return if not in a known world + if (!addon.getPlugin().getIWM().inWorld(e.getLocation())) { + return; + } + if (!addon.getSettings().getLimits().containsKey(e.getEntityType())) { + // Unknown entity limit or unlimited + return; + } + boolean bypass = false; + // Check why it was spawned + switch (e.getSpawnReason()) { + // These reasons are due to a player being involved (usually) so there may be a bypass + case BREEDING: + case BUILD_IRONGOLEM: + case BUILD_SNOWMAN: + case BUILD_WITHER: + case CURED: + case EGG: + case SPAWNER_EGG: + // If someone in that area has the bypass permission, allow the spawning + for (Entity entity : e.getLocation().getWorld().getNearbyEntities(e.getLocation(), 5, 5, 5)) { + if (entity instanceof Player) { + Player player = (Player)entity; + if (player.isOp() || player.hasPermission(addon.getPlugin().getIWM().getPermissionPrefix(e.getEntity().getWorld()) + "mod.bypass")) { + //plugin.getLogger().info("DEBUG: bypass"); + bypass = true; + break; + } + } + } + break; + default: + // Other natural reasons + break; + } + // Tag the entity with the island spawn location + tag(e, bypass); + + } + + private void tag(CreatureSpawnEvent e, boolean bypass) { + addon.getIslands().getIslandAt(e.getLocation()).ifPresent(island -> { + // Check if creature is allowed to spawn or not + if (!island.isSpawn() && atLimit(island, bypass, e.getEntity())) { + // Not allowed + e.setCancelled(true); + // If the reason is anything but because of a spawner then tell players within range + if (!e.getSpawnReason().equals(SpawnReason.SPAWNER)) { + for (Entity ent : e.getLocation().getWorld().getNearbyEntities(e.getLocation(), 5, 5, 5)) { + if (ent instanceof Player) { + User.getInstance(ent).sendMessage("entityLimitReached", "[entity]", + Util.prettifyText(e.getEntityType().toString()), + "[number]", String.valueOf(addon.getSettings().getLimits().get(e.getEntityType()))); + } + } + } + + } + }); + + } + + /** + * Checks if new entities can be added to island + * @param island + * @param bypass - true if this is being done by a player with authorization to bypass limits + * @param ent - the entity + * @return true if at the limit, false if not + */ + private boolean atLimit(Island island, boolean bypass, Entity ent) { + int count = 0; + checkLimits: + if (bypass || addon.getSettings().getLimits().get(ent.getType()) > 0) { + // If bypass, just tag the creature. If not, then we need to count creatures + if (!bypass) { + // Run through all the current entities on this world + for (Entity entity: ent.getWorld().getEntities()) { + // If it is the right one + if (entity.getType().equals(ent.getType())) { + // Check spawn location + if (entity.hasMetadata("spawnLoc")) { + // Get the meta data + List values = entity.getMetadata("spawnLoc"); + for (MetadataValue v : values) { + // There is a chance another plugin also uses the meta data spawnLoc + if (v.getOwningPlugin().equals(addon.getPlugin())) { + if (island.getUniqueId().equals(v.asString())) { + // Entity is on this island + count++; + if (count >= addon.getSettings().getLimits().get(ent.getType())) { + // No more allowed! + break checkLimits; + } + } + } + } + } + } + } + } + // Okay to spawn, but tag it + ent.setMetadata("spawnLoc", new FixedMetadataValue(addon.getPlugin(), island.getUniqueId())); + return false; + } + // Cancel - no spawning - tell nearby players + return true; + } +} diff --git a/src/main/java/bentobox/addon/limits/objects/EntityLimitsDO.java b/src/main/java/bentobox/addon/limits/objects/EntityLimitsDO.java new file mode 100644 index 0000000..030998d --- /dev/null +++ b/src/main/java/bentobox/addon/limits/objects/EntityLimitsDO.java @@ -0,0 +1,97 @@ +/** + * + */ +package bentobox.addon.limits.objects; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import com.google.gson.annotations.Expose; + +import world.bentobox.bentobox.database.objects.DataObject; + +/** + * @author tastybento + * + */ +public class EntityLimitsDO implements DataObject { + + @Expose + private String uniqueId = ""; + @Expose + private Map spawnLoc = new HashMap<>(); + + public EntityLimitsDO(String uniqueId) { + this.uniqueId = uniqueId; + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.database.objects.DataObject#getUniqueId() + */ + @Override + public String getUniqueId() { + return uniqueId; + } + + /* (non-Javadoc) + * @see world.bentobox.bentobox.database.objects.DataObject#setUniqueId(java.lang.String) + */ + @Override + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + + } + + /** + * @return the spawnLoc + */ + public Map getSpawnLoc() { + return spawnLoc; + } + + /** + * @param spawnLoc the spawnLoc to set + */ + public void setSpawnLoc(Map spawnLoc) { + this.spawnLoc = spawnLoc; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((uniqueId == null) ? 0 : uniqueId.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof EntityLimitsDO)) { + return false; + } + EntityLimitsDO other = (EntityLimitsDO) obj; + if (uniqueId == null) { + if (other.uniqueId != null) { + return false; + } + } else if (!uniqueId.equals(other.uniqueId)) { + return false; + } + return true; + } + + +} diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml new file mode 100755 index 0000000..2fb60af --- /dev/null +++ b/src/main/resources/addon.yml @@ -0,0 +1,8 @@ +name: BentoBox-Limits +main: bentobox.addon.limits.Limits +version: ${version} + +authors: tastybento + +softdepend: AcidIsland, BSkyBlock + diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..597cee1 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,67 @@ +# General entity limiting +# Use this section to limit how many entities can be added to an island. +# 0 means the item will be blocked from placement completely. +# Uncomment to set the limit. The numbers are just suggested values. +# The limit is per-world, so a hopper limit of 30 means up to 30 in the overworld and +# up to 30 in the nether. +entitylimits: + # Mobs, animals and other living entities + #BAT: 10 + #BLAZE: 10 + #BOAT: 10 + #CAVE_SPIDER: 10 + #CHICKEN: 10 + #COW: 10 + #CREEPER: 10 + #DONKEY: 10 + #ENDERMAN: 10 + #HORSE: 10 + #HUSK: 10 + #IRON_GOLEM: 10 + #LLAMA: 10 + #MAGMA_CUBE: 10 + #MULE: 10 + #MUSHROOM_COW: 10 + #OCELOT: 10 + #PIG: 10 + #PIG_ZOMBIE: 10 + #POLAR_BEAR: 10 + #RABBIT: 10 + #SHEEP: 10 + #SKELETON: 10 + #SKELETON_HORSE: 10 + #SLIME: 10 + #SNOWMAN: 10 + #SPIDER: 10 + #SQUID: 10 + #STRAY: 10 + #WITCH: 10 + #WITHER: 10 + #WITHER_SKELETON: 10 + #WOLF: 10 + #ZOMBIE: 10 + #ZOMBIE_HORSE: 10 + #ZOMBIE_VILLAGER: 10 + # These are the ONLY blocks that can be limited (because they are entities). + #BANNER: 20 + #ITEM_FRAME: 30 + #FURNACE: 10 + #CHEST: 50 + #TRAPPED_CHEST: 50 + #ENDER_CHEST: 1 + #JUKEBOX: 5 + #DISPENSER: 5 + #DROPPER: 5 + #SIGN: 10 + #MOB_SPAWNER: 10 + #NOTE_BLOCK: 5 + #ENCHANTMENT_TABLE: 5 + #BEACON: 12 + #SKULL: 50 + #DAYLIGHT_DETECTOR: 10 + HOPPER: 30 + #REDSTONE_COMPARATOR: 30 + #FLOWER_POT: 20 + #PAINTING: 5 + #ARMOR_STAND: 5 + #BREWING_STAND: 20 \ No newline at end of file diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml new file mode 100755 index 0000000..3810b4e --- /dev/null +++ b/src/main/resources/locales/en-US.yml @@ -0,0 +1,32 @@ +########################################################################################### +# This is a YML file. Be careful when editing. Check your edits in a YAML checker like # +# the one at http://yaml-online-parser.appspot.com # +########################################################################################### + +warps: + deactivate: "&cOld warp sign deactivated!" + success: "&ASuccess!" + sign-removed: "&CWarp sign removed!" + title: "Warp Signs" + player-warped: "&2[name] warped to your warp sign!" + previous: "&6Previous page" + next: "&6Next page" + warpToPlayersSign: "&6Warping to [player]'s sign" + # The [text] is replaced with the welcome line text from config.yml + warpTip: "&6Place a warp sign with [text] on the top" + error: + does-not-exist: "&cOh snap! That warp no longer exists!" + no-remove: "&CYou cannot remove that sign!" + not-enough-level: "&CYour island level is not high enough!" + no-permission: "&CYou do not have permission to do that!" + not-on-island: "&CYou must be on your island to do that!" + duplicate: "&CDuplicate sign placed" + no-warps-yet: "&CThere are no warps available yet" + your-level-is: "&cYou island level is only [level] and must be higher than [required]" + help: + description: "open the warps panel" +warp: + help: + parameters: "" + description: "warp to the player's warp sign" + \ No newline at end of file