Optimize ChunkUnloadEvent & Partial rewrite to COTW entity tracking + some tweaks to COTW entity removal

This commit is contained in:
nossr50 2020-12-31 15:25:21 -08:00
parent aed4cb87be
commit 2664ae4bd6
10 changed files with 294 additions and 156 deletions

View File

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

View File

@ -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<LivingEntity> matchingEntities = mcMMO.getTransientEntityTracker().getAllTransientEntitiesInChunk(event.getChunk());
for(LivingEntity livingEntity : matchingEntities) {
mcMMO.getTransientEntityTracker().removeSummon(livingEntity, null, false);
}
}
}

View File

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

View File

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

View File

@ -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<EnchantmentWrapper> enchantmentWrappers) {
Collections.shuffle(enchantmentWrappers, Misc.getRandom());
int randomIndex = Misc.getRandom().nextInt(enchantmentWrappers.size());
return enchantmentWrappers.get(randomIndex);
}
/**
* Handle the vanilla XP boost for Fishing
*

View File

@ -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<CallOfTheWildType, TamingSummon> cotwSummonDataProperties;
private long lastSummonTimeStamp;
private HashMap<CallOfTheWildType, List<TrackedTamingEntity>> 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<TrackedTamingEntity> 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<TrackedTamingEntity> 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<TrackedTamingEntity> getValidTrackedEntities(CallOfTheWildType callOfTheWildType) {
ArrayList<TrackedTamingEntity> 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<TrackedTamingEntity> 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());
}
}

View File

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

View File

@ -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<EnchantmentWrapper> enchantmentWrappers) {
Collections.shuffle(enchantmentWrappers, Misc.getRandom());
int randomIndex = Misc.getRandom().nextInt(enchantmentWrappers.size());
return enchantmentWrappers.get(randomIndex);
}
}

View File

@ -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<UUID, HashMap<CallOfTheWildType, HashSet<TrackedTamingEntity>>> perPlayerTransientEntityMap;
private final @NotNull HashSet<LivingEntity> 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<CallOfTheWildType, HashSet<TrackedTamingEntity>> 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<TrackedTamingEntity> getTrackedEntities(@NotNull UUID playerUUID, @NotNull CallOfTheWildType callOfTheWildType) {
HashMap<CallOfTheWildType, HashSet<TrackedTamingEntity>> 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<TrackedTamingEntity> trackedEntities = getTrackedEntities(uuid, callOfTheWildType);
if(trackedEntities == null)
continue;
Iterator<TrackedTamingEntity> iterator = trackedEntities.iterator();
while (iterator.hasNext()) {
if(iterator.next().getLivingEntity().equals(livingEntity)) {
iterator.remove();
return;
}
}
}
}
}
public @NotNull List<LivingEntity> getAllTransientEntitiesInChunk(@NotNull Chunk chunk) {
ArrayList<LivingEntity> 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<TrackedTamingEntity> 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<TrackedTamingEntity> trackedEntities = getTrackedEntities(playerUUID, callOfTheWildType);
if(trackedEntities == null)
continue;
for(TrackedTamingEntity trackedTamingEntity : trackedEntities) {
//Remove from existence
removeSummon(trackedTamingEntity.getLivingEntity(), player, false);
}
}
}
}

View File

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