Build kits from StartingKitTable.cfg

Kit is built from StartingKitTemplate._kits and looked up by race name,
sex and class name like the client's kit map (0x620250), replacing the
hard-coded racebaseclass IDs in GetKitIDByRaceClass. Equip slots come
from each item's item_eq_slots_or, filled from the last cfg item so the
weapon keeps RHELD. dbKitHandler and the ResultSet ctor stay until the
removal commit.

Parity (scripts/p42.py, dev static_rune_validkit vs cfg): 68/68 headers,
451/451 item sets, 449/451 kits with identical slots. Value changes:
- Irekei female Mage kit 3 (182460/185090/29970) was kitNumber 4 in the
  DB, so the client's kit 3 was refused at creation; now selectable.
- Dwarf male Fighter kits 4 and 5 list two boots; the DB put Black Cuffed
  Boots (185120) in CHEST. They are a FEET item, so they are now skipped
  with a warning and Black Riding Boots (185090) stay in FEET.
- Old Half-Giant male (2019) now finds the Half-Giant male kits by name
  instead of a missing map entry.
Noob gear item set unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MagicBotandClaude Opus 5.5 committed 2026-10-25 08:44:13 -04:00
1 parent 5824f34a79
commit c1e1ed9148
4 files changed
+100 -345

No files matched your search

+97 -331
View File
@@ -1,3 +1,4 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
@@ -11,58 +12,65 @@ package engine.objects;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.mbEnums.EquipSlotType;
import engine.mbEnums.ItemContainerType; import engine.mbEnums.ItemContainerType;
import engine.mbEnums.OwnerType; import engine.mbEnums.OwnerType;
import engine.parsers.objects.ItemTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.parsers.objects.wpak.StartingKitTemplate;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
public class Kit extends AbstractGameObject { public class Kit extends AbstractGameObject {
public static HashMap<Integer, Boolean> NoobGearIDS = new HashMap<>(); public static HashMap<Integer, Boolean> NoobGearIDS = new HashMap<>();
public static HashMap<Integer, ArrayList<Kit>> RaceClassIDMap = new HashMap<>(); public static HashMap<Integer, ArrayList<Kit>> RaceClassIDMap = new HashMap<>();
private final int raceBaseClassID;
// Keyed like the client's kit map (0x620250): race name, sex and class name, not rune ids
private static final HashMap<String, ArrayList<Kit>> _kitsByRaceClass = new HashMap<>();
private final byte kitNumber; private final byte kitNumber;
private final int legs; private final EnumMap<EquipSlotType, Integer> items = new EnumMap<>(EquipSlotType.class);
private final int chest;
private final int feet;
private final int offhand;
private final int weapon;
public Kit(StartingKitTemplate template) {
/**
* No Table ID Constructor
*/
public Kit(int raceBaseClassID, byte kitNumber, int legs, int chest,
int feet, int offhand, int weapon) {
super(); super();
this.raceBaseClassID = raceBaseClassID;
this.kitNumber = kitNumber;
this.legs = legs;
this.chest = chest;
this.feet = feet;
this.offhand = offhand;
this.weapon = weapon;
}
/** int itemID;
* Normal Constructor ItemTemplate itemTemplate;
*/ EquipSlotType slot;
public Kit(int raceBaseClassID, byte kitNumber, int legs, int chest,
int feet, int offhand, int weapon, int newUUID) { this.kitNumber = (byte) template.kit_number;
super(newUUID);
this.raceBaseClassID = raceBaseClassID; // The cfg lists the off-hand column before the weapon; fill from the end so the weapon takes RHELD
this.kitNumber = kitNumber; for (int i = template.item_ids.size() - 1; i >= 0; i--) {
this.legs = legs; itemID = template.item_ids.get(i);
this.chest = chest; Kit.NoobGearIDS.put(itemID, true);
this.feet = feet; itemTemplate = ItemTemplate._items.get(itemID);
this.offhand = offhand;
this.weapon = weapon; if (itemTemplate == null) {
Logger.warn("Kit " + kitKey(template.race, template.sex.name(), template.base_class) + " #" + this.kitNumber
+ ": no item template " + itemID);
continue;
}
slot = freeSlot(itemTemplate);
if (slot == null) {
Logger.warn("Kit " + kitKey(template.race, template.sex.name(), template.base_class) + " #" + this.kitNumber
+ ": no free slot for item " + itemID);
continue;
}
this.items.put(slot, itemID);
}
} }
/** /**
@@ -71,34 +79,54 @@ public class Kit extends AbstractGameObject {
public Kit(ResultSet rs) throws SQLException, UnknownHostException { public Kit(ResultSet rs) throws SQLException, UnknownHostException {
super(rs); super(rs);
this.raceBaseClassID = rs.getInt("RaceBaseClassesID"); int raceBaseClassID = rs.getInt("RaceBaseClassesID");
this.kitNumber = rs.getByte("kitNumber"); this.kitNumber = rs.getByte("kitNumber");
this.legs = rs.getInt("legs"); putColumn(EquipSlotType.LEGS, rs.getInt("legs"));
this.chest = rs.getInt("chest"); putColumn(EquipSlotType.CHEST, rs.getInt("chest"));
this.feet = rs.getInt("feet"); putColumn(EquipSlotType.FEET, rs.getInt("feet"));
this.offhand = rs.getInt("offhand"); putColumn(EquipSlotType.LHELD, rs.getInt("offhand"));
this.weapon = rs.getInt("weapon"); putColumn(EquipSlotType.RHELD, rs.getInt("weapon"));
if (Kit.RaceClassIDMap.containsKey(this.raceBaseClassID)) {
Kit.RaceClassIDMap.get(this.raceBaseClassID).add(this); if (Kit.RaceClassIDMap.containsKey(raceBaseClassID)) {
Kit.RaceClassIDMap.get(raceBaseClassID).add(this);
} else { } else {
ArrayList<Kit> tempList = new ArrayList<>(); ArrayList<Kit> tempList = new ArrayList<>();
tempList.add(this); tempList.add(this);
Kit.RaceClassIDMap.put(this.raceBaseClassID, tempList); Kit.RaceClassIDMap.put(raceBaseClassID, tempList);
}
}
public static void LoadAllKits() {
Kit kit;
String key;
for (StartingKitTemplate template : StartingKitTemplate._kits) {
kit = new Kit(template);
key = kitKey(template.race, template.sex.name(), template.base_class);
_kitsByRaceClass.computeIfAbsent(key, k -> new ArrayList<>()).add(kit);
} }
if (this.legs != 0) Logger.info("Loaded " + StartingKitTemplate._kits.size() + " kits for " + _kitsByRaceClass.size() + " race/sex/class");
Kit.NoobGearIDS.put(this.legs, true); }
if (this.chest != 0)
Kit.NoobGearIDS.put(this.chest, true);
if (this.feet != 0)
Kit.NoobGearIDS.put(this.feet, true);
if (this.offhand != 0)
Kit.NoobGearIDS.put(this.offhand, true);
if (this.weapon != 0)
Kit.NoobGearIDS.put(this.weapon, true);
public static Kit getKit(int raceRuneID, int baseClassRuneID, int kitNumber) {
RuneTemplate raceRune = RuneTemplate._runes.get(raceRuneID);
RuneTemplate classRune = RuneTemplate._runes.get(baseClassRuneID);
mbEnums.RaceType raceType = mbEnums.RaceType.getRaceTypebyRuneID(raceRuneID);
ArrayList<Kit> kits;
if (raceRune == null || classRune == null || raceType == null)
return null;
kits = _kitsByRaceClass.get(kitKey(raceRune.obj_name, raceType.getCharacterSex().name(), classRune.obj_name));
if (kits == null || kitNumber < 0 || kitNumber >= kits.size())
return null;
return kits.get(kitNumber);
} }
public static boolean IsNoobGear(int itemID) { public static boolean IsNoobGear(int itemID) {
@@ -107,9 +135,9 @@ public class Kit extends AbstractGameObject {
} }
/* private static String kitKey(String race, String sex, String baseClass) {
* Getters return race + " " + sex + " " + baseClass;
*/ }
private static boolean kitItemCreator(PlayerCharacter pc, int templateID, mbEnums.EquipSlotType slot) { private static boolean kitItemCreator(PlayerCharacter pc, int templateID, mbEnums.EquipSlotType slot) {
@@ -131,293 +159,31 @@ public class Kit extends AbstractGameObject {
return true; return true;
} }
public static int GetKitIDByRaceClass(final int raceID, final int classID) { private EquipSlotType freeSlot(ItemTemplate itemTemplate) {
switch (raceID) {
case 2000:
switch (classID) {
case 2500:
return 2;
case 2501:
return 3;
case 2502:
return 4;
case 2503:
return 5;
}
case 2001:
switch (classID) { for (EquipSlotType slot : itemTemplate.item_eq_slots_or)
case 2500: if (!this.items.containsKey(slot))
return 6; return slot;
case 2501:
return 7;
case 2502:
return 8;
case 2503:
return 9;
}
case 2002:
switch (classID) {
case 2500:
return 10;
case 2501:
return 11;
case 2502:
return 12;
} return null;
case 2003:
switch (classID) {
case 2500:
return 13;
case 2501:
return 14;
case 2502:
return 15;
}
case 2004:
switch (classID) {
case 2500:
return 16;
case 2501:
return 17;
}
case 2005:
switch (classID) {
case 2500:
return 18;
case 2501:
return 19;
}
case 2006:
switch (classID) {
case 2500:
return 20;
case 2501:
return 21;
}
case 2008:
switch (classID) {
case 2500:
return 22;
case 2501:
return 23;
case 2502:
return 24;
case 2503:
return 25;
}
case 2009:
switch (classID) {
case 2500:
return 26;
case 2501:
return 27;
case 2502:
return 28;
case 2503:
return 29;
}
case 2010:
switch (classID) {
case 2500:
return 30;
}
case 2011:
switch (classID) {
case 2500:
return 31;
case 2501:
return 32;
case 2502:
return 33;
case 2503:
return 34;
}
case 2012:
switch (classID) {
case 2500:
return 35;
case 2501:
return 36;
case 2502:
return 37;
case 2503:
return 38;
}
case 2013:
switch (classID) {
case 2500:
return 39;
case 2501:
return 40;
case 2502:
return 41;
case 2503:
return 42;
}
case 2014:
switch (classID) {
case 2500:
return 43;
case 2501:
return 44;
case 2502:
return 45;
case 2503:
return 46;
}
case 2015:
switch (classID) {
case 2500:
return 47;
case 2502:
return 48;
case 2503:
return 49;
}
case 2016:
switch (classID) {
case 2500:
return 50;
case 2502:
return 51;
case 2503:
return 52;
}
case 2017:
switch (classID) {
case 2500:
return 53;
case 2501:
return 54;
}
case 2025:
switch (classID) {
case 2500:
return 55;
case 2501:
return 56;
case 2502:
return 57;
case 2503:
return 58;
}
case 2026:
switch (classID) {
case 2500:
return 59;
case 2501:
return 60;
case 2502:
return 61;
case 2503:
return 62;
}
case 2027:
switch (classID) {
case 2500:
return 63;
}
case 2028:
switch (classID) {
case 2500:
return 64;
case 2502:
return 65;
case 2503:
return 66;
}
case 2029:
switch (classID) {
case 2500:
return 67;
case 2502:
return 68;
case 2503:
return 69;
}
}
return -1;
} }
public int getRaceBaseClassID() { private void putColumn(EquipSlotType slot, int itemID) {
return raceBaseClassID;
if (itemID == 0)
return;
this.items.put(slot, itemID);
Kit.NoobGearIDS.put(itemID, true);
} }
public byte getKitNumber() { public byte getKitNumber() {
return kitNumber; return kitNumber;
} }
public int getLegs() {
return legs;
}
public int getChest() {
return chest;
}
public int getFeet() {
return feet;
}
public int getOffhand() {
return offhand;
}
public int getWeapon() {
return weapon;
}
public void equipPCwithKit(PlayerCharacter pc) { public void equipPCwithKit(PlayerCharacter pc) {
if (weapon != 0) for (Map.Entry<EquipSlotType, Integer> entry : this.items.entrySet())
kitItemCreator(pc, weapon, mbEnums.EquipSlotType.RHELD); kitItemCreator(pc, entry.getValue(), entry.getKey());
if (offhand != 0)
kitItemCreator(pc, offhand, mbEnums.EquipSlotType.LHELD);
if (chest != 0)
kitItemCreator(pc, chest, mbEnums.EquipSlotType.CHEST);
if (legs != 0)
kitItemCreator(pc, legs, mbEnums.EquipSlotType.LEGS);
if (feet != 0)
kitItemCreator(pc, feet, mbEnums.EquipSlotType.FEET);
} }
@Override @Override
+1 -12
View File
@@ -1100,18 +1100,7 @@ public class PlayerCharacter extends AbstractCharacter {
int kitID = msg.getKit(); int kitID = msg.getKit();
// get the correctKit Kit kit = Kit.getKit(raceID, baseClassID, kitID);
int raceClassID = Kit.GetKitIDByRaceClass(raceID, baseClassID);
ArrayList<Kit> allKits = Kit.RaceClassIDMap.get(raceClassID);
Kit kit = null;
for (Kit k : allKits) {
if (k.getKitNumber() == kitID) {
kit = k;
break;
}
}
if (kit == null) { if (kit == null) {
return refuseCreation(a, clientConnection, "Unable to find matching kitID: " + kitID); return refuseCreation(a, clientConnection, "Unable to find matching kitID: " + kitID);
+1 -1
View File
@@ -390,7 +390,7 @@ public class LoginServer {
Blueprint.loadAllBlueprints(); Blueprint.loadAllBlueprints();
Logger.info("Loading Kits"); Logger.info("Loading Kits");
DbManager.KitQueries.GET_ALL_KITS(); Kit.LoadAllKits();
Logger.info("Initializing Race data"); Logger.info("Initializing Race data");
mbEnums.RaceType.initRaceTypeTables(); mbEnums.RaceType.initRaceTypeTables();
+1 -1
View File
@@ -466,7 +466,7 @@ public class WorldServer {
RealmMap.loadRealmImageMap(); RealmMap.loadRealmImageMap();
Logger.info("Loading Kits"); Logger.info("Loading Kits");
DbManager.KitQueries.GET_ALL_KITS(); Kit.LoadAllKits();
Logger.info("Loading World Grid"); Logger.info("Loading World Grid");
WorldGrid.InitializeGridObjects(); WorldGrid.InitializeGridObjects();