From 13e02707eb354f872250c0dab6c7f21508325980 Mon Sep 17 00:00:00 2001 From: MagicBot Date: Thu, 22 Oct 2026 12:10:29 -0400 Subject: [PATCH] Load player rune effects from item_user_power_action instead of static_npc_mobbase_effects MobBaseEffects.getEffectsForRune builds each rune's list from the RUNE JSON on first use and caches it: exact repeats are dropped (54 rune files list their effects twice), the action id resolves to its effect through PowerActions.cfg, token = SBStringHash(effect id), rank = arg 0, reqLvl = arg 1. Used by Race, BaseClass, PromotionClass and RuneBase; MobBase stays on GET_RUNEBASE_EFFECTS until Task 3.4. Parity (scripts/p210b.py, token level): 1074 runes match. Differences: - Tireless, Tough as Nails, Taint of Madness: JSON action TRT-* resolves to the same effect the DB stores; no change - CSR 2901-2904, 2906, 2908, 2910, 2911, Non-Aggro Rune 252624: DB token was the NoNPCAggro action (no effect found, skipped); now NONPCAGGRO, which has no mods; no gameplay change - Mob-only 12021/12022 NECROPETSTATS and 13236 PEACE are server rows on the MobBase path, untouched Co-Authored-By: Claude Opus 5.5 --- src/engine/objects/BaseClass.java | 4 +- src/engine/objects/MobBaseEffects.java | 57 ++++++++++++++++++++++++++ src/engine/objects/PromotionClass.java | 4 +- src/engine/objects/Race.java | 4 +- src/engine/objects/RuneBase.java | 6 +-- 5 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/engine/objects/BaseClass.java b/src/engine/objects/BaseClass.java index da9aa920..1f018152 100644 --- a/src/engine/objects/BaseClass.java +++ b/src/engine/objects/BaseClass.java @@ -104,7 +104,7 @@ public class BaseClass extends AbstractGameObject { this.staminaMod = rs.getInt("staminaMod"); this.skillsGranted = SkillReq.getSkillReqsForRune(this.getObjectUUID()); this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID()); - this.effectsList = (DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID())); + this.effectsList = MobBaseEffects.getEffectsForRune(this.getObjectUUID()); } @@ -127,7 +127,7 @@ public class BaseClass extends AbstractGameObject { this.staminaMod = rune.rune_stamina; this.skillsGranted = SkillReq.getSkillReqsForRune(this.getObjectUUID()); this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID()); - this.effectsList = (DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID())); + this.effectsList = MobBaseEffects.getEffectsForRune(this.getObjectUUID()); } public static void LoadAllBaseClasses() { diff --git a/src/engine/objects/MobBaseEffects.java b/src/engine/objects/MobBaseEffects.java index d773c7dd..f41ecfa7 100644 --- a/src/engine/objects/MobBaseEffects.java +++ b/src/engine/objects/MobBaseEffects.java @@ -9,12 +9,23 @@ package engine.objects; +import engine.parsers.objects.RuneTemplate; +import engine.parsers.objects.UserPowerAction; +import engine.parsers.objects.wpak.PowerActionTemplate; +import engine.server.MBServerStatics; +import engine.util.Hasher; + import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; public class MobBaseEffects { + private static final ConcurrentHashMap> effectsByRune = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); + private int mobBaseID; private int token; private int rank; @@ -30,6 +41,52 @@ public class MobBaseEffects { this.reqLvl = rs.getInt("reqLvl"); } + public MobBaseEffects(int token, int rank, int reqLvl) { + this.token = token; + this.rank = rank; + this.reqLvl = reqLvl; + } + + public static ArrayList getEffectsForRune(int runeID) { + return effectsByRune.computeIfAbsent(runeID, MobBaseEffects::loadEffectsForRune); + } + + // item_user_power_action entries are {action id, [rank, reqLvl]}; the action names the effect to apply + private static ArrayList loadEffectsForRune(int runeID) { + + ArrayList runeEffects = new ArrayList<>(); + HashSet seenEntries = new HashSet<>(); + RuneTemplate rune = RuneTemplate._runes.get(runeID); + PowerActionTemplate powerAction; + String effectID; + int rank; + int reqLvl; + + if (rune == null) + return runeEffects; + + for (UserPowerAction userPowerAction : rune.item_user_power_action) { + + rank = userPowerAction.arguments.length > 0 ? userPowerAction.arguments[0] : 0; + reqLvl = userPowerAction.arguments.length > 1 ? userPowerAction.arguments[1] : 0; + + // 54 rune files repeat their whole list; the DB kept each entry once + if (!seenEntries.add(userPowerAction.power + ":" + rank + ":" + reqLvl)) + continue; + + powerAction = PowerActionTemplate._powerActions.get(userPowerAction.power); + + if (powerAction != null && !powerAction.effect_ids.isEmpty()) + effectID = powerAction.effect_ids.get(0); + else + effectID = userPowerAction.power; + + runeEffects.add(new MobBaseEffects(Hasher.SBStringHash(effectID), rank, reqLvl)); + } + + return runeEffects; + } + /** * @return the mobBaseID diff --git a/src/engine/objects/PromotionClass.java b/src/engine/objects/PromotionClass.java index 5944c432..fc5d93d4 100644 --- a/src/engine/objects/PromotionClass.java +++ b/src/engine/objects/PromotionClass.java @@ -103,7 +103,7 @@ public class PromotionClass extends AbstractGameObject { this.effectsGrantedHealer = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2501); this.effectsGrantedRogue = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2502); this.effectsGrantedMage = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2503); - this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()); + this.effectsList = MobBaseEffects.getEffectsForRune(this.getObjectUUID()); } @@ -128,7 +128,7 @@ public class PromotionClass extends AbstractGameObject { this.effectsGrantedHealer = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2501); this.effectsGrantedRogue = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2502); this.effectsGrantedMage = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2503); - this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()); + this.effectsList = MobBaseEffects.getEffectsForRune(this.getObjectUUID()); } // Promotions are the CLASS runes that require a base class; this skips the creation classes and Pet 2522. diff --git a/src/engine/objects/Race.java b/src/engine/objects/Race.java index d290d31e..16817192 100644 --- a/src/engine/objects/Race.java +++ b/src/engine/objects/Race.java @@ -110,7 +110,7 @@ public class Race { this.baseClasses = DbManager.BaseClassQueries.GET_BASECLASS_FOR_RACE(raceRuneID); this.skillsGranted = SkillReq.getSkillReqsForRune(raceRuneID); this.powersGranted = PowerReq.getPowerReqsForRune(raceRuneID); - this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.raceRuneID); + this.effectsList = MobBaseEffects.getEffectsForRune(this.raceRuneID); } @@ -154,7 +154,7 @@ public class Race { this.baseClasses = getBaseClassesForRace(rune); this.skillsGranted = SkillReq.getSkillReqsForRune(raceRuneID); this.powersGranted = PowerReq.getPowerReqsForRune(raceRuneID); - this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.raceRuneID); + this.effectsList = MobBaseEffects.getEffectsForRune(this.raceRuneID); // Races without hair styles (Aracoix, Shade) select style 0, which creation validates if (this.hairStyles.isEmpty()) diff --git a/src/engine/objects/RuneBase.java b/src/engine/objects/RuneBase.java index 7333f669..2af7eb66 100644 --- a/src/engine/objects/RuneBase.java +++ b/src/engine/objects/RuneBase.java @@ -93,13 +93,13 @@ public class RuneBase extends AbstractGameObject { // static_rune_runereq race/class rows are whitelists, its discipline rows prohibitions this.discRestrict = true; DbManager.RuneBaseQueries.GET_RUNE_REQS(this); - this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()); + this.effectsList = MobBaseEffects.getEffectsForRune(this.getObjectUUID()); } /** * Rune Template Constructor * type and subtype are server enums that don't map cleanly from rune_type/rune_sub_type, so they - * come from the static_rune_runebase row, as do the effects until 2.10. + * come from the static_rune_runebase row. * Talents with the same sub type token replace each other by rank (client 0x4695D0), so no overwrite list. */ public RuneBase(RuneTemplate rune, RuneBase dbRuneBase) { @@ -114,7 +114,7 @@ public class RuneBase extends AbstractGameObject { this.description = rune.rune_dsc; this.type = dbRuneBase.type; this.subtype = dbRuneBase.subtype; - this.effectsList = dbRuneBase.effectsList; + this.effectsList = MobBaseEffects.getEffectsForRune(rune.template_id); this.runeType = rune.rune_type; this.rank = rune.rune_rank; this.levelRequired = rune.item_level_req;