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 <noreply@anthropic.com>
This commit is contained in:
MagicBotandClaude Opus 5.5 committed 2026-10-22 12:10:29 -04:00
1 parent 098f5d2c38
commit 13e02707eb
5 files changed
+66 -9

No files matched your search

+57
View File
@@ -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<Integer, ArrayList<MobBaseEffects>> 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<MobBaseEffects> 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<MobBaseEffects> loadEffectsForRune(int runeID) {
ArrayList<MobBaseEffects> runeEffects = new ArrayList<>();
HashSet<String> 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