diff --git a/config.yml b/config.yml index fb0d0e663..1cbaff04a 100644 --- a/config.yml +++ b/config.yml @@ -294,12 +294,12 @@ island: team-join-reset: true customranks: {} protection: - # Allow pistons to push outside of the protected area (maybe to make bridges) - allow-piston-push: true - # Restrict Wither and other flying mobs. - # Any flying mobs that exit the island space where they were spawned will be removed. - # Includes blaze and ghast. - restrict-flying-mobs: true + # Geo restrict mobs. + # Mobs that exit the island space where they were spawned will be removed. + geo-limit-settings: + - GHAST + - BAT + - BLAZE # Invincible visitors. List of damages that will not affect visitors. # Make list blank if visitors should receive all damages invincible-visitors: @@ -328,13 +328,6 @@ protection: - CRAMMING - VOID togglePvPCooldown: 0 -allowEndermanGriefing: false -endermanDeathDrop: false -allowTNTDamage: false -allowChestDamage: false -allowCreeperDamage: false -allowCreeperGriefing: false -allowMobDamageToItemFrames: false panel: close-on-click-outside: true uniqueId: config diff --git a/locales/en-US.yml b/locales/en-US.yml index e1c63bd5c..357077c18 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -442,6 +442,12 @@ protection: description: "Toggle use" name: "Gates" hint: "No gate use" + GEO_LIMIT_MOBS: + description: | + &eRemove mobs that go + &eoutside protected + &eisland space + name: "Limit mobs to island" HURT_ANIMALS: description: "Toggle hurting" name: "Hurt animals" diff --git a/src/main/java/us/tastybento/bskyblock/BSkyBlock.java b/src/main/java/us/tastybento/bskyblock/BSkyBlock.java index e33cc086d..efa163b1e 100755 --- a/src/main/java/us/tastybento/bskyblock/BSkyBlock.java +++ b/src/main/java/us/tastybento/bskyblock/BSkyBlock.java @@ -1,11 +1,13 @@ package us.tastybento.bskyblock; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import us.tastybento.bskyblock.api.configuration.BSBConfig; import us.tastybento.bskyblock.api.configuration.WorldSettings; +import us.tastybento.bskyblock.api.events.BSBReadyEvent; import us.tastybento.bskyblock.api.placeholders.PlaceholderHandler; import us.tastybento.bskyblock.api.user.Notifier; import us.tastybento.bskyblock.commands.AdminCommand; @@ -148,6 +150,8 @@ public class BSkyBlock extends JavaPlugin { instance.log("- Tastybento and Poslovitch, 2017-2018"); instance.log("#############################################"); + // Fire plugin ready event + Bukkit.getServer().getPluginManager().callEvent(new BSBReadyEvent()); }); }); } diff --git a/src/main/java/us/tastybento/bskyblock/Settings.java b/src/main/java/us/tastybento/bskyblock/Settings.java index cb7677398..62d66ade4 100644 --- a/src/main/java/us/tastybento/bskyblock/Settings.java +++ b/src/main/java/us/tastybento/bskyblock/Settings.java @@ -404,26 +404,12 @@ public class Settings implements DataObject, WorldSettings { // --------------------------------------------- /* PROTECTION */ - @ConfigComment("Allow pistons to push outside of the protected area (maybe to make bridges)") - @ConfigEntry(path = "protection.allow-piston-push") - private boolean allowPistonPush = false; - - @ConfigComment("Restrict Wither and other flying mobs.") - @ConfigComment("Any flying mobs that exit the island space where they were spawned will be removed.") - @ConfigComment("Includes blaze and ghast. ") - @ConfigEntry(path = "protection.restrict-flying-mobs") - private boolean restrictFlyingMobs = true; - private int togglePvPCooldown; - //TODO transform these options below into flags - private boolean allowEndermanGriefing; - private boolean endermanDeathDrop; - private boolean allowTNTDamage; - private boolean allowChestDamage; - private boolean allowCreeperDamage; - private boolean allowCreeperGriefing; - private boolean allowMobDamageToItemFrames; + @ConfigComment("Geo restrict mobs.") + @ConfigComment("Mobs that exit the island space where they were spawned will be removed.") + @ConfigEntry(path = "protection.geo-limit-settings") + private List geoLimitSettings = new ArrayList<>(); // Invincible visitor settings @ConfigComment("Invincible visitors. List of damages that will not affect visitors.") @@ -737,54 +723,12 @@ public class Settings implements DataObject, WorldSettings { public String getWorldName() { return worldName; } - /** - * @return the allowChestDamage - */ - public boolean isAllowChestDamage() { - return allowChestDamage; - } - /** - * @return the allowCreeperDamage - */ - public boolean isAllowCreeperDamage() { - return allowCreeperDamage; - } - /** - * @return the allowCreeperGriefing - */ - public boolean isAllowCreeperGriefing() { - return allowCreeperGriefing; - } - /** - * @return the allowEndermanGriefing - */ - public boolean isAllowEndermanGriefing() { - return allowEndermanGriefing; - } - /** - * @return the allowMobDamageToItemFrames - */ - public boolean isAllowMobDamageToItemFrames() { - return allowMobDamageToItemFrames; - } /** * @return the allowObsidianScooping */ public boolean isAllowObsidianScooping() { return allowObsidianScooping; } - /** - * @return the allowPistonPush - */ - public boolean isAllowPistonPush() { - return allowPistonPush; - } - /** - * @return the allowTNTDamage - */ - public boolean isAllowTNTDamage() { - return allowTNTDamage; - } /** * @return the checkUpdates */ @@ -797,12 +741,6 @@ public class Settings implements DataObject, WorldSettings { public boolean isDeathsSumTeam() { return deathsSumTeam; } - /** - * @return the endermanDeathDrop - */ - public boolean isEndermanDeathDrop() { - return endermanDeathDrop; - } /** * @return the endGenerate */ @@ -898,12 +836,6 @@ public class Settings implements DataObject, WorldSettings { public boolean isRespawnOnIsland() { return respawnOnIsland; } - /** - * @return the restrictFlyingMobs - */ - public boolean isRestrictFlyingMobs() { - return restrictFlyingMobs; - } /** * @return the useEconomy */ @@ -917,54 +849,12 @@ public class Settings implements DataObject, WorldSettings { public boolean isUseOwnGenerator() { return useOwnGenerator; } - /** - * @param allowChestDamage the allowChestDamage to set - */ - public void setAllowChestDamage(boolean allowChestDamage) { - this.allowChestDamage = allowChestDamage; - } - /** - * @param allowCreeperDamage the allowCreeperDamage to set - */ - public void setAllowCreeperDamage(boolean allowCreeperDamage) { - this.allowCreeperDamage = allowCreeperDamage; - } - /** - * @param allowCreeperGriefing the allowCreeperGriefing to set - */ - public void setAllowCreeperGriefing(boolean allowCreeperGriefing) { - this.allowCreeperGriefing = allowCreeperGriefing; - } - /** - * @param allowEndermanGriefing the allowEndermanGriefing to set - */ - public void setAllowEndermanGriefing(boolean allowEndermanGriefing) { - this.allowEndermanGriefing = allowEndermanGriefing; - } - /** - * @param allowMobDamageToItemFrames the allowMobDamageToItemFrames to set - */ - public void setAllowMobDamageToItemFrames(boolean allowMobDamageToItemFrames) { - this.allowMobDamageToItemFrames = allowMobDamageToItemFrames; - } /** * @param allowObsidianScooping the allowObsidianScooping to set */ public void setAllowObsidianScooping(boolean allowObsidianScooping) { this.allowObsidianScooping = allowObsidianScooping; } - /** - * @param allowPistonPush the allowPistonPush to set - */ - public void setAllowPistonPush(boolean allowPistonPush) { - this.allowPistonPush = allowPistonPush; - } - /** - * @param allowTNTDamage the allowTNTDamage to set - */ - public void setAllowTNTDamage(boolean allowTNTDamage) { - this.allowTNTDamage = allowTNTDamage; - } /** * @param checkUpdates the checkUpdates to set */ @@ -1037,12 +927,6 @@ public class Settings implements DataObject, WorldSettings { public void setDefaultLanguage(String defaultLanguage) { this.defaultLanguage = defaultLanguage; } - /** - * @param endermanDeathDrop the endermanDeathDrop to set - */ - public void setEndermanDeathDrop(boolean endermanDeathDrop) { - this.endermanDeathDrop = endermanDeathDrop; - } /** * @param endGenerate the endGenerate to set */ @@ -1253,12 +1137,6 @@ public class Settings implements DataObject, WorldSettings { public void setRespawnOnIsland(boolean respawnOnIsland) { this.respawnOnIsland = respawnOnIsland; } - /** - * @param restrictFlyingMobs the restrictFlyingMobs to set - */ - public void setRestrictFlyingMobs(boolean restrictFlyingMobs) { - this.restrictFlyingMobs = restrictFlyingMobs; - } /** * @param seaHeight the seaHeight to set */ @@ -1588,6 +1466,19 @@ public class Settings implements DataObject, WorldSettings { public boolean isWaterUnsafe() { return false; } + /** + * @return the geoLimitSettings + */ + @Override + public List getGeoLimitSettings() { + return geoLimitSettings; + } + /** + * @param geoLimitSettings the geoLimitSettings to set + */ + public void setGeoLimitSettings(List geoLimitSettings) { + this.geoLimitSettings = geoLimitSettings; + } } \ No newline at end of file diff --git a/src/main/java/us/tastybento/bskyblock/api/configuration/WorldSettings.java b/src/main/java/us/tastybento/bskyblock/api/configuration/WorldSettings.java index 52a996c61..dbae75cb3 100644 --- a/src/main/java/us/tastybento/bskyblock/api/configuration/WorldSettings.java +++ b/src/main/java/us/tastybento/bskyblock/api/configuration/WorldSettings.java @@ -230,4 +230,9 @@ public interface WorldSettings { * @return true if water is not safe in this world, e.g, should not be a home location */ boolean isWaterUnsafe(); + + /** + * @return list of entity types that should not exit the island limits + */ + List getGeoLimitSettings(); } diff --git a/src/main/java/us/tastybento/bskyblock/database/objects/Island.java b/src/main/java/us/tastybento/bskyblock/database/objects/Island.java index b4d2147dc..5365f97e6 100755 --- a/src/main/java/us/tastybento/bskyblock/database/objects/Island.java +++ b/src/main/java/us/tastybento/bskyblock/database/objects/Island.java @@ -327,6 +327,11 @@ public class Island implements DataObject { return x >= getMinX() && x < getMinX() + range*2 && z >= getMinZ() && z < getMinZ() + range*2; } + /** + * Checks if location is in full island space, not just protected space + * @param location - location + * @return true if in island space + */ public boolean inIslandSpace(Location location) { return Util.sameWorld(world, location.getWorld()) && inIslandSpace(location.getBlockX(), location.getBlockZ()); } diff --git a/src/main/java/us/tastybento/bskyblock/listeners/flags/GeoLimitClickListener.java b/src/main/java/us/tastybento/bskyblock/listeners/flags/GeoLimitClickListener.java new file mode 100644 index 000000000..550d1b70e --- /dev/null +++ b/src/main/java/us/tastybento/bskyblock/listeners/flags/GeoLimitClickListener.java @@ -0,0 +1,103 @@ +/** + * + */ +package us.tastybento.bskyblock.listeners.flags; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.EntityType; +import org.bukkit.event.inventory.ClickType; + +import us.tastybento.bskyblock.BSkyBlock; +import us.tastybento.bskyblock.api.panels.Panel; +import us.tastybento.bskyblock.api.panels.PanelItem; +import us.tastybento.bskyblock.api.panels.PanelItem.ClickHandler; +import us.tastybento.bskyblock.api.panels.builders.PanelBuilder; +import us.tastybento.bskyblock.api.panels.builders.PanelItemBuilder; +import us.tastybento.bskyblock.api.user.User; +import us.tastybento.bskyblock.managers.IslandWorldManager; +import us.tastybento.bskyblock.util.Util; + +/** + * Provide geo limiting to mobs - removed them if they go outside island bounds + * @author tastybento + * + */ +public class GeoLimitClickListener implements ClickHandler { + + /** + * A list of all living entity types, minus some + */ + private final List livingEntityTypes = Arrays.stream(EntityType.values()) + .filter(EntityType::isAlive) + .filter(t -> !(t.equals(EntityType.PLAYER) || t.equals(EntityType.GIANT) || t.equals(EntityType.ARMOR_STAND))) + .sorted(Comparator.comparing(EntityType::name)) + .collect(Collectors.toList()); + + @Override + public boolean onClick(Panel panel, User user, ClickType clickType, int slot) { + // Get the world + if (!user.inWorld()) { + user.sendMessage("general.errors.wrong-world"); + return true; + } + IslandWorldManager iwm = BSkyBlock.getInstance().getIWM(); + String reqPerm = iwm.getPermissionPrefix(Util.getWorld(user.getWorld())) + ".admin.settings.GEO_LIMIT_MOBS"; + if (!user.hasPermission(reqPerm)) { + user.sendMessage("general.errors.no-permission"); + user.sendMessage("general.errors.you-need", "[permission]", reqPerm); + user.getPlayer().playSound(user.getLocation(), Sound.BLOCK_METAL_HIT, 1F, 1F); + return true; + } + + String panelName = user.getTranslation("protection.flags.GEO_LIMIT_MOBS.name"); + if (panel.getName().equals(panelName)) { + // This is a click on the geo limit panel + // Slot relates to the enum + EntityType c = livingEntityTypes.get(slot); + if (iwm.getGeoLimitSettings(user.getWorld()).contains(c.name())) { + iwm.getGeoLimitSettings(user.getWorld()).remove(c.name()); + } else { + iwm.getGeoLimitSettings(user.getWorld()).add(c.name()); + } + // Apply change to panel + panel.getInventory().setItem(slot, getPanelItem(c, user).getItem()); + } else { + // Open the Sub Settings panel + openPanel(user, panelName); + } + return true; + } + + private void openPanel(User user, String panelName) { + // Close the current panel + user.closeInventory(); + // Open a new panel + PanelBuilder pb = new PanelBuilder(); + pb.user(user).name(panelName); + // Make panel items + livingEntityTypes.forEach(c -> pb.item(getPanelItem(c, user))); + pb.build(); + + } + + private PanelItem getPanelItem(EntityType c, User user) { + PanelItemBuilder pib = new PanelItemBuilder(); + pib.name(Util.prettifyText(c.toString())); + pib.clickHandler(this); + if (BSkyBlock.getInstance().getIWM().getGeoLimitSettings(user.getWorld()).contains(c.name())) { + pib.icon(Material.GREEN_SHULKER_BOX); + pib.description(user.getTranslation("protection.panel.flag-item.setting-active")); + } else { + pib.icon(Material.RED_SHULKER_BOX); + pib.description(user.getTranslation("protection.panel.flag-item.setting-disabled")); + } + return pib.build(); + } + +} diff --git a/src/main/java/us/tastybento/bskyblock/listeners/flags/GeoLimitMobsListener.java b/src/main/java/us/tastybento/bskyblock/listeners/flags/GeoLimitMobsListener.java new file mode 100644 index 000000000..fa9ce21dc --- /dev/null +++ b/src/main/java/us/tastybento/bskyblock/listeners/flags/GeoLimitMobsListener.java @@ -0,0 +1,85 @@ +/** + * + */ +package us.tastybento.bskyblock.listeners.flags; + +import java.util.Map; +import java.util.WeakHashMap; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.ExplosionPrimeEvent; +import org.bukkit.projectiles.ProjectileSource; + +import us.tastybento.bskyblock.api.events.BSBReadyEvent; +import us.tastybento.bskyblock.api.flags.AbstractFlagListener; +import us.tastybento.bskyblock.database.objects.Island; + +/** + * Provide geo limiting to mobs - removed them if they go outside island bounds + * @author tastybento + * + */ +public class GeoLimitMobsListener extends AbstractFlagListener { + + private Map mobSpawnTracker = new WeakHashMap<>(); + + /** + * Start the tracker when the plugin is loaded + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void pluginReady(BSBReadyEvent event) { + // Kick off the task to remove entities that go outside island boundaries + Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> { + mobSpawnTracker.entrySet().stream() + .filter(e -> !e.getValue().onIsland(e.getKey().getLocation())) + .map(Map.Entry::getKey) + .forEach(Entity::remove); + mobSpawnTracker.keySet().removeIf(e -> e == null || e.isDead()); + }, 20L, 20L); + } + + /** + * Track where the mob was created. This will determine its allowable movement zone. + * @param e - event + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void mobSpawn(CreatureSpawnEvent e) { + if (getIWM().inWorld(e.getLocation()) + && getIWM().getGeoLimitSettings(e.getLocation().getWorld()).contains(e.getEntityType().name())) { + getIslands().getIslandAt(e.getLocation()).ifPresent(i -> mobSpawnTracker.put(e.getEntity(), i)); + } + } + + /** + * Clean up the map when entity dies (does not handle entity removal) + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void MobDeath(final EntityDeathEvent e) { + mobSpawnTracker.remove(e.getEntity()); + } + + /** + * Deal with projectiles fired by entities + * @param e + */ + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void ProjectileExplode(final ExplosionPrimeEvent e) { + if (e.getEntity() instanceof Projectile && getIWM().inWorld(e.getEntity().getLocation())) { + ProjectileSource source = ((Projectile)e.getEntity()).getShooter(); + if (source instanceof Entity) { + Entity shooter = (Entity)source; + if (mobSpawnTracker.containsKey(shooter) + && !mobSpawnTracker.get(shooter).onIsland(e.getEntity().getLocation())) { + e.getEntity().remove(); + e.setCancelled(true); + } + } + } + } +} diff --git a/src/main/java/us/tastybento/bskyblock/lists/Flags.java b/src/main/java/us/tastybento/bskyblock/lists/Flags.java index 5ebcaad8b..4960900a7 100644 --- a/src/main/java/us/tastybento/bskyblock/lists/Flags.java +++ b/src/main/java/us/tastybento/bskyblock/lists/Flags.java @@ -23,6 +23,7 @@ import us.tastybento.bskyblock.listeners.flags.EndermanListener; import us.tastybento.bskyblock.listeners.flags.EnterExitListener; import us.tastybento.bskyblock.listeners.flags.EntityInteractListener; import us.tastybento.bskyblock.listeners.flags.FireListener; +import us.tastybento.bskyblock.listeners.flags.GeoLimitClickListener; import us.tastybento.bskyblock.listeners.flags.GeoLimitMobsListener; import us.tastybento.bskyblock.listeners.flags.HurtingListener; import us.tastybento.bskyblock.listeners.flags.InventoryListener; @@ -191,9 +192,8 @@ public class Flags { public static final Flag INVINCIBLE_VISITORS = new FlagBuilder().id("INVINCIBLE_VISITORS").icon(Material.DIAMOND_CHESTPLATE).type(Type.WORLD_SETTING) .listener(ilv).onClick(ilv).subPanel(true).build(); - private static GeoLimitMobsListener glm = new GeoLimitMobsListener(); - static final Flag GEO_LIMIT_MOBS = new FlagBuilder().id("GEO_LIMIT_MOBS").icon(Material.CHAINMAIL_CHESTPLATE).type(Type.WORLD_SETTING) - .listener(glm).onClick(glm).subPanel(true).build(); + public static final Flag GEO_LIMIT_MOBS = new FlagBuilder().id("GEO_LIMIT_MOBS").icon(Material.CHAINMAIL_CHESTPLATE).type(Type.WORLD_SETTING) + .listener(new GeoLimitMobsListener()).onClick(new GeoLimitClickListener()).subPanel(true).build(); public static final Flag REMOVE_MOBS = new FlagBuilder().id("REMOVE_MOBS").icon(Material.GLOWSTONE_DUST).type(Type.WORLD_SETTING) .listener(new RemoveMobsListener()).allowedByDefault(true).build(); diff --git a/src/main/java/us/tastybento/bskyblock/managers/IslandWorldManager.java b/src/main/java/us/tastybento/bskyblock/managers/IslandWorldManager.java index 19293d781..6e61c5d10 100644 --- a/src/main/java/us/tastybento/bskyblock/managers/IslandWorldManager.java +++ b/src/main/java/us/tastybento/bskyblock/managers/IslandWorldManager.java @@ -573,7 +573,7 @@ public class IslandWorldManager { * * @param world * - world - * @return invisible visitor settings + * @return invincible visitor settings */ public List getIvSettings(World world) { return worldSettings.get(Util.getWorld(world)).getIvSettings(); @@ -725,4 +725,13 @@ public class IslandWorldManager { public boolean isWaterNotSafe(World world) { return worldSettings.get(Util.getWorld(world)).isWaterUnsafe(); } + + /** + * Get a list of entity types that should not exit the island limits + * @param world - world + * @return list + */ + public List getGeoLimitSettings(World world) { + return worldSettings.get(Util.getWorld(world)).getGeoLimitSettings(); + } }