Load Skills, StartingKitTable, CharCreateRuneList and PowActionCostInfo

SkillParser: uid, name, icon, attribute weights and description per
Skills.cfg line (94 skills; the server's ids 1-63 stay in the DB).
StartingKitParser: each kit line is a list of up to 10 item ids under
its RACE= line, as the client reads it; the file's tab columns are not
used (451 kits, same count as static_rune_validkit).
CharCreateRuneParser: race (with sex), class, promotion and attribute
creation rune lists. PowActionCostParser: resource costs per id, the
source of static_effect_costmaps (524 entries). No consumers change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MagicBotandClaude Opus 5.5 committed 2026-10-22 11:22:27 -04:00
1 parent bd145b1e55
commit 3ae1c72f46
9 files changed
+457

No files matched your search

@@ -0,0 +1,27 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.ArrayList;
import java.util.LinkedHashMap;
// CharCreateRuneList.cfg: the rune ids the client offers at character creation,
// in file order. Race runes carry the sex they are offered to (NONE = both).
public class CharCreateRuneList {
public static CharCreateRuneList _runeList;
public final LinkedHashMap<Integer, mbEnums.SexType> race_runes = new LinkedHashMap<>();
public final ArrayList<Integer> class_runes = new ArrayList<>();
public final ArrayList<Integer> promotion_runes = new ArrayList<>();
public final ArrayList<Integer> attr_runes = new ArrayList<>();
}
@@ -0,0 +1,26 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.HashMap;
import java.util.LinkedHashMap;
// One line of PowActionCostInfo.cfg: <id> ("<resource>" <amount>)*.
// The resource cost of a prefix/suffix (client EffectCostMap 0x6E00C0); the source of
// the server's static_effect_costmaps.
public class PowActionCostTemplate {
public static HashMap<String, PowActionCostTemplate> _costs = new HashMap<>();
public String id;
public final LinkedHashMap<mbEnums.ResourceType, Integer> costs = new LinkedHashMap<>();
}
@@ -0,0 +1,28 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.HashMap;
import java.util.LinkedHashMap;
// One line of Skills.cfg: "<uid>" "<name>" <icon> (<attribute> <weight>)* = "<description>".
// The cfg has no numeric ids; the server's skill ids 1-63 stay in static_skill_skillsbase.
public class SkillTemplate {
public static HashMap<String, SkillTemplate> _skills = new HashMap<>();
public String skill_uid;
public String skill_name;
public int icon_number;
public final LinkedHashMap<mbEnums.AttributeType, Integer> attr_weights = new LinkedHashMap<>();
public String description;
}
@@ -0,0 +1,29 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.ArrayList;
// One kit of StartingKitTable.cfg: a kit line under RACE= <race> <sex> "<class>".
// The client reads a kit as a plain list of up to 10 item template ids; the tab
// columns in the file (Legs, Torso, ...) are not used (0x620250).
// kit_number counts the kits of one race/sex/class from 0.
public class StartingKitTemplate {
public static ArrayList<StartingKitTemplate> _kits = new ArrayList<>();
public String race;
public mbEnums.SexType sex;
public String base_class;
public int kit_number;
public final ArrayList<Integer> item_ids = new ArrayList<>();
}
@@ -0,0 +1,90 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.mbEnums;
import engine.parsers.objects.wpak.CharCreateRuneList;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
// Loads CharCreateRuneList.cfg (client loaders at 0xBA4C20 and three siblings, one per section).
public class CharCreateRuneParser {
private static final String fileName = "CharCreateRuneList.cfg";
public static CharCreateRuneList parseCharCreateRuneFile() {
CharCreateRuneList runeList = new CharCreateRuneList();
WpakBlock root;
int failedCount = 0;
try {
root = WpakFileReader.readBlocks(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : root.lines()) {
Logger.error(line.location() + ": line outside a XxxRUNESBEGIN/END section");
failedCount++;
}
for (WpakBlock block : root.blocks()) {
try {
switch (block.name) {
case "RACERUNES":
for (WpakLine line : block.lines())
runeList.race_runes.put(line.intToken(0), parseSex(line));
break;
case "CLASSRUNES":
readRuneIds(block, runeList.class_runes);
break;
case "PROMOTIONRUNES":
readRuneIds(block, runeList.promotion_runes);
break;
case "ATTRRUNES":
readRuneIds(block, runeList.attr_runes);
break;
default:
throw block.beginLine.error("unexpected " + block.name + "BEGIN");
}
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
}
}
Logger.info("Loaded " + runeList.race_runes.size() + " race, " + runeList.class_runes.size() + " class, "
+ runeList.promotion_runes.size() + " promotion and " + runeList.attr_runes.size() + " attribute creation runes");
if (failedCount > 0)
return null;
return runeList;
}
// The client keeps the previous line's sex when one is missing; every race line in the file has one
private static mbEnums.SexType parseSex(WpakLine line) {
if (!line.has(1))
return mbEnums.SexType.NONE;
return line.enumToken(mbEnums.SexType.class, 1);
}
private static void readRuneIds(WpakBlock block, ArrayList<Integer> runeIds) {
for (WpakLine line : block.lines())
runeIds.add(line.intToken(0));
}
}
@@ -0,0 +1,59 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.mbEnums;
import engine.parsers.objects.wpak.PowActionCostTemplate;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.HashMap;
// Loads PowActionCostInfo.cfg (client loader EffectCostMap_LoadPowActionCostInfo 0x6E00C0).
public class PowActionCostParser {
private static final String fileName = "PowActionCostInfo.cfg";
public static HashMap<String, PowActionCostTemplate> parsePowActionCostFile() {
HashMap<String, PowActionCostTemplate> costs = new HashMap<>();
ArrayList<WpakLine> lines;
PowActionCostTemplate cost;
int failedCount = 0;
try {
lines = WpakFileReader.readLines(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : lines) {
try {
// A repeated id adds its pairs to the existing entry, as the client does
cost = costs.computeIfAbsent(line.token(0), id -> new PowActionCostTemplate());
cost.id = line.token(0);
for (int i = 1; i < line.size(); i += 2)
cost.costs.put(line.enumToken(mbEnums.ResourceType.class, i), line.intToken(i + 1));
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
}
}
Logger.info("Loaded " + costs.size() + " power action resource costs from " + lines.size() + " lines");
if (failedCount > 0)
return null;
return costs;
}
}
+80
View File
@@ -0,0 +1,80 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.mbEnums;
import engine.parsers.objects.wpak.SkillTemplate;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.HashMap;
// Loads Skills.cfg (client loader ArcSkill_LoadSkillsCfg 0x584430).
public class SkillParser {
private static final String fileName = "Skills.cfg";
public static HashMap<String, SkillTemplate> parseSkillsFile() {
HashMap<String, SkillTemplate> skills = new HashMap<>();
ArrayList<WpakLine> lines;
SkillTemplate skill;
int failedCount = 0;
try {
lines = WpakFileReader.readLines(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : lines) {
try {
skill = parseSkillLine(line);
if (skills.containsKey(skill.skill_uid))
Logger.warn(line.location() + ": duplicate skill " + skill.skill_uid + "; the later entry wins");
skills.put(skill.skill_uid, skill);
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
}
}
Logger.info("Loaded " + skills.size() + " of " + lines.size() + " skills");
if (failedCount > 0)
return null;
return skills;
}
// Attribute/weight pairs run until the "=" token; the description follows it
private static SkillTemplate parseSkillLine(WpakLine line) {
SkillTemplate skill = new SkillTemplate();
int index = 3;
skill.skill_uid = line.token(0);
skill.skill_name = line.token(1);
skill.icon_number = line.intToken(2);
while (line.has(index) && !line.token(index).equals("=")) {
skill.attr_weights.put(line.enumToken(mbEnums.AttributeType.class, index), line.intToken(index + 1));
index += 2;
}
skill.description = line.has(index + 1) ? line.token(index + 1) : "";
return skill;
}
}
@@ -0,0 +1,78 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.mbEnums;
import engine.parsers.objects.wpak.StartingKitTemplate;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
// Loads StartingKitTable.cfg (client loader ArcBaseKitManager_LoadStartingKitTable 0x620250).
public class StartingKitParser {
private static final String fileName = "StartingKitTable.cfg";
private static final int maxKitItems = 10;
public static ArrayList<StartingKitTemplate> parseStartingKitFile() {
ArrayList<StartingKitTemplate> kits = new ArrayList<>();
ArrayList<WpakLine> lines;
StartingKitTemplate kit;
String race = null;
mbEnums.SexType sex = null;
String baseClass = null;
int kitNumber = 0;
int failedCount = 0;
try {
lines = WpakFileReader.readLines(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : lines) {
try {
if (line.token(0).equals("RACE=")) {
race = line.token(1);
sex = line.enumToken(mbEnums.SexType.class, 2);
baseClass = line.token(3);
kitNumber = 0;
continue;
}
if (race == null)
throw line.error("kit line before the first RACE=");
if (line.size() > maxKitItems)
throw line.error("kit has " + line.size() + " items; the client reads at most " + maxKitItems);
kit = new StartingKitTemplate();
kit.race = race;
kit.sex = sex;
kit.base_class = baseClass;
kit.kit_number = kitNumber++;
kit.item_ids.addAll(line.intTokensFrom(0));
kits.add(kit);
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
}
}
Logger.info("Loaded " + kits.size() + " starting kits");
if (failedCount > 0)
return null;
return kits;
}
}
+40
View File
@@ -39,16 +39,24 @@ import engine.parsers.cache.RuneParser;
import engine.parsers.objects.DeedTemplate;
import engine.parsers.objects.ItemTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.parsers.objects.wpak.CharCreateRuneList;
import engine.parsers.objects.wpak.CompoundCurveTemplate;
import engine.parsers.objects.wpak.EffectTemplate;
import engine.parsers.objects.wpak.ModTypeTemplate;
import engine.parsers.objects.wpak.PowActionCostTemplate;
import engine.parsers.objects.wpak.PowerActionTemplate;
import engine.parsers.objects.wpak.PowerTemplate;
import engine.parsers.objects.wpak.SkillTemplate;
import engine.parsers.objects.wpak.StartingKitTemplate;
import engine.parsers.wpak.CharCreateRuneParser;
import engine.parsers.wpak.CompoundCurveParser;
import engine.parsers.wpak.EffectParser;
import engine.parsers.wpak.ModTypeParser;
import engine.parsers.wpak.PowActionCostParser;
import engine.parsers.wpak.PowerActionParser;
import engine.parsers.wpak.PowerParser;
import engine.parsers.wpak.SkillParser;
import engine.parsers.wpak.StartingKitParser;
import engine.powers.RunePowerEntry;
import engine.powers.RuneSkillAdjustEntry;
import engine.server.MBServerStatics;
@@ -384,6 +392,38 @@ public class WorldServer {
exit(1);
}
Logger.info("Loading Skills");
SkillTemplate._skills = SkillParser.parseSkillsFile();
if (SkillTemplate._skills == null || SkillTemplate._skills.isEmpty()) {
Logger.error("ABORT! Skills failed to load!");
exit(1);
}
Logger.info("Loading Starting Kits");
StartingKitTemplate._kits = StartingKitParser.parseStartingKitFile();
if (StartingKitTemplate._kits == null || StartingKitTemplate._kits.isEmpty()) {
Logger.error("ABORT! Starting kits failed to load!");
exit(1);
}
Logger.info("Loading Character Creation Runes");
CharCreateRuneList._runeList = CharCreateRuneParser.parseCharCreateRuneFile();
if (CharCreateRuneList._runeList == null) {
Logger.error("ABORT! Character creation runes failed to load!");
exit(1);
}
Logger.info("Loading Power Action Resource Costs");
PowActionCostTemplate._costs = PowActionCostParser.parsePowActionCostFile();
if (PowActionCostTemplate._costs == null || PowActionCostTemplate._costs.isEmpty()) {
Logger.error("ABORT! Power action resource costs failed to load!");
exit(1);
}
Logger.info("Loading PromotionClasses");
PromotionClass.LoadAllPromotions();