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