From 2664ae4bd609261fcb448c871ecbaca3d0dc8f2e Mon Sep 17 00:00:00 2001 From: nossr50 Date: Thu, 31 Dec 2020 15:25:21 -0800 Subject: [PATCH] Optimize ChunkUnloadEvent & Partial rewrite to COTW entity tracking + some tweaks to COTW entity removal --- Changelog.txt | 4 + .../nossr50/listeners/ChunkListener.java | 20 +- .../nossr50/listeners/EntityListener.java | 10 +- src/main/java/com/gmail/nossr50/mcMMO.java | 7 + .../skills/fishing/FishingManager.java | 27 +-- .../nossr50/skills/taming/TamingManager.java | 86 +------ .../skills/taming/TrackedTamingEntity.java | 43 +--- .../com/gmail/nossr50/util/ItemUtils.java | 27 +++ .../nossr50/util/TransientEntityTracker.java | 225 ++++++++++++++++++ .../resources/locale/locale_en_US.properties | 1 + 10 files changed, 294 insertions(+), 156 deletions(-) create mode 100644 src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java diff --git a/Changelog.txt b/Changelog.txt index d45f0f9b3..1be88f3c3 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,8 +1,12 @@ Version 2.1.165 Fixed a bug where Enchanted Books dropped by mcMMO (in Fishing) did not function correctly The mcMMO system which tracks player placed blocks has had some major rewrites (thanks t00thpick1) + Optimized our ChunkUnloadEvent, this should improve timings in this area + How mcMMO tracks COTW entities has been rewritten + When COTW summons are killed players are now informed (from anything other than the time expiring). mcMMO will now be compatible with changes to world height (1.17 compatibility) Added missing cooldown locale message 'Commands.Database.Cooldown' + Added new locale message 'Taming.Summon.COTW.Removed' NOTES: Books dropped before this fix will not be usable and should just be chucked in lava, the broken books have blue names, the working books have yellow names. diff --git a/src/main/java/com/gmail/nossr50/listeners/ChunkListener.java b/src/main/java/com/gmail/nossr50/listeners/ChunkListener.java index 406a02436..761516deb 100644 --- a/src/main/java/com/gmail/nossr50/listeners/ChunkListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/ChunkListener.java @@ -1,30 +1,20 @@ package com.gmail.nossr50.listeners; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.util.compat.layers.persistentdata.MobMetaFlagType; -import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.world.ChunkUnloadEvent; +import java.util.List; + public class ChunkListener implements Listener { @EventHandler(ignoreCancelled = true) public void onChunkUnload(ChunkUnloadEvent event) { - for(Entity entity : event.getChunk().getEntities()) { - if(entity instanceof LivingEntity) { - LivingEntity livingEntity = (LivingEntity) entity; - if(mcMMO.getCompatibilityManager().getPersistentDataLayer().hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, livingEntity)) { - - //Remove from existence - if(livingEntity.isValid()) { - mcMMO.getCompatibilityManager().getPersistentDataLayer().removeMobFlags(livingEntity); - livingEntity.setHealth(0); - livingEntity.remove(); - } - } - } + List matchingEntities = mcMMO.getTransientEntityTracker().getAllTransientEntitiesInChunk(event.getChunk()); + for(LivingEntity livingEntity : matchingEntities) { + mcMMO.getTransientEntityTracker().removeSummon(livingEntity, null, false); } } } diff --git a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java index 73f9b064b..24dd48031 100644 --- a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java @@ -644,11 +644,13 @@ public class EntityListener implements Listener { */ @EventHandler(ignoreCancelled = true) public void onEntityDeath(EntityDeathEvent event) { - /* WORLD BLACKLIST CHECK */ - if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) - return; - LivingEntity entity = event.getEntity(); + mcMMO.getTransientEntityTracker().removeSummon(entity, null, false); + + /* WORLD BLACKLIST CHECK */ + if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) { + return; + } if (ExperienceConfig.getInstance().isNPCInteractionPrevented() && Misc.isNPCEntityExcludingVillagers(entity)) { return; diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index 529f70fb5..06347a952 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -87,6 +87,7 @@ public class mcMMO extends JavaPlugin { private static TransientMetadataTools transientMetadataTools; private static ChatManager chatManager; private static CommandManager commandManager; //ACF + private static TransientEntityTracker transientEntityTracker; /* Adventure */ private static BukkitAudiences audiences; @@ -289,6 +290,8 @@ public class mcMMO extends JavaPlugin { chatManager = new ChatManager(this); commandManager = new CommandManager(this); + + transientEntityTracker = new TransientEntityTracker(); } public static PlayerLevelUtils getPlayerLevelUtils() { @@ -720,4 +723,8 @@ public class mcMMO extends JavaPlugin { public CommandManager getCommandManager() { return commandManager; } + + public static TransientEntityTracker getTransientEntityTracker() { + return transientEntityTracker; + } } diff --git a/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java b/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java index 90e909525..8360825c9 100644 --- a/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java +++ b/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java @@ -37,8 +37,6 @@ import org.bukkit.entity.*; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.EnchantmentStorageMeta; -import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.util.BoundingBox; import org.bukkit.util.Vector; @@ -397,7 +395,7 @@ public class FishingManager extends SkillManager { if (treasure != null) { if(treasure instanceof FishingTreasureBook) { - treasureDrop = createEnchantBook((FishingTreasureBook) treasure); + treasureDrop = ItemUtils.createEnchantBook((FishingTreasureBook) treasure); } else { treasureDrop = treasure.getDrop().clone(); // Not cloning is bad, m'kay? @@ -461,29 +459,6 @@ public class FishingManager extends SkillManager { applyXpGain(fishXp + treasureXp, XPGainReason.PVE); } - - private @NotNull ItemStack createEnchantBook(@NotNull FishingTreasureBook fishingTreasureBook) { - ItemStack itemStack = fishingTreasureBook.getDrop().clone(); - EnchantmentWrapper enchantmentWrapper = getRandomEnchantment(fishingTreasureBook.getLegalEnchantments()); - ItemMeta itemMeta = itemStack.getItemMeta(); - - if(itemMeta == null) { - return itemStack; - } - - EnchantmentStorageMeta enchantmentStorageMeta = (EnchantmentStorageMeta) itemMeta; - enchantmentStorageMeta.addStoredEnchant(enchantmentWrapper.getEnchantment(), enchantmentWrapper.getEnchantmentLevel(), ExperienceConfig.getInstance().allowUnsafeEnchantments()); - itemStack.setItemMeta(enchantmentStorageMeta); - return itemStack; - } - - private @NotNull EnchantmentWrapper getRandomEnchantment(@NotNull List enchantmentWrappers) { - Collections.shuffle(enchantmentWrappers, Misc.getRandom()); - - int randomIndex = Misc.getRandom().nextInt(enchantmentWrappers.size()); - return enchantmentWrappers.get(randomIndex); - } - /** * Handle the vanilla XP boost for Fishing * diff --git a/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java b/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java index 3fce43f26..d3160f50c 100644 --- a/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java +++ b/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java @@ -33,9 +33,7 @@ import org.bukkit.entity.*; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; public class TamingManager extends SkillManager { //TODO: Temporary static cache, will be changed in 2.2 @@ -43,8 +41,6 @@ public class TamingManager extends SkillManager { private static HashMap cotwSummonDataProperties; private long lastSummonTimeStamp; - private HashMap> playerSummonedEntities; - public TamingManager(McMMOPlayer mcMMOPlayer) { super(mcMMOPlayer, PrimarySkillType.TAMING); init(); @@ -56,20 +52,12 @@ public class TamingManager extends SkillManager { lastSummonTimeStamp = 0L; //Init per-player tracking of summoned entities - initPerPlayerSummonTracking(); + mcMMO.getTransientEntityTracker().initPlayer(mmoPlayer.getPlayer().getUniqueId()); //Hacky stuff used as a band-aid initStaticCaches(); } - private void initPerPlayerSummonTracking() { - playerSummonedEntities = new HashMap<>(); - - for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) { - playerSummonedEntities.put(callOfTheWildType, new ArrayList<>()); - } - } - private void initStaticCaches() { //TODO: Temporary static cache, will be changed in 2.2 //This is shared between instances of TamingManager @@ -500,62 +488,16 @@ public class TamingManager extends SkillManager { * @param itemStack target ItemStack * @return true if it is used for any COTW */ - public boolean isCOTWItem(ItemStack itemStack) { + public boolean isCOTWItem(@NotNull ItemStack itemStack) { return summoningItems.containsKey(itemStack.getType()); } - //TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update - private int getAmountCurrentlySummoned(CallOfTheWildType callOfTheWildType) { - //The tracker is unreliable so validate its contents first - recalibrateTracker(); - - return playerSummonedEntities.get(callOfTheWildType).size(); + private int getAmountCurrentlySummoned(@NotNull CallOfTheWildType callOfTheWildType) { + return mcMMO.getTransientEntityTracker().getAmountCurrentlySummoned(getPlayer().getUniqueId(), callOfTheWildType); } - //TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update - private void addToTracker(LivingEntity livingEntity, CallOfTheWildType callOfTheWildType) { - TrackedTamingEntity trackedEntity = new TrackedTamingEntity(livingEntity, callOfTheWildType, this); - - playerSummonedEntities.get(callOfTheWildType).add(trackedEntity); - } - - //TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update - public List getTrackedEntities(CallOfTheWildType callOfTheWildType) { - return playerSummonedEntities.get(callOfTheWildType); - } - - //TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update - public void removeFromTracker(TrackedTamingEntity trackedEntity) { - playerSummonedEntities.get(trackedEntity.getCallOfTheWildType()).remove(trackedEntity); - - NotificationManager.sendPlayerInformationChatOnly(getPlayer(), "Taming.Summon.COTW.TimeExpired", StringUtils.getPrettyEntityTypeString(trackedEntity.getLivingEntity().getType())); - } - - /** - * Builds a new tracked list by determining which tracked things are still valid - */ - //TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update - private void recalibrateTracker() { - for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) { - ArrayList validEntities = getValidTrackedEntities(callOfTheWildType); - playerSummonedEntities.put(callOfTheWildType, validEntities); //Replace the old list with the new list - } - } - - //TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update - private ArrayList getValidTrackedEntities(CallOfTheWildType callOfTheWildType) { - ArrayList validTrackedEntities = new ArrayList<>(); - - for(TrackedTamingEntity trackedTamingEntity : getTrackedEntities(callOfTheWildType)) { - LivingEntity livingEntity = trackedTamingEntity.getLivingEntity(); - - //Remove from existence - if(livingEntity != null && livingEntity.isValid()) { - validTrackedEntities.add(trackedTamingEntity); - } - } - - return validTrackedEntities; + private void addToTracker(@NotNull LivingEntity livingEntity, @NotNull CallOfTheWildType callOfTheWildType) { + mcMMO.getTransientEntityTracker().registerEntity(getPlayer().getUniqueId(), new TrackedTamingEntity(livingEntity, callOfTheWildType, getPlayer())); } /** @@ -564,20 +506,6 @@ public class TamingManager extends SkillManager { */ //TODO: The way this tracker was written is garbo, I should just rewrite it, I'll save that for a future update public void cleanupAllSummons() { - for(List trackedTamingEntities : playerSummonedEntities.values()) { - for(TrackedTamingEntity trackedTamingEntity : trackedTamingEntities) { - LivingEntity livingEntity = trackedTamingEntity.getLivingEntity(); - - //Remove from existence - if(livingEntity != null && livingEntity.isValid()) { - mcMMO.getCompatibilityManager().getPersistentDataLayer().removeMobFlags(livingEntity); - livingEntity.setHealth(0); - livingEntity.remove(); - } - } - - //Clear the list - trackedTamingEntities.clear(); - } + mcMMO.getTransientEntityTracker().cleanupPlayer(getPlayer()); } } diff --git a/src/main/java/com/gmail/nossr50/skills/taming/TrackedTamingEntity.java b/src/main/java/com/gmail/nossr50/skills/taming/TrackedTamingEntity.java index aabcd44da..9116c4fdf 100644 --- a/src/main/java/com/gmail/nossr50/skills/taming/TrackedTamingEntity.java +++ b/src/main/java/com/gmail/nossr50/skills/taming/TrackedTamingEntity.java @@ -4,61 +4,40 @@ import com.gmail.nossr50.config.Config; import com.gmail.nossr50.datatypes.skills.subskills.taming.CallOfTheWildType; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.Misc; -import com.gmail.nossr50.util.skills.ParticleEffectUtils; -import org.bukkit.Location; -import org.bukkit.Sound; import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; - -import java.util.UUID; +import org.jetbrains.annotations.NotNull; public class TrackedTamingEntity extends BukkitRunnable { - private final LivingEntity livingEntity; - private final CallOfTheWildType callOfTheWildType; - private final UUID id; - private int length; - private final TamingManager tamingManagerRef; + private final @NotNull LivingEntity livingEntity; + private final @NotNull CallOfTheWildType callOfTheWildType; + private final @NotNull Player player; - protected TrackedTamingEntity(LivingEntity livingEntity, CallOfTheWildType callOfTheWildType, TamingManager tamingManagerRef) { - this.tamingManagerRef = tamingManagerRef; + protected TrackedTamingEntity(@NotNull LivingEntity livingEntity, @NotNull CallOfTheWildType callOfTheWildType, @NotNull Player player) { + this.player = player; this.callOfTheWildType = callOfTheWildType; this.livingEntity = livingEntity; - this.id = livingEntity.getUniqueId(); int tamingCOTWLength = Config.getInstance().getTamingCOTWLength(callOfTheWildType.getConfigEntityTypeEntry()); if (tamingCOTWLength > 0) { - this.length = tamingCOTWLength * Misc.TICK_CONVERSION_FACTOR; + int length = tamingCOTWLength * Misc.TICK_CONVERSION_FACTOR; this.runTaskLater(mcMMO.p, length); } } @Override public void run() { - if (livingEntity.isValid()) { - Location location = livingEntity.getLocation(); - location.getWorld().playSound(location, Sound.BLOCK_FIRE_EXTINGUISH, 0.8F, 0.8F); - ParticleEffectUtils.playCallOfTheWildEffect(livingEntity); - - if(tamingManagerRef != null) - tamingManagerRef.removeFromTracker(this); - - livingEntity.setHealth(0); - livingEntity.remove(); - } - + mcMMO.getTransientEntityTracker().removeSummon(this.getLivingEntity(), player, true); this.cancel(); } - public CallOfTheWildType getCallOfTheWildType() { + public @NotNull CallOfTheWildType getCallOfTheWildType() { return callOfTheWildType; } - public LivingEntity getLivingEntity() { + public @NotNull LivingEntity getLivingEntity() { return livingEntity; } - - public UUID getID() { - return id; - } } diff --git a/src/main/java/com/gmail/nossr50/util/ItemUtils.java b/src/main/java/com/gmail/nossr50/util/ItemUtils.java index 4e70625e6..62c8d2165 100644 --- a/src/main/java/com/gmail/nossr50/util/ItemUtils.java +++ b/src/main/java/com/gmail/nossr50/util/ItemUtils.java @@ -2,7 +2,10 @@ package com.gmail.nossr50.util; import com.gmail.nossr50.config.AdvancedConfig; import com.gmail.nossr50.config.Config; +import com.gmail.nossr50.config.experience.ExperienceConfig; import com.gmail.nossr50.config.party.ItemWeightConfig; +import com.gmail.nossr50.datatypes.treasure.EnchantmentWrapper; +import com.gmail.nossr50.datatypes.treasure.FishingTreasureBook; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; import org.bukkit.ChatColor; @@ -12,9 +15,11 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.FurnaceRecipe; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; +import java.util.Collections; import java.util.List; public final class ItemUtils { @@ -538,4 +543,26 @@ public final class ItemUtils { public static boolean canBeSuperAbilityDigBoosted(@NotNull ItemStack itemStack) { return isShovel(itemStack) || isPickaxe(itemStack); } + + public static @NotNull ItemStack createEnchantBook(@NotNull FishingTreasureBook fishingTreasureBook) { + ItemStack itemStack = fishingTreasureBook.getDrop().clone(); + EnchantmentWrapper enchantmentWrapper = getRandomEnchantment(fishingTreasureBook.getLegalEnchantments()); + ItemMeta itemMeta = itemStack.getItemMeta(); + + if(itemMeta == null) { + return itemStack; + } + + EnchantmentStorageMeta enchantmentStorageMeta = (EnchantmentStorageMeta) itemMeta; + enchantmentStorageMeta.addStoredEnchant(enchantmentWrapper.getEnchantment(), enchantmentWrapper.getEnchantmentLevel(), ExperienceConfig.getInstance().allowUnsafeEnchantments()); + itemStack.setItemMeta(enchantmentStorageMeta); + return itemStack; + } + + public static @NotNull EnchantmentWrapper getRandomEnchantment(@NotNull List enchantmentWrappers) { + Collections.shuffle(enchantmentWrappers, Misc.getRandom()); + + int randomIndex = Misc.getRandom().nextInt(enchantmentWrappers.size()); + return enchantmentWrappers.get(randomIndex); + } } diff --git a/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java b/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java new file mode 100644 index 000000000..a5495864e --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/TransientEntityTracker.java @@ -0,0 +1,225 @@ +package com.gmail.nossr50.util; + +import com.gmail.nossr50.datatypes.skills.subskills.taming.CallOfTheWildType; +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.skills.taming.TrackedTamingEntity; +import com.gmail.nossr50.util.player.NotificationManager; +import com.gmail.nossr50.util.skills.ParticleEffectUtils; +import com.gmail.nossr50.util.text.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class TransientEntityTracker { + private final @NotNull HashMap>> perPlayerTransientEntityMap; + private final @NotNull HashSet chunkLookupCache; + + public TransientEntityTracker() { + perPlayerTransientEntityMap = new HashMap<>(); + chunkLookupCache = new HashSet<>(); + } + + public void initPlayer(@NotNull UUID playerUUID) { + if (!isPlayerRegistered(playerUUID)) { + registerPlayer(playerUUID); + } + } + + public void cleanupPlayer(@NotNull UUID playerUUID) { + cleanupAllSummons(null, playerUUID); + } + + public void cleanupPlayer(@NotNull Player player) { + //First remove all entities related to this player + cleanupAllSummons(player, player.getUniqueId()); + } + + private boolean isPlayerRegistered(@NotNull UUID playerUUID) { + return perPlayerTransientEntityMap.get(playerUUID) != null; + } + + private void registerPlayer(@NotNull UUID playerUUID) { + for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) { + perPlayerTransientEntityMap.get(playerUUID).put(callOfTheWildType, new HashSet<>()); + } + } + + /** + * Get the tracked transient entities map for a specific player + * + * @param playerUUID the target uuid of the player + * @return the tracked entities map for the player, null if the player isn't registered + */ + public @Nullable HashMap> getPlayerTrackedEntityMap(@NotNull UUID playerUUID) { + return perPlayerTransientEntityMap.get(playerUUID); + } + + public void registerEntity(@NotNull UUID playerUUID, @NotNull TrackedTamingEntity trackedTamingEntity) { + if(!isPlayerRegistered(playerUUID)) { + mcMMO.p.getLogger().severe("Attempting to register entity to a player which hasn't been initialized!"); + initPlayer(playerUUID); + } + + //Add to map entry + getTrackedEntities(playerUUID, trackedTamingEntity.getCallOfTheWildType()).add(trackedTamingEntity); + + //Add to cache for chunk lookups + addToChunkLookupCache(trackedTamingEntity); + } + + /** + * Get the tracked taming entities for a player + * If the player isn't registered this will return null + * + * @param playerUUID the target uuid of the player + * @param callOfTheWildType target type + * @return the set of tracked entities for the player, null if the player isn't registered, the set can be empty + */ + private @Nullable HashSet getTrackedEntities(@NotNull UUID playerUUID, @NotNull CallOfTheWildType callOfTheWildType) { + HashMap> playerEntityMap = getPlayerTrackedEntityMap(playerUUID); + + if(playerEntityMap == null) + return null; + + return playerEntityMap.get(callOfTheWildType); + } + + private void addToChunkLookupCache(@NotNull TrackedTamingEntity trackedTamingEntity) { + chunkLookupCache.add(trackedTamingEntity.getLivingEntity()); + } + + public void unregisterEntity(@NotNull LivingEntity livingEntity) { + chunkLookupCacheCleanup(livingEntity); + perPlayerTransientMapCleanup(livingEntity); + } + + private void chunkLookupCacheCleanup(@NotNull LivingEntity livingEntity) { + chunkLookupCache.remove(livingEntity); + } + + private void perPlayerTransientMapCleanup(@NotNull LivingEntity livingEntity) { + for(UUID uuid : perPlayerTransientEntityMap.keySet()) { + for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) { + + HashSet trackedEntities = getTrackedEntities(uuid, callOfTheWildType); + + if(trackedEntities == null) + continue; + + Iterator iterator = trackedEntities.iterator(); + while (iterator.hasNext()) { + if(iterator.next().getLivingEntity().equals(livingEntity)) { + iterator.remove(); + return; + } + } + } + } + } + + public @NotNull List getAllTransientEntitiesInChunk(@NotNull Chunk chunk) { + ArrayList matchingEntities = new ArrayList<>(); + + for(LivingEntity livingEntity : chunkLookupCache) { + if(livingEntity.getLocation().getChunk().equals(chunk)) { + matchingEntities.add(livingEntity); + } + } + + return matchingEntities; + } + + /* + * Gross code below + */ + + /** + * Get the amount of a summon currently active for a player + * @param playerUUID target player + * @param callOfTheWildType summon type + * @return the amount of summons currently active for player of target type + */ + public int getAmountCurrentlySummoned(@NotNull UUID playerUUID, @NotNull CallOfTheWildType callOfTheWildType) { + HashSet trackedEntities = getTrackedEntities(playerUUID, callOfTheWildType); + + if(trackedEntities == null) + return 0; + + return trackedEntities.size(); + } + + /** + * Kills a summon and removes its metadata + * Then it removes it from the tracker / chunk lookup cache + * + * @param livingEntity entity to remove + * @param player associated player + */ + public void removeSummon(@NotNull LivingEntity livingEntity, @Nullable Player player, boolean timeExpired) { + //Kill the summon & remove it + if(livingEntity.isValid()) { + livingEntity.setHealth(0); //Should trigger entity death events + livingEntity.remove(); + + Location location = livingEntity.getLocation(); + + if (location.getWorld() != null) { + location.getWorld().playSound(location, Sound.BLOCK_FIRE_EXTINGUISH, 0.8F, 0.8F); + ParticleEffectUtils.playCallOfTheWildEffect(livingEntity); + } + + //Inform player of summon death + if(player != null && player.isOnline()) { + if(timeExpired) { + NotificationManager.sendPlayerInformationChatOnly(player, "Taming.Summon.COTW.TimeExpired", StringUtils.getPrettyEntityTypeString(livingEntity.getType())); + } else { + NotificationManager.sendPlayerInformationChatOnly(player, "Taming.Summon.COTW.Removed", StringUtils.getPrettyEntityTypeString(livingEntity.getType())); + } + } + } + + //Remove our metadata + mcMMO.getCompatibilityManager().getPersistentDataLayer().removeMobFlags(livingEntity); + + //Clean from trackers + unregisterEntity(livingEntity); + } + + /** + * Remove all tracked entities from existence if they currently exist + * Clear the tracked entity lists afterwards + * + * @deprecated use {@link #cleanupAllSummons(Player, UUID)} instead + */ + @Deprecated + private void cleanupAllSummons(@NotNull UUID playerUUID) { + cleanupAllSummons(Bukkit.getPlayer(playerUUID), playerUUID); + } + + /** + * Kills and cleans up all data related to all summoned entities for a player + * + * @param player used to send messages, can be null + * @param playerUUID used to grab associated data, cannot be null + */ + private void cleanupAllSummons(@Nullable Player player, @NotNull UUID playerUUID) { + for(CallOfTheWildType callOfTheWildType : CallOfTheWildType.values()) { + HashSet trackedEntities = getTrackedEntities(playerUUID, callOfTheWildType); + + if(trackedEntities == null) + continue; + + for(TrackedTamingEntity trackedTamingEntity : trackedEntities) { + //Remove from existence + removeSummon(trackedTamingEntity.getLivingEntity(), player, false); + } + } + } +} diff --git a/src/main/resources/locale/locale_en_US.properties b/src/main/resources/locale/locale_en_US.properties index 939132271..1020210c1 100644 --- a/src/main/resources/locale/locale_en_US.properties +++ b/src/main/resources/locale/locale_en_US.properties @@ -487,6 +487,7 @@ Taming.Summon.COTW.Success.WithoutLifespan=&a(Call Of The Wild) &7You have summo Taming.Summon.COTW.Success.WithLifespan=&a(Call Of The Wild) &7You have summoned a &6{0}&7 and it has a duration of &6{1}&7 seconds. Taming.Summon.COTW.Limit=&a(Call Of The Wild) &7You can only have &c{0} &7summoned &7{1} pets at the same time. Taming.Summon.COTW.TimeExpired=&a(Call Of The Wild) &7Time is up, your &6{0}&7 departs. +Taming.Summon.COTW.Removed=&a(Call Of The Wild) &7Your summoned &6{0}&7 has vanished from this world. Taming.Summon.COTW.BreedingDisallowed=&a(Call Of The Wild) &cYou cannot breed a summoned animal. Taming.Summon.COTW.NeedMoreItems=&a(Call Of The Wild) &7You need &e{0}&7 more &3{1}&7(s) Taming.Summon.Name.Format=&6(COTW) &f{0}'s {1}