diff --git a/.idea/LevelledMobs.iml b/.idea/LevelledMobs.iml deleted file mode 100644 index fa63d4bf5..000000000 --- a/.idea/LevelledMobs.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - SPIGOT - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2131ede7e..0e49f92aa 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ me.lokka30 LevelledMobs - 3.3.2 b596 + 3.3.3 b604 jar LevelledMobs diff --git a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SummonSubcommand.java b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SummonSubcommand.java index 27e8b5520..8a40820ad 100644 --- a/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SummonSubcommand.java +++ b/src/main/java/me/lokka30/levelledmobs/commands/subcommands/SummonSubcommand.java @@ -196,6 +196,11 @@ private void parseSubcommand2(final String @NotNull [] args, final boolean overr location = (target.getLocation()); world = location.getWorld(); } + else { + location = target.getLocation(); + world = target.getWorld(); + } + if (offline || world == null) { showMessage("common.player-offline", "%player%", args[5]); diff --git a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropProcessingInfo.java b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropProcessingInfo.java index 8fe95a348..80a0243fe 100644 --- a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropProcessingInfo.java +++ b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropProcessingInfo.java @@ -9,11 +9,11 @@ import me.lokka30.levelledmobs.rules.CustomDropsRuleSet; import me.lokka30.microlib.messaging.MessageUtils; import org.bukkit.entity.Player; -import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -33,6 +33,7 @@ class CustomDropProcessingInfo { this.groupIDsDroppedAlready = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); this.allDropInstances = new LinkedList<>(); this.playerLevelVariableCache = new TreeMap<>(); + this.stackToItem = new HashMap<>(); } public LivingEntityWrapper lmEntity; @@ -58,6 +59,7 @@ class CustomDropProcessingInfo { @NotNull final List allDropInstances; private StringBuilder debugMessages; + public final Map stackToItem; void addDebugMessage(final String message){ if (this.debugMessages == null) diff --git a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropResult.java b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropResult.java index 7eb423306..e6e610e7b 100644 --- a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropResult.java +++ b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropResult.java @@ -4,6 +4,11 @@ package me.lokka30.levelledmobs.customdrops; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + /** * Used internally to determine if the mob's * vanilla items should be removed or not @@ -11,7 +16,11 @@ * @author stumper66 * @since 2.6.0 */ -public enum CustomDropResult { - HAS_OVERRIDE, - NO_OVERRIDE +public class CustomDropResult { + public CustomDropResult(final @NotNull Map stackToItem, final boolean hasOverride){ + this.stackToItem = stackToItem; + this.hasOverride = hasOverride; + } + public final boolean hasOverride; + public final Map stackToItem; } diff --git a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsHandler.java b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsHandler.java index 47e66dd1d..e1753a546 100644 --- a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsHandler.java +++ b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsHandler.java @@ -41,6 +41,7 @@ import java.util.Map; import java.util.Objects; import java.util.TreeMap; +import java.util.WeakHashMap; import java.util.concurrent.ThreadLocalRandom; /** @@ -51,16 +52,6 @@ * @since 2.4.0 */ public class CustomDropsHandler { - private final LevelledMobs main; - - final Map customDropsitems; - final Map customDropsitems_Babies; - final Map customDropsitems_groups; - final Map customDropIDs; - @Nullable Map customItemGroups; - public final CustomDropsParser customDropsParser; - private final YmlParsingHelper ymlHelper; - public CustomDropsHandler(final LevelledMobs main) { this.main = main; this.customDropsitems = new TreeMap<>(); @@ -69,8 +60,19 @@ public CustomDropsHandler(final LevelledMobs main) { this.customDropIDs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); customDropsParser = new CustomDropsParser(main, this); this.ymlHelper = customDropsParser.ymlHelper; + this.customEquippedItems = new WeakHashMap<>(); } + private final LevelledMobs main; + final Map customDropsitems; + final Map customDropsitems_Babies; + final Map customDropsitems_groups; + final Map customDropIDs; + @Nullable Map customItemGroups; + public final CustomDropsParser customDropsParser; + private final YmlParsingHelper ymlHelper; + private final WeakHashMap customEquippedItems; + public CustomDropResult getCustomItemDrops(final LivingEntityWrapper lmEntity, final List drops, final boolean equippedOnly) { final CustomDropProcessingInfo processingInfo = new CustomDropProcessingInfo(); processingInfo.lmEntity = lmEntity; @@ -135,8 +137,7 @@ public CustomDropResult getCustomItemDrops(final LivingEntityWrapper lmEntity, f lmEntity.getTypeName(), lmEntity.getMobLevel(), processingInfo.mobKiller == null ? "(null)" : processingInfo.mobKiller.getName())); processingInfo.writeAnyDebugMessages(); } - return processingInfo.hasOverride ? - CustomDropResult.HAS_OVERRIDE : CustomDropResult.NO_OVERRIDE; + return new CustomDropResult(processingInfo.stackToItem, processingInfo.hasOverride); } getCustomItemsFromDropInstance(processingInfo); // payload @@ -161,8 +162,7 @@ public CustomDropResult getCustomItemDrops(final LivingEntityWrapper lmEntity, f processingInfo.writeAnyDebugMessages(); } - return processingInfo.hasOverride ? - CustomDropResult.HAS_OVERRIDE : CustomDropResult.NO_OVERRIDE; + return new CustomDropResult(processingInfo.stackToItem, processingInfo.hasOverride); } private DropInstanceBuildResult buildDropsListFromGroupsAndEntity(final List groups, final EntityType entityType, @NotNull final CustomDropProcessingInfo info){ @@ -530,6 +530,7 @@ else if (dropBase instanceof CustomCommand) { newItem = main.mobHeadManager.getMobHeadFromPlayerHead(newItem, info.lmEntity, dropItem); info.newDrops.add(newItem); + info.stackToItem.put(newItem, dropItem); } private boolean shouldDenyDeathCause(final @NotNull CustomDropBase dropBase, final @NotNull CustomDropProcessingInfo info){ @@ -594,41 +595,52 @@ private boolean checkIfMadeEquippedDropChance(final CustomDropProcessingInfo inf if (item.equippedSpawnChance >= 1.0F || !item.onlyDropIfEquipped) return true; if (item.equippedSpawnChance <= 0.0F) return false; - return isMobWearingItem(item.getItemStack(), info.lmEntity.getLivingEntity()); + + return isMobWearingItem(item.getItemStack(), info.lmEntity.getLivingEntity(), item); } - private boolean isMobWearingItem(final ItemStack item, final @NotNull LivingEntity mob){ + private boolean isMobWearingItem(final ItemStack item, final @NotNull LivingEntity mob, final CustomDropItem customDropItem){ final EntityEquipment equipment = mob.getEquipment(); if (equipment == null) return false; + final EquippedItemsInfo equippedItemsInfo = this.customEquippedItems.get(mob); + if (equippedItemsInfo == null) return false; + switch (item.getType()){ + case LEATHER_HELMET: + case CHAINMAIL_HELMET: + case IRON_HELMET: + case DIAMOND_HELMET: + case NETHERITE_HELMET: + if (equippedItemsInfo.helmet != null && customDropItem == equippedItemsInfo.helmet) + return true; case LEATHER_CHESTPLATE: case CHAINMAIL_CHESTPLATE: case IRON_CHESTPLATE: case DIAMOND_CHESTPLATE: case NETHERITE_CHESTPLATE: - return item.isSimilar(equipment.getChestplate()); + if (equippedItemsInfo.chestplate != null && customDropItem == equippedItemsInfo.chestplate) + return true; case LEATHER_LEGGINGS: case CHAINMAIL_LEGGINGS: case IRON_LEGGINGS: case DIAMOND_LEGGINGS: case NETHERITE_LEGGINGS: - return item.isSimilar(equipment.getLeggings()); + if (equippedItemsInfo.leggings != null && customDropItem == equippedItemsInfo.leggings) + return true; case LEATHER_BOOTS: case CHAINMAIL_BOOTS: case IRON_BOOTS: case DIAMOND_BOOTS: case NETHERITE_BOOTS: - return item.isSimilar(equipment.getBoots()); + if (equippedItemsInfo.boots != null && customDropItem == equippedItemsInfo.boots) + return true; } - if (item.isSimilar(equipment.getItemInMainHand())) + if (equippedItemsInfo.mainHand != null && customDropItem == equippedItemsInfo.mainHand) return true; - if (item.isSimilar(equipment.getItemInOffHand())) - return true; - - return item.isSimilar(equipment.getHelmet()); + return equippedItemsInfo.offhand != null && customDropItem == equippedItemsInfo.offhand; } private boolean madePlayerLevelRequirement(final @NotNull CustomDropProcessingInfo info, final CustomDropBase dropBase){ @@ -759,6 +771,10 @@ private ItemStack getCookedVariantOfMeat(@NotNull final ItemStack itemStack){ } } + public void addEntityEquippedItems(final @NotNull LivingEntity livingEntity, final @NotNull EquippedItemsInfo equippedItemsInfo){ + this.customEquippedItems.put(livingEntity, equippedItemsInfo); + } + private boolean isCustomDropsDebuggingEnabled() { return main.companion.debugsEnabled.contains(DebugType.CUSTOM_DROPS); } diff --git a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsParser.java b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsParser.java index 4f658b39f..693740cd7 100644 --- a/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsParser.java +++ b/src/main/java/me/lokka30/levelledmobs/customdrops/CustomDropsParser.java @@ -25,7 +25,6 @@ import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; -import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.bukkit.inventory.meta.ItemMeta; diff --git a/src/main/java/me/lokka30/levelledmobs/customdrops/EquippedItemsInfo.java b/src/main/java/me/lokka30/levelledmobs/customdrops/EquippedItemsInfo.java new file mode 100644 index 000000000..9a8e05034 --- /dev/null +++ b/src/main/java/me/lokka30/levelledmobs/customdrops/EquippedItemsInfo.java @@ -0,0 +1,10 @@ +package me.lokka30.levelledmobs.customdrops; + +public class EquippedItemsInfo { + public CustomDropItem helmet; + public CustomDropItem chestplate; + public CustomDropItem leggings; + public CustomDropItem boots; + public CustomDropItem mainHand; + public CustomDropItem offhand; +} diff --git a/src/main/java/me/lokka30/levelledmobs/listeners/EntityDamageListener.java b/src/main/java/me/lokka30/levelledmobs/listeners/EntityDamageListener.java index 4d8a150fe..c421b452a 100644 --- a/src/main/java/me/lokka30/levelledmobs/listeners/EntityDamageListener.java +++ b/src/main/java/me/lokka30/levelledmobs/listeners/EntityDamageListener.java @@ -11,6 +11,9 @@ import me.lokka30.levelledmobs.misc.QueueItem; import me.lokka30.levelledmobs.misc.Utils; import me.lokka30.levelledmobs.rules.NametagVisibilityEnum; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Ghast; import org.bukkit.entity.Guardian; import org.bukkit.entity.LivingEntity; @@ -116,6 +119,16 @@ public void onEntityDamageByEntityEvent(final @NotNull EntityDamageByEntityEvent } private void processRangedDamage(@NotNull final EntityDamageByEntityEvent event) { + if (event.getDamager().getType() == EntityType.AREA_EFFECT_CLOUD) { + // ender dragon breath + final AreaEffectCloud aec = (AreaEffectCloud) event.getDamager(); + if (!(aec.getSource() instanceof EnderDragon)) return; + final LivingEntityWrapper lmEntity = LivingEntityWrapper.getInstance((LivingEntity) aec.getSource(), main); + processRangedDamage2(lmEntity, event); + lmEntity.free(); + return; + } + if (!(event.getDamager() instanceof Projectile)) return; final Projectile projectile = (Projectile) event.getDamager(); @@ -150,12 +163,11 @@ private void processRangedDamage2(@NotNull final LivingEntityWrapper shooter, @N main._mobsQueueManager.addToQueue(new QueueItem(shooter, event)); } - Utils.debugLog(main, DebugType.RANGED_DAMAGE_MODIFICATION, "Range attack damage modified for &b" + shooter.getLivingEntity().getName() + "&7:"); - Utils.debugLog(main, DebugType.RANGED_DAMAGE_MODIFICATION, "Previous rangedDamage: &b" + event.getDamage()); - final double newDamage = event.getDamage() + main.mobDataManager.getAdditionsForLevel(shooter, Addition.CUSTOM_RANGED_ATTACK_DAMAGE, event.getDamage()); + Utils.debugLog(main, DebugType.RANGED_DAMAGE_MODIFICATION, String.format( + "&7Source: &b%s&7 (lvl &b%s&7), damage: &b%s&7, new damage: &b%s&7", + shooter.getNameIfBaby(), shooter.getMobLevel(), event.getDamage(), newDamage)); event.setDamage(newDamage); - Utils.debugLog(main, DebugType.RANGED_DAMAGE_MODIFICATION, "New rangedDamage: &b" + newDamage); } private void processOtherRangedDamage(@NotNull final EntityDamageByEntityEvent event) { diff --git a/src/main/java/me/lokka30/levelledmobs/listeners/EntityDeathListener.java b/src/main/java/me/lokka30/levelledmobs/listeners/EntityDeathListener.java index 2eb137a52..86719fb9c 100644 --- a/src/main/java/me/lokka30/levelledmobs/listeners/EntityDeathListener.java +++ b/src/main/java/me/lokka30/levelledmobs/listeners/EntityDeathListener.java @@ -70,7 +70,7 @@ public void onDeath(@NotNull final EntityDeathEvent event) { } else if (main.rulesManager.getRule_UseCustomDropsForMob(lmEntity).useDrops) { final List drops = new LinkedList<>(); final CustomDropResult result = main.customDropsHandler.getCustomItemDrops(lmEntity, drops, false); - if (result == CustomDropResult.HAS_OVERRIDE) + if (result.hasOverride) main.levelManager.removeVanillaDrops(lmEntity, event.getDrops()); event.getDrops().addAll(drops); diff --git a/src/main/java/me/lokka30/levelledmobs/listeners/EntitySpawnListener.java b/src/main/java/me/lokka30/levelledmobs/listeners/EntitySpawnListener.java index cfb41e864..1e8adc4e0 100644 --- a/src/main/java/me/lokka30/levelledmobs/listeners/EntitySpawnListener.java +++ b/src/main/java/me/lokka30/levelledmobs/listeners/EntitySpawnListener.java @@ -65,6 +65,7 @@ public void onEntitySpawn(@NotNull final EntitySpawnEvent event) { if (!(event.getEntity() instanceof LivingEntity)) return; final LivingEntityWrapper lmEntity = LivingEntityWrapper.getInstance((LivingEntity) event.getEntity(), main); + lmEntity.setSkylightLevelAtSpawn(); if (event instanceof CreatureSpawnEvent) { final CreatureSpawnEvent.SpawnReason spawnReason = ((CreatureSpawnEvent) event).getSpawnReason(); @@ -80,15 +81,14 @@ public void onEntitySpawn(@NotNull final EntitySpawnEvent event) { return; } } + else if (event instanceof SpawnerSpawnEvent) + lmEntity.setSpawnReason(LevelledMobSpawnReason.SPAWNER); if (!processMobSpawns) { lmEntity.free(); return; } - if (event instanceof CreatureSpawnEvent) - lmEntity.setSpawnReason(adaptVanillaSpawnReason(((CreatureSpawnEvent) event).getSpawnReason())); - if (main.configUtils.playerLevellingEnabled && lmEntity.getPlayerForLevelling() == null) updateMobForPlayerLevelling(lmEntity); diff --git a/src/main/java/me/lokka30/levelledmobs/managers/ExternalCompatibilityManager.java b/src/main/java/me/lokka30/levelledmobs/managers/ExternalCompatibilityManager.java index d5765c2b3..6b8ebae0e 100644 --- a/src/main/java/me/lokka30/levelledmobs/managers/ExternalCompatibilityManager.java +++ b/src/main/java/me/lokka30/levelledmobs/managers/ExternalCompatibilityManager.java @@ -9,6 +9,8 @@ import me.lokka30.levelledmobs.misc.LevellableState; import me.lokka30.levelledmobs.misc.LivingEntityWrapper; import me.lokka30.levelledmobs.misc.PlayerHomeCheckResult; +import me.lokka30.levelledmobs.misc.Utils; +import me.lokka30.levelledmobs.misc.VersionInfo; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.NamespacedKey; @@ -21,6 +23,7 @@ import org.jetbrains.annotations.NotNull; import simplepets.brainsynder.api.plugin.SimplePets; +import java.io.InvalidObjectException; import java.util.Collections; import java.util.List; import java.util.Map; @@ -32,6 +35,7 @@ * @since 2.4.0 */ public class ExternalCompatibilityManager { + private static Boolean useNewerEliteMobsKey = null; public enum ExternalCompatibility { NOT_APPLICABLE, @@ -62,7 +66,9 @@ public enum ExternalCompatibility { SIMPLE_PETS, - ELITE_BOSSES + ELITE_BOSSES, + + BLOOD_NIGHT } /* Store any external namespaced keys with null values by default */ @@ -126,6 +132,13 @@ private static boolean isMobOfEliteBosses(@NotNull final LivingEntityWrapper lmE return false; } + private static boolean isMobOfBloodNight(@NotNull final LivingEntityWrapper lmEntity){ + final Plugin plugin = Bukkit.getPluginManager().getPlugin("BloodNight"); + if (plugin == null) return false; + + return lmEntity.getPDC().has(new NamespacedKey(plugin, "mobtype"), PersistentDataType.STRING); + } + public static boolean isMythicMob(@NotNull final LivingEntityWrapper lmEntity) { final Plugin p = Bukkit.getPluginManager().getPlugin("MythicMobs"); if (p == null) return false; @@ -220,6 +233,10 @@ static LevellableState checkAllExternalCompats(final LivingEntityWrapper lmEntit result == LevellableState.ALLOWED) result = LevellableState.DENIED_CONFIGURATION_COMPATIBILITY_ELITE_BOSSES; + if (isMobOfBloodNight(lmEntity) && !isExternalCompatibilityEnabled(ExternalCompatibility.BLOOD_NIGHT, compatRules) && + result == LevellableState.ALLOWED) + result = LevellableState.DENIED_CONFIGURATION_COMPATIBILITY_BLOOD_NIGHT; + return result; } @@ -288,9 +305,28 @@ private static boolean isMobOfMythicMobs(final LivingEntityWrapper lmEntity) { private static boolean isMobOfEliteMobs(final LivingEntityWrapper lmEntity) { final Plugin p = Bukkit.getPluginManager().getPlugin("EliteMobs"); if (p != null){ + // 7.3.12 and newer uses a different namespaced key + if (useNewerEliteMobsKey == null){ + final int theDash = p.getDescription().getVersion().indexOf('-'); + final String version = theDash > 3 ? + p.getDescription().getVersion().substring(0, theDash) : p.getDescription().getVersion(); + try { + VersionInfo pluginVer = new VersionInfo(version); + VersionInfo cutoverVersion = new VersionInfo("7.3.12"); + useNewerEliteMobsKey = pluginVer.compareTo(cutoverVersion) >= 0; + } + catch (InvalidObjectException e){ + Utils.logger.warning("Got error comparing EliteMob versions: " + e.getMessage()); + // default to newer version on error + useNewerEliteMobsKey = true; + } + } + + final String checkKey = useNewerEliteMobsKey ? + "eliteentity" : "EliteMobsCullable"; final boolean isEliteMob; synchronized (lmEntity.getLivingEntity().getPersistentDataContainer()) { - isEliteMob = lmEntity.getPDC().has(new NamespacedKey(p, "EliteMobsCullable"), PersistentDataType.STRING); + isEliteMob = lmEntity.getPDC().has(new NamespacedKey(p, checkKey), PersistentDataType.STRING); } if (isEliteMob){ diff --git a/src/main/java/me/lokka30/levelledmobs/managers/LevelManager.java b/src/main/java/me/lokka30/levelledmobs/managers/LevelManager.java index 6531d4959..6c0435799 100644 --- a/src/main/java/me/lokka30/levelledmobs/managers/LevelManager.java +++ b/src/main/java/me/lokka30/levelledmobs/managers/LevelManager.java @@ -9,6 +9,7 @@ import me.lokka30.levelledmobs.LivingEntityInterface; import me.lokka30.levelledmobs.compatibility.Compat1_17; import me.lokka30.levelledmobs.customdrops.CustomDropResult; +import me.lokka30.levelledmobs.customdrops.EquippedItemsInfo; import me.lokka30.levelledmobs.events.MobPostLevelEvent; import me.lokka30.levelledmobs.events.MobPreLevelEvent; import me.lokka30.levelledmobs.events.SummonedMobPreLevelEvent; @@ -419,23 +420,29 @@ public void setLevelledItemDrops(final LivingEntityWrapper lmEntity, final @NotN // custom drops also get multiplied in the custom drops handler final CustomDropResult dropResult = main.customDropsHandler.getCustomItemDrops(lmEntity, customDrops, false); - if (dropResult == CustomDropResult.HAS_OVERRIDE) { + if (dropResult.hasOverride) { hasOverride = true; removeVanillaDrops(lmEntity, dropsToMultiply); } } int additionUsed = 0; - int dropsChecked = 0; if (!doNotMultiplyDrops && !dropsToMultiply.isEmpty()) { - // Get currentDrops added per level value - final int addition = BigDecimal.valueOf(main.mobDataManager.getAdditionsForLevel(lmEntity, Addition.CUSTOM_ITEM_DROP, 2.0)) - .setScale(0, RoundingMode.HALF_DOWN).intValueExact(); // truncate double to int + // Get currentDrops added per level valu + final double additionValue = main.mobDataManager.getAdditionsForLevel(lmEntity, Addition.CUSTOM_ITEM_DROP, 2.0); + if (additionValue == -1){ + Utils.debugLog(main, DebugType.SET_LEVELLED_ITEM_DROPS, String.format( + "&7Mob: &b%s&7, mob-lvl: &b%s&7, removing any drops present", + lmEntity.getNameIfBaby(), lmEntity.getMobLevel())); + currentDrops.clear(); + return; + } + + final int addition = BigDecimal.valueOf(additionValue).setScale(0, RoundingMode.HALF_DOWN).intValueExact(); // truncate double to int additionUsed = addition; // Modify current drops - dropsChecked = dropsToMultiply.size(); for (final ItemStack currentDrop : dropsToMultiply) multiplyDrop(lmEntity, currentDrop, addition, false); } @@ -524,7 +531,11 @@ public void removeVanillaDrops(@NotNull final LivingEntityWrapper lmEntity, fina //Calculates the XP dropped when a levellable creature dies. public int getLevelledExpDrops(@NotNull final LivingEntityWrapper lmEntity, final int xp) { if (lmEntity.isLevelled()) { - final int newXp = (int) Math.round(xp + (xp * main.mobDataManager.getAdditionsForLevel(lmEntity, Addition.CUSTOM_XP_DROP, 3.0))); + final double dropAddition = main.mobDataManager.getAdditionsForLevel(lmEntity, Addition.CUSTOM_XP_DROP, 3.0); + int newXp = 0; + if (dropAddition > -1) + newXp = (int) Math.round(xp + (xp * dropAddition)); + Utils.debugLog(main, DebugType.SET_LEVELLED_XP_DROPS, String.format("&7Mob: &b%s&7: lvl: &b%s&7, xp-vanilla: &b%s&7, new-xp: &b%s&7", lmEntity.getNameIfBaby(), lmEntity.getMobLevel(), xp, newXp)); return newXp; @@ -1018,7 +1029,7 @@ private void applyLevelledEquipment(@NotNull final LivingEntityWrapper lmEntity, if (!main.rulesManager.getRule_UseCustomDropsForMob(lmEntity).useDrops) return; final List items = new LinkedList<>(); - main.customDropsHandler.getCustomItemDrops(lmEntity, items, true); + final CustomDropResult dropResult = main.customDropsHandler.getCustomItemDrops(lmEntity, items, true); if (items.isEmpty()) return; final EntityEquipment equipment = lmEntity.getLivingEntity().getEquipment(); @@ -1026,34 +1037,42 @@ private void applyLevelledEquipment(@NotNull final LivingEntityWrapper lmEntity, boolean hadMainItem = false; boolean hadPlayerHead = false; + final EquippedItemsInfo equippedItemsInfo = new EquippedItemsInfo(); - - for (final ItemStack itemStack : items) { + for (final ItemStack itemStack : dropResult.stackToItem.keySet()) { final Material material = itemStack.getType(); if (EnchantmentTarget.ARMOR_FEET.includes(material)) { equipment.setBoots(itemStack, true); equipment.setBootsDropChance(0); + equippedItemsInfo.boots = dropResult.stackToItem.get(itemStack); } else if (EnchantmentTarget.ARMOR_LEGS.includes(material)) { equipment.setLeggings(itemStack, true); equipment.setLeggingsDropChance(0); + equippedItemsInfo.leggings = dropResult.stackToItem.get(itemStack); } else if (EnchantmentTarget.ARMOR_TORSO.includes(material)) { equipment.setChestplate(itemStack, true); equipment.setChestplateDropChance(0); + equippedItemsInfo.chestplate = dropResult.stackToItem.get(itemStack); } else if (EnchantmentTarget.ARMOR_HEAD.includes(material) || material.name().endsWith("_HEAD") && !hadPlayerHead) { equipment.setHelmet(itemStack, true); equipment.setHelmetDropChance(0); + equippedItemsInfo.helmet = dropResult.stackToItem.get(itemStack); if (material == Material.PLAYER_HEAD) hadPlayerHead = true; } else { if (!hadMainItem) { equipment.setItemInMainHand(itemStack); equipment.setItemInMainHandDropChance(0); + equippedItemsInfo.mainHand = dropResult.stackToItem.get(itemStack); hadMainItem = true; } else { equipment.setItemInOffHand(itemStack); equipment.setItemInOffHandDropChance(0); + equippedItemsInfo.offhand = dropResult.stackToItem.get(itemStack); } } } + + main.customDropsHandler.addEntityEquippedItems(lmEntity.getLivingEntity(), equippedItemsInfo); } private double getMobAttributeValue(@NotNull final LivingEntityWrapper lmEntity){ diff --git a/src/main/java/me/lokka30/levelledmobs/managers/MobDataManager.java b/src/main/java/me/lokka30/levelledmobs/managers/MobDataManager.java index c3a6e9eb7..bcc3d161e 100644 --- a/src/main/java/me/lokka30/levelledmobs/managers/MobDataManager.java +++ b/src/main/java/me/lokka30/levelledmobs/managers/MobDataManager.java @@ -122,9 +122,11 @@ public final double getAdditionsForLevel(final LivingEntityWrapper lmEntity, fin switch (addition){ case CUSTOM_XP_DROP: if (lmEntity.getFineTuningAttributes().xpDrop != null) attributeValue = lmEntity.getFineTuningAttributes().xpDrop; + if (attributeValue == -1.0) return -1; break; case CUSTOM_ITEM_DROP: if (lmEntity.getFineTuningAttributes().itemDrop != null) attributeValue = lmEntity.getFineTuningAttributes().itemDrop; + if (attributeValue == -1.0) return -1; break; case ATTRIBUTE_MAX_HEALTH: if (lmEntity.getFineTuningAttributes().maxHealth != null) attributeValue = lmEntity.getFineTuningAttributes().maxHealth; diff --git a/src/main/java/me/lokka30/levelledmobs/misc/DebugType.java b/src/main/java/me/lokka30/levelledmobs/misc/DebugType.java index 0ece015bc..c7c3e4cc2 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/DebugType.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/DebugType.java @@ -137,5 +137,7 @@ public enum DebugType { THREAD_LOCKS, - SCOREBOARD_TAGS + SCOREBOARD_TAGS, + + SKYLIGHT_LEVEL } diff --git a/src/main/java/me/lokka30/levelledmobs/misc/LevellableState.java b/src/main/java/me/lokka30/levelledmobs/misc/LevellableState.java index 213d90ed0..add726687 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/LevellableState.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/LevellableState.java @@ -87,6 +87,12 @@ public enum LevellableState { */ DENIED_CONFIGURATION_COMPATIBILITY_ELITE_BOSSES, + /** + * A rule has been configured to block + * Blood Night from being levelled + */ + DENIED_CONFIGURATION_COMPATIBILITY_BLOOD_NIGHT, + /** * A rule has been configured to block * nametagged mobs from being levelled. diff --git a/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapper.java b/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapper.java index 68e7cadc5..9533355a5 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapper.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/LivingEntityWrapper.java @@ -30,6 +30,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ConcurrentModificationException; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -83,6 +84,7 @@ public LivingEntityWrapper(final @NotNull LivingEntity livingEntity, final @NotN private boolean isBuildingCache; private boolean groupsAreBuilt; private Integer mobLevel; + private Integer skylightLevelAtSpawn; private int nametagCooldownTime; private String sourceSpawnerName; private String sourceSpawnEggName; @@ -174,6 +176,7 @@ public void clearEntityData(){ this.summonedSender = null; this.playerLevellingAllowDecrease = null; this.pendingPlayerIdToSet = null; + this.skylightLevelAtSpawn = null; super.clearEntityData(); } @@ -428,22 +431,105 @@ public LevelledMobSpawnReason getSpawnReason() { if (this.spawnReason != null) return this.spawnReason; if (!getPDCLock()) return LevelledMobSpawnReason.DEFAULT; + boolean hadError = false; + boolean succeeded = false; try { - if (livingEntity.getPersistentDataContainer().has(main.namespaced_keys.spawnReasonKey, PersistentDataType.STRING)) { - this.spawnReason = LevelledMobSpawnReason.valueOf( - livingEntity.getPersistentDataContainer().get(main.namespaced_keys.spawnReasonKey, PersistentDataType.STRING) - ); + for (int i = 0; i < 2; i++) { + try { + if (livingEntity.getPersistentDataContainer().has(main.namespaced_keys.spawnReasonKey, PersistentDataType.STRING)) { + this.spawnReason = LevelledMobSpawnReason.valueOf( + livingEntity.getPersistentDataContainer().get(main.namespaced_keys.spawnReasonKey, PersistentDataType.STRING) + ); + } + succeeded = true; + break; + } catch (ConcurrentModificationException ignored) { + hadError = true; + try + { Thread.sleep(5); } + catch (InterruptedException ignored2) { return LevelledMobSpawnReason.DEFAULT; } + } + finally { + releasePDCLock(); + } } } finally { releasePDCLock(); } + if (hadError) { + if (succeeded) + Utils.logger.warning("Got ConcurrentModificationException in LivingEntityWrapper getting spawn reason, succeeded on retry"); + else + Utils.logger.warning("Got ConcurrentModificationException (2x) in LivingEntityWrapper getting spawn reason"); + } + return this.spawnReason != null ? this.spawnReason : LevelledMobSpawnReason.DEFAULT; } + public int getSkylightLevel() { + if (this.skylightLevelAtSpawn != null) return this.skylightLevelAtSpawn; + + if (!getPDCLock()) return getCurrentSkyLightLevel(); + boolean hadError = false; + boolean succeeded = false; + + try { + for (int i = 0; i < 2; i++) { + try { + if (livingEntity.getPersistentDataContainer().has(main.namespaced_keys.skyLightLevel, PersistentDataType.INTEGER)) { + this.skylightLevelAtSpawn = livingEntity.getPersistentDataContainer().get(main.namespaced_keys.skyLightLevel, PersistentDataType.INTEGER); + } + succeeded = true; + break; + } catch (ConcurrentModificationException ignored) { + hadError = true; + try + { Thread.sleep(5); } + catch (InterruptedException ignored2) { return 0; } + } + finally { + releasePDCLock(); + } + } + } + finally { + releasePDCLock(); + } + + if (hadError) { + if (succeeded) + Utils.logger.warning("Got ConcurrentModificationException in LivingEntityWrapper getting skyLightLevel, succeeded on retry"); + else + Utils.logger.warning("Got ConcurrentModificationException (2x) in LivingEntityWrapper getting skyLightLevel"); + } + + return this.skylightLevelAtSpawn != null ? + this.skylightLevelAtSpawn: getCurrentSkyLightLevel(); + } + + public void setSkylightLevelAtSpawn(){ + this.skylightLevelAtSpawn = getCurrentSkyLightLevel(); + + if (!getPDCLock()) return; + + try { + if (!livingEntity.getPersistentDataContainer().has(main.namespaced_keys.skyLightLevel, PersistentDataType.INTEGER)) { + livingEntity.getPersistentDataContainer().set(main.namespaced_keys.skyLightLevel, PersistentDataType.INTEGER, this.skylightLevelAtSpawn); + } + } + finally { + releasePDCLock(); + } + } + + private int getCurrentSkyLightLevel(){ + return this.getLocation().getBlock().getLightFromSky(); + } + public void setSpawnReason(final LevelledMobSpawnReason spawnReason) { this.spawnReason = spawnReason; diff --git a/src/main/java/me/lokka30/levelledmobs/misc/Namespaced_Keys.java b/src/main/java/me/lokka30/levelledmobs/misc/Namespaced_Keys.java index 0f7d7cb1b..3b2fab864 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/Namespaced_Keys.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/Namespaced_Keys.java @@ -26,6 +26,7 @@ public Namespaced_Keys(final LevelledMobs main){ wasSummoned = new NamespacedKey(main, "wasSummoned"); playerNetherCoords = new NamespacedKey(main, "playerNetherCoords"); playerNetherCoords_IntoWorld = new NamespacedKey(main, "playerNetherCoords_IntoWorld"); + skyLightLevel = new NamespacedKey(main, "skyLightLevel"); spawnerEgg = new NamespacedKey(main, "spawnerEgg"); spawnerEggName = new NamespacedKey(main, "spawnerEggName"); @@ -62,6 +63,7 @@ public Namespaced_Keys(final LevelledMobs main){ public final NamespacedKey wasSummoned; public final NamespacedKey playerNetherCoords; public final NamespacedKey playerNetherCoords_IntoWorld; + public final NamespacedKey skyLightLevel; final public NamespacedKey spawnerEgg; final public NamespacedKey spawnerEggName; diff --git a/src/main/java/me/lokka30/levelledmobs/misc/YmlParsingHelper.java b/src/main/java/me/lokka30/levelledmobs/misc/YmlParsingHelper.java index 0da14495c..863b71a09 100644 --- a/src/main/java/me/lokka30/levelledmobs/misc/YmlParsingHelper.java +++ b/src/main/java/me/lokka30/levelledmobs/misc/YmlParsingHelper.java @@ -60,7 +60,10 @@ public Set getStringSet(final ConfigurationSection cs, @NotNull final St final String useName = getKeyNameFromConfig(cs, name); final Set results = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - results.addAll(cs.getStringList(useName)); + // rather than use addAll we'll make sure there no empty strings + for (final String item : cs.getStringList(useName)) + if (!item.isEmpty()) results.add(item); + return results; } diff --git a/src/main/java/me/lokka30/levelledmobs/rules/LevelledMobSpawnReason.java b/src/main/java/me/lokka30/levelledmobs/rules/LevelledMobSpawnReason.java index 519b29b19..91a191c82 100644 --- a/src/main/java/me/lokka30/levelledmobs/rules/LevelledMobSpawnReason.java +++ b/src/main/java/me/lokka30/levelledmobs/rules/LevelledMobSpawnReason.java @@ -51,5 +51,6 @@ public enum LevelledMobSpawnReason { FROZEN, COMMAND, CUSTOM, + SPELL, DEFAULT } diff --git a/src/main/java/me/lokka30/levelledmobs/rules/RuleInfo.java b/src/main/java/me/lokka30/levelledmobs/rules/RuleInfo.java index d025be2e8..828e0b8e6 100644 --- a/src/main/java/me/lokka30/levelledmobs/rules/RuleInfo.java +++ b/src/main/java/me/lokka30/levelledmobs/rules/RuleInfo.java @@ -83,6 +83,7 @@ public RuleInfo(final String id){ Map enabledExtCompats; MergeableStringList mobNBT_Data; CachedModalList allowedEntities; + MinAndMax conditions_SkyLightLevel; CachedModalList conditions_Worlds; CachedModalList conditions_Entities; CachedModalList conditions_Biomes; diff --git a/src/main/java/me/lokka30/levelledmobs/rules/RulesManager.java b/src/main/java/me/lokka30/levelledmobs/rules/RulesManager.java index 8f3e90c8e..deb00e5cb 100644 --- a/src/main/java/me/lokka30/levelledmobs/rules/RulesManager.java +++ b/src/main/java/me/lokka30/levelledmobs/rules/RulesManager.java @@ -641,6 +641,16 @@ private boolean isRuleApplicable_Entity(final LivingEntityWrapper lmEntity, @Not } } + if (ri.conditions_SkyLightLevel != null){ + final int lightLevel = lmEntity.getSkylightLevel(); + if (lightLevel < ri.conditions_SkyLightLevel.min || lightLevel > ri.conditions_SkyLightLevel.max){ + Utils.debugLog(main, DebugType.SKYLIGHT_LEVEL, String.format( + "&b%s&7, mob: &b%s&7, skylight: %s, criteria: %s", + ri.getRuleName(), lmEntity.getNameIfBaby(), lightLevel, ri.conditions_SkyLightLevel)); + return false; + } + } + return true; } diff --git a/src/main/java/me/lokka30/levelledmobs/rules/RulesParsingManager.java b/src/main/java/me/lokka30/levelledmobs/rules/RulesParsingManager.java index 999f592fa..bca5b2a01 100644 --- a/src/main/java/me/lokka30/levelledmobs/rules/RulesParsingManager.java +++ b/src/main/java/me/lokka30/levelledmobs/rules/RulesParsingManager.java @@ -740,6 +740,7 @@ private void parseConditions(final @Nullable ConfigurationSection cs){ parsingInfo.conditions_WorldTickTime = parseWorldTimeTicks(cs, parsingInfo.conditions_WorldTickTime); parsingInfo.conditions_Permission = buildCachedModalListOfString(cs, "permission", parsingInfo.conditions_Permission); parsingInfo.conditions_ScoreboardTags = buildCachedModalListOfString(cs, "scoreboard-tags", parsingInfo.conditions_ScoreboardTags); + parsingInfo.conditions_SkyLightLevel = parseMinMaxValue(ymlHelper.getString(cs, "skylight-level"), "skylight-level"); } private void parseStrategies(final ConfigurationSection cs){ @@ -813,19 +814,32 @@ private void parseStrategies(final ConfigurationSection cs){ private CachedModalList parseWorldTimeTicks(final ConfigurationSection cs, final CachedModalList existingList){ if (cs == null) return existingList; - final CachedModalList temp = buildCachedModalListOfString(cs, "world-time-tick", null); + final String configName = "world-time-tick"; + final CachedModalList temp = buildCachedModalListOfString(cs, configName, null); if (temp == null) return existingList; final CachedModalList result = new CachedModalList<>(); result.allowAll = temp.allowAll; result.excludeAll = temp.excludeAll; - result.excludedList.addAll(parseMinMaxValue(temp.excludedList)); - result.allowedList.addAll(parseMinMaxValue(temp.allowedList)); + result.excludedList.addAll(parseMinMaxValue(temp.excludedList, configName)); + result.allowedList.addAll(parseMinMaxValue(temp.allowedList, configName)); return result; } + @Nullable + private MinAndMax parseMinMaxValue(@Nullable final String numberPair, @SuppressWarnings("SameParameterValue") final @NotNull String configName){ + if (numberPair == null) return null; + + final Set result = parseMinMaxValue(Set.of(numberPair), configName); + + if (result.isEmpty()) + return null; + else + return result.iterator().next(); + } + @NotNull - private Set parseMinMaxValue(@NotNull final Set numberPairs){ + private Set parseMinMaxValue(@NotNull final Set numberPairs, final @NotNull String configName){ final Set result = new TreeSet<>(); for (final String numberPair : numberPairs) { @@ -834,7 +848,7 @@ private Set parseMinMaxValue(@NotNull final Set numberPairs){ boolean hadInvalidValue = false; for (int i = 0; i <= 1; i++) { if (!Utils.isInteger(split[i])) { - Utils.logger.warning("Invalid world-time-tick value: '" + split[i] + "' in rule " + parsingInfo.getRuleName()); + Utils.logger.info(String.format("Invalid value for %s: '%s' in rule %s", configName, split[i], parsingInfo.getRuleName())); hadInvalidValue = true; break; } @@ -994,9 +1008,8 @@ private void parseFineTuning(final ConfigurationSection cs){ if (checkName.toLowerCase().startsWith("baby_")) checkName = checkName.substring(5); - final EntityType entityType; try { - entityType = EntityType.valueOf(checkName.toUpperCase()); + EntityType.valueOf(checkName.toUpperCase()); } catch (final IllegalArgumentException e) { Utils.logger.warning("Invalid entity type: " + mobName + " for fine-tuning in rule: " + parsingInfo.getRuleName()); continue; diff --git a/src/main/resources/predefined/rules_easy.yml b/src/main/resources/predefined/rules_easy.yml index 41326ba28..961721212 100644 --- a/src/main/resources/predefined/rules_easy.yml +++ b/src/main/resources/predefined/rules_easy.yml @@ -355,6 +355,7 @@ default-rule: SHOPKEEPERS: false SIMPLE_PETS: false ELITE_BOSSES: false + BLOOD_NIGHT: false # apply-above-y: 64 # apply-below-y: 59 diff --git a/src/main/resources/predefined/rules_hard.yml b/src/main/resources/predefined/rules_hard.yml index a6b4c849c..4aa0b9a05 100644 --- a/src/main/resources/predefined/rules_hard.yml +++ b/src/main/resources/predefined/rules_hard.yml @@ -355,6 +355,7 @@ default-rule: SHOPKEEPERS: false SIMPLE_PETS: false ELITE_BOSSES: false + BLOOD_NIGHT: false # apply-above-y: 64 # apply-below-y: 59 diff --git a/src/main/resources/rules.yml b/src/main/resources/rules.yml index 97eb55f0c..74eaf3ae0 100644 --- a/src/main/resources/rules.yml +++ b/src/main/resources/rules.yml @@ -355,6 +355,7 @@ default-rule: SHOPKEEPERS: false SIMPLE_PETS: false ELITE_BOSSES: false + BLOOD_NIGHT: false # apply-above-y: 64 # apply-below-y: 59