From e4033481bcd77e791ee1915bb8181810f79d427f Mon Sep 17 00:00:00 2001 From: Kam Date: Wed, 4 Mar 2026 17:28:45 -0500 Subject: [PATCH] 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); } }