Compare commits

...
Author SHA1 Message Date
MagicBotandClaude Opus 5.5 b63826a7b7 Import DeedTemplate in PlaceAssetMsgHandler
c7747467 used DeedTemplate._deeds in the deed placement check without
importing it, which broke the build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:52:22 -04:00
MagicBotandClaude Opus 5.5 2774d8a510 Read offering info and bane rank through the bridge
Shrine boons read getItemOfferingInfo() and the banestone and bane
production paths read getItemBaneRank() on template.cacheTemplate.

The legacy parser only kept item_bane_rank when ACTIONRESPONSE is
4250517122; the cache parsers always read it. Parity shows this makes
no difference: every template outside that gate has bane rank 0.

Parity (offline, scripts/p14.py, 7586 templates): 0 differences in
item_offering_info and item_bane_rank. No value changes expected.

JSON-Refactor-Plan Task 1.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:46:14 -04:00
MagicBotandClaude Opus 5.5 9a10574e59 Read defense, bulk and primary attr from the cache ItemTemplate
item_defense_rating, item_bulk_factor and item_primary_attr are
Item-only fields, not on CObjectTemplate, so their consumers read
engine.parsers.objects.ItemTemplate._items by templateID, like the
RuneTemplate and DeedTemplate consumers. The primary attr checks
compare with == because the cache template leaves the attr null
(legacy: None) on non-weapons.

Parity (offline, scripts/p14.py, 7586 templates): 0 differences in
item_defense_rating, item_bulk_factor and item_primary_attr. Every DB
ARMOR and WEAPON template is in the ITEM folder, so both sides gate
these fields on the same templates. No value changes expected.

JSON-Refactor-Plan Task 1.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:45:59 -04:00
MagicBotandClaude Opus 5.5 d197f62c1f Read template health, charges and flags through the bridge
Consumers of the legacy ItemTemplate combat_health_full,
item_initial_charges and item_flags now read getCombatHealthFull(),
getItemInitialCharges() and getItemFlags() on template.cacheTemplate.
No consumer read the template's combat_health_current; the
combat_health_current reads are the Item's own field.

Parity (offline, scripts/p14.py, 7586 templates): 0 differences in
combat_health_current, combat_health_full, item_initial_charges and
item_flags ("None" dropped on both sides). No value changes expected.

JSON-Refactor-Plan Task 1.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:45:17 -04:00
MagicBotandClaude Opus 5.5 dc4a9ee08a Read item weight, value and base name through the bridge
Consumers of the legacy ItemTemplate item_wt, item_value and
item_base_name now read getItemWt(), getItemValue() and
getItemBaseName() on template.cacheTemplate.

Parity (offline, scripts/p14.py, 7586 templates): 0 differences in
item_wt, item_value and item_base_name. No value changes expected.

JSON-Refactor-Plan Task 1.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:44:35 -04:00
MagicBotandClaude Opus 5.5 b8ac8cce3e Read item_type through the cache template bridge
Every consumer of the legacy ItemTemplate item_type now reads
template.cacheTemplate.getItemType(). Left on the legacy field: the
type count in dbItemHandler.LOAD_ITEM_TEMPLATES, which runs before the
bridge is linked. The charter checks already read DeedTemplate.

Parity (offline, static_item_templates vs the template
CObjectIndex.lookup picks, scripts/p14.py, 7586 templates: ITEM 4011,
DEED 880, RUNE 2695): 0 differences in item_type. No value changes
expected.

JSON-Refactor-Plan Task 1.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:43:57 -04:00
MagicBotandClaude Opus 5.5 6825f94f1e Check charters through DeedTemplate
GuildCreationFinalizeHandler and UseCharterMsg now match charters by
DeedTemplate._deeds instead of the shared item_type getter, like the
other deed paths.

Value change: template 545 "Charter" (ITEM folder, CHARTER) no longer
counts as a charter. It is a base template that players can't get: its
only DB reference is static_vendor_items with vendorType 0, it has no
loot rows, and it is not in GuildCharterType.templateLookup, so guild
creation already rejected it.

JSON-Refactor-Plan Task 1.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:47:09 -04:00
MagicBotandClaude Opus 5.5 c774746736 Read deed consumers from DeedTemplate
Deed placement (ObjectActionMsgHandler, PlaceAssetMsgHandler) reads
deed_structure_id from DeedTemplate._deeds. The deed, employment
contract and charter type checks (PlaceAssetMsgHandler,
ActivateNPCMsgHandler, GuildCreationFinalizeHandler, UseCharterMsg,
Item.serialize) read item_type through the cache template bridge.
Charters go through the shared getter rather than _deeds because
template 545 "Charter" is a CHARTER in the ITEM folder.

Parity (offline, static_item_templates vs COBJECTS, scripts/p13.py,
881 templates: all 880 DEED plus ITEM 545): 0 differences in
item_type and deed_structure_id. Every DB DEED, EMPLOYMENTCONTRACT,
PETTOTEM and FURNITUREDEED id is in the DEED folder. No value changes
expected. No pet totem or furniture deed consumer reads template
fields, so neither changes.

JSON-Refactor-Plan Task 1.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:42:54 -04:00
MagicBotandClaude Opus 5.5 3e087bd1ac Read mob rune damage and natural attacks from RuneTemplate
AbstractCharacter mob damage/ATR and CombatManager.getNaturalAttack now
read RuneTemplate._runes instead of the legacy ItemTemplate map.

Parity (offline, static_item_templates vs COBJECTS/RUNE, 2695 runes):
0 differences in rune_min_damage, rune_max_damage, rune_attack and
rune_naturalattacks. All 2366 runes with natural attacks use the
10-value layout. No value changes expected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:35:02 -04:00
MagicBotandClaude Opus 5.5 765cb9edb6 Bridge legacy ItemTemplate to its cache template
Each engine.objects.ItemTemplate gets a cacheTemplate field, filled by
linkCacheTemplates() right after CObjectIndex.build() on both servers,
so consumers can move to CObjectTemplate one field at a time.

ParityCheck gains a static_item_templates comparator: a null bridge is
reported as missing in json, and obj_name and item_type are compared
through the interface to catch a wrong folder pick.

Import convention until the legacy class is removed (Task 1.8): the
legacy engine.objects.ItemTemplate keeps the plain import; the cache
class is always written engine.parsers.objects.ItemTemplate. Migrated
consumers should depend on CObjectTemplate instead.

JSON-Refactor-Plan Task 1.1. No consumers change; no values change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:21:47 -04:00
MagicBotandClaude Opus 5.5 4765e7993f Add shared obj/combat/item getters to CObjectTemplate
The ITEM, DEED and RUNE cache templates implement the getters for the
65 fields all three define. The Item-only fields (weapon, primary and
secondary attr, bulk, defense rating, sheath, stub flag/id) stay off the
interface. CObjectTemplate.lookup delegates to CObjectIndex.lookup.

JSON-Refactor-Plan Task 1.1 (D1). No consumers change; no values change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:20:33 -04:00
MagicBotandClaude Opus 5.5 6ef987b7c6 Block all non-siege building placement while the city has a bane
Moves the warehouse-only check into validateBuildingPlacement, which every
city asset placement except siege equipment and trees passes through.
Matches client error 74, "Only buildings of war may be placed during a bane".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:15:54 -04:00
MagicBotandClaude Opus 5.5 9db194889f Use client error 74 for warehouse placement during a bane
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:14:37 -04:00
MagicBotandClaude Opus 5.5 c97b4d7c3d Declare the bane local at the top of placeWarehouse
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:12:56 -04:00
MagicBotandClaude Opus 5.5 e450224902 Block warehouse placement while the city has a bane
Placing a new warehouse discards the destroyed warehouse's rubble loot,
so defenders could deny attackers the loot by rebuilding mid-siege.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:09:31 -04:00
MagicBotandClaude Opus 5.5 875809efe0 Look up resource templates at loot time instead of caching them on ResourceType
ResourceType.template was filled in the enum constructor, which can run
before ItemTemplate.templates is loaded, leaving it null. lootBuilding was
its only reader and threw on the first non-gold resource.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:38:55 -04:00
MagicBotandClaude Opus 5.5 ef6fe4aa5b Skip rubble when locating a city's warehouse building at load
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:21:57 -04:00
MagicBotandClaude Opus 5.5 145bb15232 Skip the warehouse delete when the owner destroys warehouse rubble
city.warehouse is already null after a combat destruction, which threw in
DELETE_WAREHOUSE. If a new warehouse was placed it would have deleted that
one instead, so only a live (rank != -1) warehouse clears city.warehouse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:20:21 -04:00
MagicBotandClaude Opus 5.5 e64d64bf18 Keep destroyed warehouse contents in memory so the rubble can be looted
destroyOrDerank nulled city.warehouse on destruction, so the loot click
found no warehouse and returned silently. The destroyed Warehouse is now
held in Warehouse.destroyedWarehouses keyed by the rubble building UUID
until it is looted empty, the rubble is destroyed, or the city places a
new warehouse. Contents are not persisted across a reboot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:19:40 -04:00
MagicBotandClaude Opus 5.5 0865a7be74 Log only the JSON error and workorder ID when a workorder fails to load
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:24:04 -04:00
MagicBotandClaude Opus 5.5 a079deef8d Log only the JSON error and city UUID when a warehouse fails to load
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:23:18 -04:00
MagicBotandClaude Opus 5.5 963130565e Make MB_MINION_SPAWN_TIME a required ConfigManager entry
0 or blank keeps the normal guard/siege minion spawn timers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 08:00:53 -04:00
MagicBotandClaude Opus 5.5 becab380d2 Add optional MB_MINION_SPAWN_TIME override for guard and siege minion spawns
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 07:41:16 -04:00
MagicBotandClaude Opus 5.5 5c78bc7fa5 Skip the shopkeeper rune when applying an NPC rune set
applyRunesForNPC already adds 252620, and most rune sets repeat it, so
NPCs with a set listed the shopkeeper rune twice after the runeSet fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:30:18 -04:00
MagicBotandClaude Opus 5.5 4e30a04619 Fix /makeitem looking up the template by string instead of template id
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:06:10 -04:00
MagicBotandClaude Opus 5.5 42a1c8a532 Fix Warehouse.loot looking up the template by enum instead of template id
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:06:10 -04:00
MagicBotandClaude Opus 5.5 2a2c528981 Fix NPC rune sets storing the set id instead of the rune id
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:06:10 -04:00
MagicBotandClaude Opus 5.5 58e0fa32fb Add dev-only DB/JSON parity check at world boot
ParityCheck compares DB-built objects with the cache JSON and logs
table/id/field differences plus a per-table summary. It runs only when
the optional env var MB_PARITY_CHECK=true is set; it is not added to the
ConfigManager enum because init() aborts on any missing enum entry.

First comparator: static_rune_baseclass name vs RUNE obj_name.
No gameplay values change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:51:22 -04:00
MagicBotandClaude Opus 5.5 62b4a2a897 Skip cache JSON files whose name is not a 32-bit template id
The client cache holds RUNE/10788957847552 (2512 << 32), which would
overflow Integer.parseInt and abort the rune load. Such files are now
skipped with a warning. The file is not in MBDATA yet, so the
880/2695/4011 counts are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:44:45 -04:00
MagicBotandClaude Opus 5.5 d9d1c41d9e Add CObjectIndex lookups over the cache templates
Built at login and world bootstrap after the three parsers load (Task 0.1):
- lookup(id) across ITEM, then DEED, then RUNE
- race (MonsterType) -> creation race rune ids, male and female
- ClassType -> class rune id, DisciplineType -> discipline rune id
- SBStringHash(rune_sub_type) token -> runes
- skill and power name <-> SBStringHash token, from every template field
  that names one

No consumers yet; no values change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:33:35 -04:00
MagicBotandClaude Opus 5.5 cb427de440 Add CObjectTemplate marker interface for cache templates
ITEM, DEED and RUNE templates share one id space (JSON-Refactor-Plan D1).
Empty for now; the shared getters come with Task 1.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:33:34 -04:00
MagicBotandClaude Opus 5.5 74828c620d Load item cache JSON at login and world server bootstrap
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:51:20 -04:00
MagicBotandClaude Opus 5.5 fbd38732dc Add ITEM cache JSON parser
ItemTemplate follows ArcItemTemplate_Load (sb.exe 0x4AABD0): weapon and
armor blocks keyed by item type, sheath and stub blocks, and empty equip
slot masks read as empty sets. Adds SheathSlot and readHashLabel, which
keeps unresolved hashes such as item_skill_used in 195340.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:51:20 -04:00
MagicBot 75e10f613f Reporting within parser 2026-10-17 08:46:23 -04:00
MagicBot 783d2d4dc9 Bootstrap count reporting for DEED/RUNE 2026-10-17 08:40:37 -04:00
MagicBot 3870b283d8 Bootstrap count reporting for DEED/RUNE 2026-10-17 08:39:48 -04:00
MagicBotandClaude Opus 5.5 542ae79e2c Load deed and rune cache JSON at login server bootstrap
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:49:09 -04:00
MagicBotandClaude Opus 5.5 08d4bc2501 Treat unknown enum values in deed and rune JSON as parse failures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:48:11 -04:00
MagicBotandClaude Opus 5.5 7309159f83 Abort world server startup when any deed or rune file fails to parse
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:45:24 -04:00
MagicBotandClaude Opus 5.5 ad476db166 Abort world server startup when deed or rune templates fail to load
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:44:42 -04:00
MagicBotandClaude Opus 5.5 a75cbb5cc1 Load deed and rune cache JSON at world server bootstrap
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:43:48 -04:00
MagicBotandClaude Opus 5.5 2bee93087d Add static template maps to DeedTemplate and RuneTemplate
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 07:42:00 -04:00
MagicBotandClaude Opus 5.5 99a4a6741a Remove unused StringUtils.hashString and FastMath.hash
Neither matches the client string hash; Hasher.SBStringHash does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:52:46 -04:00
MagicBotandClaude Opus 5.5 1ac5a248db Read named grant keys and signed values from new COBJECTS JSON
Grant pair lists now carry name strings ("HLR-021", "Unarmed Combat")
instead of integer hashes, so they are stored as name -> value maps.
Unresolved hashes are kept as their decimal string and logged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:46:37 -04:00
MagicBotandClaude Opus 5.5 b56d4ee243 Note lossy power action maps in ItemTemplate
item_user_power_action and item_power_action are keyed by power, so
rune step tables (repeated powers, multiple power_actions entries) are
collapsed to one entry. Nothing reads the rune data today; the new
RuneTemplate keeps the full lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:21:41 -04:00
MagicBotandClaude Opus 5.5 0fb1c078ad Normalize case and punctuation in DamageType.getDamageType
The validity check compared names case-sensitively, so mixed-case
input such as "Fire" or "Crushing" from static_power_failcondition and
static_power_poweraction, and SourceType names in the fortitude check,
all resolved to NONE. Input is now stripped of spaces, commas and
hyphens and upper-cased before lookup, so "Anti-Siege" maps to
ANTISIEGE as well.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:18:38 -04:00
MagicBotandClaude Opus 5.5 c14231afb9 Add RuneType enum and DamageType.ANTISIEGE
RuneType covers the rune_type values in the client cache data
(CLASS, DISCIPLINE, RACE, TALENT). ANTISIEGE matches the
combat_attack_resist key in the cache data.

RuneTemplate.rune_type and combat_attack_resist on both cache
templates now use these enums.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 20:16:05 -04:00
MagicBotandClaude Opus 5.5 0d48080e38 Add cache parsers and POJOs for DEED and RUNE templates
New engine.parsers package:
- cache: DeedParser and RuneParser load COBJECTS/DEED and COBJECTS/RUNE
  JSON straight from the data dir, keyed by file id. CacheParserUtil
  holds the shared JSON helpers.
- objects: standalone DeedTemplate and RuneTemplate covering every
  field in the client data, plus shared PowerGrant/UserPowerAction.
- wpak: placeholder for upcoming wpak parsers.

Not wired into server startup yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:09:41 -04:00
74 changed files with 3538 additions and 309 deletions

No files matched your search

+3 -3
View File
@@ -325,7 +325,7 @@ public class dbItemHandler extends dbHandlerBase {
for (Item item : inventory) {
if (item.template.item_type.equals(ItemType.GOLD))
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
continue;
try (Connection connection = DbManager.getConnection();
@@ -474,7 +474,7 @@ public class dbItemHandler extends dbHandlerBase {
*/
public boolean UPDATE_GOLD(final Item item, int newValue, int oldValue) {
if (!item.template.item_type.equals(ItemType.GOLD))
if (!item.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
return false;
try (Connection connection = DbManager.getConnection();
@@ -554,7 +554,7 @@ public class dbItemHandler extends dbHandlerBase {
// Write 0 if we will not modify the value from template
if (value == item.template.item_value)
if (value == item.template.cacheTemplate.getItemValue())
value = 0;
try (Connection connection = DbManager.getConnection();
+1 -1
View File
@@ -57,7 +57,7 @@ public class dbNPCHandler extends dbHandlerBase {
runeSets.put(runeSetID, runeList);
} else {
ArrayList<Integer> runeList = runeSets.get(runeSetID);
runeList.add(runeSetID);
runeList.add(runeBaseID);
runeSets.put(runeSetID, runeList);
}
}
+19 -5
View File
@@ -22,6 +22,7 @@ import engine.objects.Warehouse;
import engine.server.MBServerStatics;
import org.joda.time.DateTime;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
@@ -134,7 +135,6 @@ public class dbWarehouseHandler extends dbHandlerBase {
while (rs.next()) {
int cityUID = rs.getInt("cityUUID");
JSONObject jsonObject = new JSONObject(rs.getString("warehouse"));
City city = City.getCity(cityUID);
if (city == null) {
@@ -142,12 +142,18 @@ public class dbWarehouseHandler extends dbHandlerBase {
continue;
}
city.warehouse = new Warehouse(jsonObject);
try {
city.warehouse = new Warehouse(new JSONObject(rs.getString("warehouse")));
} catch (JSONException e) {
Logger.error("Warehouse for city " + cityUID + " failed to load: " + e.getMessage());
continue;
}
city.warehouse.city = city;
// Locate warehouse building
for (Building building : city.parentZone.zoneBuildingSet) {
if (building.getBlueprint().getBuildingGroup().equals(mbEnums.BuildingGroup.WAREHOUSE)) {
if (building.getBlueprint().getBuildingGroup().equals(mbEnums.BuildingGroup.WAREHOUSE) && building.getRank() != -1) {
city.warehouse.building = building;
break;
}
@@ -205,8 +211,16 @@ public class dbWarehouseHandler extends dbHandlerBase {
ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) {
JSONObject jsonObject = new JSONObject(rs.getString("workorder"));
WorkOrder workOrder = new WorkOrder(jsonObject);
int workOrderID = rs.getInt("workorderID");
WorkOrder workOrder;
try {
workOrder = new WorkOrder(new JSONObject(rs.getString("workorder")));
} catch (JSONException e) {
Logger.error("WorkOrder " + workOrderID + " failed to load: " + e.getMessage());
continue;
}
submitList.add(workOrder);
}
} catch (Exception e) {
+1 -1
View File
@@ -51,7 +51,7 @@ public class CreateItemCmd extends AbstractDevCmd {
for (int i = 0; i < size; i++) {
if (!playerCharacter.charItemManager.hasRoomInventory(template.item_wt)) {
if (!playerCharacter.charItemManager.hasRoomInventory(template.cacheTemplate.getItemWt())) {
ChatManager.chatSystemInfo(playerCharacter, "You are encumbered!.");
break;
}
+3 -3
View File
@@ -509,15 +509,15 @@ public class InfoCmd extends AbstractDevCmd {
Item item = (Item) target;
ItemTemplate template = ItemTemplate.templates.get(item.templateID);
output += StringUtils.addWS("Template: " + template.template_id, 20);
output += "Weight: " + template.item_wt;
output += "Weight: " + template.cacheTemplate.getItemWt();
output += newline;
DecimalFormat df = new DecimalFormat("###,###,###,###,##0");
output += StringUtils.addWS("Qty: "
+ df.format(item.getNumOfItems()), 20);
output += "Charges: " + (byte) item.chargesRemaining
+ '/' + item.template.item_initial_charges;
+ '/' + item.template.cacheTemplate.getItemInitialCharges();
output += newline;
output += "Name: " + template.item_base_name;
output += "Name: " + template.cacheTemplate.getItemBaseName();
output += newline;
output += item.getContainerInfo();
+6 -7
View File
@@ -62,15 +62,14 @@ public class MakeItemCmd extends AbstractDevCmd {
ItemTemplate template;
try {
templateID = Integer.parseInt(words[0]);
template = ItemTemplate.templates.get(words[0].toLowerCase());
template = ItemTemplate.templates.get(templateID);
if (template == null) {
throwbackError(pc, "Supplied type " + words[0]
+ " failed to parse to an Integer");
throwbackError(pc, "No item template with id " + words[0]);
return;
}
if (template.item_type == ItemType.GOLD) {
if (template.cacheTemplate.getItemType() == ItemType.GOLD) {
this.throwbackInfo(pc, "use /addgold to add gold.");
return;
}
@@ -140,8 +139,8 @@ public class MakeItemCmd extends AbstractDevCmd {
}
if ((numItems > 1)
&& (template.item_type.equals(ItemType.RESOURCE) == false)
&& (template.item_type.equals(ItemType.OFFERING)) == false)
&& (template.cacheTemplate.getItemType().equals(ItemType.RESOURCE) == false)
&& (template.cacheTemplate.getItemType().equals(ItemType.OFFERING)) == false)
numItems = 1;
CharacterItemManager cim = pc.charItemManager;
@@ -155,7 +154,7 @@ public class MakeItemCmd extends AbstractDevCmd {
for (int i = 0; i < quantity; i++) {
int weight = template.item_wt;
int weight = template.cacheTemplate.getItemWt();
if (!cim.hasRoomInventory(weight)) {
throwbackError(pc, "Not enough room in inventory for any more of this item. " + i + " produced.");
+1 -1
View File
@@ -53,7 +53,7 @@ public class PrintBankCmd extends AbstractDevCmd {
for (Item item : list) {
ItemTemplate template = ItemTemplate.templates.get(item.templateID);
throwbackInfo(pc, " " + template.item_base_name + ", count: " + item.getNumOfItems());
throwbackInfo(pc, " " + template.cacheTemplate.getItemBaseName() + ", count: " + item.getNumOfItems());
}
Item gold = cim.getGoldBank();
+1 -1
View File
@@ -34,7 +34,7 @@ public class PrintBonusesCmd extends AbstractDevCmd {
type = "Item";
tar = (Item) target;
ItemTemplate template = ItemTemplate.templates.get(((Item) tar).templateID);
name = template.item_base_name;
name = template.cacheTemplate.getItemBaseName();
} else if (target instanceof AbstractCharacter) {
tar = (AbstractCharacter) target;
name = ((AbstractCharacter) tar).getFirstName();
+3 -3
View File
@@ -66,7 +66,7 @@ public class PrintEquipCmd extends AbstractDevCmd {
throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) {
Item equip = tarMob.charItemManager.equipped.get(slot);
throwbackInfo(pc, equip.templateID + " : " + equip.template.item_base_name + ", slot: " + slot);
throwbackInfo(pc, equip.templateID + " : " + equip.template.cacheTemplate.getItemBaseName() + ", slot: " + slot);
}
return;
}
@@ -76,7 +76,7 @@ public class PrintEquipCmd extends AbstractDevCmd {
throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) {
Item equip = tarMob.charItemManager.equipped.get(slot);
throwbackInfo(pc, equip.templateID + " : " + equip.template.item_base_name + ", slot: " + slot);
throwbackInfo(pc, equip.templateID + " : " + equip.template.cacheTemplate.getItemBaseName() + ", slot: " + slot);
}
return;
}
@@ -87,7 +87,7 @@ public class PrintEquipCmd extends AbstractDevCmd {
for (mbEnums.EquipSlotType slot : list.keySet()) {
Item item = list.get(slot);
throwbackInfo(pc, " " + item.template.item_base_name + ", slot: " + slot);
throwbackInfo(pc, " " + item.template.cacheTemplate.getItemBaseName() + ", slot: " + slot);
}
}
@@ -77,16 +77,16 @@ public class PrintInventoryCmd extends AbstractDevCmd {
for (Item item : inventory) {
ItemTemplate template = ItemTemplate.templates.get(item.templateID);
if (item.template.item_type.equals(ItemType.GOLD) == false) {
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD) == false) {
String chargeInfo = "";
int chargeMax = template.item_initial_charges;
int chargeMax = template.cacheTemplate.getItemInitialCharges();
if (chargeMax > 0) {
int charges = item.chargesRemaining;
chargeInfo = " charges: " + charges + '/' + chargeMax;
}
throwbackInfo(pc, " " + template.item_base_name + ", count: " + item.getNumOfItems() + chargeInfo);
throwbackInfo(pc, " " + template.cacheTemplate.getItemBaseName() + ", count: " + item.getNumOfItems() + chargeInfo);
} else
goldCount += item.getNumOfItems();
}
+1 -1
View File
@@ -51,7 +51,7 @@ public class PrintVaultCmd extends AbstractDevCmd {
throwbackInfo(pc, "Vault for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (Item item : list) {
ItemTemplate template = ItemTemplate.templates.get(item.templateID);
throwbackInfo(pc, " " + template.item_base_name + ", count: " + item.getNumOfItems());
throwbackInfo(pc, " " + template.cacheTemplate.getItemBaseName() + ", count: " + item.getNumOfItems());
}
Item gold = cim.getGoldVault();
if (gold != null)
+2 -2
View File
@@ -37,7 +37,7 @@ public class SimulateBootyCmd extends AbstractDevCmd {
ItemTemplate template = ItemTemplate.templates.get(entry.templateID);
if (template != null)
output += "[" + entry.bootyType + "] " + template.item_base_name + " [Chance] " + entry.dropChance + newline;
output += "[" + entry.bootyType + "] " + template.cacheTemplate.getItemBaseName() + " [Chance] " + entry.dropChance + newline;
}
}
@@ -57,7 +57,7 @@ public class SimulateBootyCmd extends AbstractDevCmd {
try {
mob.loadInventory();
for (Item lootItem : mob.charItemManager.getInventory()) {
switch (lootItem.template.item_type) {
switch (lootItem.template.cacheTemplate.getItemType()) {
case EMPLOYMENTCONTRACT: //CONTRACT
Contracts.add(lootItem);
break;
+11 -8
View File
@@ -201,7 +201,7 @@ public enum BuildingManager {
ItemTemplate template = ItemTemplate.templates.get(1705032); // Elan Stone
if (!player.charItemManager.hasRoomInventory(template.item_wt))
if (!player.charItemManager.hasRoomInventory(template.cacheTemplate.getItemWt()))
return false;
if (!ItemManager.MakeItemForPlayer(template.template_id, player, amount))
@@ -211,19 +211,19 @@ public enum BuildingManager {
break;
case WAREHOUSE:
City city = building.getCity();
if (city == null)
return true;
Warehouse warehouse = city.warehouse;
Warehouse warehouse = Warehouse.destroyedWarehouses.get(building.getObjectUUID());
if (warehouse == null)
return false;
for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) {
if (!player.charItemManager.hasRoomInventory(resourceType.template.item_wt)) {
ItemTemplate resourceTemplate = ItemTemplate.templates.get(resourceType.templateID);
if (resourceTemplate == null)
continue;
if (!player.charItemManager.hasRoomInventory(resourceTemplate.cacheTemplate.getItemWt())) {
ChatManager.chatSystemInfo(player, "You can not carry any more of that item.");
return false;
}
@@ -241,6 +241,9 @@ public enum BuildingManager {
}
}
if (Warehouse.isEmpty(warehouse))
Warehouse.destroyedWarehouses.remove(building.getObjectUUID());
break;
}
+2 -2
View File
@@ -535,12 +535,12 @@ public enum ChatManager {
if (item != null) //Steal
if (success) {
String name = "";
if (item.template.item_type.equals(mbEnums.ItemType.GOLD))
if (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD))
name = amount + " gold ";
else {
String vowels = "aeiou";
ItemTemplate template = ItemTemplate.templates.get(item.templateID);
String iName = template.item_base_name;
String iName = template.cacheTemplate.getItemBaseName();
if (iName.length() > 0)
if (vowels.indexOf(iName.substring(0, 1).toLowerCase()) >= 0)
name = "an " + iName + ' ';
+8 -7
View File
@@ -18,6 +18,7 @@ import engine.net.client.msg.PowerProjectileMsg;
import engine.net.client.msg.TargetedActionMsg;
import engine.net.client.msg.UpdateStateMsg;
import engine.objects.*;
import engine.parsers.objects.RuneTemplate;
import engine.powers.DamageShield;
import engine.powers.effectmodifiers.AbstractEffectModifier;
import engine.powers.effectmodifiers.WeaponProcEffectModifier;
@@ -270,13 +271,13 @@ public enum CombatManager {
if (weapon != null && !attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) {
//check if Out of Stamina
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) {
if (attacker.getStamina() < (weapon.template.item_wt / 3f)) {
if (attacker.getStamina() < (weapon.template.cacheTemplate.getItemWt() / 3f)) {
//set auto attack job
setAutoAttackJob(attacker, slot, delay);
return;
}
}
float stam = weapon.template.item_wt / 3f;
float stam = weapon.template.cacheTemplate.getItemWt() / 3f;
stam = (stam < 1) ? 1 : stam;
attacker.modifyStamina(-(stam), attacker, true);
} else
@@ -713,7 +714,7 @@ public enum CombatManager {
// Mobs use their mobbase rune, players use their race rune.
int runeID;
ItemTemplate rune;
RuneTemplate rune;
int hand = slot.equals(mbEnums.EquipSlotType.LHELD) ? 2 : 1;
switch (attacker.getObjectType()) {
@@ -727,18 +728,18 @@ public enum CombatManager {
return null;
}
rune = ItemTemplate.templates.get(runeID);
rune = RuneTemplate._runes.get(runeID);
if (rune == null || rune.rune_natural_attacks.isEmpty())
if (rune == null || rune.rune_naturalattacks.isEmpty())
return null;
for (float[] attack : rune.rune_natural_attacks)
for (float[] attack : rune.rune_naturalattacks)
if (attack.length > 3 && (int) attack[3] == hand)
return attack;
// No entry for this hand, fall back to the first natural attack
return rune.rune_natural_attacks.get(0);
return rune.rune_naturalattacks.get(0);
}
public static int calculatePetDamage(AbstractCharacter agent) {
@@ -75,6 +75,7 @@ public enum ConfigManager {
// Mobile AI modifiers
MB_AI_CAST_FREQUENCY,
MB_AI_AGGRO_RANGE,
MB_MINION_SPAWN_TIME,
//drop rates
MB_NORMAL_EXP_RATE,
+1 -1
View File
@@ -226,7 +226,7 @@ public enum ForgeManager implements Runnable {
// Add gold and resource costs from template
production_cost.put(mbEnums.ResourceType.GOLD, template.item_value);
production_cost.put(mbEnums.ResourceType.GOLD, template.cacheTemplate.getItemValue());
production_cost.putAll(template.item_resource_cost);
// Calculate cost of prefix and suffix
+1 -1
View File
@@ -307,7 +307,7 @@ public enum GroupManager {
return false;
}
if (!item.template.item_type.equals(mbEnums.ItemType.GOLD)) //only split goldItem
if (!item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD)) //only split goldItem
return false;
Group group = getGroup(pc);
+4 -4
View File
@@ -112,7 +112,7 @@ public enum ItemManager {
public static boolean isTwoHanded(Item item) {
if (!item.template.item_type.equals(mbEnums.ItemType.WEAPON))
if (!item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.WEAPON))
return false;
return item.template.item_eq_slots_and.contains(EnumSet.of(mbEnums.EquipSlotType.LHELD, mbEnums.EquipSlotType.RHELD));
@@ -120,7 +120,7 @@ public enum ItemManager {
public static boolean isTwoHanded(ItemTemplate template) {
if (!template.item_type.equals(mbEnums.ItemType.WEAPON))
if (!template.cacheTemplate.getItemType().equals(mbEnums.ItemType.WEAPON))
return false;
return template.item_eq_slots_and.contains(EnumSet.of(mbEnums.EquipSlotType.LHELD, mbEnums.EquipSlotType.RHELD));
@@ -165,7 +165,7 @@ public enum ItemManager {
(slot == mbEnums.EquipSlotType.RHELD && equipped.get(mbEnums.EquipSlotType.LHELD) != null)))
return false;
if (item.template.item_type.equals(mbEnums.ItemType.WEAPON))
if (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.WEAPON))
if (equipped.get(slot) != null && equipped.get(slot).equals(item) == false)
return false;
@@ -197,7 +197,7 @@ public enum ItemManager {
//players can't wear 0 value items.
return item.template.item_value != 0 || Kit.IsNoobGear(item.templateID);
return item.template.cacheTemplate.getItemValue() != 0 || Kit.IsNoobGear(item.templateID);
}
+4 -4
View File
@@ -91,8 +91,8 @@ public enum LootManager {
//lastly, check mobs inventory for godly or disc runes to send a server announcement
for (Item item : mob.getInventory()) {
if ((item.templateID > 2499 && item.templateID <= 3050) || item.template.item_base_name.toLowerCase().contains("of the gods")) {
ChatSystemMsg chatMsg = new ChatSystemMsg(null, mob.getName() + " in " + mob.getParentZone().zoneName + " has found the " + item.template.item_base_name + ". Are you tough enough to take it?");
if ((item.templateID > 2499 && item.templateID <= 3050) || item.template.cacheTemplate.getItemBaseName().toLowerCase().contains("of the gods")) {
ChatSystemMsg chatMsg = new ChatSystemMsg(null, mob.getName() + " in " + mob.getParentZone().zoneName + " has found the " + item.template.cacheTemplate.getItemBaseName() + ". Are you tough enough to take it?");
chatMsg.setMessageType(10);
chatMsg.setChannel(mbEnums.ChatChannelType.SYSTEM.getChannelID());
DispatchManager.dispatchMsgToAll(chatMsg);
@@ -179,7 +179,7 @@ public enum LootManager {
if (itemUUID == 0)
return null;
if (ItemTemplate.templates.get(itemUUID).item_type.equals(mbEnums.ItemType.RESOURCE)) {
if (ItemTemplate.templates.get(itemUUID).cacheTemplate.getItemType().equals(mbEnums.ItemType.RESOURCE)) {
int amount = ThreadLocalRandom.current().nextInt(tableRow.minSpawn, tableRow.maxSpawn + 1);
return new MobLoot(mob, ItemTemplate.templates.get(itemUUID), amount, false);
}
@@ -429,7 +429,7 @@ public enum LootManager {
//early exit if the inventory of the player will not old the item
if (itemMan.hasRoomInventory(winnings.template.item_wt) == false) {
if (itemMan.hasRoomInventory(winnings.template.cacheTemplate.getItemWt()) == false) {
ErrorPopupMsg.sendErrorPopup(playerCharacter, 21);
return;
}
+12 -7
View File
@@ -315,7 +315,7 @@ public enum NPCManager {
//has main hand weapon
Item weapon = guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD);
if (weapon.template.item_primary_attr.equals(mbEnums.AttributeType.Strength))
if (engine.parsers.objects.ItemTemplate._items.get(weapon.templateID).item_primary_attr == mbEnums.AttributeType.Strength)
primaryStat = guard.getStatStrCurrent();
else
primaryStat = guard.getStatDexCurrent();
@@ -327,7 +327,7 @@ public enum NPCManager {
} else if (guard.charItemManager.equipped.containsKey(mbEnums.EquipSlotType.LHELD) && !ItemManager.isShield(guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template)) {
//has off hand weapon
Item weapon = guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD);
if (weapon.template.item_primary_attr.equals(mbEnums.AttributeType.Strength))
if (engine.parsers.objects.ItemTemplate._items.get(weapon.templateID).item_primary_attr == mbEnums.AttributeType.Strength)
primaryStat = guard.getStatStrCurrent();
else
primaryStat = guard.getStatDexCurrent();
@@ -352,8 +352,8 @@ public enum NPCManager {
int baseDef = guard.mobBase.getDefenseRating();
int armorDefense = 0;
for (Item equipped : guard.charItemManager.equipped.values())
if (equipped.template.item_type.equals(mbEnums.ItemType.ARMOR) || ItemManager.isShield(equipped.template))
armorDefense += equipped.template.item_defense_rating;
if (equipped.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.ARMOR) || ItemManager.isShield(equipped.template))
armorDefense += engine.parsers.objects.ItemTemplate._items.get(equipped.templateID).item_defense_rating;
guard.defenseRating = dexterity + baseDef + armorDefense;
}
@@ -442,7 +442,7 @@ public enum NPCManager {
return;
for (Item equipped : mob.charItemManager.equipped.values()) {
if (equipped.template.item_type.equals(mbEnums.ItemType.ARMOR)) {
if (equipped.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.ARMOR)) {
mob.resists.setResist(mbEnums.DamageType.SLASHING, mob.resists.getResist(mbEnums.DamageType.SLASHING, 0) + equipped.template.combat_attack_resist.get("SLASHING"));
mob.resists.setResist(mbEnums.DamageType.CRUSHING, mob.resists.getResist(mbEnums.DamageType.CRUSHING, 0) + equipped.template.combat_attack_resist.get("CRUSHING"));
mob.resists.setResist(mbEnums.DamageType.PIERCING, mob.resists.getResist(mbEnums.DamageType.PIERCING, 0) + equipped.template.combat_attack_resist.get("PIERCING"));
@@ -480,6 +480,11 @@ public enum NPCManager {
npc.runes.add(shopkeeper);
if (NPCManager._runeSetMap.containsKey(npc.runeSetID)) {
for (int runeID : _runeSetMap.get(npc.runeSetID)) {
// Shopkeeper is already applied above and most rune sets repeat it
if (runeID == 252620)
continue;
RuneBase rb = RuneBase.getRuneBase(runeID);
if (rb != null) {
CharacterRune toApply = new CharacterRune(rb, npc.getObjectUUID());
@@ -542,8 +547,8 @@ public enum NPCManager {
if (template == null)
return 0;
if (template.item_bane_rank > 0)
return (long) (template.item_bane_rank * 60 * 60 * 3 * 1000 * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue()));
if (template.cacheTemplate.getItemBaneRank() > 0)
return (long) (template.cacheTemplate.getItemBaneRank() * 60 * 60 * 3 * 1000 * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue()));
if (vendor.building == null)
return 600;
+3 -3
View File
@@ -2255,12 +2255,12 @@ public enum PowersManager {
if (pb.targetItem())
return true;
// TODO add these checks later
else if (pb.targetArmor() && item.template.item_type.equals(ItemType.ARMOR))
else if (pb.targetArmor() && item.template.cacheTemplate.getItemType().equals(ItemType.ARMOR))
return true;
else if (pb.targetJewelry() && item.template.item_type.equals(ItemType.JEWELRY))
else if (pb.targetJewelry() && item.template.cacheTemplate.getItemType().equals(ItemType.JEWELRY))
return true;
else
return pb.targetWeapon() && item.template.item_type.equals(ItemType.WEAPON);
return pb.targetWeapon() && item.template.cacheTemplate.getItemType().equals(ItemType.WEAPON);
} // How did we get here? all valid targets have been covered
else
return false;
+1 -1
View File
@@ -132,7 +132,7 @@ public class WorkOrder implements Delayed {
if (workOrder.vendor.getBuilding() == null)
return 58; //58: The formula is beyond the means of this facility
if (!workOrder.vendor.charItemManager.hasRoomInventory(template.item_wt))
if (!workOrder.vendor.charItemManager.hasRoomInventory(template.cacheTemplate.getItemWt()))
return 30; //30: That person cannot carry that item
if ((workOrder.prefixToken != 0 || workOrder.suffixToken != 0) &&
-30
View File
@@ -583,36 +583,6 @@ final public class FastMath {
return ret.normalize();
}
/**
* Turns a String into an Integer hash that the client will recognize.
*
* @param s The String to hash
* @return the Integer hash
*/
public static int hash(String s) {
int out = 0, pad = 0;
for (char c : s.toCharArray()) {
if (c == ' ' || c == '\\') {
pad += 5;
continue;
}
if (c > 0x40 && c < 0x60)
out ^= _rotl((byte) 0x60, pad);
out ^= _rotl((byte) (c & 0xDF), pad);
pad += 5;
}
return out;
}
/**
* Utility function for making hashes
*/
private static int _rotl(int value, int shift) {
if ((shift &= 31) == 0)
return value;
return (value << shift) | (value >> (32 - shift));
}
/**
* Gets number of seconds until next hours
*/
+30 -23
View File
@@ -15,7 +15,6 @@ import engine.math.Vector2f;
import engine.math.Vector3f;
import engine.math.Vector3fImmutable;
import engine.objects.AbstractCharacter;
import engine.objects.ItemTemplate;
import engine.objects.Shrine;
import engine.objects.Zone;
import engine.powers.EffectsBase;
@@ -824,6 +823,7 @@ public class mbEnums {
ACID,
DISEASE,
UNKNOWN,
ANTISIEGE,
// these added for immunities
ATTACK,
POWERS,
@@ -842,34 +842,26 @@ public class mbEnums {
public static DamageType getDamageType(String modName) {
String name;
if (modName.isEmpty())
return DamageType.NONE;
if(modName.toLowerCase().equals("blind"))
modName = "BLINDNESS";
// Client and DB strings vary in case and punctuation ("Fire", "Anti-Siege")
if(modName.toLowerCase().equals("powerblock"))
modName = "POWERINHIBITOR";
name = modName.replaceAll("[\\s,-]", "").toUpperCase();
//validity check for not looking up damage type that doesn't exist
boolean valid = false;
for(DamageType type : DamageType.values()){
if(type.name().equals(modName))
valid = true;
}
if (name.equals("BLIND"))
name = "BLINDNESS";
if(!valid)
return DamageType.NONE;
if (name.equals("POWERBLOCK"))
name = "POWERINHIBITOR";
DamageType damageType;
for (DamageType type : DamageType.values())
if (type.name().equals(name))
return type;
try {
damageType = DamageType.valueOf(modName.replace(",", "").toUpperCase());
} catch (Exception e) {
Logger.error(e);
return DamageType.NONE;
}
return damageType;
return DamageType.NONE;
}
}
@@ -1509,6 +1501,15 @@ public class mbEnums {
}
}
// rune_type values from the client cache data (COBJECTS/RUNE)
public enum RuneType {
CLASS,
DISCIPLINE,
RACE,
TALENT
}
public enum ShrineType {
Aelfborn(1701900, -75506007, true),
@@ -2761,7 +2762,6 @@ public class mbEnums {
public static HashMap<Integer, ResourceType> templateHashLookup = new HashMap<>();
public static HashMap<Integer, ResourceType> resourceHashLookup = new HashMap<>();
public int templateID;
public ItemTemplate template;
public int resourceHash;
public int templateHash;
public int deposit_limit;
@@ -2769,7 +2769,6 @@ public class mbEnums {
ResourceType(int templateID, int resourceHash, int templateHash, int deposit_limit, int mine_production) {
this.templateID = templateID;
this.template = ItemTemplate.templates.get(this.templateID);
this.resourceHash = resourceHash;
this.templateHash = templateHash;
this.deposit_limit = deposit_limit;
@@ -2908,6 +2907,14 @@ public class mbEnums {
EMPTY_NEWITEM;
}
// Cache JSON names. UNKNOWN is raw 0, which the client treats as no slot (sb.exe 0x4AC810).
public enum SheathSlot {
UNKNOWN,
BACK,
WAIST,
NONE
}
public enum AttributeType {
None,
Strength,
@@ -46,7 +46,7 @@ public class ActivateNPCMsgHandler extends AbstractClientMsgHandler {
// Filter hirelings by slot type
for (Item hirelings : player.getInventory()) {
if (hirelings.template.item_type.equals(ItemType.EMPLOYMENTCONTRACT)) {
if (hirelings.template.cacheTemplate.getItemType().equals(ItemType.EMPLOYMENTCONTRACT)) {
contract = DbManager.ContractQueries.GET_CONTRACT(hirelings.templateID);
@@ -78,7 +78,7 @@ public class AddItemToTradeWindowMsgHandler extends AbstractClientMsgHandler {
if (!item.validForInventory(source.getClientConnection(), source, source.charItemManager))
return false;
if (!tradingWith.hasRoomTrade(item.template.item_wt)) {
if (!tradingWith.hasRoomTrade(item.template.cacheTemplate.getItemWt())) {
dispatch = Dispatch.borrow(source, msg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY);
return false;
@@ -97,9 +97,13 @@ public class DestroyBuildingHandler extends AbstractClientMsgHandler {
if (city == null)
return true;
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
Warehouse.destroyedWarehouses.remove(building.getObjectUUID());
city.warehouse = null;
// Rubble no longer owns city.warehouse, which may be null or a newly placed warehouse
if (city.warehouse != null && building.getRank() != -1) {
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
city.warehouse = null;
}
}
@@ -24,6 +24,7 @@ import engine.net.client.msg.guild.GuildInfoMsg;
import engine.objects.Guild;
import engine.objects.Item;
import engine.objects.PlayerCharacter;
import engine.parsers.objects.DeedTemplate;
import engine.util.StringUtils;
public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
@@ -42,6 +43,7 @@ public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
Guild playerGuild;
Item charter;
DeedTemplate charterDeed;
Dispatch dispatch;
msg = (GuildCreationFinalizeMsg) baseMsg;
@@ -65,7 +67,9 @@ public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
// Item must be a valid charterType (type 10 in db)
if (charter == null || (charter.template.item_type.equals(ItemType.CHARTER) == false)) {
charterDeed = DeedTemplate._deeds.get(charter.templateID);
if (charterDeed == null || (charterDeed.item_type.equals(ItemType.CHARTER) == false)) {
ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND);
return true;
}
@@ -238,10 +238,10 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (targetItem == null)
return;
if (targetItem.template.item_type.equals(ItemType.GOLD))
if (targetItem.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
return;
if (!vendor.charItemManager.hasRoomInventory(targetItem.template.item_wt)) {
if (!vendor.charItemManager.hasRoomInventory(targetItem.template.cacheTemplate.getItemWt())) {
ErrorPopupMsg.sendErrorPopup(player, 21);
return;
}
@@ -319,7 +319,7 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (targetItem == null)
continue;
if (targetItem.template.item_type.equals(ItemType.GOLD))
if (targetItem.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
return;
if (!vendor.charItemManager.doesCharOwnThisItem(targetItem.getObjectUUID()))
@@ -328,14 +328,14 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (!vendor.charItemManager.inventoryContains(targetItem))
continue;
itemValue = targetItem.template.item_value;
itemValue = targetItem.template.cacheTemplate.getItemValue();
if (vendor.getBuilding().getStrongboxValue() + itemValue > vendor.getBuilding().getMaxGold()) {
ErrorPopupMsg.sendErrorPopup(player, 201);
break;
}
switch (targetItem.template.item_type) {
switch (targetItem.template.cacheTemplate.getItemType()) {
case EMPLOYMENTCONTRACT:
case CHARTER:
case DEED:
@@ -465,13 +465,13 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (targetItem == null)
return;
if (targetItem.template.item_type.equals(ItemType.GOLD))
if (targetItem.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
return;
if (!vendor.charItemManager.inventoryContains(targetItem))
return;
if (!player.charItemManager.hasRoomInventory(targetItem.template.item_wt))
if (!player.charItemManager.hasRoomInventory(targetItem.template.cacheTemplate.getItemWt()))
return;
player.charItemManager.buyFromNPC(targetItem, vendor);
@@ -161,7 +161,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
if (itemRet == null)
return true;
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD)) {
// this is done to prevent the temporary goldItem item
// (from the mob) from appearing in player's inventory.
// It also updates the goldItem quantity display
@@ -187,7 +187,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
//TODO send group loot message if player is grouped and visible
Group group = GroupManager.getGroup(player);
if (group != null && group.getSplitGold() && (item.template.item_type.equals(mbEnums.ItemType.GOLD) == false)) {
if (group != null && group.getSplitGold() && (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD) == false)) {
String name = item.getName();
String text = player.getFirstName() + " has looted " + name + '.';
ChatManager.chatGroupInfoCanSee(player, text);
@@ -202,7 +202,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
if (itemRet == null)
return true;
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD)) {
// this is done to prevent the temporary goldItem item
// (from the mob) from appearing in player's inventory.
// It also updates the goldItem quantity display
@@ -241,7 +241,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
//TODO send group loot message if player is grouped and visible
Group group = GroupManager.getGroup(player);
if (group != null && group.getSplitGold() && (item.template.item_type.equals(mbEnums.ItemType.GOLD) == false)) {
if (group != null && group.getSplitGold() && (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD) == false)) {
String name = item.getName();
String text = player.getFirstName() + " has looted " + name + '.';
ChatManager.chatGroupInfoCanSee(player, text);
@@ -141,17 +141,9 @@ public class ManageCityAssetMsgHandler extends AbstractClientMsgHandler {
return true;
}
City city = building.getCity();
Warehouse warehouse = Warehouse.destroyedWarehouses.get(building.getObjectUUID());
if (city == null)
return true;
Warehouse warehouse = city.warehouse;
if (warehouse == null)
return true;
if (Warehouse.isEmpty(warehouse)) {
if (warehouse == null || Warehouse.isEmpty(warehouse)) {
ErrorPopupMsg.sendErrorPopup(player, 167); // no more resources.
return true;
}
@@ -8,6 +8,7 @@ import engine.net.Dispatch;
import engine.net.client.ClientConnection;
import engine.net.client.msg.*;
import engine.objects.*;
import engine.parsers.objects.DeedTemplate;
import engine.powers.PowersBase;
import java.util.ArrayList;
@@ -78,7 +79,7 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
int uuid = item.templateID;
switch (item.template.item_type) {
switch (item.template.cacheTemplate.getItemType()) {
case DEED: //buildings
//Call add building screen here, ib.getUseID() get's building ID
@@ -99,7 +100,7 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
pam.setX(loc.getX() + 64); //offset grid from tol
pam.setY(loc.getY());
pam.setZ(loc.getZ() + 64); //offset grid from tol
pam.addPlacementInfo(item.template.deed_structure_id);
pam.addPlacementInfo(DeedTemplate._deeds.get(item.templateID).deed_structure_id);
dispatch = Dispatch.borrow(player, pam);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
@@ -187,14 +188,14 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
if (uuid > 680069 && uuid < 680074) //Handle Charter, Deed, Petition, Warrant here
break;
if (item.template.item_bane_rank > 0) {
if (item.template.cacheTemplate.getItemBaneRank() > 0) {
// Only one banestone at a time
lock.writeLock().lock();
try {
if (Bane.summonBanestone(player, origin, item.template.item_bane_rank) == true)
if (Bane.summonBanestone(player, origin, item.template.cacheTemplate.getItemBaneRank()) == true)
itemMan.consume(item);
} finally {
lock.writeLock().unlock();
@@ -225,7 +226,7 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
// Bypass for waterbuckets
if (item.template.item_type.equals(mbEnums.ItemType.BUCKET)) {
if (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.BUCKET)) {
// test for valid target type
if (target.getObjectType() == mbEnums.GameObjectType.PlayerCharacter)
@@ -18,6 +18,7 @@ import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.PlaceAssetMsg;
import engine.net.client.msg.PlaceAssetMsg.PlacementInfo;
import engine.objects.*;
import engine.parsers.objects.DeedTemplate;
import engine.server.MBServerStatics;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
@@ -135,6 +136,8 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
private static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) {
Bane bane;
if (serverZone.guild_zone == false) {
PlaceAssetMsg.sendPlaceAssetError(origin, 52, player.getName());
return false;
@@ -205,6 +208,15 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
return false;
}
// Siege equipment has its own validation and never reaches here
bane = city.getBane();
if (bane != null) {
PlaceAssetMsg.sendPlaceAssetError(origin, 74, ""); //"Only buildings of war may be placed during a bane"
return false;
}
if (placementCollisionCheck(serverZone, origin, placementInfo)) {
PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets
return false;
@@ -345,8 +357,8 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
Item contract = null;
for (Item inventoryItem : playerCharacter.getInventory()) {
if (inventoryItem.template.item_type.equals(ItemType.DEED) &&
inventoryItem.template.deed_structure_id == buildingList.getBlueprintUUID()) {
if (inventoryItem.template.cacheTemplate.getItemType().equals(ItemType.DEED) &&
DeedTemplate._deeds.get(inventoryItem.templateID).deed_structure_id == buildingList.getBlueprintUUID()) {
contract = inventoryItem;
break;
}
@@ -1341,6 +1353,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
}
Warehouse warehouse = new Warehouse(warehouseBuilding);
Warehouse.destroyedWarehouses.values().removeIf(destroyed -> destroyed.city == city);
city.warehouse = warehouse;
DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse);
@@ -85,7 +85,7 @@ public class RepairMsgHandler extends AbstractClientMsgHandler {
//make sure item is damaged and not destroyed
short dur = (short) toRepair.combat_health_current;
short max = (short) toRepair.template.combat_health_full;
short max = (short) toRepair.template.cacheTemplate.getCombatHealthFull();
//account for durability modifications
@@ -68,7 +68,7 @@ public class TransferItemFromBankMsgHandler extends AbstractClientMsgHandler {
return true;
;
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD)) {
if (!itemManager.moveGoldToInventory(item, msg.getNumItems()))
return true;
@@ -85,7 +85,7 @@ public class TransferItemFromBankMsgHandler extends AbstractClientMsgHandler {
// Not gold, process update here
if (!itemManager.hasRoomInventory(item.template.item_wt))
if (!itemManager.hasRoomInventory(item.template.cacheTemplate.getItemWt()))
return true;
if (itemManager.moveItemToInventory(item) == false)
@@ -65,7 +65,7 @@ public class TransferItemToBankMsgHandler extends AbstractClientMsgHandler {
return true;
if (item.containerType == mbEnums.ItemContainerType.INVENTORY && itemManager.isBankOpen())
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD)) {
if (!itemManager.moveGoldToBank(item, msg.getNumItems()))
return true;
UpdateGoldMsg goldMes = new UpdateGoldMsg(player);
@@ -76,7 +76,7 @@ public class TransferItemToBankMsgHandler extends AbstractClientMsgHandler {
} else {
if (!itemManager.hasRoomBank(item.template.item_wt))
if (!itemManager.hasRoomBank(item.template.cacheTemplate.getItemWt()))
return true;
if (!itemManager.moveItemToBank(item))
@@ -86,7 +86,7 @@ public class VendorBuyMsgHandler extends AbstractClientMsgHandler {
if (me.objectUUID == vendorBuyMsg.getItemID()) {
//test room available for item
if (!itemMan.hasRoomInventory(me.template.item_wt))
if (!itemMan.hasRoomInventory(me.template.cacheTemplate.getItemWt()))
return true;
int cost = me.getValue();
@@ -148,10 +148,10 @@ public class VendorBuyMsgHandler extends AbstractClientMsgHandler {
//test room available for item
if (!itemMan.hasRoomInventory(vendorItem.template.item_wt))
if (!itemMan.hasRoomInventory(vendorItem.template.cacheTemplate.getItemWt()))
return true;
int cost = vendorItem.template.item_value;
int cost = vendorItem.template.cacheTemplate.getItemValue();
if (vendorItem.flags.contains(mbEnums.ItemFlags.Identified) || vendorItem.isCustomValue())
cost = vendorItem.magicValue;
@@ -94,7 +94,7 @@ public class VendorSellMsgHandler extends AbstractClientMsgHandler {
return true;
if (npc.getParentZone() != null && npc.getParentZone().playerCityUUID != 0)
if (!npc.charItemManager.hasRoomInventory(sell.template.item_wt)) {
if (!npc.charItemManager.hasRoomInventory(sell.template.cacheTemplate.getItemWt())) {
ErrorPopupMsg.sendErrorPopup(player, 21);
return true;
}
@@ -104,7 +104,7 @@ public class VendorSellMsgHandler extends AbstractClientMsgHandler {
//get goldItem cost to sell
cost = (int) (sell.template.item_value * (sell.combat_health_current / sell.template.combat_health_full));
cost = (int) (sell.template.cacheTemplate.getItemValue() * (sell.combat_health_current / sell.template.cacheTemplate.getCombatHealthFull()));
float bargain = player.getBargain();
float profit = npc.getBuyPercent(player) + bargain;
+2 -2
View File
@@ -424,7 +424,7 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.put((byte) 1);
writer.putInt(0);
writer.putInt(templateID); //itemID
writer.putInt(template.item_value);
writer.putInt(template.cacheTemplate.getItemValue());
writer.putInt(600);
writer.put((byte) 1);
writer.put((byte) template.modTable);
@@ -444,7 +444,7 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(0);
writer.putInt(item.templateID);
writer.putInt(item.template.item_value);
writer.putInt(item.template.cacheTemplate.getItemValue());
long timeLife = item.getDateToUpgrade() - System.currentTimeMillis();
@@ -54,7 +54,7 @@ public class UpdateInventoryMsg extends ClientNetMsg {
for (Item item : list) {
if (item.template.item_type.equals(ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD)) {
if (item.getNumOfItems() == 0)
continue;
}
+6 -1
View File
@@ -16,6 +16,7 @@ import engine.net.ByteBufferWriter;
import engine.net.Protocol;
import engine.objects.Item;
import engine.objects.PlayerCharacter;
import engine.parsers.objects.DeedTemplate;
public class UseCharterMsg extends ClientNetMsg {
@@ -61,9 +62,13 @@ public class UseCharterMsg extends ClientNetMsg {
public void configure() {
DeedTemplate charterDeed;
if (close) {
for (Item i : player.getInventory()) {
if (i.template.item_type.equals(ItemType.CHARTER)) {
charterDeed = DeedTemplate._deeds.get(i.templateID);
if (charterDeed != null && charterDeed.item_type.equals(ItemType.CHARTER)) {
charterUUID = i.getObjectUUID();
break;
}
@@ -156,7 +156,7 @@ public class VendorBuyWindowMsg extends ClientNetMsg {
for (Item item : inventory) {
if (item.getOwnerID() != ownerID)
continue;
if (item.template.item_type.equals(ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD)) {
if (item.getNumOfItems() == 0)
continue;
}
+11 -10
View File
@@ -29,6 +29,7 @@ import engine.net.Dispatch;
import engine.net.client.ClientConnection;
import engine.net.client.msg.ApplyRuneMsg;
import engine.net.client.msg.UpdateStateMsg;
import engine.parsers.objects.RuneTemplate;
import engine.powers.EffectsBase;
import engine.powers.PowersBase;
import engine.powers.effectmodifiers.AbstractEffectModifier;
@@ -656,7 +657,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.LHELD));
if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
Mob mob = (Mob) abstractCharacter;
ItemTemplate rune = ItemTemplate.templates.get(mob.loadID);
RuneTemplate rune = RuneTemplate._runes.get(mob.loadID);
float minDamage = mob.mobBase.getDamageMin();
float maxDamage = mob.mobBase.getDamageMax();
int attackRating = mob.mobBase.getAttackRating();
@@ -700,7 +701,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
if (itemTemplate == null)
noWeapon = true;
else if (!weapon.template.item_type.equals(ItemType.WEAPON)) {
else if (!weapon.template.cacheTemplate.getItemType().equals(ItemType.WEAPON)) {
defaultAtrAndDamage(abstractCharacter, mainHand);
return;
} else
@@ -717,7 +718,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
if (noWeapon) {
if (mainHand) {
Item off = abstractCharacter.charItemManager.getEquipped().get(EquipSlotType.LHELD);
if (off != null && off.template != null && off.template.item_type.equals(ItemType.WEAPON))
if (off != null && off.template != null && off.template.cacheTemplate.getItemType().equals(ItemType.WEAPON))
abstractCharacter.rangeHandOne = 10 * (1 + (abstractCharacter.statStrBase / 600)); // Set
// to
// no
@@ -777,7 +778,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
min = damages[0];
max = damages[1];
strBased = weaponTemplate.item_primary_attr.equals(AttributeType.Strength);
strBased = engine.parsers.objects.ItemTemplate._items.get(weapon.templateID).item_primary_attr == AttributeType.Strength;
}
if (abstractCharacter.effects != null && abstractCharacter.effects.containsKey("DeathShroud"))
@@ -969,7 +970,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
} else
skillMod = blockSkill.getModifiedAmount();
float def = template.item_defense_rating;
float def = engine.parsers.objects.ItemTemplate._items.get(shield.templateID).item_defense_rating;
//apply item defense bonuses
@@ -996,24 +997,24 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
if (template == null)
return 0;
if (!armor.template.item_type.equals(ItemType.ARMOR))
if (!armor.template.cacheTemplate.getItemType().equals(ItemType.ARMOR))
return 0;
if (armor.template.item_skill_used.isEmpty())
return template.item_defense_rating;
return engine.parsers.objects.ItemTemplate._items.get(armor.templateID).item_defense_rating;
CharacterSkill armorSkill = abstractCharacter.skills.get(armor.template.item_skill_used);
if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) && armorSkill == null) {
Logger.error("Player " + abstractCharacter.getObjectUUID()
+ " has armor equipped without the nescessary skill to equip it");
return template.item_defense_rating;
return engine.parsers.objects.ItemTemplate._items.get(armor.templateID).item_defense_rating;
}
if (armorSkill == null)
return template.item_defense_rating; // Mobiles do not have armor skills @TODO
return engine.parsers.objects.ItemTemplate._items.get(armor.templateID).item_defense_rating; // Mobiles do not have armor skills @TODO
float def = template.item_defense_rating;
float def = engine.parsers.objects.ItemTemplate._items.get(armor.templateID).item_defense_rating;
//apply item defense bonuses
+3 -3
View File
@@ -172,7 +172,7 @@ public class Account extends AbstractGameObject {
this.vault = DbManager.ItemQueries.GET_ITEMS_FOR_ACCOUNT(this.getObjectUUID());
for (Item item : this.vault) {
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD)) {
this.vaultGold = item;
}
}
@@ -216,7 +216,7 @@ public class Account extends AbstractGameObject {
return;
if (item.containerType == mbEnums.ItemContainerType.INVENTORY && player.charItemManager.isVaultOpen()) {
if (!player.charItemManager.hasRoomVault(item.template.item_wt)) {
if (!player.charItemManager.hasRoomVault(item.template.cacheTemplate.getItemWt())) {
ItemToVaultMsgHandler.forceTransferFromVaultToInventory(msg, origin, "There is no room in your vault.");
return;
}
@@ -262,7 +262,7 @@ public class Account extends AbstractGameObject {
return;
if (item.containerType == mbEnums.ItemContainerType.VAULT && itemManager.isVaultOpen()) {
if (!itemManager.hasRoomInventory(item.template.item_wt)) {
if (!itemManager.hasRoomInventory(item.template.cacheTemplate.getItemWt())) {
ItemFromVaultMsgHandler.forceTransferFromInventoryToVault(msg, origin, "There is no room in your inventory.");
return;
}
+1
View File
@@ -433,6 +433,7 @@ public class Building extends AbstractWorldObject {
// Special handling of warehouses
if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) {
Warehouse.destroyedWarehouses.put(this.getObjectUUID(), city.warehouse);
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
city.warehouse = null;
}
+31 -31
View File
@@ -130,19 +130,19 @@ public class CharacterItemManager {
this.equipped.put(i.equipSlot, i);
break;
case BANK:
if (i.template.item_type.equals(ItemType.GOLD))
if (i.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
this.goldBank = i;
else if (this.bank.contains(i) == false)
this.bank.add(i);
break;
case INVENTORY:
if (i.template.item_type.equals(ItemType.GOLD))
if (i.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
this.goldInventory = i;
else if (this.inventory.contains(i) == false)
this.inventory.add(i);
break;
case VAULT:
if (i.template.item_type.equals(ItemType.GOLD))
if (i.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
this.goldVault = i;
else if (this.vault.contains(i) == false)
this.vault.add(i);
@@ -175,13 +175,13 @@ public class CharacterItemManager {
this.equipped.put(i.equipSlot, i);
break;
case BANK:
if (i.template.item_type.equals(ItemType.GOLD))
if (i.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
this.goldBank = i;
else if (this.bank.contains(i) == false)
this.bank.add(i);
break;
case INVENTORY:
if (i.template.item_type.equals(ItemType.GOLD))
if (i.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
this.goldInventory = i;
else if (this.inventory.contains(i) == false)
this.inventory.add(i);
@@ -511,7 +511,7 @@ public class CharacterItemManager {
if (i == null)
return false;
if (i.template.item_type.equals(ItemType.GOLD)) {
if (i.template.cacheTemplate.getItemType().equals(ItemType.GOLD)) {
if (this.getGoldInventory() != null) {
if (i.getObjectUUID() == this.getGoldInventory().getObjectUUID())
this.goldInventory = null;
@@ -541,7 +541,7 @@ public class CharacterItemManager {
}
private synchronized boolean junk(Item i, boolean updateInventory) {
if (i.template.item_type.equals(ItemType.GOLD)) {
if (i.template.cacheTemplate.getItemType().equals(ItemType.GOLD)) {
if (this.getGoldInventory().getObjectUUID() == i.getObjectUUID())
if (DbManager.ItemQueries.UPDATE_GOLD(i, 0)) {
this.getGoldInventory().setNumOfItems(0);
@@ -596,7 +596,7 @@ public class CharacterItemManager {
if (equippedContains(item)) {
fromEquip = true;
if (item.template.item_type.equals(ItemType.GOLD))
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
this.absCharacter.cancelOnUnEquip();
}
@@ -814,7 +814,7 @@ public class CharacterItemManager {
// This removes ingame item from inventory for loot.
private synchronized boolean removeItemFromInventory(Item i) {
if (i.template.item_type.equals(ItemType.GOLD)) {
if (i.template.cacheTemplate.getItemType().equals(ItemType.GOLD)) {
if (i.getObjectUUID() != this.getGoldInventory().getObjectUUID())
return false;
if (!DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) {
@@ -837,7 +837,7 @@ public class CharacterItemManager {
// This adds item to inventory for loot. Validity checks already handled
public synchronized boolean addItemToInventory(Item i) {
if (i.template.item_type.equals(ItemType.GOLD))
if (i.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
if (this.absCharacter.getObjectType() == GameObjectType.Mob) {
if (this.goldInventory == null)
loadGoldItems();
@@ -856,7 +856,7 @@ public class CharacterItemManager {
this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal());
if (i.template != null)
this.inventoryWeight += i.template.item_wt;
this.inventoryWeight += i.template.cacheTemplate.getItemWt();
return true;
}
@@ -1041,7 +1041,7 @@ public class CharacterItemManager {
if (npc.getContractID() >= 1900 && npc.getContractID() <= 1906)
eatItem = true;
if (itemToSell.template.item_type.equals(ItemType.POTION))
if (itemToSell.template.cacheTemplate.getItemType().equals(ItemType.POTION))
eatItem = true;
if (eatItem) {
@@ -1092,7 +1092,7 @@ public class CharacterItemManager {
//test inventory is not full
if (!hasRoomInventory(template.item_wt))
if (!hasRoomInventory(template.cacheTemplate.getItemWt()))
return false;
if (!itemMan.inventory.contains(purchasedItem))
@@ -1105,8 +1105,8 @@ public class CharacterItemManager {
// Reset value
purchasedItem.value = (int) (purchasedItem.template.item_value * (purchasedItem.combat_health_current / purchasedItem.template.combat_health_full));
purchasedItem.magicValue = purchasedItem.template.item_value + purchasedItem.calcMagicValue();
purchasedItem.value = (int) (purchasedItem.template.cacheTemplate.getItemValue() * (purchasedItem.combat_health_current / purchasedItem.template.cacheTemplate.getCombatHealthFull()));
purchasedItem.magicValue = purchasedItem.template.cacheTemplate.getItemValue() + purchasedItem.calcMagicValue();
// db transfer successfully, remove from this character
// skip this check if this is a mobLoot item (which is not in any inventory)
@@ -1184,9 +1184,9 @@ public class CharacterItemManager {
synchronized (lockFirst) {
synchronized (lockSecond) {
// make sure current player has item in inventory
if (lootItem.template.item_type.equals(ItemType.GOLD) && lootItem.getObjectUUID() != this.getGoldInventory().getObjectUUID() && !(this.absCharacter.getObjectType().equals(GameObjectType.Mob)))
if (lootItem.template.cacheTemplate.getItemType().equals(ItemType.GOLD) && lootItem.getObjectUUID() != this.getGoldInventory().getObjectUUID() && !(this.absCharacter.getObjectType().equals(GameObjectType.Mob)))
return null;
else if (!this.inventory.contains(lootItem) && !this.equipped.containsValue(lootItem) && !lootItem.template.item_type.equals(ItemType.GOLD))
else if (!this.inventory.contains(lootItem) && !this.equipped.containsValue(lootItem) && !lootItem.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
return null;
// get weight of item
@@ -1194,13 +1194,13 @@ public class CharacterItemManager {
if (lootItem.template == null)
return null;
int weight = lootItem.template.item_wt;
int weight = lootItem.template.cacheTemplate.getItemWt();
// make sure lootingPlayer has room for item
if (!lootItem.template.item_type.equals(ItemType.GOLD) && !looterItems.hasRoomInventory(weight))
if (!lootItem.template.cacheTemplate.getItemType().equals(ItemType.GOLD) && !looterItems.hasRoomInventory(weight))
return null;
if (lootItem.template.item_type.equals(ItemType.GOLD))
if (lootItem.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
if (amount != -1) { //from steal
int total = lootItem.getNumOfItems();
amount = (amount > total) ? total : amount;
@@ -1263,19 +1263,19 @@ public class CharacterItemManager {
* Delegates
*/
public synchronized boolean bankContains(Item item) {
if (item.template.item_type.equals(ItemType.GOLD))
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
return (this.getGoldBank() != null && this.goldBank.getObjectUUID() == item.getObjectUUID());
return bank.contains(item);
}
public synchronized boolean inventoryContains(Item item) {
if (item.template.item_type.equals(ItemType.GOLD))
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
return (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == item.getObjectUUID());
return inventory.contains(item);
}
public synchronized boolean vaultContains(Item item) {
if (item.template.item_type.equals(ItemType.GOLD))
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
return (this.getGoldVault() != null && this.goldVault.getObjectUUID() == item.getObjectUUID());
return this.account.getVault().contains(item);
}
@@ -1518,13 +1518,13 @@ public class CharacterItemManager {
item.zeroItem();
item.containerType = mbEnums.ItemContainerType.INVENTORY;
if (item.template.item_type.equals(ItemType.GOLD))
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
//only add gold item once
if (!corpse.hasGold())
corpse.setHasGold(true);
newInventory.add(item);
} else //item
if (item.template.item_type.equals(ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD)) {
int amt = item.getNumOfItems();
item.setNumOfItems(0);
MobLoot ml = new MobLoot(this.absCharacter, amt);
@@ -1676,7 +1676,7 @@ public class CharacterItemManager {
if (item == null)
continue;
weight += item.template.item_wt;
weight += item.template.cacheTemplate.getItemWt();
}
return weight;
}
@@ -1696,7 +1696,7 @@ public class CharacterItemManager {
this.bankWeight = 0;
for (Item item : this.bank) {
if (item.template != null)
this.bankWeight += item.template.item_wt;
this.bankWeight += item.template.cacheTemplate.getItemWt();
}
}
@@ -1707,7 +1707,7 @@ public class CharacterItemManager {
while (it.hasNext()) {
Item item = it.next();
if (item.template != null)
this.equipWeight += item.template.item_wt;
this.equipWeight += item.template.cacheTemplate.getItemWt();
}
}
@@ -1715,7 +1715,7 @@ public class CharacterItemManager {
this.inventoryWeight = 0;
for (Item item : this.inventory) {
if (item.template != null)
this.inventoryWeight += item.template.item_wt;
this.inventoryWeight += item.template.cacheTemplate.getItemWt();
}
}
@@ -1723,7 +1723,7 @@ public class CharacterItemManager {
this.vaultWeight = 0;
for (Item item : this.vault) {
if (item.template != null)
this.vaultWeight += item.template.item_wt;
this.vaultWeight += item.template.cacheTemplate.getItemWt();
}
}
@@ -1932,7 +1932,7 @@ public class CharacterItemManager {
return;
//don't damage noob gear, hair or beards.
if (item.template.combat_health_full == 0)
if (item.template.cacheTemplate.getCombatHealthFull() == 0)
return;
if (!item.isCanDestroy())
+3 -3
View File
@@ -296,14 +296,14 @@ public class Corpse extends AbstractWorldObject {
//get weight of item
int weight = item.template.item_wt;
int weight = item.template.cacheTemplate.getItemWt();
//make sure looter has room for item
if (item.template.item_type.equals(ItemType.GOLD) == false && !looterItems.hasRoomInventory(weight))
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD) == false && !looterItems.hasRoomInventory(weight))
return null;
//attempt to transfer item in db
if (item.template.item_type.equals(ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD)) {
if (!looterItems.moveGoldToInventory(item, item.getNumOfItems()))
return null;
} else if (!item.moveItemToInventory(looter))
+17 -17
View File
@@ -69,13 +69,13 @@ public class Item extends AbstractWorldObject {
super();
this.templateID = templateID;
this.template = ItemTemplate.templates.get(templateID);
this.chargesRemaining = this.template.item_initial_charges;
this.combat_health_current = this.template.combat_health_full;
this.chargesRemaining = this.template.cacheTemplate.getItemInitialCharges();
this.combat_health_current = this.template.cacheTemplate.getCombatHealthFull();
this.equipSlot = EquipSlotType.NONE;
this.containerType = ItemContainerType.NONE;
this.numberOfItems = 1;
this.flags.addAll(this.template.item_flags);
this.value = this.template.item_value;
this.flags.addAll(this.template.cacheTemplate.getItemFlags());
this.value = this.template.cacheTemplate.getItemValue();
this.magicValue = this.value + calcMagicValue();
loadTemplateEnchantments();
}
@@ -166,7 +166,7 @@ public class Item extends AbstractWorldObject {
this.value = rs.getInt("value");
if (this.value == 0) {
this.value = this.template.item_value;
this.value = this.template.cacheTemplate.getItemValue();
this.magicValue = this.value + calcMagicValue();
} else
this.magicValue = this.value; // Value was overridden in vendor inventory
@@ -238,7 +238,7 @@ public class Item extends AbstractWorldObject {
writer.putString(item.name); // Unknown. pad?
writer.put((byte) 1); // End Datablock byte
writer.putFloat(item.template.combat_health_full);
writer.putFloat(item.template.cacheTemplate.getCombatHealthFull());
writer.putFloat((float) item.combat_health_current);
writer.put((byte) 1); // End Datablock byte
@@ -255,7 +255,7 @@ public class Item extends AbstractWorldObject {
} else
writer.putInt(item.numberOfItems); // Amount of gold
} else
writer.putInt(item.template.item_value);
writer.putInt(item.template.cacheTemplate.getItemValue());
writer.putInt(item.getValue());
@@ -298,7 +298,7 @@ public class Item extends AbstractWorldObject {
writer.put((byte) 0);
if (item.template.item_type.equals(ItemType.EMPLOYMENTCONTRACT) == false) {
if (item.template.cacheTemplate.getItemType().equals(ItemType.EMPLOYMENTCONTRACT) == false) {
writer.putShort((short) 0);
return;
}
@@ -463,7 +463,7 @@ public class Item extends AbstractWorldObject {
int serialized = 0;
for (Item item : list) {
if (item.template.item_type.equals(ItemType.GOLD))
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
if (item.numberOfItems == 0)
continue;
try {
@@ -497,7 +497,7 @@ public class Item extends AbstractWorldObject {
int serialized = 0;
for (Item item : list) {
if (item.template.item_type.equals(ItemType.GOLD))
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
if (item.numberOfItems == 0)
continue;
try {
@@ -955,7 +955,7 @@ public class Item extends AbstractWorldObject {
// No enchantments for negative id items in the db. Early exit.
if (this.objectUUID < 0) {
this.magicValue = this.template.item_value + calcMagicValue();
this.magicValue = this.template.cacheTemplate.getItemValue() + calcMagicValue();
return;
}
@@ -971,8 +971,8 @@ public class Item extends AbstractWorldObject {
// Value is overridden the db
if (this.value == this.template.item_value)
this.magicValue = this.template.item_value + calcMagicValue();
if (this.value == this.template.cacheTemplate.getItemValue())
this.magicValue = this.template.cacheTemplate.getItemValue() + calcMagicValue();
}
public void addPermanentEnchantment(String enchantID, int rank) {
@@ -1116,11 +1116,11 @@ public class Item extends AbstractWorldObject {
else
modifiedValue = this.value;
if (this.template.item_initial_charges > 0)
modifiedValue = modifiedValue * (this.chargesRemaining / this.template.item_initial_charges);
if (this.template.cacheTemplate.getItemInitialCharges() > 0)
modifiedValue = modifiedValue * (this.chargesRemaining / this.template.cacheTemplate.getItemInitialCharges());
if (this.template.combat_health_full > 0)
modifiedValue = (int) (modifiedValue * (this.combat_health_current / this.template.combat_health_full));
if (this.template.cacheTemplate.getCombatHealthFull() > 0)
modifiedValue = (int) (modifiedValue * (this.combat_health_current / this.template.cacheTemplate.getCombatHealthFull()));
return modifiedValue;
}
+28
View File
@@ -10,6 +10,7 @@ package engine.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import engine.parsers.objects.CObjectTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
@@ -90,8 +91,14 @@ public class ItemTemplate {
public final EnumSet<mbEnums.DisciplineType> item_disc_res = EnumSet.noneOf(mbEnums.DisciplineType.class);
public int item_level_req;
public mbEnums.SexType item_sex_req;
// Lossy: some runes list the same power several times with different
// arguments (a step table, e.g. PET_DISC). Only the last entry is kept.
// engine.parsers.objects.RuneTemplate keeps the full list.
public final HashMap<String, int[]> item_user_power_action = new HashMap<>();
public final HashMap<String, Integer> item_power_grant = new HashMap<>();
// Lossy: some runes carry up to 35 power_actions entries per power
// (a step table, e.g. HNT-031). Only the first entry is kept.
// engine.parsers.objects.RuneTemplate keeps the full list.
public final HashMap<String, int[]> item_power_action = new HashMap<>();
public final HashMap<mbEnums.ResourceType, Integer> item_resource_cost = new HashMap<>();
public int modTable;
@@ -151,6 +158,9 @@ public class ItemTemplate {
public final ArrayList<Integer> rune_helper_monster_types = new ArrayList<>();
public int item_bane_rank;
// Cache JSON counterpart, so consumers can move to the new templates one field at a time.
public CObjectTemplate cacheTemplate;
public ItemTemplate(JSONObject jsonObject) {
try {
@@ -640,4 +650,22 @@ public class ItemTemplate {
}
}
// Run after CObjectIndex.build(); the DB templates load before the cache parsers.
public static void linkCacheTemplates() {
int missingCount = 0;
for (ItemTemplate template : templates.values()) {
template.cacheTemplate = CObjectTemplate.lookup(template.template_id);
if (template.cacheTemplate == null) {
missingCount++;
Logger.warn(template.template_id + ": " + template.obj_name + " has no cache template");
}
}
Logger.info("Linked " + (templates.size() - missingCount) + " of " + templates.size() + " item templates to cache templates");
}
}
+26 -2
View File
@@ -436,6 +436,30 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
return mobile;
}
// MB_MINION_SPAWN_TIME (seconds) shortens guard/siege minion spawns for testing; 0 or blank keeps the normal timers.
private static int minionSpawnDelay(int defaultDelay) {
String overrideValue;
int overrideDelay;
overrideValue = ConfigManager.MB_MINION_SPAWN_TIME.getValue();
if (overrideValue == null || overrideValue.trim().isEmpty())
return defaultDelay;
try {
overrideDelay = Integer.parseInt(overrideValue.trim());
} catch (NumberFormatException e) {
Logger.warn("MB_MINION_SPAWN_TIME is not a number: " + overrideValue);
return defaultDelay;
}
if (overrideDelay <= 0)
return defaultDelay;
return overrideDelay;
}
public static synchronized Mob createGuardMinion(Mob guardCaptain, short level, String minionName) {
Mob minionMobile;
@@ -455,7 +479,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
minionMobile.deathTime = System.currentTimeMillis();
minionMobile.guardCaptain = guardCaptain;
minionMobile.spawnDelay = (int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60;
minionMobile.spawnDelay = minionSpawnDelay((int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60);
minionMobile.behaviourType = mbEnums.MobBehaviourType.GuardMinion;
minionMobile.agentType = AIAgentType.GUARDMINION;
minionMobile.guardedCity = guardCaptain.guardedCity;
@@ -514,7 +538,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
siegeMinion.behaviourType = MobBehaviourType.SiegeEngine;
siegeMinion.agentType = AIAgentType.SIEGEENGINE;
siegeMinion.bindLoc = Vector3fImmutable.ZERO;
siegeMinion.spawnDelay = (60 * 15);
siegeMinion.spawnDelay = minionSpawnDelay(60 * 15);
siegeMinion.runAfterLoad();
+2 -2
View File
@@ -45,7 +45,7 @@ public final class MobLoot extends Item {
this.ownerID = mob.getObjectUUID();
this.objectUUID = generateId();
if (quantity == 0 && template.item_type == ItemType.RESOURCE)
if (quantity == 0 && template.cacheTemplate.getItemType() == ItemType.RESOURCE)
quantity = 1;
if (quantity > 0)
@@ -105,7 +105,7 @@ public final class MobLoot extends Item {
if (isDeleted)
return null;
if (this.template.item_type.equals(ItemType.GOLD))
if (this.template.cacheTemplate.getItemType().equals(ItemType.GOLD))
return null;
Item item = this;
+1 -1
View File
@@ -969,7 +969,7 @@ public class NPC extends AbstractCharacter {
if (itemTemplate.item_skill_required.values().iterator().hasNext())
skillRequired = itemTemplate.item_skill_required.values().iterator().next();
boolean exclude = skillRequired == 0 && ItemTemplate.templates.get(itemID).item_type.equals(ItemType.WEAPON);
boolean exclude = skillRequired == 0 && ItemTemplate.templates.get(itemID).cacheTemplate.getItemType().equals(ItemType.WEAPON);
if (skillRequired <= maxSkill && !exclude)
filteredItemList.add(itemID);
}
+3 -3
View File
@@ -1356,7 +1356,7 @@ public class PlayerCharacter extends AbstractCharacter {
ItemTemplate ab = armor.template;
if (ab == null)
return 0f;
return ab.item_bulk_factor;
return engine.parsers.objects.ItemTemplate._items.get(armor.templateID).item_bulk_factor;
}
public static void InitializeSkillsOnLoad(PlayerCharacter pc) {
@@ -2819,11 +2819,11 @@ public class PlayerCharacter extends AbstractCharacter {
ItemTemplate ib = item.template;
if (ib != null) {
if ((item.template.item_type.equals(ItemType.WEAPON))
if ((item.template.cacheTemplate.getItemType().equals(ItemType.WEAPON))
&& (item.template.item_skill_used.equals(type) || ib.item_skill_mastery_used.equals(type)))
return true;
return (item.template.item_type.equals(ItemType.ARMOR))
return (item.template.cacheTemplate.getItemType().equals(ItemType.ARMOR))
&& (item.template.item_skill_used.equals(type));
}
+1 -1
View File
@@ -162,7 +162,7 @@ public class Resists {
if (armor == null)
return phys;
if (armor.template.item_type.equals(mbEnums.ItemType.ARMOR)) {
if (armor.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.ARMOR)) {
phys[0] += armor.template.combat_attack_resist.get("SLASHING");
phys[1] += armor.template.combat_attack_resist.get("CRUSHING");
phys[2] += armor.template.combat_attack_resist.get("PIERCING");
+2 -2
View File
@@ -256,13 +256,13 @@ public class Shrine extends AbstractWorldObject implements Comparable<Shrine> {
if (!boonOwner.charItemManager.doesCharOwnThisItem(boonItem.getObjectUUID()))
return false;
for (ShrineType boonShrineType : boonItem.template.item_offering_info.keySet()) {
for (ShrineType boonShrineType : boonItem.template.cacheTemplate.getItemOfferingInfo().keySet()) {
if (boonShrineType != shrineType)
continue;
//Same Shrine Type, add favors and stop loop.
int amount = boonItem.template.item_offering_info.get(boonShrineType) * boonItem.getNumOfItems();
int amount = boonItem.template.cacheTemplate.getItemOfferingInfo().get(boonShrineType) * boonItem.getNumOfItems();
int oldAmount = this.favors;
if (!DbManager.ShrineQueries.updateFavors(this, this.favors + amount, oldAmount)) {
+8 -5
View File
@@ -31,6 +31,9 @@ import java.util.concurrent.ConcurrentHashMap;
public class Warehouse {
// Destroyed warehouses keyed by rubble building UUID; memory only so the rubble can be looted
public static final ConcurrentHashMap<Integer, Warehouse> destroyedWarehouses = new ConcurrentHashMap<>();
public EnumSet<mbEnums.ResourceType> locked = EnumSet.noneOf(mbEnums.ResourceType.class);
public Building building;
public City city;
@@ -301,10 +304,10 @@ public class Warehouse {
return false;
}
if (resource.template.item_type.equals(mbEnums.ItemType.GOLD))
if (resource.template.cacheTemplate.getItemType().equals(mbEnums.ItemType.GOLD))
resourceType = mbEnums.ResourceType.GOLD;
else
resourceType = mbEnums.ResourceType.valueOf(ItemTemplate.templates.get(resource.templateID).item_base_name.toUpperCase());
resourceType = mbEnums.ResourceType.valueOf(ItemTemplate.templates.get(resource.templateID).cacheTemplate.getItemBaseName().toUpperCase());
if (transaction)
AddTransactionToWarehouse(warehouse, playerCharacter.getObjectType(), playerCharacter.getObjectUUID(), mbEnums.TransactionType.DEPOSIT, resourceType, amount);
@@ -429,7 +432,7 @@ public class Warehouse {
return false;
if (addToInventory)
if (!itemMan.hasRoomInventory(template.item_wt)) {
if (!itemMan.hasRoomInventory(template.cacheTemplate.getItemWt())) {
ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item.");
return false;
}
@@ -498,7 +501,7 @@ public class Warehouse {
if (playerCharacter == null)
return false;
ItemTemplate template = ItemTemplate.templates.get(resourceType);
ItemTemplate template = ItemTemplate.templates.get(resourceType.templateID);
if (template == null)
return false;
@@ -514,7 +517,7 @@ public class Warehouse {
if (itemMan == null)
return false;
if (!itemMan.hasRoomInventory(template.item_wt)) {
if (!itemMan.hasRoomInventory(template.cacheTemplate.getItemWt())) {
ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item.");
return false;
}
+235
View File
@@ -0,0 +1,235 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.mbEnums;
import engine.parsers.objects.CObjectTemplate;
import engine.parsers.objects.DeedTemplate;
import engine.parsers.objects.ItemTemplate;
import engine.parsers.objects.PowerGrant;
import engine.parsers.objects.RuneTemplate;
import engine.parsers.objects.UserPowerAction;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
// Lookups across the ITEM, DEED and RUNE cache templates.
// The JSON references runes, skills and powers by name; the server and DB use ids and tokens.
// Build once after all three parsers have loaded. Read only afterwards.
public class CObjectIndex {
private static final EnumMap<mbEnums.MonsterType, ArrayList<Integer>> raceRuneIds = new EnumMap<>(mbEnums.MonsterType.class);
private static final EnumMap<mbEnums.ClassType, Integer> classRuneIds = new EnumMap<>(mbEnums.ClassType.class);
private static final EnumMap<mbEnums.DisciplineType, Integer> disciplineRuneIds = new EnumMap<>(mbEnums.DisciplineType.class);
private static final HashMap<Integer, ArrayList<RuneTemplate>> runesBySubTypeToken = new HashMap<>();
private static final HashMap<String, Integer> skillTokens = new HashMap<>();
private static final HashMap<Integer, String> skillNames = new HashMap<>();
private static final HashMap<String, Integer> powerTokens = new HashMap<>();
private static final HashMap<Integer, String> powerNames = new HashMap<>();
// Same order as the client's id space checks (JSON-Refactor-Plan D1).
public static CObjectTemplate lookup(int templateId) {
CObjectTemplate template;
template = ItemTemplate._items.get(templateId);
if (template == null)
template = DeedTemplate._deeds.get(templateId);
if (template == null)
template = RuneTemplate._runes.get(templateId);
return template;
}
// Male and female runes share a race, so most races return two ids.
public static ArrayList<Integer> getRaceRuneIds(mbEnums.MonsterType race) {
return raceRuneIds.getOrDefault(race, new ArrayList<>());
}
public static Integer getClassRuneId(mbEnums.ClassType classType) {
return classRuneIds.get(classType);
}
public static Integer getDisciplineRuneId(mbEnums.DisciplineType discipline) {
return disciplineRuneIds.get(discipline);
}
// DB race and class tokens are SBStringHash(rune_sub_type). Mob and talent runes share sub types.
public static ArrayList<RuneTemplate> getRunesBySubTypeToken(int token) {
return runesBySubTypeToken.getOrDefault(token, new ArrayList<>());
}
public static Integer getSkillToken(String skillName) {
return skillTokens.get(skillName);
}
public static String getSkillName(int token) {
return skillNames.get(token);
}
public static Integer getPowerToken(String powerName) {
return powerTokens.get(powerName);
}
public static String getPowerName(int token) {
return powerNames.get(token);
}
public static void build() {
int overlapCount = 0;
for (Integer templateId : RuneTemplate._runes.keySet())
if (ItemTemplate._items.containsKey(templateId) || DeedTemplate._deeds.containsKey(templateId))
overlapCount++;
for (Integer templateId : DeedTemplate._deeds.keySet())
if (ItemTemplate._items.containsKey(templateId))
overlapCount++;
if (overlapCount > 0)
Logger.warn(overlapCount + " template ids exist in more than one of ITEM/DEED/RUNE");
for (RuneTemplate rune : RuneTemplate._runes.values()) {
indexRuneName(rune);
indexSubTypeToken(rune);
indexRuneNames(rune);
}
for (ItemTemplate item : ItemTemplate._items.values()) {
addSkill(item.item_skill_used);
addSkill(item.item_skill_mastery_used);
indexItemFields(item.item_skill_req, item.item_user_power_action, item.item_power_grant, item.item_power_action);
}
for (DeedTemplate deed : DeedTemplate._deeds.values()) {
addSkill(deed.item_skill_used);
addSkill(deed.item_skill_mastery_used);
indexItemFields(deed.item_skill_req, deed.item_user_power_action, deed.item_power_grant, deed.item_power_action);
}
Logger.info("Indexed " + raceRuneIds.size() + " races, " + classRuneIds.size() + " classes, "
+ disciplineRuneIds.size() + " disciplines, " + runesBySubTypeToken.size() + " rune sub type tokens, "
+ skillTokens.size() + " skills, " + powerTokens.size() + " powers");
}
private static void indexRuneName(RuneTemplate rune) {
Integer previousId;
if (rune.rune_type == null || rune.rune_sub_type.isEmpty())
return;
try {
switch (rune.rune_type) {
case RACE:
// Mob runes reuse race sub types; only creation races map to player race ids
if (rune.rune_is_standard_character_creation)
raceRuneIds.computeIfAbsent(CacheParserUtil.readEnum(mbEnums.MonsterType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"),
k -> new ArrayList<>()).add(rune.template_id);
break;
case CLASS:
previousId = classRuneIds.put(CacheParserUtil.readEnum(mbEnums.ClassType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"), rune.template_id);
if (previousId != null)
Logger.warn(rune.template_id + ": class " + rune.rune_sub_type + " already indexed by " + previousId);
break;
case DISCIPLINE:
previousId = disciplineRuneIds.put(CacheParserUtil.readEnum(mbEnums.DisciplineType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"), rune.template_id);
if (previousId != null)
Logger.warn(rune.template_id + ": discipline " + rune.rune_sub_type + " already indexed by " + previousId);
break;
default:
break;
}
} catch (IllegalArgumentException e) {
// The Pet class rune has no ClassType
Logger.warn(e.getMessage());
}
}
private static void indexSubTypeToken(RuneTemplate rune) {
int token;
if (!rune.rune_sub_type.isEmpty())
token = Hasher.SBStringHash(rune.rune_sub_type);
else if (rune.rune_sub_type_hash != 0)
token = rune.rune_sub_type_hash;
else
return;
runesBySubTypeToken.computeIfAbsent(token, k -> new ArrayList<>()).add(rune);
}
private static void indexRuneNames(RuneTemplate rune) {
for (RuneTemplate.SkillGrant grant : rune.rune_skill_grant) {
addSkill(grant.skill_type);
grant.skill_granted_skills.keySet().forEach(CObjectIndex::addSkill);
grant.skill_granted_powers.keySet().forEach(CObjectIndex::addPower);
}
rune.rune_skill_adj.keySet().forEach(CObjectIndex::addSkill);
addSkill(rune.item_skill_used);
addSkill(rune.item_skill_mastery_used);
indexItemFields(rune.item_skill_req, rune.item_user_power_action, rune.item_power_grant, rune.item_power_action);
}
private static void indexItemFields(Map<String, Integer> skillReq, ArrayList<UserPowerAction> userPowerActions,
ArrayList<PowerGrant> powerGrants, Map<String, ArrayList<int[]>> powerActions) {
skillReq.keySet().forEach(CObjectIndex::addSkill);
for (UserPowerAction action : userPowerActions)
addPower(action.power);
for (PowerGrant grant : powerGrants) {
addPower(grant.power_type);
grant.power_granted_skills.keySet().forEach(CObjectIndex::addSkill);
grant.power_granted_powers.keySet().forEach(CObjectIndex::addPower);
}
powerActions.keySet().forEach(CObjectIndex::addPower);
}
private static void addSkill(String skillName) {
addName(skillName, skillTokens, skillNames, "skill");
}
private static void addPower(String powerName) {
addName(powerName, powerTokens, powerNames, "power");
}
private static void addName(String name, HashMap<String, Integer> tokens, HashMap<Integer, String> names, String kind) {
int token;
String previousName;
// readHashLabel keeps unresolved hashes as decimal strings; those are not names
if (name == null || name.isEmpty() || name.matches("-?\\d+") || tokens.containsKey(name))
return;
token = Hasher.SBStringHash(name);
tokens.put(name, token);
previousName = names.putIfAbsent(token, name);
if (previousName != null)
Logger.warn(kind + " token " + token + " shared by '" + previousName + "' and '" + name + "'");
}
}
+353
View File
@@ -0,0 +1,353 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.math.Vector3fImmutable;
import engine.parsers.objects.PowerGrant;
import engine.parsers.objects.UserPowerAction;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Shared JSON helpers for the COBJECTS cache parsers.
// Only generic reading lives here; each parser owns its own field mapping.
public class CacheParserUtil {
// Files are named <template id>.json
public static File[] listJsonFiles(String folderPath) {
File folder = new File(folderPath);
File[] jsonFiles = folder.listFiles((dir, name) -> name.endsWith(".json") && hasTemplateId(name));
if (jsonFiles == null) {
Logger.error("Cache folder not found: " + folder.getAbsolutePath());
return new File[0];
}
return jsonFiles;
}
// Template ids are 32-bit. The client cache has RUNE/10788957847552, which is
// 2512 << 32 (the Huntress id in the high word); files like it are skipped.
private static boolean hasTemplateId(String fileName) {
String idText = fileName.substring(0, fileName.length() - ".json".length());
try {
Integer.parseInt(idText);
return true;
} catch (NumberFormatException e) {
Logger.warn("Skipping " + fileName + ": name is not a 32-bit template id");
return false;
}
}
public static int templateIdFromFile(File jsonFile) {
String fileName = jsonFile.getName();
return Integer.parseInt(fileName.substring(0, fileName.length() - ".json".length()));
}
public static JSONObject readJsonFile(File jsonFile) throws IOException {
byte[] fileData = Files.readAllBytes(jsonFile.toPath());
return new JSONObject(new String(fileData, StandardCharsets.UTF_8));
}
// [x, y, z] -> vector. Returns null for an empty array.
public static Vector3fImmutable readVector3(JSONArray vectorJson) {
if (vectorJson.length() < 3)
return null;
return new Vector3fImmutable(vectorJson.getFloat(0), vectorJson.getFloat(1), vectorJson.getFloat(2));
}
// Client strings carry spaces, hyphens and commas that enum constants
// can't ("Half-Giant", "Battle Magus").
public static String enumName(String value) {
String name = value;
name = name.replaceAll("\\s", "");
name = name.replaceAll("-", "");
name = name.replaceAll(",", "");
return name;
}
// Exact match on the cleaned name first, then upper case (ResourceType).
// Unknown values throw so the template fails to load.
public static <E extends Enum<E>> E readEnum(Class<E> enumClass, String value, int templateId, String fieldName) {
String name = enumName(value);
try {
return Enum.valueOf(enumClass, name);
} catch (IllegalArgumentException e) {
// fall through to upper case
}
try {
return Enum.valueOf(enumClass, name.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(templateId + ": " + fieldName + " has no " + enumClass.getSimpleName() + " for '" + value + "'");
}
}
public static <E extends Enum<E>> EnumSet<E> readEnumSet(Class<E> enumClass, JSONArray valuesJson, int templateId, String fieldName) {
EnumSet<E> enumSet = EnumSet.noneOf(enumClass);
for (int i = 0; i < valuesJson.length(); i++)
enumSet.add(readEnum(enumClass, valuesJson.getString(i), templateId, fieldName));
return enumSet;
}
// [{keyName: "Strength", valueName: 40}, ...] -> enum map
public static <E extends Enum<E>> EnumMap<E, Integer> readEnumValueMap(Class<E> enumClass, JSONArray entriesJson,
String keyName, String valueName,
int templateId, String fieldName) {
EnumMap<E, Integer> enumMap = new EnumMap<>(enumClass);
JSONObject entry;
for (int i = 0; i < entriesJson.length(); i++) {
entry = entriesJson.getJSONObject(i);
enumMap.put(readEnum(enumClass, entry.getString(keyName), templateId, fieldName), entry.getInt(valueName));
}
return enumMap;
}
// [{keyName: "Sword", valueName: 20}, ...] -> map in file order
public static LinkedHashMap<String, Integer> readStringValueMap(JSONArray entriesJson, String keyName, String valueName) {
LinkedHashMap<String, Integer> valueMap = new LinkedHashMap<>();
JSONObject entry;
for (int i = 0; i < entriesJson.length(); i++) {
entry = entriesJson.getJSONObject(i);
valueMap.put(entry.getString(keyName), (int) entry.getLong(valueName));
}
return valueMap;
}
// {"SLASHING": 0.0, ...} -> enum map
public static <E extends Enum<E>> EnumMap<E, Float> readEnumFloatMap(Class<E> enumClass, JSONObject mapJson,
int templateId, String fieldName) {
EnumMap<E, Float> enumMap = new EnumMap<>(enumClass);
for (String key : mapJson.keySet())
enumMap.put(readEnum(enumClass, key, templateId, fieldName), mapJson.getFloat(key));
return enumMap;
}
// Hash and flag fields are still unsigned in the JSON; read as long and wrapped to int.
public static int[] readIntArray(JSONArray valuesJson) {
int[] values = new int[valuesJson.length()];
for (int i = 0; i < valuesJson.length(); i++)
values[i] = (int) valuesJson.getLong(i);
return values;
}
public static float[] readFloatArray(JSONArray valuesJson) {
float[] values = new float[valuesJson.length()];
for (int i = 0; i < valuesJson.length(); i++)
values[i] = valuesJson.getFloat(i);
return values;
}
public static ArrayList<Integer> readIntList(JSONArray valuesJson) {
ArrayList<Integer> values = new ArrayList<>();
for (int i = 0; i < valuesJson.length(); i++)
values.add((int) valuesJson.getLong(i));
return values;
}
// [[a, b], [a, b], ...] -> list of int[]
public static ArrayList<int[]> readIntArrays(JSONArray arraysJson) {
ArrayList<int[]> arrays = new ArrayList<>();
for (int i = 0; i < arraysJson.length(); i++)
arrays.add(readIntArray(arraysJson.getJSONArray(i)));
return arrays;
}
// Sparse data values are mixed bool/int/string; stored as strings.
public static HashMap<String, String> readSparseData(JSONObject sparseJson) {
HashMap<String, String> sparseData = new HashMap<>();
for (String key : sparseJson.keySet())
sparseData.put(key, sparseJson.get(key).toString());
return sparseData;
}
// Some fields are a name when set and the number 0 when not
// (obj_sound_table, item_skill_used). The number becomes "".
public static String readLabel(JSONObject json, String key) {
Object value = json.get(key);
if (value instanceof String)
return (String) value;
return "";
}
// Like readLabel, but a non-zero number is a hash the exporter could not
// name; it is kept as its decimal string rather than dropped.
public static String readHashLabel(JSONObject json, String key, int templateId) {
Object value = json.get(key);
long hash;
if (value instanceof String)
return (String) value;
hash = json.getLong(key);
if (hash == 0)
return "";
Logger.warn(templateId + ": " + key + " has unresolved hash " + hash);
return Long.toString(hash);
}
public static ArrayList<UserPowerAction> readUserPowerActions(JSONArray actionsJson) {
ArrayList<UserPowerAction> userPowerActions = new ArrayList<>();
JSONObject entry;
for (int i = 0; i < actionsJson.length(); i++) {
entry = actionsJson.getJSONObject(i);
userPowerActions.add(new UserPowerAction(entry.getString("power"), readIntArray(entry.getJSONArray("arguments"))));
}
return userPowerActions;
}
// [[key, value], ...] -> map in file order. Keys are names when the exporter
// knows the hash, otherwise the raw hash, which is kept as its decimal string.
public static LinkedHashMap<String, Integer> readGrantPairs(JSONArray pairsJson, int templateId, String fieldName) {
LinkedHashMap<String, Integer> grantPairs = new LinkedHashMap<>();
JSONArray pair;
Object key;
String keyName;
for (int i = 0; i < pairsJson.length(); i++) {
pair = pairsJson.getJSONArray(i);
key = pair.get(0);
if (key instanceof String) {
keyName = (String) key;
} else {
keyName = Long.toString(pair.getLong(0));
Logger.warn(templateId + ": " + fieldName + " has unresolved hash " + keyName);
}
grantPairs.put(keyName, pair.getInt(1));
}
return grantPairs;
}
public static ArrayList<PowerGrant> readPowerGrants(JSONArray grantsJson, int templateId) {
ArrayList<PowerGrant> powerGrants = new ArrayList<>();
JSONObject entry;
PowerGrant powerGrant;
for (int i = 0; i < grantsJson.length(); i++) {
entry = grantsJson.getJSONObject(i);
powerGrant = new PowerGrant();
powerGrant.power_type = entry.getString("power_type");
powerGrant.power_value = entry.getInt("power_value");
powerGrant.power_granted_attrs.putAll(readGrantPairs(entry.getJSONArray("power_granted_attrs"), templateId, "power_granted_attrs"));
powerGrant.power_granted_skills.putAll(readGrantPairs(entry.getJSONArray("power_granted_skills"), templateId, "power_granted_skills"));
powerGrant.power_granted_powers.putAll(readGrantPairs(entry.getJSONArray("power_granted_powers"), templateId, "power_granted_powers"));
powerGrant.power_monster_types = entry.getJSONArray("power_monster_types");
powerGrants.add(powerGrant);
}
return powerGrants;
}
// [{power_type: "X", power_actions: [{power_arguments: [a, b]}, ...]}, ...]
// -> power_type -> list of argument arrays (one per power_actions entry)
public static LinkedHashMap<String, ArrayList<int[]>> readPowerActions(JSONArray actionsJson) {
LinkedHashMap<String, ArrayList<int[]>> powerActions = new LinkedHashMap<>();
JSONObject entry;
JSONArray argumentsJson;
ArrayList<int[]> arguments;
for (int i = 0; i < actionsJson.length(); i++) {
entry = actionsJson.getJSONObject(i);
argumentsJson = entry.getJSONArray("power_actions");
arguments = new ArrayList<>();
for (int j = 0; j < argumentsJson.length(); j++)
arguments.add(readIntArray(argumentsJson.getJSONObject(j).getJSONArray("power_arguments")));
powerActions.put(entry.getString("power_type"), arguments);
}
return powerActions;
}
}
+180
View File
@@ -0,0 +1,180 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.DeedTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads DEED templates from the client cache JSON (COBJECTS/DEED/<id>.json).
public class DeedParser {
private static final String deedPath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/DEED/";
public static HashMap<Integer, DeedTemplate> parseDeedFolder() {
HashMap<Integer, DeedTemplate> deedTemplates = new HashMap<>();
File[] deedFiles = CacheParserUtil.listJsonFiles(deedPath);
int templateId;
JSONObject deedJson;
int failedCount = 0;
for (File deedFile : deedFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(deedFile);
deedJson = CacheParserUtil.readJsonFile(deedFile);
deedTemplates.put(templateId, parseDeedEntry(templateId, deedJson));
} catch (Exception e) {
Logger.error(deedFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + deedTemplates.size() + " of " + deedFiles.length + " deed templates");
// Partial deed data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return deedTemplates;
}
private static DeedTemplate parseDeedEntry(int templateId, JSONObject deedJson) {
DeedTemplate deed = new DeedTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
String flag;
deed.template_id = templateId;
// obj_
deed.obj_name = deedJson.getString("obj_name");
deed.obj_pickable = deedJson.getBoolean("obj_pickable");
deed.obj_gravity = deedJson.getFloat("obj_gravity");
deed.obj_cull_distance = deedJson.getFloat("obj_cull_distance");
deed.obj_scale = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_scale"));
deed.obj_render_object = deedJson.getInt("obj_render_object");
deed.obj_double_fusion = deedJson.getBoolean("obj_double_fusion");
deed.obj_forward_vector = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_forward_vector"));
deed.obj_tracking_name = deedJson.getString("obj_tracking_name");
deed.obj_max_tracking_distance = deedJson.getFloat("obj_max_tracking_distance");
deed.obj_icon = deedJson.getInt("obj_icon");
deed.obj_gravity_f = deedJson.getFloat("obj_gravity_f");
deed.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(deedJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
deed.obj_arc_hardpoint_list = deedJson.getJSONArray("obj_arc_hardpoint_list");
deed.obj_sparse_data.putAll(CacheParserUtil.readSparseData(deedJson.getJSONObject("obj_sparse_data")));
deed.obj_render_object_low_detail = deedJson.getInt("obj_render_object_low_detail");
deed.obj_default_alignment = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_default_alignment"));
deed.obj_sound_table = CacheParserUtil.readLabel(deedJson, "obj_sound_table");
// combat_
deed.combat_health_current = deedJson.getFloat("combat_health_current");
deed.combat_health_full = deedJson.getFloat("combat_health_full");
deed.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, deedJson.getJSONObject("combat_attack_resist"),
templateId, "combat_attack_resist"));
deed.combat_powers = deedJson.getJSONArray("combat_powers");
// item_
deed.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, deedJson.getString("item_type"), templateId, "item_type");
deed.item_eq_slots_value = deedJson.getInt("item_eq_slots_value");
deed.item_eq_slots_type = deedJson.getBoolean("item_eq_slots_type");
deed.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, deedJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
deed.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, deedJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
deed.item_takeable = deedJson.getBoolean("item_takeable");
deed.item_value = deedJson.getInt("item_value");
deed.item_wt = deedJson.getInt("item_wt");
deed.item_passive_defense_mod = deedJson.getFloat("item_passive_defense_mod");
deed.item_base_name = deedJson.getString("item_base_name");
deed.item_dsc = deedJson.getString("item_dsc");
deed.item_render_object_female = deedJson.getInt("item_render_object_female");
deed.item_health_full = deedJson.getFloat("item_health_full");
deed.item_skill_used = CacheParserUtil.readLabel(deedJson, "item_skill_used");
deed.item_skill_mastery_used = CacheParserUtil.readLabel(deedJson, "item_skill_mastery_used");
deed.item_parry_anim_id = deedJson.getInt("item_parry_anim_id");
deed.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, deedJson.getJSONArray("item_offering_info"),
"offering_type", "offering_value", templateId, "item_offering_info"));
// "None" is the client's empty marker, not a flag
itemFlags = deedJson.getJSONArray("item_flags");
for (int i = 0; i < itemFlags.length(); i++) {
flag = itemFlags.getString(i);
if (flag.equals("None"))
continue;
deed.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
}
deed.item_use_flags.addAll(CacheParserUtil.readEnumSet(mbEnums.ItemUseFlags.class, deedJson.getJSONArray("item_use_flags"), templateId, "item_use_flags"));
deed.item_post_item_id = deedJson.getInt("item_post_item_id");
deed.item_initial_charges = deedJson.getInt("item_initial_charges");
deed.item_book_arcana = deedJson.getInt("item_book_arcana");
deed.item_skill_req.putAll(CacheParserUtil.readStringValueMap(deedJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
raceReq = deedJson.getJSONObject("item_race_req");
deed.item_race_req_restrict = raceReq.getBoolean("restrict");
deed.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
classReq = deedJson.getJSONObject("item_class_req");
deed.item_class_req_restrict = classReq.getBoolean("restrict");
deed.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
discReq = deedJson.getJSONObject("item_disc_req");
deed.item_disc_req_restrict = discReq.getBoolean("restrict");
deed.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
deed.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, deedJson.getJSONArray("item_attr_req"),
"attr_type", "attr_value", templateId, "item_attr_req"));
deed.item_level_req = deedJson.getInt("item_level_req");
deed.item_rank_req = deedJson.getInt("item_rank_req");
deed.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, deedJson.getString("item_sex_req"), templateId, "item_sex_req");
deed.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(deedJson.getJSONArray("item_user_power_action")));
deed.item_power_grant.addAll(CacheParserUtil.readPowerGrants(deedJson.getJSONArray("item_power_grant"), templateId));
deed.item_power_action.putAll(CacheParserUtil.readPowerActions(deedJson.getJSONArray("item_power_action")));
deed.item_sheathable = deedJson.getBoolean("item_sheathable");
deed.item_has_stub = deedJson.getBoolean("item_has_stub");
deed.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, deedJson.getJSONArray("item_offering_adjustments"),
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
deed.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, deedJson.getJSONArray("item_resource_costs"),
"resource_type", "resource_value", templateId, "item_resource_costs"));
deed.item_bane_rank = deedJson.getInt("item_bane_rank");
deed.item_ignore_saved_actions = deedJson.getBoolean("item_ignore_saved_actions");
// deed_
deed.deed_type = deedJson.getInt("deed_type");
deed.deed_furniture_id = deedJson.getInt("deed_furniture_id");
deed.deed_target_id = deedJson.getInt("deed_target_id");
deed.deed_employment = deedJson.getInt("deed_employment");
deed.deed_start_rank = deedJson.getInt("deed_start_rank");
deed.deed_name_lookup = deedJson.getInt("deed_name_lookup");
deed.deed_indoors = deedJson.getBoolean("deed_indoors");
deed.deed_is_fortress = deedJson.getBoolean("deed_is_fortress");
deed.deed_namelookup_val = deedJson.getFloat("deed_namelookup_val");
deed.deed_custom_city = deedJson.getBoolean("deed_custom_city");
deed.deed_structure_id = deedJson.getInt("deed_structure_id");
return deed;
}
}
+248
View File
@@ -0,0 +1,248 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.ItemTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads ITEM templates from the client cache JSON (COBJECTS/ITEM/<id>.json).
public class ItemParser {
private static final String itemPath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/ITEM/";
public static HashMap<Integer, ItemTemplate> parseItemFolder() {
HashMap<Integer, ItemTemplate> itemTemplates = new HashMap<>();
File[] itemFiles = CacheParserUtil.listJsonFiles(itemPath);
int templateId;
JSONObject itemJson;
int failedCount = 0;
for (File itemFile : itemFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(itemFile);
itemJson = CacheParserUtil.readJsonFile(itemFile);
itemTemplates.put(templateId, parseItemEntry(templateId, itemJson));
} catch (Exception e) {
Logger.error(itemFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + itemTemplates.size() + " of " + itemFiles.length + " item templates");
// Partial item data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return itemTemplates;
}
private static ItemTemplate parseItemEntry(int templateId, JSONObject itemJson) {
ItemTemplate item = new ItemTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
JSONArray useFlags;
String flag;
Object stubJson;
JSONArray stubHolder;
item.template_id = templateId;
// obj_
item.obj_name = itemJson.getString("obj_name");
item.obj_pickable = itemJson.getBoolean("obj_pickable");
item.obj_gravity = itemJson.getFloat("obj_gravity");
item.obj_cull_distance = itemJson.getFloat("obj_cull_distance");
item.obj_scale = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_scale"));
item.obj_render_object = itemJson.getInt("obj_render_object");
item.obj_double_fusion = itemJson.getBoolean("obj_double_fusion");
item.obj_forward_vector = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_forward_vector"));
item.obj_tracking_name = itemJson.getString("obj_tracking_name");
item.obj_max_tracking_distance = itemJson.getFloat("obj_max_tracking_distance");
item.obj_icon = itemJson.getInt("obj_icon");
item.obj_gravity_f = itemJson.getFloat("obj_gravity_f");
item.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(itemJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
item.obj_arc_hardpoint_list = itemJson.getJSONArray("obj_arc_hardpoint_list");
item.obj_sparse_data.putAll(CacheParserUtil.readSparseData(itemJson.getJSONObject("obj_sparse_data")));
item.obj_render_object_low_detail = itemJson.getInt("obj_render_object_low_detail");
item.obj_default_alignment = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_default_alignment"));
item.obj_sound_table = CacheParserUtil.readHashLabel(itemJson, "obj_sound_table", templateId);
// combat_
item.combat_health_current = itemJson.getFloat("combat_health_current");
item.combat_health_full = itemJson.getFloat("combat_health_full");
item.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, itemJson.getJSONObject("combat_attack_resist"),
templateId, "combat_attack_resist"));
item.combat_powers = itemJson.getJSONArray("combat_powers");
// item_
item.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, itemJson.getString("item_type"), templateId, "item_type");
item.item_eq_slots_value = itemJson.getInt("item_eq_slots_value");
item.item_eq_slots_type = itemJson.getBoolean("item_eq_slots_type");
// The exporter writes ["NONE"] for an empty slot mask
item.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, itemJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
item.item_eq_slots_or.remove(mbEnums.EquipSlotType.NONE);
item.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, itemJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
item.item_eq_slots_and.remove(mbEnums.EquipSlotType.NONE);
item.item_takeable = itemJson.getBoolean("item_takeable");
item.item_value = itemJson.getInt("item_value");
item.item_wt = itemJson.getInt("item_wt");
item.item_passive_defense_mod = itemJson.getFloat("item_passive_defense_mod");
item.item_base_name = itemJson.getString("item_base_name");
item.item_dsc = itemJson.getString("item_dsc");
item.item_render_object_female = itemJson.getInt("item_render_object_female");
item.item_health_full = itemJson.getFloat("item_health_full");
item.item_skill_used = CacheParserUtil.readHashLabel(itemJson, "item_skill_used", templateId);
item.item_skill_mastery_used = CacheParserUtil.readHashLabel(itemJson, "item_skill_mastery_used", templateId);
item.item_parry_anim_id = itemJson.getInt("item_parry_anim_id");
item.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, itemJson.getJSONArray("item_offering_info"),
"offering_type", "offering_value", templateId, "item_offering_info"));
// The client reads these blocks by item type, not by key presence
if (item.item_type == mbEnums.ItemType.WEAPON) {
item.item_weapon = parseWeapon(templateId, itemJson.getJSONObject("item_weapon"));
item.item_primary_attr = CacheParserUtil.readEnum(mbEnums.AttributeType.class, itemJson.getString("item_primary_attr"), templateId, "item_primary_attr");
item.item_secondary_attr = CacheParserUtil.readEnum(mbEnums.AttributeType.class, itemJson.getString("item_secondary_attr"), templateId, "item_secondary_attr");
}
if (item.item_type == mbEnums.ItemType.ARMOR) {
item.item_bulk_factor = itemJson.getFloat("item_bulk_factor");
item.item_defense_rating = itemJson.getInt("item_defense_rating");
}
// "None" is bit 0 in the client's flag tables, not a real flag
itemFlags = itemJson.getJSONArray("item_flags");
for (int i = 0; i < itemFlags.length(); i++) {
flag = itemFlags.getString(i);
if (flag.equals("None"))
continue;
item.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
}
useFlags = itemJson.getJSONArray("item_use_flags");
for (int i = 0; i < useFlags.length(); i++) {
flag = useFlags.getString(i);
if (flag.equals("NONE"))
continue;
item.item_use_flags.add(CacheParserUtil.readEnum(mbEnums.ItemUseFlags.class, flag, templateId, "item_use_flags"));
}
item.item_post_item_id = itemJson.getInt("item_post_item_id");
item.item_initial_charges = itemJson.getInt("item_initial_charges");
item.item_book_arcana = itemJson.getInt("item_book_arcana");
item.item_skill_req.putAll(CacheParserUtil.readStringValueMap(itemJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
raceReq = itemJson.getJSONObject("item_race_req");
item.item_race_req_restrict = raceReq.getBoolean("restrict");
item.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
classReq = itemJson.getJSONObject("item_class_req");
item.item_class_req_restrict = classReq.getBoolean("restrict");
item.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
discReq = itemJson.getJSONObject("item_disc_req");
item.item_disc_req_restrict = discReq.getBoolean("restrict");
item.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
item.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, itemJson.getJSONArray("item_attr_req"),
"attr_type", "attr_value", templateId, "item_attr_req"));
item.item_level_req = itemJson.getInt("item_level_req");
item.item_rank_req = itemJson.getInt("item_rank_req");
item.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, itemJson.getString("item_sex_req"), templateId, "item_sex_req");
item.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(itemJson.getJSONArray("item_user_power_action")));
item.item_power_grant.addAll(CacheParserUtil.readPowerGrants(itemJson.getJSONArray("item_power_grant"), templateId));
item.item_power_action.putAll(CacheParserUtil.readPowerActions(itemJson.getJSONArray("item_power_action")));
item.item_sheathable = itemJson.getBoolean("item_sheathable");
if (item.item_sheathable) {
item.item_sheath_slot = CacheParserUtil.readEnum(mbEnums.SheathSlot.class, itemJson.getString("item_sheath_slot"), templateId, "item_sheath_slot");
item.item_sheath_offset = CacheParserUtil.readVector3(itemJson.getJSONArray("item_sheath_offset"));
item.item_sheath_rotation = CacheParserUtil.readVector3(itemJson.getJSONArray("item_sheath_rotation"));
}
stubJson = itemJson.get("item_has_stub");
if (stubJson instanceof JSONArray) {
stubHolder = (JSONArray) stubJson;
item.item_has_stub = true;
item.item_stub_flag = stubHolder.getBoolean(0);
item.item_stub_id = stubHolder.getInt(1);
} else {
item.item_has_stub = itemJson.getBoolean("item_has_stub");
}
item.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, itemJson.getJSONArray("item_offering_adjustments"),
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
item.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, itemJson.getJSONArray("item_resource_costs"),
"resource_type", "resource_value", templateId, "item_resource_costs"));
item.item_bane_rank = itemJson.getInt("item_bane_rank");
item.item_ignore_saved_actions = itemJson.getBoolean("item_ignore_saved_actions");
return item;
}
private static ItemTemplate.Weapon parseWeapon(int templateId, JSONObject weaponJson) {
ItemTemplate.Weapon weapon = new ItemTemplate.Weapon();
JSONArray damageJson;
JSONObject damageEntry;
ItemTemplate.Damage damage;
weapon.weapon_wepspeed = weaponJson.getFloat("weapon_wepspeed");
weapon.weapon_max_range = weaponJson.getFloat("weapon_max_range");
weapon.weapon_projectile_id = weaponJson.getInt("weapon_projectile_id");
weapon.weapon_projectile_speed = weaponJson.getFloat("weapon_projectile_speed");
weapon.weapon_combat_idle_anim = weaponJson.getInt("weapon_combat_idle_anim");
damageJson = weaponJson.getJSONArray("weapon_damage");
for (int i = 0; i < damageJson.length(); i++) {
damageEntry = damageJson.getJSONObject(i);
damage = new ItemTemplate.Damage();
damage.damage_type = CacheParserUtil.readEnum(mbEnums.DamageType.class, damageEntry.getString("damage_type"), templateId, "weapon_damage");
damage.damage_min = damageEntry.getInt("damage_min");
damage.damage_max = damageEntry.getInt("damage_max");
weapon.weapon_damage.add(damage);
}
weapon.weapon_attack_anim_right.addAll(CacheParserUtil.readIntArrays(weaponJson.getJSONArray("weapon_attack_anim_right")));
weapon.weapon_attack_anim_left.addAll(CacheParserUtil.readIntArrays(weaponJson.getJSONArray("weapon_attack_anim_left")));
return weapon;
}
}
+134
View File
@@ -0,0 +1,134 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.objects.AbstractGameObject;
import engine.objects.BaseClass;
import engine.objects.ItemTemplate;
import engine.parsers.objects.RuneTemplate;
import org.pmw.tinylog.Logger;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
// Dev-only check that compares DB-built objects with the cache JSON before a loader is switched.
// Enabled by the optional env var MB_PARITY_CHECK; not in the ConfigManager enum, which requires every entry.
public class ParityCheck {
private static final String FLAG_NAME = "MB_PARITY_CHECK";
private static final LinkedHashMap<String, Consumer<ParityCheck>> comparators = new LinkedHashMap<>();
private final String table;
private int comparedCount = 0;
private int differenceCount = 0;
private int missingCount = 0;
static {
register("static_rune_baseclass", ParityCheck::compareBaseClasses);
register("static_item_templates", ParityCheck::compareItemTemplates);
}
private ParityCheck(String table) {
this.table = table;
}
public static boolean isEnabled() {
return ConfigManager.configMap.getOrDefault(FLAG_NAME, "false").equalsIgnoreCase("true");
}
public static void register(String table, Consumer<ParityCheck> comparator) {
comparators.put(table, comparator);
}
public static void run() {
ParityCheck check;
for (String table : comparators.keySet()) {
check = new ParityCheck(table);
try {
comparators.get(table).accept(check);
} catch (Exception e) {
Logger.error("Parity: " + table + ": comparator failed: " + e);
}
Logger.info("Parity: " + table + ": " + check.comparedCount + " compared, "
+ check.differenceCount + " differences, " + check.missingCount + " missing in json");
}
}
// Returns false when the row has no JSON counterpart, so the comparator can skip its fields.
public boolean compareRow(int id, Object jsonObject) {
comparedCount++;
if (jsonObject == null) {
missingCount++;
Logger.warn("Parity: " + table + "/" + id + ": missing in json");
return false;
}
return true;
}
public void compareField(int id, String field, Object dbValue, Object jsonValue) {
if (Objects.equals(dbValue, jsonValue))
return;
differenceCount++;
Logger.warn("Parity: " + table + "/" + id + "/" + field + ": db=" + dbValue + " json=" + jsonValue);
}
private static void compareBaseClasses(ParityCheck check) {
ConcurrentHashMap<Integer, AbstractGameObject> cache;
BaseClass baseClass;
RuneTemplate rune;
cache = DbManager.getMap(mbEnums.GameObjectType.BaseClass);
if (cache == null) {
Logger.warn("Parity: static_rune_baseclass: no BaseClass objects loaded");
return;
}
for (AbstractGameObject cached : cache.values()) {
baseClass = (BaseClass) cached;
rune = RuneTemplate._runes.get(baseClass.getObjectUUID());
if (!check.compareRow(baseClass.getObjectUUID(), rune))
continue;
check.compareField(baseClass.getObjectUUID(), "name", baseClass.getName(), rune.obj_name);
}
}
// A wrong item_type means the bridge picked the wrong folder for an id found in more than one.
private static void compareItemTemplates(ParityCheck check) {
for (ItemTemplate template : ItemTemplate.templates.values()) {
if (!check.compareRow(template.template_id, template.cacheTemplate))
continue;
check.compareField(template.template_id, "obj_name", template.obj_name, template.cacheTemplate.getObjName());
check.compareField(template.template_id, "item_type", template.item_type, template.cacheTemplate.getItemType());
}
}
}
+332
View File
@@ -0,0 +1,332 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.RuneTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads RUNE templates from the client cache JSON (COBJECTS/RUNE/<id>.json).
public class RuneParser {
private static final String runePath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/RUNE/";
public static HashMap<Integer, RuneTemplate> parseRuneFolder() {
HashMap<Integer, RuneTemplate> runeTemplates = new HashMap<>();
File[] runeFiles = CacheParserUtil.listJsonFiles(runePath);
int templateId;
JSONObject runeJson;
int failedCount = 0;
for (File runeFile : runeFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(runeFile);
runeJson = CacheParserUtil.readJsonFile(runeFile);
runeTemplates.put(templateId, parseRuneEntry(templateId, runeJson));
} catch (Exception e) {
Logger.error(runeFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + runeTemplates.size() + " of " + runeFiles.length + " rune templates");
// Partial rune data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return runeTemplates;
}
private static RuneTemplate parseRuneEntry(int templateId, JSONObject runeJson) {
RuneTemplate rune = new RuneTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
String flag;
Object subType;
JSONObject speedJson;
JSONObject groupJson;
JSONArray skillGrantsJson;
JSONObject skillGrantJson;
RuneTemplate.SkillGrant skillGrant;
JSONArray skillAdjJson;
JSONObject skillAdjEntry;
JSONArray naturalAttacksJson;
JSONArray inventoryJson;
JSONObject inventoryEntryJson;
RuneTemplate.InventoryEntry inventoryEntry;
JSONArray bodyPartsJson;
JSONObject bodyPartJson;
RuneTemplate.BodyPart bodyPart;
rune.template_id = templateId;
// obj_
rune.obj_name = runeJson.getString("obj_name");
rune.obj_pickable = runeJson.getBoolean("obj_pickable");
rune.obj_gravity = runeJson.getFloat("obj_gravity");
rune.obj_cull_distance = runeJson.getFloat("obj_cull_distance");
rune.obj_scale = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_scale"));
rune.obj_render_object = runeJson.getInt("obj_render_object");
rune.obj_double_fusion = runeJson.getBoolean("obj_double_fusion");
rune.obj_forward_vector = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_forward_vector"));
rune.obj_tracking_name = runeJson.getString("obj_tracking_name");
rune.obj_max_tracking_distance = runeJson.getFloat("obj_max_tracking_distance");
rune.obj_icon = runeJson.getInt("obj_icon");
rune.obj_gravity_f = runeJson.getFloat("obj_gravity_f");
rune.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
rune.obj_arc_hardpoint_list = runeJson.getJSONArray("obj_arc_hardpoint_list");
rune.obj_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("obj_sparse_data")));
rune.obj_render_object_low_detail = runeJson.getInt("obj_render_object_low_detail");
rune.obj_default_alignment = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_default_alignment"));
rune.obj_sound_table = CacheParserUtil.readLabel(runeJson, "obj_sound_table");
// combat_
rune.combat_health_current = runeJson.getFloat("combat_health_current");
rune.combat_health_full = runeJson.getFloat("combat_health_full");
rune.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, runeJson.getJSONObject("combat_attack_resist"),
templateId, "combat_attack_resist"));
rune.combat_powers = runeJson.getJSONArray("combat_powers");
// item_
rune.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, runeJson.getString("item_type"), templateId, "item_type");
rune.item_eq_slots_value = runeJson.getInt("item_eq_slots_value");
rune.item_eq_slots_type = runeJson.getBoolean("item_eq_slots_type");
rune.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
rune.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
rune.item_takeable = runeJson.getBoolean("item_takeable");
rune.item_value = runeJson.getInt("item_value");
rune.item_wt = runeJson.getInt("item_wt");
rune.item_passive_defense_mod = runeJson.getFloat("item_passive_defense_mod");
rune.item_base_name = runeJson.getString("item_base_name");
rune.item_dsc = runeJson.getString("item_dsc");
rune.item_render_object_female = runeJson.getInt("item_render_object_female");
rune.item_health_full = runeJson.getFloat("item_health_full");
rune.item_skill_used = CacheParserUtil.readLabel(runeJson, "item_skill_used");
rune.item_skill_mastery_used = CacheParserUtil.readLabel(runeJson, "item_skill_mastery_used");
rune.item_parry_anim_id = runeJson.getInt("item_parry_anim_id");
rune.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_info"),
"offering_type", "offering_value", templateId, "item_offering_info"));
// "None" is the client's empty marker, not a flag
itemFlags = runeJson.getJSONArray("item_flags");
for (int i = 0; i < itemFlags.length(); i++) {
flag = itemFlags.getString(i);
if (flag.equals("None"))
continue;
rune.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
}
rune.item_use_flags.addAll(CacheParserUtil.readEnumSet(mbEnums.ItemUseFlags.class, runeJson.getJSONArray("item_use_flags"), templateId, "item_use_flags"));
rune.item_post_item_id = runeJson.getInt("item_post_item_id");
rune.item_initial_charges = runeJson.getInt("item_initial_charges");
rune.item_book_arcana = runeJson.getInt("item_book_arcana");
rune.item_skill_req.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
raceReq = runeJson.getJSONObject("item_race_req");
rune.item_race_req_restrict = raceReq.getBoolean("restrict");
rune.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
classReq = runeJson.getJSONObject("item_class_req");
rune.item_class_req_restrict = classReq.getBoolean("restrict");
rune.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
discReq = runeJson.getJSONObject("item_disc_req");
rune.item_disc_req_restrict = discReq.getBoolean("restrict");
rune.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
rune.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("item_attr_req"),
"attr_type", "attr_value", templateId, "item_attr_req"));
rune.item_level_req = runeJson.getInt("item_level_req");
rune.item_rank_req = runeJson.getInt("item_rank_req");
rune.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("item_sex_req"), templateId, "item_sex_req");
rune.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(runeJson.getJSONArray("item_user_power_action")));
rune.item_power_grant.addAll(CacheParserUtil.readPowerGrants(runeJson.getJSONArray("item_power_grant"), templateId));
rune.item_power_action.putAll(CacheParserUtil.readPowerActions(runeJson.getJSONArray("item_power_action")));
rune.item_sheathable = runeJson.getBoolean("item_sheathable");
rune.item_has_stub = runeJson.getBoolean("item_has_stub");
rune.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_adjustments"),
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
rune.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, runeJson.getJSONArray("item_resource_costs"),
"resource_type", "resource_value", templateId, "item_resource_costs"));
rune.item_bane_rank = runeJson.getInt("item_bane_rank");
rune.item_ignore_saved_actions = runeJson.getBoolean("item_ignore_saved_actions");
// rune_
rune.rune_type = CacheParserUtil.readEnum(mbEnums.RuneType.class, runeJson.getString("rune_type"), templateId, "rune_type");
subType = runeJson.get("rune_sub_type");
if (subType instanceof String) {
rune.rune_sub_type = (String) subType;
} else {
rune.rune_sub_type = "";
rune.rune_sub_type_hash = (int) runeJson.getLong("rune_sub_type");
}
rune.rune_is_standard_character_creation = runeJson.getBoolean("rune_is_standard_character_creation");
rune.rune_creation_cost = runeJson.getInt("rune_creation_cost");
rune.rune_rank = runeJson.getInt("rune_rank");
rune.rune_pracs_per_level = runeJson.getInt("rune_pracs_per_level");
rune.rune_exp_req_to_level = runeJson.getFloat("rune_exp_req_to_level");
rune.rune_sex = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("rune_sex"), templateId, "rune_sex");
rune.rune_class_icon = runeJson.getInt("rune_class_icon");
rune.rune_health = runeJson.getInt("rune_health");
rune.rune_mana = runeJson.getInt("rune_mana");
rune.rune_stamina = runeJson.getInt("rune_stamina");
rune.rune_min_damage = runeJson.getFloat("rune_min_damage");
rune.rune_max_damage = runeJson.getFloat("rune_max_damage");
rune.rune_attack = runeJson.getInt("rune_attack");
rune.rune_defense = runeJson.getInt("rune_defense");
rune.rune_level = runeJson.getInt("rune_level");
speedJson = runeJson.getJSONObject("rune_speed");
for (String speedKey : speedJson.keySet())
rune.rune_speed.put(CacheParserUtil.readEnum(mbEnums.MovementType.class, speedKey, templateId, "rune_speed"), speedJson.getFloat(speedKey));
groupJson = runeJson.getJSONObject("rune_group");
rune.rune_group_type = groupJson.getInt("group_type");
rune.rune_group_is_faction = groupJson.getBoolean("group_is_faction");
rune.rune_group_is_guild = groupJson.getBoolean("group_is_guild");
rune.rune_dsc = runeJson.getString("rune_dsc");
rune.rune_fx_txt = runeJson.getString("rune_fx_txt");
rune.rune_group_tactics = runeJson.getLong("rune_group_tactics");
rune.rune_group_role_set = runeJson.getLong("rune_group_role_set");
rune.rune_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_enemy_monster_types"),
templateId, "rune_enemy_monster_types"));
rune.rune_groupee_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_groupee_monster_types")));
rune.rune_helper_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_helper_monster_types")));
rune.rune_not_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_not_enemy_monster_types"),
templateId, "rune_not_enemy_monster_types"));
rune.rune_enemy_gender = runeJson.getJSONArray("rune_enemy_gender");
skillGrantsJson = runeJson.getJSONArray("rune_skill_grant");
for (int i = 0; i < skillGrantsJson.length(); i++) {
skillGrantJson = skillGrantsJson.getJSONObject(i);
skillGrant = new RuneTemplate.SkillGrant();
skillGrant.skill_type = skillGrantJson.getString("skill_type");
skillGrant.skill_value = skillGrantJson.getInt("skill_value");
skillGrant.skill_granted_attrs.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_attrs"),
templateId, "skill_granted_attrs"));
skillGrant.skill_granted_skills.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_skills"),
templateId, "skill_granted_skills"));
skillGrant.skill_granted_powers.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_powers"),
templateId, "skill_granted_powers"));
skillGrant.skill_monster_types = skillGrantJson.getJSONArray("skill_monster_types");
rune.rune_skill_grant.add(skillGrant);
}
skillAdjJson = runeJson.getJSONArray("rune_skill_adj");
for (int i = 0; i < skillAdjJson.length(); i++) {
skillAdjEntry = skillAdjJson.getJSONObject(i);
rune.rune_skill_adj.put(skillAdjEntry.getString("skill_type"),
CacheParserUtil.readIntArrays(skillAdjEntry.getJSONArray("skill_adjusts")));
}
rune.rune_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_attr_adj"),
"attr_type", "attr_value", templateId, "rune_attr_adj"));
rune.rune_max_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_max_attr_adj"),
"attr_type", "attr_value", templateId, "rune_max_attr_adj"));
naturalAttacksJson = runeJson.getJSONArray("rune_naturalattacks");
for (int i = 0; i < naturalAttacksJson.length(); i++)
rune.rune_naturalattacks.add(CacheParserUtil.readFloatArray(naturalAttacksJson.getJSONArray(i)));
rune.rune_enchantment_type = runeJson.getJSONArray("rune_enchantment_type");
inventoryJson = runeJson.getJSONArray("rune_inventory_contents");
for (int i = 0; i < inventoryJson.length(); i++) {
inventoryEntryJson = inventoryJson.getJSONObject(i);
inventoryEntry = new RuneTemplate.InventoryEntry();
inventoryEntry.type = inventoryEntryJson.getString("type");
if (inventoryEntryJson.has("gold"))
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("gold"));
else if (inventoryEntryJson.has("table"))
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("table"));
else
Logger.error(templateId + ": rune_inventory_contents entry of type " + inventoryEntry.type + " has no values");
rune.rune_inventory_contents.add(inventoryEntry);
}
rune.rune_renderable = runeJson.getBoolean("rune_renderable");
// Render fields are absent on some runes
if (runeJson.has("rune_scale_factor"))
rune.rune_scale_factor = CacheParserUtil.readVector3(runeJson.getJSONArray("rune_scale_factor"));
if (runeJson.has("rune_skeleton"))
rune.rune_skeleton = runeJson.getInt("rune_skeleton");
if (runeJson.has("rune_slope_hugger"))
rune.rune_slope_hugger = runeJson.getBoolean("rune_slope_hugger");
if (runeJson.has("rune_can_fly"))
rune.rune_can_fly = runeJson.getBoolean("rune_can_fly");
if (runeJson.has("rune_death_effect"))
rune.rune_death_effect = runeJson.getInt("rune_death_effect");
if (runeJson.has("rune_tombstone_id"))
rune.rune_tombstone_id = runeJson.getInt("rune_tombstone_id");
if (runeJson.has("rune_body_parts")) {
bodyPartsJson = runeJson.getJSONArray("rune_body_parts");
for (int i = 0; i < bodyPartsJson.length(); i++) {
bodyPartJson = bodyPartsJson.getJSONObject(i);
bodyPart = new RuneTemplate.BodyPart();
bodyPart.body_part_render = bodyPartJson.getInt("body_part_render");
bodyPart.body_part_position = CacheParserUtil.readVector3(bodyPartJson.getJSONArray("body_part_position"));
rune.rune_body_parts.add(bodyPart);
}
}
if (runeJson.has("rune_hair"))
rune.rune_hair.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_hair")));
if (runeJson.has("rune_beard"))
rune.rune_beard.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_beard")));
rune.rune_natural_power_attack = runeJson.getInt("rune_natural_power_attack");
rune.rune_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("rune_sparse_data")));
return rune;
}
}
@@ -0,0 +1,99 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import engine.parsers.cache.CObjectIndex;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Common type for ITEM, DEED and RUNE cache templates, which share one id space.
// Getters cover only the obj_/combat_/item_ fields all three define (JSON-Refactor-Plan D1).
// Returned collections are the template's own; never modify them.
public interface CObjectTemplate {
static CObjectTemplate lookup(int templateId) {
return CObjectIndex.lookup(templateId);
}
int getTemplateId();
String getObjName();
boolean getObjPickable();
float getObjGravity();
float getObjCullDistance();
Vector3fImmutable getObjScale();
int getObjRenderObject();
boolean getObjDoubleFusion();
Vector3fImmutable getObjForwardVector();
String getObjTrackingName();
float getObjMaxTrackingDistance();
int getObjIcon();
float getObjGravityF();
LinkedHashMap<String, Integer> getObjSoundEvents();
JSONArray getObjArcHardpointList();
HashMap<String, String> getObjSparseData();
int getObjRenderObjectLowDetail();
Vector3fImmutable getObjDefaultAlignment();
String getObjSoundTable();
float getCombatHealthCurrent();
float getCombatHealthFull();
EnumMap<mbEnums.DamageType, Float> getCombatAttackResist();
JSONArray getCombatPowers();
mbEnums.ItemType getItemType();
int getItemEqSlotsValue();
boolean getItemEqSlotsType();
EnumSet<mbEnums.EquipSlotType> getItemEqSlotsOr();
EnumSet<mbEnums.EquipSlotType> getItemEqSlotsAnd();
boolean getItemTakeable();
int getItemValue();
int getItemWt();
float getItemPassiveDefenseMod();
String getItemBaseName();
String getItemDsc();
int getItemRenderObjectFemale();
float getItemHealthFull();
String getItemSkillUsed();
String getItemSkillMasteryUsed();
int getItemParryAnimId();
EnumMap<mbEnums.ShrineType, Integer> getItemOfferingInfo();
EnumSet<mbEnums.ItemFlags> getItemFlags();
EnumSet<mbEnums.ItemUseFlags> getItemUseFlags();
int getItemPostItemId();
int getItemInitialCharges();
int getItemBookArcana();
LinkedHashMap<String, Integer> getItemSkillReq();
boolean getItemRaceReqRestrict();
EnumSet<mbEnums.MonsterType> getItemRaceReq();
boolean getItemClassReqRestrict();
EnumSet<mbEnums.ClassType> getItemClassReq();
boolean getItemDiscReqRestrict();
EnumSet<mbEnums.DisciplineType> getItemDiscReq();
EnumMap<mbEnums.AttributeType, Integer> getItemAttrReq();
int getItemLevelReq();
int getItemRankReq();
mbEnums.SexType getItemSexReq();
ArrayList<UserPowerAction> getItemUserPowerAction();
ArrayList<PowerGrant> getItemPowerGrant();
LinkedHashMap<String, ArrayList<int[]>> getItemPowerAction();
boolean getItemSheathable();
boolean getItemHasStub();
EnumMap<mbEnums.ShrineType, Integer> getItemOfferingAdjustments();
EnumMap<mbEnums.ResourceType, Integer> getItemResourceCosts();
int getItemBaneRank();
boolean getItemIgnoreSavedActions();
}
@@ -0,0 +1,447 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Deed template as defined by the client's cache data
// (MBDATA/data/COBJECTS/DEED). Field names match the JSON keys.
//
// Values are kept as the client defines them: no req/res splitting,
// no defaults substituted. Strings with a matching enum in mbEnums
// are stored as that enum; everything else stays a String.
//
// Read only once loaded.
public class DeedTemplate implements CObjectTemplate {
public static HashMap<Integer, DeedTemplate> _deeds = new HashMap<>();
public int template_id;
// obj_
public String obj_name;
public boolean obj_pickable;
public float obj_gravity;
public float obj_cull_distance;
public Vector3fImmutable obj_scale;
public int obj_render_object;
public boolean obj_double_fusion;
public Vector3fImmutable obj_forward_vector;
public String obj_tracking_name;
public float obj_max_tracking_distance;
public int obj_icon;
public float obj_gravity_f;
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
public JSONArray obj_arc_hardpoint_list;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public int obj_render_object_low_detail;
public Vector3fImmutable obj_default_alignment;
public String obj_sound_table;
// combat_
public float combat_health_current;
public float combat_health_full;
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
public JSONArray combat_powers;
// item_
public mbEnums.ItemType item_type;
public int item_eq_slots_value;
public boolean item_eq_slots_type;
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public boolean item_takeable;
public int item_value;
public int item_wt;
public float item_passive_defense_mod;
public String item_base_name;
public String item_dsc;
public int item_render_object_female;
public float item_health_full;
public String item_skill_used;
public String item_skill_mastery_used;
public int item_parry_anim_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
public int item_post_item_id;
public int item_initial_charges;
public int item_book_arcana;
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
public boolean item_race_req_restrict;
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public boolean item_class_req_restrict;
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public boolean item_disc_req_restrict;
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
public int item_level_req;
public int item_rank_req;
public mbEnums.SexType item_sex_req;
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
public boolean item_sheathable;
public boolean item_has_stub;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
public int item_bane_rank;
public boolean item_ignore_saved_actions;
// deed_
public int deed_type;
public int deed_furniture_id;
public int deed_target_id;
public int deed_employment;
public int deed_start_rank;
public int deed_name_lookup;
public boolean deed_indoors;
public boolean deed_is_fortress;
public float deed_namelookup_val;
public boolean deed_custom_city;
public int deed_structure_id;
@Override
public int getTemplateId() {
return template_id;
}
@Override
public String getObjName() {
return obj_name;
}
@Override
public boolean getObjPickable() {
return obj_pickable;
}
@Override
public float getObjGravity() {
return obj_gravity;
}
@Override
public float getObjCullDistance() {
return obj_cull_distance;
}
@Override
public Vector3fImmutable getObjScale() {
return obj_scale;
}
@Override
public int getObjRenderObject() {
return obj_render_object;
}
@Override
public boolean getObjDoubleFusion() {
return obj_double_fusion;
}
@Override
public Vector3fImmutable getObjForwardVector() {
return obj_forward_vector;
}
@Override
public String getObjTrackingName() {
return obj_tracking_name;
}
@Override
public float getObjMaxTrackingDistance() {
return obj_max_tracking_distance;
}
@Override
public int getObjIcon() {
return obj_icon;
}
@Override
public float getObjGravityF() {
return obj_gravity_f;
}
@Override
public LinkedHashMap<String, Integer> getObjSoundEvents() {
return obj_sound_events;
}
@Override
public JSONArray getObjArcHardpointList() {
return obj_arc_hardpoint_list;
}
@Override
public HashMap<String, String> getObjSparseData() {
return obj_sparse_data;
}
@Override
public int getObjRenderObjectLowDetail() {
return obj_render_object_low_detail;
}
@Override
public Vector3fImmutable getObjDefaultAlignment() {
return obj_default_alignment;
}
@Override
public String getObjSoundTable() {
return obj_sound_table;
}
@Override
public float getCombatHealthCurrent() {
return combat_health_current;
}
@Override
public float getCombatHealthFull() {
return combat_health_full;
}
@Override
public EnumMap<mbEnums.DamageType, Float> getCombatAttackResist() {
return combat_attack_resist;
}
@Override
public JSONArray getCombatPowers() {
return combat_powers;
}
@Override
public mbEnums.ItemType getItemType() {
return item_type;
}
@Override
public int getItemEqSlotsValue() {
return item_eq_slots_value;
}
@Override
public boolean getItemEqSlotsType() {
return item_eq_slots_type;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsOr() {
return item_eq_slots_or;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsAnd() {
return item_eq_slots_and;
}
@Override
public boolean getItemTakeable() {
return item_takeable;
}
@Override
public int getItemValue() {
return item_value;
}
@Override
public int getItemWt() {
return item_wt;
}
@Override
public float getItemPassiveDefenseMod() {
return item_passive_defense_mod;
}
@Override
public String getItemBaseName() {
return item_base_name;
}
@Override
public String getItemDsc() {
return item_dsc;
}
@Override
public int getItemRenderObjectFemale() {
return item_render_object_female;
}
@Override
public float getItemHealthFull() {
return item_health_full;
}
@Override
public String getItemSkillUsed() {
return item_skill_used;
}
@Override
public String getItemSkillMasteryUsed() {
return item_skill_mastery_used;
}
@Override
public int getItemParryAnimId() {
return item_parry_anim_id;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingInfo() {
return item_offering_info;
}
@Override
public EnumSet<mbEnums.ItemFlags> getItemFlags() {
return item_flags;
}
@Override
public EnumSet<mbEnums.ItemUseFlags> getItemUseFlags() {
return item_use_flags;
}
@Override
public int getItemPostItemId() {
return item_post_item_id;
}
@Override
public int getItemInitialCharges() {
return item_initial_charges;
}
@Override
public int getItemBookArcana() {
return item_book_arcana;
}
@Override
public LinkedHashMap<String, Integer> getItemSkillReq() {
return item_skill_req;
}
@Override
public boolean getItemRaceReqRestrict() {
return item_race_req_restrict;
}
@Override
public EnumSet<mbEnums.MonsterType> getItemRaceReq() {
return item_race_req;
}
@Override
public boolean getItemClassReqRestrict() {
return item_class_req_restrict;
}
@Override
public EnumSet<mbEnums.ClassType> getItemClassReq() {
return item_class_req;
}
@Override
public boolean getItemDiscReqRestrict() {
return item_disc_req_restrict;
}
@Override
public EnumSet<mbEnums.DisciplineType> getItemDiscReq() {
return item_disc_req;
}
@Override
public EnumMap<mbEnums.AttributeType, Integer> getItemAttrReq() {
return item_attr_req;
}
@Override
public int getItemLevelReq() {
return item_level_req;
}
@Override
public int getItemRankReq() {
return item_rank_req;
}
@Override
public mbEnums.SexType getItemSexReq() {
return item_sex_req;
}
@Override
public ArrayList<UserPowerAction> getItemUserPowerAction() {
return item_user_power_action;
}
@Override
public ArrayList<PowerGrant> getItemPowerGrant() {
return item_power_grant;
}
@Override
public LinkedHashMap<String, ArrayList<int[]>> getItemPowerAction() {
return item_power_action;
}
@Override
public boolean getItemSheathable() {
return item_sheathable;
}
@Override
public boolean getItemHasStub() {
return item_has_stub;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingAdjustments() {
return item_offering_adjustments;
}
@Override
public EnumMap<mbEnums.ResourceType, Integer> getItemResourceCosts() {
return item_resource_costs;
}
@Override
public int getItemBaneRank() {
return item_bane_rank;
}
@Override
public boolean getItemIgnoreSavedActions() {
return item_ignore_saved_actions;
}
}
@@ -0,0 +1,490 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Item template as defined by the client's cache data
// (MBDATA/data/COBJECTS/ITEM). Field names match the JSON keys.
// Field layout follows ArcItemTemplate_Load (sb.exe 0x4AABD0).
//
// Values are kept as the client defines them: no req/res splitting,
// no defaults substituted. Strings with a matching enum in mbEnums
// are stored as that enum; everything else stays a String.
//
// Not to be confused with engine.objects.ItemTemplate, which is loaded
// from the database. Read only once loaded.
public class ItemTemplate implements CObjectTemplate {
public static HashMap<Integer, ItemTemplate> _items = new HashMap<>();
public int template_id;
// obj_
public String obj_name;
public boolean obj_pickable;
public float obj_gravity;
public float obj_cull_distance;
public Vector3fImmutable obj_scale;
public int obj_render_object;
public boolean obj_double_fusion;
public Vector3fImmutable obj_forward_vector;
public String obj_tracking_name;
public float obj_max_tracking_distance;
public int obj_icon;
public float obj_gravity_f;
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
public JSONArray obj_arc_hardpoint_list;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public int obj_render_object_low_detail;
public Vector3fImmutable obj_default_alignment;
public String obj_sound_table;
// combat_
public float combat_health_current;
public float combat_health_full;
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
public JSONArray combat_powers;
// item_
public mbEnums.ItemType item_type;
// Index into the client's equip slot names; the leading slot of EQSLOTAND.
public int item_eq_slots_value;
// true: any one of item_eq_slots_or; false: all of item_eq_slots_and (sb.exe 0x4AC430).
public boolean item_eq_slots_type;
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public boolean item_takeable;
public int item_value;
public int item_wt;
public float item_passive_defense_mod;
public String item_base_name;
public String item_dsc;
public int item_render_object_female;
public float item_health_full;
public String item_skill_used;
public String item_skill_mastery_used;
public int item_parry_anim_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
// WEAPON only; null otherwise.
public Weapon item_weapon;
public mbEnums.AttributeType item_primary_attr;
public mbEnums.AttributeType item_secondary_attr;
// ARMOR only.
public float item_bulk_factor;
public int item_defense_rating;
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
public int item_post_item_id;
public int item_initial_charges;
// Read as an unsigned value, possibly a hash; 0 in every ITEM file.
public int item_book_arcana;
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
// For race, class and disc: restrict true means the list is excluded
// (RACERESTRICT), false means it is required (RACEREQ).
public boolean item_race_req_restrict;
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public boolean item_class_req_restrict;
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public boolean item_disc_req_restrict;
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
public int item_level_req;
public int item_rank_req;
public mbEnums.SexType item_sex_req;
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
// Sheath fields are only present when item_sheathable is true.
public boolean item_sheathable;
public mbEnums.SheathSlot item_sheath_slot;
public Vector3fImmutable item_sheath_offset;
public Vector3fImmutable item_sheath_rotation;
// The JSON holds false, or [flag, id] when the stub is present (ArcItemStubHolder2, sb.exe 0x4A8D40).
// Meaning of the flag is not known.
public boolean item_has_stub;
public boolean item_stub_flag;
public int item_stub_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
public int item_bane_rank;
public boolean item_ignore_saved_actions;
@Override
public int getTemplateId() {
return template_id;
}
@Override
public String getObjName() {
return obj_name;
}
@Override
public boolean getObjPickable() {
return obj_pickable;
}
@Override
public float getObjGravity() {
return obj_gravity;
}
@Override
public float getObjCullDistance() {
return obj_cull_distance;
}
@Override
public Vector3fImmutable getObjScale() {
return obj_scale;
}
@Override
public int getObjRenderObject() {
return obj_render_object;
}
@Override
public boolean getObjDoubleFusion() {
return obj_double_fusion;
}
@Override
public Vector3fImmutable getObjForwardVector() {
return obj_forward_vector;
}
@Override
public String getObjTrackingName() {
return obj_tracking_name;
}
@Override
public float getObjMaxTrackingDistance() {
return obj_max_tracking_distance;
}
@Override
public int getObjIcon() {
return obj_icon;
}
@Override
public float getObjGravityF() {
return obj_gravity_f;
}
@Override
public LinkedHashMap<String, Integer> getObjSoundEvents() {
return obj_sound_events;
}
@Override
public JSONArray getObjArcHardpointList() {
return obj_arc_hardpoint_list;
}
@Override
public HashMap<String, String> getObjSparseData() {
return obj_sparse_data;
}
@Override
public int getObjRenderObjectLowDetail() {
return obj_render_object_low_detail;
}
@Override
public Vector3fImmutable getObjDefaultAlignment() {
return obj_default_alignment;
}
@Override
public String getObjSoundTable() {
return obj_sound_table;
}
@Override
public float getCombatHealthCurrent() {
return combat_health_current;
}
@Override
public float getCombatHealthFull() {
return combat_health_full;
}
@Override
public EnumMap<mbEnums.DamageType, Float> getCombatAttackResist() {
return combat_attack_resist;
}
@Override
public JSONArray getCombatPowers() {
return combat_powers;
}
@Override
public mbEnums.ItemType getItemType() {
return item_type;
}
@Override
public int getItemEqSlotsValue() {
return item_eq_slots_value;
}
@Override
public boolean getItemEqSlotsType() {
return item_eq_slots_type;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsOr() {
return item_eq_slots_or;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsAnd() {
return item_eq_slots_and;
}
@Override
public boolean getItemTakeable() {
return item_takeable;
}
@Override
public int getItemValue() {
return item_value;
}
@Override
public int getItemWt() {
return item_wt;
}
@Override
public float getItemPassiveDefenseMod() {
return item_passive_defense_mod;
}
@Override
public String getItemBaseName() {
return item_base_name;
}
@Override
public String getItemDsc() {
return item_dsc;
}
@Override
public int getItemRenderObjectFemale() {
return item_render_object_female;
}
@Override
public float getItemHealthFull() {
return item_health_full;
}
@Override
public String getItemSkillUsed() {
return item_skill_used;
}
@Override
public String getItemSkillMasteryUsed() {
return item_skill_mastery_used;
}
@Override
public int getItemParryAnimId() {
return item_parry_anim_id;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingInfo() {
return item_offering_info;
}
@Override
public EnumSet<mbEnums.ItemFlags> getItemFlags() {
return item_flags;
}
@Override
public EnumSet<mbEnums.ItemUseFlags> getItemUseFlags() {
return item_use_flags;
}
@Override
public int getItemPostItemId() {
return item_post_item_id;
}
@Override
public int getItemInitialCharges() {
return item_initial_charges;
}
@Override
public int getItemBookArcana() {
return item_book_arcana;
}
@Override
public LinkedHashMap<String, Integer> getItemSkillReq() {
return item_skill_req;
}
@Override
public boolean getItemRaceReqRestrict() {
return item_race_req_restrict;
}
@Override
public EnumSet<mbEnums.MonsterType> getItemRaceReq() {
return item_race_req;
}
@Override
public boolean getItemClassReqRestrict() {
return item_class_req_restrict;
}
@Override
public EnumSet<mbEnums.ClassType> getItemClassReq() {
return item_class_req;
}
@Override
public boolean getItemDiscReqRestrict() {
return item_disc_req_restrict;
}
@Override
public EnumSet<mbEnums.DisciplineType> getItemDiscReq() {
return item_disc_req;
}
@Override
public EnumMap<mbEnums.AttributeType, Integer> getItemAttrReq() {
return item_attr_req;
}
@Override
public int getItemLevelReq() {
return item_level_req;
}
@Override
public int getItemRankReq() {
return item_rank_req;
}
@Override
public mbEnums.SexType getItemSexReq() {
return item_sex_req;
}
@Override
public ArrayList<UserPowerAction> getItemUserPowerAction() {
return item_user_power_action;
}
@Override
public ArrayList<PowerGrant> getItemPowerGrant() {
return item_power_grant;
}
@Override
public LinkedHashMap<String, ArrayList<int[]>> getItemPowerAction() {
return item_power_action;
}
@Override
public boolean getItemSheathable() {
return item_sheathable;
}
@Override
public boolean getItemHasStub() {
return item_has_stub;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingAdjustments() {
return item_offering_adjustments;
}
@Override
public EnumMap<mbEnums.ResourceType, Integer> getItemResourceCosts() {
return item_resource_costs;
}
@Override
public int getItemBaneRank() {
return item_bane_rank;
}
@Override
public boolean getItemIgnoreSavedActions() {
return item_ignore_saved_actions;
}
// item_weapon (ArcAttackInfo_LoadWeapon, sb.exe 0x61FA60).
public static class Weapon {
public float weapon_wepspeed;
public float weapon_max_range;
public int weapon_projectile_id;
public float weapon_projectile_speed;
public int weapon_combat_idle_anim;
public final ArrayList<Damage> weapon_damage = new ArrayList<>();
// [animation, duration] pairs in file order; names from engine.objects.ItemTemplate, not the client.
public final ArrayList<int[]> weapon_attack_anim_right = new ArrayList<>();
public final ArrayList<int[]> weapon_attack_anim_left = new ArrayList<>();
}
// One entry of weapon_damage.
public static class Damage {
public mbEnums.DamageType damage_type;
public int damage_min;
public int damage_max;
}
}
@@ -0,0 +1,27 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import org.json.JSONArray;
import java.util.LinkedHashMap;
// One entry of item_power_grant.
// The granted lists are [key, value] pairs, unsigned key and signed value (sb.exe 0x6D7640).
// power_monster_types is empty in every DEED and RUNE file, so it is kept as raw JSON.
public class PowerGrant {
public String power_type;
public int power_value;
public final LinkedHashMap<String, Integer> power_granted_attrs = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> power_granted_skills = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> power_granted_powers = new LinkedHashMap<>();
public JSONArray power_monster_types;
}
@@ -0,0 +1,550 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Rune template as defined by the client's cache data
// (MBDATA/data/COBJECTS/RUNE). Field names match the JSON keys.
//
// Covers every rune the client knows about: player races, classes,
// disciplines, talents and mob/pet/npc runes alike.
//
// Values are kept as the client defines them: no req/res splitting,
// no defaults substituted. Strings with a matching enum in mbEnums
// are stored as that enum; everything else stays a String.
//
// Read only once loaded.
public class RuneTemplate implements CObjectTemplate {
public static HashMap<Integer, RuneTemplate> _runes = new HashMap<>();
public int template_id;
// obj_
public String obj_name;
public boolean obj_pickable;
public float obj_gravity;
public float obj_cull_distance;
public Vector3fImmutable obj_scale;
public int obj_render_object;
public boolean obj_double_fusion;
public Vector3fImmutable obj_forward_vector;
public String obj_tracking_name;
public float obj_max_tracking_distance;
public int obj_icon;
public float obj_gravity_f;
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
public JSONArray obj_arc_hardpoint_list;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public int obj_render_object_low_detail;
public Vector3fImmutable obj_default_alignment;
public String obj_sound_table;
// combat_
public float combat_health_current;
public float combat_health_full;
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
public JSONArray combat_powers;
// item_
public mbEnums.ItemType item_type;
public int item_eq_slots_value;
public boolean item_eq_slots_type;
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public boolean item_takeable;
public int item_value;
public int item_wt;
public float item_passive_defense_mod;
public String item_base_name;
public String item_dsc;
public int item_render_object_female;
public float item_health_full;
public String item_skill_used;
public String item_skill_mastery_used;
public int item_parry_anim_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
public int item_post_item_id;
public int item_initial_charges;
public int item_book_arcana;
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
public boolean item_race_req_restrict;
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public boolean item_class_req_restrict;
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public boolean item_disc_req_restrict;
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
public int item_level_req;
public int item_rank_req;
public mbEnums.SexType item_sex_req;
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
public boolean item_sheathable;
public boolean item_has_stub;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
public int item_bane_rank;
public boolean item_ignore_saved_actions;
// rune_
public mbEnums.RuneType rune_type;
public String rune_sub_type;
// A handful of runes carry a number instead of a name in rune_sub_type.
// These look like string hashes the cache converter could not resolve.
// Stored here (wrapped to int) with rune_sub_type left empty.
public int rune_sub_type_hash;
public boolean rune_is_standard_character_creation;
public int rune_creation_cost;
public int rune_rank;
public int rune_pracs_per_level;
public float rune_exp_req_to_level;
public mbEnums.SexType rune_sex;
public int rune_class_icon;
public int rune_health;
public int rune_mana;
public int rune_stamina;
public float rune_min_damage;
public float rune_max_damage;
public int rune_attack;
public int rune_defense;
public int rune_level;
public final EnumMap<mbEnums.MovementType, Float> rune_speed = new EnumMap<>(mbEnums.MovementType.class);
public int rune_group_type;
public boolean rune_group_is_faction;
public boolean rune_group_is_guild;
public String rune_dsc;
public String rune_fx_txt;
// Bit vectors. The existing ItemTemplate decodes these as MobBehaviourType.
public long rune_group_tactics;
public long rune_group_role_set;
public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
public final ArrayList<Integer> rune_groupee_monster_types = new ArrayList<>();
public final ArrayList<Integer> rune_helper_monster_types = new ArrayList<>();
public final EnumSet<mbEnums.MonsterType> rune_not_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
// Empty in every RUNE file; shape unknown.
public JSONArray rune_enemy_gender;
public final ArrayList<SkillGrant> rune_skill_grant = new ArrayList<>();
// skill_type -> list of [value, value] pairs, in file order.
public final LinkedHashMap<String, ArrayList<int[]>> rune_skill_adj = new LinkedHashMap<>();
public final EnumMap<mbEnums.AttributeType, Integer> rune_attr_adj = new EnumMap<>(mbEnums.AttributeType.class);
public final EnumMap<mbEnums.AttributeType, Integer> rune_max_attr_adj = new EnumMap<>(mbEnums.AttributeType.class);
// One float[10] per natural attack. Same "attack info" struct as a weapon
// (see MBDATA/docs/client-map.md): [0] attack speed, [1] range, [2] unknown,
// [3] equip slot (1 = RHELD, 2 = LHELD), [4] projectile template id,
// [5] projectile speed, [6] swing anim, [7] damage type, [8] min damage,
// [9] max damage
public final ArrayList<float[]> rune_naturalattacks = new ArrayList<>();
// Empty in every RUNE file; shape unknown.
public JSONArray rune_enchantment_type;
public final ArrayList<InventoryEntry> rune_inventory_contents = new ArrayList<>();
public boolean rune_renderable;
// The following fields are only present when the rune is renderable
// (missing from 315 of 2695 files). They keep their defaults otherwise.
public Vector3fImmutable rune_scale_factor;
public int rune_skeleton;
public boolean rune_slope_hugger;
public boolean rune_can_fly;
public int rune_death_effect;
public int rune_tombstone_id;
public final ArrayList<BodyPart> rune_body_parts = new ArrayList<>();
public final ArrayList<Integer> rune_hair = new ArrayList<>();
public final ArrayList<Integer> rune_beard = new ArrayList<>();
public int rune_natural_power_attack;
public final HashMap<String, String> rune_sparse_data = new HashMap<>();
@Override
public int getTemplateId() {
return template_id;
}
@Override
public String getObjName() {
return obj_name;
}
@Override
public boolean getObjPickable() {
return obj_pickable;
}
@Override
public float getObjGravity() {
return obj_gravity;
}
@Override
public float getObjCullDistance() {
return obj_cull_distance;
}
@Override
public Vector3fImmutable getObjScale() {
return obj_scale;
}
@Override
public int getObjRenderObject() {
return obj_render_object;
}
@Override
public boolean getObjDoubleFusion() {
return obj_double_fusion;
}
@Override
public Vector3fImmutable getObjForwardVector() {
return obj_forward_vector;
}
@Override
public String getObjTrackingName() {
return obj_tracking_name;
}
@Override
public float getObjMaxTrackingDistance() {
return obj_max_tracking_distance;
}
@Override
public int getObjIcon() {
return obj_icon;
}
@Override
public float getObjGravityF() {
return obj_gravity_f;
}
@Override
public LinkedHashMap<String, Integer> getObjSoundEvents() {
return obj_sound_events;
}
@Override
public JSONArray getObjArcHardpointList() {
return obj_arc_hardpoint_list;
}
@Override
public HashMap<String, String> getObjSparseData() {
return obj_sparse_data;
}
@Override
public int getObjRenderObjectLowDetail() {
return obj_render_object_low_detail;
}
@Override
public Vector3fImmutable getObjDefaultAlignment() {
return obj_default_alignment;
}
@Override
public String getObjSoundTable() {
return obj_sound_table;
}
@Override
public float getCombatHealthCurrent() {
return combat_health_current;
}
@Override
public float getCombatHealthFull() {
return combat_health_full;
}
@Override
public EnumMap<mbEnums.DamageType, Float> getCombatAttackResist() {
return combat_attack_resist;
}
@Override
public JSONArray getCombatPowers() {
return combat_powers;
}
@Override
public mbEnums.ItemType getItemType() {
return item_type;
}
@Override
public int getItemEqSlotsValue() {
return item_eq_slots_value;
}
@Override
public boolean getItemEqSlotsType() {
return item_eq_slots_type;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsOr() {
return item_eq_slots_or;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsAnd() {
return item_eq_slots_and;
}
@Override
public boolean getItemTakeable() {
return item_takeable;
}
@Override
public int getItemValue() {
return item_value;
}
@Override
public int getItemWt() {
return item_wt;
}
@Override
public float getItemPassiveDefenseMod() {
return item_passive_defense_mod;
}
@Override
public String getItemBaseName() {
return item_base_name;
}
@Override
public String getItemDsc() {
return item_dsc;
}
@Override
public int getItemRenderObjectFemale() {
return item_render_object_female;
}
@Override
public float getItemHealthFull() {
return item_health_full;
}
@Override
public String getItemSkillUsed() {
return item_skill_used;
}
@Override
public String getItemSkillMasteryUsed() {
return item_skill_mastery_used;
}
@Override
public int getItemParryAnimId() {
return item_parry_anim_id;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingInfo() {
return item_offering_info;
}
@Override
public EnumSet<mbEnums.ItemFlags> getItemFlags() {
return item_flags;
}
@Override
public EnumSet<mbEnums.ItemUseFlags> getItemUseFlags() {
return item_use_flags;
}
@Override
public int getItemPostItemId() {
return item_post_item_id;
}
@Override
public int getItemInitialCharges() {
return item_initial_charges;
}
@Override
public int getItemBookArcana() {
return item_book_arcana;
}
@Override
public LinkedHashMap<String, Integer> getItemSkillReq() {
return item_skill_req;
}
@Override
public boolean getItemRaceReqRestrict() {
return item_race_req_restrict;
}
@Override
public EnumSet<mbEnums.MonsterType> getItemRaceReq() {
return item_race_req;
}
@Override
public boolean getItemClassReqRestrict() {
return item_class_req_restrict;
}
@Override
public EnumSet<mbEnums.ClassType> getItemClassReq() {
return item_class_req;
}
@Override
public boolean getItemDiscReqRestrict() {
return item_disc_req_restrict;
}
@Override
public EnumSet<mbEnums.DisciplineType> getItemDiscReq() {
return item_disc_req;
}
@Override
public EnumMap<mbEnums.AttributeType, Integer> getItemAttrReq() {
return item_attr_req;
}
@Override
public int getItemLevelReq() {
return item_level_req;
}
@Override
public int getItemRankReq() {
return item_rank_req;
}
@Override
public mbEnums.SexType getItemSexReq() {
return item_sex_req;
}
@Override
public ArrayList<UserPowerAction> getItemUserPowerAction() {
return item_user_power_action;
}
@Override
public ArrayList<PowerGrant> getItemPowerGrant() {
return item_power_grant;
}
@Override
public LinkedHashMap<String, ArrayList<int[]>> getItemPowerAction() {
return item_power_action;
}
@Override
public boolean getItemSheathable() {
return item_sheathable;
}
@Override
public boolean getItemHasStub() {
return item_has_stub;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingAdjustments() {
return item_offering_adjustments;
}
@Override
public EnumMap<mbEnums.ResourceType, Integer> getItemResourceCosts() {
return item_resource_costs;
}
@Override
public int getItemBaneRank() {
return item_bane_rank;
}
@Override
public boolean getItemIgnoreSavedActions() {
return item_ignore_saved_actions;
}
// One entry of rune_skill_grant; same layout as PowerGrant.
// skill_monster_types is empty in every RUNE file, so it is kept as raw JSON.
public static class SkillGrant {
public String skill_type;
public int skill_value;
public final LinkedHashMap<String, Integer> skill_granted_attrs = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> skill_granted_skills = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> skill_granted_powers = new LinkedHashMap<>();
public JSONArray skill_monster_types;
}
// One entry of rune_body_parts.
public static class BodyPart {
public int body_part_render;
public Vector3fImmutable body_part_position;
}
// One entry of rune_inventory_contents.
// type is GOLD or BOOTYTABLE.
// GOLD carries "gold": three floats (meaning not yet confirmed).
// BOOTYTABLE carries "table": two numbers (meaning not yet confirmed).
public static class InventoryEntry {
public String type;
public float[] values;
}
}
@@ -0,0 +1,24 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
// One entry of item_user_power_action.
// The same power can appear more than once on a single template,
// so these are kept in a list rather than a map keyed by power.
public class UserPowerAction {
public String power;
public int[] arguments;
public UserPowerAction(String power, int[] arguments) {
this.power = power;
this.arguments = arguments;
}
}
+11
View File
@@ -0,0 +1,11 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
// Parsers for the client's .wpak configuration files (powers, effects, etc).
package engine.parsers.wpak;
@@ -63,7 +63,7 @@ public class StealPowerAction extends AbstractPowerAction {
//called to get amount of gold to steal between 0 and max gold
protected static int getAmountToSteal(Item item) {
if (item.template.item_type.equals(ItemType.GOLD)) {
if (item.template.cacheTemplate.getItemType().equals(ItemType.GOLD)) {
int amount = item.getNumOfItems();
@@ -159,7 +159,7 @@ public class StealPowerAction extends AbstractPowerAction {
if (myCIM == null || ownerCIM == null)
return;
if (tar.template.item_type.equals(ItemType.GOLD)) {
if (tar.template.cacheTemplate.getItemType().equals(ItemType.GOLD)) {
//stealing gold
if (!myCIM.transferGoldToMyInventory((AbstractCharacter) owner, amount))
return;
+37
View File
@@ -21,6 +21,12 @@ import engine.net.client.ClientConnection;
import engine.net.client.ClientConnectionManager;
import engine.net.client.msg.login.*;
import engine.objects.*;
import engine.parsers.cache.CObjectIndex;
import engine.parsers.cache.DeedParser;
import engine.parsers.cache.ItemParser;
import engine.parsers.cache.RuneParser;
import engine.parsers.objects.DeedTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics;
import engine.server.world.WorldServer;
import engine.session.Session;
@@ -284,6 +290,37 @@ public class LoginServer {
Logger.info("Loading Item Templates");
DbManager.ItemQueries.LOAD_ITEM_TEMPLATES();
Logger.info("Loading Deed Templates");
DeedTemplate._deeds = DeedParser.parseDeedFolder();
if (DeedTemplate._deeds == null || DeedTemplate._deeds.isEmpty()) {
Logger.error("ABORT! Deed templates failed to load!");
exit(1);
}
Logger.info("Loading Rune Templates");
RuneTemplate._runes = RuneParser.parseRuneFolder();
if (RuneTemplate._runes == null || RuneTemplate._runes.isEmpty()) {
Logger.error("ABORT! Rune templates failed to load!");
exit(1);
}
// Qualified: engine.objects.ItemTemplate is the database-loaded class
Logger.info("Loading Cache Item Templates");
engine.parsers.objects.ItemTemplate._items = ItemParser.parseItemFolder();
if (engine.parsers.objects.ItemTemplate._items == null || engine.parsers.objects.ItemTemplate._items.isEmpty()) {
Logger.error("ABORT! Cache item templates failed to load!");
exit(1);
}
Logger.info("Indexing Cache Templates");
CObjectIndex.build();
Logger.info("Linking Item Templates to Cache Templates");
ItemTemplate.linkCacheTemplates();
Logger.info("Initializing Network");
Network.init();
+43
View File
@@ -31,6 +31,13 @@ import engine.net.client.ClientConnectionManager;
import engine.net.client.msg.UpdateStateMsg;
import engine.net.client.msg.chat.ChatSystemMsg;
import engine.objects.*;
import engine.parsers.cache.CObjectIndex;
import engine.parsers.cache.DeedParser;
import engine.parsers.cache.ItemParser;
import engine.parsers.cache.ParityCheck;
import engine.parsers.cache.RuneParser;
import engine.parsers.objects.DeedTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics;
import engine.util.MapLoader;
import engine.util.ThreadUtils;
@@ -303,6 +310,37 @@ public class WorldServer {
Logger.info("Back fill template modTables");
DbManager.ItemQueries.LOAD_TEMPLATE_MODTABLES();
Logger.info("Loading Deed Templates");
DeedTemplate._deeds = DeedParser.parseDeedFolder();
if (DeedTemplate._deeds == null || DeedTemplate._deeds.isEmpty()) {
Logger.error("ABORT! Deed templates failed to load!");
exit(1);
}
Logger.info("Loading Rune Templates");
RuneTemplate._runes = RuneParser.parseRuneFolder();
if (RuneTemplate._runes == null || RuneTemplate._runes.isEmpty()) {
Logger.error("ABORT! Rune templates failed to load!");
exit(1);
}
// Qualified: engine.objects.ItemTemplate is the database-loaded class
Logger.info("Loading Cache Item Templates");
engine.parsers.objects.ItemTemplate._items = ItemParser.parseItemFolder();
if (engine.parsers.objects.ItemTemplate._items == null || engine.parsers.objects.ItemTemplate._items.isEmpty()) {
Logger.error("ABORT! Cache item templates failed to load!");
exit(1);
}
Logger.info("Indexing Cache Templates");
CObjectIndex.build();
Logger.info("Linking Item Templates to Cache Templates");
ItemTemplate.linkCacheTemplates();
Logger.info("Loading PromotionClasses");
DbManager.PromotionQueries.GET_ALL_PROMOTIONS();
@@ -361,6 +399,11 @@ public class WorldServer {
mbEnums.RaceType.initRaceTypeTables();
Race.loadAllRaces();
if (ParityCheck.isEnabled()) {
Logger.info("Running DB/JSON parity check");
ParityCheck.run();
}
Logger.info("Loading building slot/stuck location data.");
BuildingLocation.loadBuildingLocations();
-57
View File
@@ -57,63 +57,6 @@ public class StringUtils {
return Long.toHexString(l).toUpperCase();
}
// Well done IDA Pro.
public static int hashString(String toHash) {
byte[] hashArray = toHash.getBytes();
int hash = 0;
int shift = 0;
if (hashArray.length == 8 || hashArray.length == 7) {
int ecx = 0;
if (hashArray.length == 8) {
ecx = hashArray[7];
}
int eax = hashArray[4];
int esi = ecx * 0x8;
eax ^= esi;
ecx ^= 0x5A0;
esi = hashArray[5];
eax <<= 4;
eax ^= esi;
esi = hashArray[6];
eax <<= 4;
eax ^= esi;
esi = hashArray[2];
eax <<= 5;
eax ^= esi;
esi = hashArray[1];
int edx = hashArray[0];
eax <<= 5;
eax ^= esi;
ecx /= 2;
ecx /= 2;
eax <<= 5;
ecx ^= edx;
eax ^= ecx;
return eax;
} else {
for (int i = 0; i < hashArray.length; i++) {
if (i == 0)
shift = 0;
else
shift += 5;
int toShift = hashArray[i] - 0x20;
int shifted = (toShift << shift);
hash ^= shifted;
if (shift > 24) {
int newShift = 0x20 - shift;
int newShifted = toShift >> newShift;
hash ^= newShifted;
if (shift > 27) {
shift -= 0x20;
}
}
}
return hash;
}
}
public static String wordWrap(String text, int LineWidth) {
StringTokenizer st = new StringTokenizer(text);
int SpaceLeft = LineWidth;