From bed959c6588f153237f23ea04f48e5a2b8869abe Mon Sep 17 00:00:00 2001 From: Vidal Date: Wed, 4 Mar 2026 19:27:27 -0300 Subject: [PATCH 1/4] ConditionCompare abstraction for Skill and DBCLevel --- .../ability/conditions/ConditionCompare.java | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionCompare.java diff --git a/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionCompare.java b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionCompare.java new file mode 100644 index 000000000..3460f43f4 --- /dev/null +++ b/src/main/java/kamkeel/npcs/controllers/data/ability/conditions/ConditionCompare.java @@ -0,0 +1,118 @@ +package kamkeel.npcs.controllers.data.ability.conditions; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.gui.builder.FieldDef; + +import java.util.List; + +/** + * Abstract base for float comparison-based conditions. + * Provides shared CompareType enum, NBT serialization, and FieldDef generation. + */ +public abstract class ConditionCompare extends AbilityCondition { + + public enum CompareType { + ABOVE { + @Override + public boolean test(float value, float threshold) { + return value >= threshold; + } + }, + BELOW { + @Override + public boolean test(float value, float threshold) { + return value <= threshold; + } + }, + EQUAL { + @Override + public boolean test(float value, float threshold) { + return value == threshold; + } + }; + + public abstract boolean test(float value, float threshold); + + public static CompareType fromOrdinal(int ordinal) { + CompareType[] values = values(); + return (ordinal >= 0 && ordinal < values.length) ? values[ordinal] : ABOVE; + } + + @Override + public String toString() { + return "condition." + name().toLowerCase(); + } + } + + protected CompareType compareType = CompareType.ABOVE; + + /** + * Returns the value to be compared for this entity (e.g. level, skill level). + * Return -1 to indicate the condition should fail immediately. + */ + protected abstract float getEntityValue(EntityLivingBase entity); + + /** + * Returns the threshold that getEntityValue() is compared against. + */ + protected abstract float getThreshold(); + + /** + * Appends subclass-specific FieldDefs (e.g. skill picker, level input). + * Called before the shared compareType field is added. + */ + @SideOnly(Side.CLIENT) + protected abstract void getExtraDefinitions(List defs); + + /** + * Subclasses write their own NBT fields here. + */ + protected abstract void writeExtraNBT(NBTTagCompound nbt); + + /** + * Subclasses read their own NBT fields here. + */ + protected abstract void readExtraNBT(NBTTagCompound nbt); + + @Override + protected boolean checkEntity(EntityLivingBase entity) { + float value = getEntityValue(entity); + if (value < 0) return false; + return compareType.test(value, getThreshold()); + } + + @SideOnly(Side.CLIENT) + @Override + public void getConditionDefinitions(List defs) { + defs.add(FieldDef.enumField("condition.compare_type", CompareType.class, this::getCompareType, this::setCompareType)); + getExtraDefinitions(defs); + } + + @Override + public void writeTypeNBT(NBTTagCompound nbt) { + nbt.setInteger("compareType", compareType.ordinal()); + writeExtraNBT(nbt); + } + + @Override + public void readTypeNBT(NBTTagCompound nbt) { + compareType = CompareType.fromOrdinal(nbt.getInteger("compareType")); + readExtraNBT(nbt); + } + + // ═══════════════════════════════════════════════════════════════════ + // GETTERS / SETTERS + // ═══════════════════════════════════════════════════════════════════ + + public CompareType getCompareType() { + return compareType; + } + + public void setCompareType(CompareType compareType) { + this.compareType = compareType; + } +} From cf49d9a7783079f912f4c44a932a0e2f119538f6 Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 4 Mar 2026 17:28:18 -0500 Subject: [PATCH 2/4] Revert "Fix Timer not being able to be called from a Spawning NPC [Revert if not working]" This reverts commit d1b32ea8a1bd2ad53e2deb9e4d78379d5221d2a0. --- .../npcs/controllers/ScriptContainer.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/noppes/npcs/controllers/ScriptContainer.java b/src/main/java/noppes/npcs/controllers/ScriptContainer.java index 7cd691c49..e8dbe22b1 100644 --- a/src/main/java/noppes/npcs/controllers/ScriptContainer.java +++ b/src/main/java/noppes/npcs/controllers/ScriptContainer.java @@ -14,7 +14,6 @@ import noppes.npcs.scripted.NpcAPI; import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; -import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngine; import java.io.PrintWriter; @@ -273,18 +272,20 @@ public class ScriptContainer implements IScriptUnit { unknownFunctions.add(type); } } else { - if (instanceBindings != null) { - if (!this.cachedFunctions.containsKey(type)) { + if (!this.cachedFunctions.containsKey(type)) { + ScriptObjectMirror func; + if (instanceBindings != null) { Object val = instanceBindings.get(type); - ScriptObjectMirror func = val instanceof ScriptObjectMirror ? (ScriptObjectMirror) val : null; - this.cachedFunctions.put(type, func); + func = val instanceof ScriptObjectMirror ? (ScriptObjectMirror) val : null; + } else { + ScriptObjectMirror global = (ScriptObjectMirror) engine.getBindings(ScriptContext.ENGINE_SCOPE); + func = (ScriptObjectMirror) global.get(type); } - ScriptObjectMirror func = this.cachedFunctions.get(type); - if (func != null) { - func.call(null, event); - } - } else { - ((Invocable) engine).invokeFunction(type, event); + this.cachedFunctions.put(type, func); + } + ScriptObjectMirror func = this.cachedFunctions.get(type); + if (func != null) { + func.call(null, event); } } } catch (NoSuchMethodException e) { From 88d8af8b96cecef11d857cd680835682fbc6d9da Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 4 Mar 2026 17:28:23 -0500 Subject: [PATCH 3/4] Revert "Crazy Logging for Health Spam Debugging" This reverts commit a981060e9e02cf0d27c0282d4ad135066711fc34. --- .../npcs/entity/EntityNPCInterface.java | 52 +------------------ 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java index 8326cacf9..6045317d8 100644 --- a/src/main/java/noppes/npcs/entity/EntityNPCInterface.java +++ b/src/main/java/noppes/npcs/entity/EntityNPCInterface.java @@ -85,7 +85,6 @@ import noppes.npcs.DataInventory; import noppes.npcs.DataStats; import noppes.npcs.EventHooks; import noppes.npcs.IChatMessages; -import noppes.npcs.LogWriter; import noppes.npcs.NBTTags; import noppes.npcs.NoppesUtilPlayer; import noppes.npcs.NoppesUtilServer; @@ -483,42 +482,8 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti abilities.tick(); } - // DEBUG: Log client-side health state every tick to diagnose DI green health spam - if (worldObj.isRemote && ticksExisted % 20 == 0) { - float h = getHealth(); - float mh = (float) getMaxHealth(); - int flags = dataWatcher.getWatchableObjectInt(15); - boolean killed = isKilled(); - LogWriter.info("[CLIENT-HEALTH-DEBUG] NPC=" + display.name - + " id=" + getEntityId() - + " health=" + h - + " maxHealth=" + mh - + " flags=" + Integer.toBinaryString(flags) - + " isKilled=" + killed - + " wasKilled=" + wasKilled - + " isDead=" + isDead - + " deathTime=" + deathTime - + " statsMaxHP=" + stats.maxHealth - + " tick=" + ticksExisted); - } - if (wasKilled != isKilled() && wasKilled) { - if (!worldObj.isRemote) { - reset(); - } else { - // Client: only reset visual state. Health/flags are synced via DataWatcher. - // Full reset() on client can cause health oscillation when maxHealth is desynced. - LogWriter.info("[CLIENT-RESET-DEBUG] NPC=" + display.name - + " id=" + getEntityId() - + " health=" + getHealth() - + " maxHealth=" + (float) getMaxHealth() - + " wasKilled=" + wasKilled - + " isKilled=" + isKilled() - + " tick=" + ticksExisted); - deathTime = 0; - currentAnimation = EnumAnimation.NONE; - updateHitbox(); - } + reset(); } wasKilled = isKilled(); @@ -1916,10 +1881,6 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti readSpawnData(ByteBufUtils.readNBT(buf)); } catch (IOException e) { if (this.worldObj != null && this.worldObj.isRemote) { - LogWriter.info("[SPAWN-DATA-DEBUG] ByteBuf readSpawnData FAILED for id=" + getEntityId() - + " health=" + getHealth() - + " maxHealth=" + (float) getMaxHealth() - + " statsMaxHP=" + stats.maxHealth); RequestProperSpawnData.reportMissingData(this); } } @@ -1927,17 +1888,8 @@ public abstract class EntityNPCInterface extends EntityCreature implements IEnti public void readSpawnData(NBTTagCompound compound) { if (this.worldObj != null && this.worldObj.isRemote) { - if(requestClientFix(compound)) { - LogWriter.info("[SPAWN-DATA-DEBUG] requestClientFix returned true (bad compound) for id=" + getEntityId() - + " health=" + getHealth() - + " maxHealth=" + (float) getMaxHealth() - + " statsMaxHP=" + stats.maxHealth); + if(requestClientFix(compound)) return; - } - LogWriter.info("[SPAWN-DATA-DEBUG] readSpawnData SUCCESS for id=" + getEntityId() - + " health=" + getHealth() - + " oldMaxHP=" + (float) getMaxHealth() - + " newMaxHP=" + compound.getDouble("MaxHealth")); } stats.maxHealth = compound.getDouble("MaxHealth"); From e4033481bcd77e791ee1915bb8181810f79d427f Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 4 Mar 2026 17:28:45 -0500 Subject: [PATCH 4/4] Better Script Session Token Handling --- .../java/kamkeel/npcs/network/PacketUtil.java | 25 ++++++++++++++++--- .../java/noppes/npcs/ServerTickHandler.java | 4 +++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/kamkeel/npcs/network/PacketUtil.java b/src/main/java/kamkeel/npcs/network/PacketUtil.java index 0db53b7fc..94719ccb3 100644 --- a/src/main/java/kamkeel/npcs/network/PacketUtil.java +++ b/src/main/java/kamkeel/npcs/network/PacketUtil.java @@ -19,6 +19,7 @@ import noppes.npcs.controllers.data.IScriptUnit; import noppes.npcs.items.ItemNpcTool; import java.io.IOException; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -32,20 +33,36 @@ public class PacketUtil { // ==================== SCRIPT SESSION TOKEN SYSTEM ==================== // Prevents saving script data before the client has received it from the server. // Each GET generates a token sent to the client; each SAVE must echo it back. + // Multiple tokens per player are allowed so concurrent script editors don't invalidate each other. + // Tokens are consumed on successful verify to prevent unbounded growth. - private static final Map scriptSessionTokens = new ConcurrentHashMap<>(); + private static final int MAX_TOKENS_PER_PLAYER = 10; + private static final Map> scriptSessionTokens = new ConcurrentHashMap<>(); public static String createScriptSession(EntityPlayerMP player) { String token = Long.toHexString(ThreadLocalRandom.current().nextLong()); - scriptSessionTokens.put(player.getUniqueID(), token); + Set tokens = scriptSessionTokens.computeIfAbsent(player.getUniqueID(), k -> new LinkedHashSet<>()); + synchronized (tokens) { + // Evict oldest tokens if at capacity + while (tokens.size() >= MAX_TOKENS_PER_PLAYER) { + Iterator it = tokens.iterator(); + it.next(); + it.remove(); + } + tokens.add(token); + } return token; } public static boolean verifyScriptSession(EntityPlayer player, String token) { if (token == null || token.isEmpty()) return false; - String expected = scriptSessionTokens.get(player.getUniqueID()); - return expected != null && expected.equals(token); + Set tokens = scriptSessionTokens.get(player.getUniqueID()); + if (tokens == null) + return false; + synchronized (tokens) { + return tokens.remove(token); + } } public static void clearScriptSession(EntityPlayer player) { diff --git a/src/main/java/noppes/npcs/ServerTickHandler.java b/src/main/java/noppes/npcs/ServerTickHandler.java index 12a0f29ce..959cdddd5 100644 --- a/src/main/java/noppes/npcs/ServerTickHandler.java +++ b/src/main/java/noppes/npcs/ServerTickHandler.java @@ -6,6 +6,7 @@ import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; import kamkeel.npcs.controllers.ProfileController; import kamkeel.npcs.controllers.SyncController; +import kamkeel.npcs.network.PacketUtil; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.world.WorldServer; import noppes.npcs.controllers.AuctionController; @@ -78,5 +79,8 @@ public class ServerTickHandler { // Save and unload the player's profile data on logout ProfileController.Instance.logout(event.player); + + // Clear script session tokens + PacketUtil.clearScriptSession(event.player); } }