From 3565c66cf89ab3f604cff9f5ea783c52aec4dd83 Mon Sep 17 00:00:00 2001 From: KAMKEEL <28842281+KAMKEEL@users.noreply.github.com> Date: Tue, 11 Aug 2026 03:24:19 -0400 Subject: [PATCH] Keep ability cooldowns across logout Dimension change and teleport were already fixed, but disconnect still cleared cooldowns and login cleared them again, and they were never written to NBT - so relogging reset every cooldown. They are absolute world-time deadlines, so they now persist and are restored on load. --- .../controllers/data/PlayerAbilityData.java | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java index c979d5641..1da16a3af 100644 --- a/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java +++ b/src/main/java/noppes/npcs/controllers/data/PlayerAbilityData.java @@ -832,9 +832,8 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA currentTarget = null; lastAbilityActivationTime = -1; - // Clear cooldowns (fresh start after relog) - cooldownEndTime = 0; - resetAllPerAbilityCooldowns(); + // Cooldowns are absolute world-time deadlines and are restored from NBT, so relogging + // must not clear them or it becomes a way to bypass them. interruptCooldownRolled = false; // Reset sync tracking so next sync sends fresh state @@ -852,9 +851,10 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA if (currentAbility != null && currentAbility.isExecuting()) { interruptCurrentAbility(null, 0); } - // Clear any remaining transient state (cooldowns, chains, locks) + // Clear any remaining transient state (chains, locks) // Don't clear toggles — they were already saved to NBT by this point - onEntityReconstructed(true, false); + // Don't clear cooldowns — they persist across sessions + onEntityReconstructed(false, false); } // ═══════════════════════════════════════════════════════════════════ @@ -1048,6 +1048,22 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA toggleList.appendTag(toggleNbt); } compound.setTag("ActiveToggles", toggleList); + + // Cooldowns are absolute world-time deadlines, so they stay meaningful across sessions + NBTTagCompound cooldowns = new NBTTagCompound(); + cooldowns.setLong("GlobalEnd", cooldownEndTime); + cooldowns.setInteger("GlobalDuration", globalCooldownDuration); + NBTTagList perAbilityList = new NBTTagList(); + for (Map.Entry entry : perAbilityCooldownEndTimes.entrySet()) { + NBTTagCompound cooldownNbt = new NBTTagCompound(); + cooldownNbt.setString("Key", entry.getKey()); + cooldownNbt.setLong("End", entry.getValue()); + Integer duration = perAbilityCooldownDurations.get(entry.getKey()); + cooldownNbt.setInteger("Duration", duration == null ? 0 : duration); + perAbilityList.appendTag(cooldownNbt); + } + cooldowns.setTag("PerAbility", perAbilityList); + compound.setTag("AbilityCooldowns", cooldowns); } public void readFromNBT(NBTTagCompound compound) { @@ -1082,6 +1098,24 @@ public class PlayerAbilityData extends AbstractDataAbilities implements IPlayerA setToggleEntryDirect(key, state); } } + + if (compound.hasKey("AbilityCooldowns")) { + cooldownEndTime = 0; + globalCooldownDuration = 0; + resetAllPerAbilityCooldowns(); + + NBTTagCompound cooldowns = compound.getCompoundTag("AbilityCooldowns"); + cooldownEndTime = cooldowns.getLong("GlobalEnd"); + globalCooldownDuration = cooldowns.getInteger("GlobalDuration"); + NBTTagList perAbilityList = cooldowns.getTagList("PerAbility", 10); // 10 = TAG_COMPOUND + for (int i = 0; i < perAbilityList.tagCount(); i++) { + NBTTagCompound entry = perAbilityList.getCompoundTagAt(i); + String key = entry.getString("Key"); + if (key == null || key.isEmpty()) continue; + perAbilityCooldownEndTimes.put(key, entry.getLong("End")); + perAbilityCooldownDurations.put(key, entry.getInteger("Duration")); + } + } } /**