Compare commits

...
Author SHA1 Message Date
MagicBotandClaude Opus 5.5 26aa8de86c Refuse a talent unless it outranks the held one with the same sub type
Test on dev (character Moo): with Enhanced Strength held, the client shaded
an Increased Strength runestone red and showed its refusal popup, but the
server applied it as a downgrade. The client's 0x4768D0 (ArcRuneTemplate
vtable slot 0x1E8) blocks a talent when a held talent with the same
rune_sub_type has an equal or higher rank, rank 0 included. AddRune's
rank-0 replace branch is never reached past that check.

Value changes vs the previous commit:
- Rank-0 stat runes are refused while any rank of their chain is held
  (same as the old DB type 4 block)
- Rank-0 talents in the same group (e.g. Bloodline sub-race runestones)
  are refused instead of replacing each other

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:29:02 -04:00
MagicBotandClaude Opus 5.5 0536aea27a Build rune requirements from the runes and check them like the client
RuneBase(RuneTemplate) fills the race, class and discipline maps from
item_race_req / item_class_req / item_disc_req (names to rune ids through
CObjectIndex; class runes with rune_is_standard_character_creation go to
baseClass, the rest to promotionClass) with a restrict flag for each, and
the level from item_level_req. static_rune_runereq is no longer used for
cached rune bases; GET_RUNE_REQS stays until 2.11.

ApplyRuneMsg follows ArcCharacter_MeetsItemReqs (0x475F40) and
ArcItemRestrict_Check (0x4BAC60): restrict true = blacklist, false =
whitelist. The class check passes when the base class or the promotion is
listed. The discipline check is against the discs already held, using the
new rune's list only.

Parity (offline, scripts/p25a.py with feasible race/promotion pairs under
the client rule; scripts/p25b.py over all 315 rune bases). Value changes,
all decided JSON (D6, 2026-10-21):
- 3001 Animator: + Irekei (4 pairs); Summoner blocks it
- 3008 Commander: Saboteur blocks it
- 3010 Duelist: level 20, 5 races, 7 promotions (had no DB rows; -111 pairs)
- 3018 Enchanter, 3030 Conjurer: + Vampire (4 pairs each)
- 3020 Summoner: - Thief/Warrior/Barbarian (28 pairs), + Centaur Druid/Priest
  (4); Animator blocks it
- 3031 Darksworn: + Irekei Priest (2); 3032 Valkyr: + Human/Aelfborn Warrior (2)
- 3037 Artillerist: + Sentinel (11); 3044 Strigoi: Belgosch blocks it
- 252127 Master of Throwing: - Scout (16 pairs)
- 70 more runes with no DB rows gain their JSON lists (class runes 2500-2526
  except the Pet rune, creation talents and Bloodline talents); only runestones are checked
- Stat chains: the DB "higher rank held" blocks leave the discipline map.
  Ranks 1-8 are still refused by the sub type rule; a rank-0 rune now
  replaces a higher rank as the client does (decided: follow the client)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:02:10 -04:00
MagicBotandClaude Opus 5.5 c3f1861cf7 Replace talents by sub type and rank, cap talents at 8
Follows the client's ArcCharacter_AddRune (0x4695D0, specs/RuneRequirements.md):
a talent replaces a held talent with the same non-zero rune_sub_type when its
rank is 0 or higher than the held rank; otherwise the apply fails. With no
talent to replace, a new talent needs fewer than 8 held talents.

RuneBase gets runeType, a sub type token (SBStringHash(rune_sub_type), or the
stored hash) and rank from the rune. It no longer copies the static_rune_runereq
type 5 overwrite list, and ApplyRuneMsg no longer reads it.

Value changes:
- Stat chains (*MaxIncrease) replace the same ranks as before; the DB typos go
  away (250026 listed 250019 twice and missed 250018)
- Talents in the same group now replace each other when applied from a
  runestone (e.g. Bloodline sub-race talents); before, both were added
- Limit: 8 talents instead of more than 12 runes of any kind
- Rank-0 downgrade (decided: follow the client) takes effect in the next
  commit, when the DB stat-chain blocks leave the discipline map

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:00:46 -04:00
MagicBotandClaude Opus 5.5 8f87778c43 Show "You cannot apply that rune" when a rune is refused
applyRune returns false on every failed requirement without telling the
player, so using a rune the character doesn't qualify for did nothing. The
caller now sends popup 161 on refusal, matching popup 160 on success. The id
comes from the server's ErrorPopupMsg list; the popup strings aren't in the
client exe, so it isn't confirmed in IDA.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:53:52 -04:00
MagicBotandClaude Opus 5.5 0ff7e7736c Load RuneBase and its attributes from the runes
LoadAllRuneBases builds one RuneBase per static_rune_runebase row from the rune
with the same id (all 315 have one) and fills runeBaseAttributeMap from the
rune; the row now only supplies type, subtype, the runereq maps and effects. A
row without a rune would stay the DB object, without attributes, with a
warning. static_rune_runebaseattribute is no longer read at boot (the
LoadAllAttributes calls are gone from both servers). The SQL loaders stay until
task 2.11.

Value changes (decisions: JSON, D2):
- name: Confesser -> Confessor, Theif -> Thief, LagMaster -> Lagmaster,
  WP Dev -> Wolfpack Developer, Exception X -> Exceptional X (Strength,
  Dexterity, Constitution, Intelligence, Spirit). No code matches these names.
- description: '' / CSR text -> rune_dsc (unread)
- attributes added where the DB had none:
  - admin 2905 Lagmaster spi +200, spi max +200; 2907 Watcher con +200;
    2909 Feature Character every max +200; 2911 QA Test Rune con +200
    (applies to characters granted them; none hold them on dev)
  - creation cost 5/12/10/10/10 on 250116, 250117, 250123-250125 (duplicate
    talents not offered at creation)
  - base classes 2500-2503, Pet 2522, guards 252621-252623: start/max
    attributes (never player CharacterRunes; NPC rune attributes are unread)

All 365 DB attribute rows match the JSON; final parity before the switch
(scripts/p24.py): 0 other differences. The static_rune_runebase comparator is
removed; it would only compare the JSON with itself now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:30:43 -04:00
MagicBotandClaude Opus 5.5 83f57b4def Build RuneBase and its attributes from the rune and compare them
Adds RuneBase(RuneTemplate, RuneBase): name from item_base_name, description
from rune_dsc. type and subtype stay the static_rune_runebase values: they are
server enums that don't map cleanly from rune_type/rune_sub_type (CLASS runes
carry 0/2/3/5/256/289; the creation exclusive-group subtypes 1-10 leave out
Blood Gift, the MaxIncrease chains and the hashed sub types). The requirement
maps and effects are copied from the DB row until 2.5b/2.10.
RuneBaseAttribute.getAttributesForRune builds attribute 0 from
rune_creation_cost, 1-5 from rune_attr_adj, 6-10 from rune_max_attr_adj and
11-15 from item_attr_req. A static_rune_runebase comparator checks name and
attributes against the JSON.

Offline parity (scripts/p24.py, 315 runebase rows + 365 attribute rows vs the
RUNE JSON): every row has a rune; all 365 DB attribute rows match. Differences:
- name: 9 DB typos (Confesser, Theif, LagMaster, WP Dev, Exception Strength/
  Dexterity/Constitution/Intelligence/Spirit)
- attributes: 17 runes with no DB rows gain JSON values: base classes
  2500-2503, Pet 2522, admin 2905/2907/2909/2911, guards 252621-252623, and
  creation cost on 250116, 250117, 250123-250125
- description: '' (or CSR text on 2901-2911) -> rune_dsc; nothing reads it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 08:25:20 -04:00
MagicBotandClaude Opus 5.5 98f09d0713 Load Race from the race runes instead of static_rune_race
loadAllRaces builds one Race per static_rune_race row from the RACE rune with
the same id (all 26, including 2018-2021, decision: JSON); the row now only
supplies minHeight and strHeightMod, and a row without a race rune would load
from the DB with a warning. Colors stay in the DB. racebaseclass,
racehairstyle and racebeardstyle are no longer read at boot. The SQL loader
stays until task 2.11.

Value changes (decisions: JSON, D2):
- name drops the ", Male"/", Female" suffix ("Aelfborn, Male" -> "Aelfborn").
  WhoResponseMsg's split(",") and runAfterLoad's suffix strip still work;
  runAfterLoad now resolves 2019 to HalfGiant (it threw on the DB name).
- description '' -> rune_dsc blurb (unread)
- Minotaur hair 6958 -> 6598; Vampire M beard 5020170 -> 5020300
- beard style 0 dropped on Shade M/F, Minotaur, Vampire F (beard styles are
  never validated); hair 0 kept on Aracoix M/F and Shade M/F by the
  empty-hair rule
- 2018 Archon: startingPoints 0 -> 30, token 0 -> -317458791, health 0 -> 20,
  stamina 0 -> 90
- 2019 Half-Giant-Old Man: strMax 150 -> 140, dex 40/65 -> 30/75, con 60/140
  -> 50/130, spi 30/60 -> 35/80, startingPoints 20 -> 15, health/mana/stamina
  0 -> 10/5/70, baseClasses {} -> {Fighter 2500}
- 2020/2021 CSR: all stats 60/120 -> 40/100, startingPoints 0 -> 30, token
  0 -> 52803, health/mana/stamina 0 -> 40/40/10000

All other fields unchanged; final parity before the switch (scripts/p23.py):
22 creation races, 0 differences in attributes, bonuses, startingPoints,
token and base classes. The static_rune_race comparator is removed; it would
only compare the JSON with itself now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:02:06 -04:00
MagicBotandClaude Opus 5.5 a30a065841 Build Race from its race rune and compare every field
Adds Race(RuneTemplate, Race): start/max attributes from rune_attr_adj/
rune_max_attr_adj (min is always 0), health/mana/stamina bonus from
rune_health/mana/stamina, startingPoints = 30 - rune_creation_cost, token =
SBStringHash(rune_sub_type), name from obj_name, description from rune_dsc,
hair/beard styles from rune_hair/rune_beard (style 0 when rune_hair is empty),
and baseClasses = the creation class runes whose item_race_req names the race
sub type. minHeight/strHeightMod come from the static_rune_race row; colors,
skills, powers and effects still load from the DB by rune id. A
static_rune_race comparator checks every field against a JSON-built Race.

Offline parity (scripts/p23.py, 26 rows + racebaseclass/hairstyle/beardstyle
vs the RACE runes): the 22 creation races match in every attribute, bonus,
startingPoints, token and base class. Differences:
- name: "Aelfborn, Male" -> "Aelfborn" on every row
- hair 0 dropped by the JSON on Aracoix M/F and Shade M/F (kept by the
  empty-hair rule); beard 0 dropped on Shade M/F, Minotaur, Vampire F
- Minotaur hair 6958 -> 6598, Vampire M beard 5020170 -> 5020300
- 2018 Archon: startingPoints 0 -> 30, token 0 -> SBStringHash("Celestial"),
  health 0 -> 20, stamina 0 -> 90
- 2019 Half-Giant-Old Man: str/dex/con/spi start and max, startingPoints
  20 -> 15, health/mana/stamina 0 -> 10/5/70, baseClasses {} -> {Fighter}
- 2020/2021 CSR: stats 60/120 -> 40/100, startingPoints 0 -> 30, token 0 ->
  SBStringHash("CSR"), health/mana/stamina 0 -> 40/40/10000

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:01:43 -04:00
MagicBotandClaude Opus 5.5 957293165b Load PromotionClass from the promotion runes instead of static_rune_promotion
LoadAllPromotions builds one PromotionClass per promotion CLASS rune (22,
2504-2526 without 2522) and caches it, then runs the DB loader, which skips
cached ids, so only the CSR/admin rows 2901-2911 still come from
static_rune_promotion (decision: DB fallback). The world server calls it in
place of GET_ALL_PROMOTIONS; the login server now preloads promotions too, so
character load there gets the same cached objects. The SQL loader stays until
task 2.11.

Value changes: Templar 2516 manaMod 14 -> 15 (decision: JSON, D2).
description NULL -> rune_dsc blurb (unread). All other fields unchanged;
final parity before the switch (scripts/p22.py): 22 promotion rows, 0
differences in name, token, health, stamina and the 163 allowed rune tokens.
The static_rune_promotion comparator is removed; it would only compare the
JSON with itself now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:33:45 -04:00
MagicBotandClaude Opus 5.5 e91c803a32 Build PromotionClass from its class rune and compare every field
Adds PromotionClass(RuneTemplate): health/mana/stamina from rune_health/mana/
stamina, token = SBStringHash(rune_sub_type), description from rune_dsc, and
allowedRunes = the sub type tokens of the race and base class runes named in
item_race_req/item_class_req (all whitelists). Skills, powers and effects still
load from the DB by rune id (tasks 2.8, 2.9, 2.10). isPromotionRune selects the
non-creation CLASS runes that require a base class (skips 2500-2503 and Pet).
A static_rune_promotion comparator checks every field against a JSON-built
PromotionClass; rows 2901+ (TALENT runes in the JSON) report as missing.

Offline parity (scripts/p22.py, 33 rows + 163 promotionrunereq rows vs 22
promotion runes): 0 differences in name, token, health, stamina and allowed
rune tokens. Templar 2516 manaMod is 14 in the DB and 15 in the JSON.
2901-2911 have no promotion rune. description is NULL in the DB and the
rune_dsc blurb in the JSON; nothing reads PromotionClass.getDescription().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:33:08 -04:00
MagicBotandClaude Opus 5.5 992177eb0c Return the cached PromotionClass from GET_PROMOTION_CLASS
Character load and the promotion trainer re-read static_rune_promotion on every
call and replaced the cached object. They now get the object loaded at boot,
as GET_BASE_CLASS already does; the DB query is only a miss fallback.
No value changes: the cached and re-read objects hold the same row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:32:24 -04:00
MagicBotandClaude Opus 5.5 c8c322a3f5 Load BaseClass from the class runes instead of static_rune_baseclass
LoadAllBaseClasses builds one BaseClass per CLASS rune with
rune_is_standard_character_creation (2500-2503; promotions and Pet are
skipped) and caches it. Race.baseClasses picks up the cached objects through
GET_BASECLASS_FOR_RACE. The SQL loader stays until task 2.11.

Value changes: description goes from '' to the rune_dsc class blurb (unread).
All other fields unchanged; final parity before the switch (scripts/p21.py):
4 rows, 0 differences in name, attribute mods, token, health/mana/stamina mods.
The static_rune_baseclass comparator is removed; it would only compare the
JSON with itself now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:55:09 -04:00
MagicBotandClaude Opus 5.5 5acd3492ed Build BaseClass from its class rune and compare every field
Adds BaseClass(RuneTemplate): attributes from rune_attr_adj, health/mana/stamina
from rune_health/mana/stamina, token = SBStringHash(rune_sub_type), description
from rune_dsc. Skills, powers and effects still load from the DB by rune id
(tasks 2.8, 2.9, 2.10). The static_rune_baseclass comparator now checks every
field against a JSON-built BaseClass.

Offline parity (scripts/p21.py, 4 rows vs the CLASS runes with
rune_is_standard_character_creation = 2500-2503): 0 differences except
description, which is '' in the DB and the class blurb in rune_dsc. Nothing
reads BaseClass.getDescription().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:54:46 -04:00
MagicBotandClaude Opus 5.5 0283f85e87 Remove the legacy ItemTemplate and static_item_templates loader
Every reader moved to CObjectTemplate in 1.7, so the DB-loaded
engine.objects.ItemTemplate, LOAD_ITEM_TEMPLATES and linkCacheTemplates
are dead. No value changes: nothing read the legacy maps any more.
The boot log loses the legacy per-type count and the link warnings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 08:53:14 -04:00
MagicBotandClaude Opus 5.5 4b8430b0ba Use GameObjectType to check the ./fill target
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 08:44:11 -04:00
MagicBotandClaude Opus 5.5 f07f9945d7 Compare the selected warehouse by UUID in ./fill
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 08:42:29 -04:00
MagicBotandClaude Opus 5.5 077dcc071d Rename ./fillwarehouse to ./fill and require a selected warehouse
The command now acts on the selected building and refuses anything
whose blueprint is not a warehouse, or a warehouse that is not its
city's active one, instead of using the city the player stands in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 08:42:09 -04:00
MagicBotandClaude Opus 5.5 c616afa33b Add ./fillwarehouse dev command
Sets every resource in the warehouse of the city the player stands in,
either to an optional amount or, by default, to each resource's
deposit limit. Makes forge and work order testing practical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 08:40:01 -04:00
MagicBotandClaude Opus 5.5 1ac522a2e0 Fix /makeitem failing to persist items
MakeItemCmd left the container at NONE, which item_CREATE's cont
parameter rejects, so PERSIST returned null and addItemToInventory
threw a NullPointerException. Set the container to INVENTORY as
CreateItemCmd does, and report an error if PERSIST still fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 08:27:47 -04:00
MagicBotandClaude Opus 5.5 566870de79 Move the remaining legacy item template lookups to CObjectTemplate
Dev commands, BuildingManager, ChatManager, ForgeManager, NPCManager,
NPC, Warehouse, WorkOrder and ManageNPCMsg now call
CObjectTemplate.lookup instead of ItemTemplate.templates.get. Forge
production cost reads getItemResourceCosts(). Only the DB loader and
linkCacheTemplates still use the legacy class (removed in Task 1.8).

Removes the static_item_templates ParityCheck comparator. Final parity
(offline p17.py, 7586 rows): every id has a cache template, 0 item_type
differences, 0 item_resource_costs differences (84 ITEMs have costs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:56:08 -04:00
MagicBotandClaude Opus 5.5 6b4e9815e0 Switch Item.template and MobLoot to CObjectTemplate
Item.template is now the cache template from CObjectTemplate.lookup
instead of the legacy DB ItemTemplate, so item.template.cacheTemplate.X
becomes item.template.X. MobLoot, LootManager, ItemManager.isTwoHanded/
isShield and CombatManager.getSwingAnimation take a CObjectTemplate.

No value changes: every getter already read the cache template through
the bridge, and the lookup resolves the same 7586 ids as
static_item_templates with the same item_type (offline parity p17.py,
0 differences).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:55:28 -04:00
MagicBotandClaude Opus 5.5 f39e35ac23 Apply the dual-wield damage cut only when both hands hold weapons
The 30% cut never applied: each hand was passed itself as the other
hand, and the check compared the template to ItemType.WEAPON. The
client (0x466EB0 / 0x46D200) multiplies min and max damage by 0.7 when
both held slots contain an item_type WEAPON. Value change: players
wielding two weapons now deal 70% damage per hand; single weapon,
weapon + shield and unarmed are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:54:02 -04:00
MagicBotandClaude Opus 5.5 70ea9b3bc9 Move the vendor mod table off the legacy item template
LOAD_TEMPLATE_MODTABLES now returns a templateID -> modTable map that
ForgeManager owns (vendorModTables). ForgeManager.forgeItem, WorkOrder
validation and the ManageNPCMsg forge item list read it through
ForgeManager.getVendorModTable(), and ItemTemplate.modTable is removed.

Values are unchanged: static_vendor_items has 5089 rows, one per
templateID, all present in static_item_templates, modTable 0-30.
Templates not in the table still read 0. The old loader stopped at the
first templateID missing from ItemTemplate.templates; there are none.
No comparator added or removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:37:54 -04:00
MagicBotandClaude Opus 5.5 2b4c1a73d1 Read item user power actions from the cache template
The magical name color check and loadTemplateEnchantments read
getItemUserPowerAction(). The list keeps duplicates, so the baked-in
enchantments collapse a repeated power to its last entry, as the legacy
HashMap did; applying it twice would add its name to effectNames twice and
double its share of calcMagicValue.

Offline parity (p15e.py, 7586 rows): 0 differences in the lists (power and
arguments, in order). Duplicates exist only on 81 RUNE templates (mob,
guard and siege runes, plus discipline rune 3006 Blade Weaver with two
identical AMBIDEXTERITY entries); none on ITEM or DEED.

Value changes: none. Enchantments are now applied in file order instead
of HashMap order; effects are keyed by effect id and calcMagicValue is a
sum, so the result is the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:35:38 -04:00
MagicBotandClaude Opus 5.5 a37de9fc31 Read the item power grant from the cache template
The BUCKET/POTION/SCROLL use path takes the first item_power_grant entry
in file order from getItemPowerGrant() instead of the first key of the
legacy HashMap.

Offline parity (p15e.py, 7586 rows): 0 differences in the grant lists.
The legacy HashMap order picks a different power on 664 templates, all
RUNE folder runes with item_type RUNE, which take the RUNE case and never
reach this path. Every ITEM-folder grant (scrolls, potions, bucket, rod,
gift rings) has exactly one entry and no DEED has one, so the power and
value used are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:35:19 -04:00
MagicBotandClaude Opus 5.5 81d4ab7b99 Read item skill used and mastery from the cache template
CombatManager shield checks, weapon skill/mastery lookups in
AbstractCharacter, armor defense and PlayerCharacter.validEquip read
getItemSkillUsed/getItemSkillMasteryUsed. Offline parity (p15d.py, 7586
rows): 0 mastery differences; skill used differs only where the legacy
loader blanked it.

The cache keeps "Cloth" (226 ARMOR templates), which the legacy loader
turned into "". Cloth is a placeholder in Skills.cfg that no character
has, so getArmorDefense now checks for it explicitly and still returns the
flat defense rating. No weapon uses Cloth and no power equip prereq names
it, so the other sites behave the same.

Value changes:
- 195340 Fortified Dragon Plate Helm of the Lion: skill used is the
  unresolved hash "2624663" instead of "". Defense is still the flat
  rating (no such skill), but players wearing it now hit the
  "armor equipped without the necessary skill" error log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:29:01 -04:00
MagicBotandClaude Opus 5.5 7abc63ffc3 Read item skill requirements from the cache template
validForSkills, isShield, the guard attack rating and the NPC vendor skill
filter read getItemSkillReq. Offline parity (p15d.py, 7586 rows): 0
differences. No template lists "Cloth" in item_skill_req and none has more
than one entry, so file order vs HashMap order is moot.

Value changes: none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:28:32 -04:00
MagicBotandClaude Opus 5.5 5b3c511b55 Read item eq slots from the cache template
isTwoHanded, validForSlot, getSwingAnimation and the mob equipment set
read getItemEqSlotsOr/And, which drop the exporter's NONE. Offline parity
(p15c.py, 7586 rows): 0 differences once NONE is ignored.

Value changes:
- validForSlot: items whose only slot is NONE no longer pass for slot NONE.
- getSwingAnimation: the 7 slotless weapons (Rock 19297, Arrow 25650,
  siege ammo 29000/29010/29020/29030/29350) return the default 75 instead
  of indexing their empty left-hand anim list.
- MobBase equipment sets skip slotless items instead of keying them under
  NONE. No EQUIP booty entry references one (the 104 sets that do are ITEM
  booty sets).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:23:41 -04:00
MagicBotandClaude Opus 5.5 73947f3dc8 Drop NONE from rune and deed eq slots
Matches ItemParser, so every CObjectTemplate returns an empty slot set for
the exporter's ["NONE"] mask. All 2695 RUNE and 880 DEED templates have
["NONE"] only; nothing reads these sets yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:22:57 -04:00
MagicBotandClaude Opus 5.5 c7d8483d55 Read item race/class/disc requirements from the cache template
ValidRace/ValidClass/ValidDiscipline use the single req set plus the
restrict flag (true = blacklist, false = whitelist) instead of the legacy
req/res split. Offline parity (p15c.py, 7586 rows): 0 differences in
restrict or set for race, class and disc.

Value changes: none for equip checks. The legacy disc blacklist branch was
inverted (passed only when a listed disc was held); the new check passes
when none is held. No ITEM has a disc blacklist (only 19 RUNEs), and
ValidDiscipline has no callers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:22:36 -04:00
MagicBotandClaude Opus 5.5 0688cf231f Fix isTwoHanded always returning false
EnumSet.contains(EnumSet) is never true; use containsAll so the 350
weapons with item_eq_slots_and {LHELD, RHELD} block the other hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:22:09 -04:00
MagicBotandClaude Opus 5.5 6b2f000434 Read the weapon block from the cache template item_weapon
Range, speed, damage list and swing anims now come from
engine.parsers.objects.ItemTemplate.getWeapon(templateID). Non-weapons and
missing templates get an empty block, matching the legacy zero defaults.

Offline parity (scripts/p15b.py, 811 WEAPON rows vs COBJECTS/ITEM): 0
differences in speed, range, projectile, idle anim, damage list or either
anim list. No duplicate damage types exist, so sum vs first is moot; first
entry is used as before. 26 weapons (ammo, lutes, no-stats crossbows) have an
empty damage list, same as the DB. No value changes. No comparator removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:17:24 -04:00
MagicBotandClaude Opus 5.5 441a935bd9 Read armor resists from the cache template EnumMap
Resists.getArmorResists and NPCManager.applyEquipmentResists now read
cacheTemplate.getCombatAttackResist() with DamageType keys instead of the
legacy String-keyed map, and default a missing key to 0 instead of an
unboxing NPE.

Offline parity (scripts/p15a.py, all 7586 static_item_templates rows vs
the template CObjectIndex.lookup picks): 0 differences in the full
combat_attack_resist map. Every ITEM/DEED/RUNE template carries all 16
keys, so no value changes. No comparator added or removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:11:21 -04:00
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
84 changed files with 4286 additions and 1254 deletions

No files matched your search

+9 -35
View File
@@ -16,8 +16,6 @@ import engine.mbEnums.ItemType;
import engine.objects.AbstractCharacter; import engine.objects.AbstractCharacter;
import engine.objects.CharacterItemManager; import engine.objects.CharacterItemManager;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.ItemTemplate;
import org.json.JSONObject;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.Connection; import java.sql.Connection;
@@ -177,47 +175,23 @@ public class dbItemHandler extends dbHandlerBase {
return itemList; return itemList;
} }
public void LOAD_ITEM_TEMPLATES() { public HashMap<Integer, Integer> LOAD_TEMPLATE_MODTABLES() {
HashMap<ItemType, Integer> templateTCountMap = new HashMap<>(); HashMap<Integer, Integer> modTables = new HashMap<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_item_templates`;");
ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) {
int templateID = rs.getInt("id");
JSONObject jsonObject = new JSONObject(rs.getString("template"));
ItemTemplate itemTemplate = new ItemTemplate(jsonObject);
itemTemplate.template_id = templateID;
ItemTemplate.templates.put(templateID, itemTemplate);
templateTCountMap.merge(itemTemplate.item_type, 1, Integer::sum);
}
Logger.info(templateTCountMap.toString());
} catch (Exception e) {
Logger.error(e);
}
}
public void LOAD_TEMPLATE_MODTABLES() {
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_vendor_items`;"); PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_vendor_items`;");
ResultSet rs = preparedStatement.executeQuery()) { ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) { while (rs.next())
int templateID = rs.getInt("templateID"); modTables.put(rs.getInt("templateID"), rs.getInt("modTable"));
int modTable = rs.getInt("modTable");
ItemTemplate template = ItemTemplate.templates.get(templateID);
template.modTable = modTable;
}
} catch (Exception e) { } catch (Exception e) {
Logger.error(e); Logger.error(e);
} }
Logger.info("read: " + modTables.size());
return modTables;
} }
public Item GET_ITEM(final int itemUUID) { public Item GET_ITEM(final int itemUUID) {
@@ -325,7 +299,7 @@ public class dbItemHandler extends dbHandlerBase {
for (Item item : inventory) { for (Item item : inventory) {
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
continue; continue;
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
@@ -474,7 +448,7 @@ public class dbItemHandler extends dbHandlerBase {
*/ */
public boolean UPDATE_GOLD(final Item item, int newValue, int oldValue) { public boolean UPDATE_GOLD(final Item item, int newValue, int oldValue) {
if (!item.template.item_type.equals(ItemType.GOLD)) if (!item.template.getItemType().equals(ItemType.GOLD))
return false; return false;
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
@@ -554,7 +528,7 @@ public class dbItemHandler extends dbHandlerBase {
// Write 0 if we will not modify the value from template // Write 0 if we will not modify the value from template
if (value == item.template.item_value) if (value == item.template.getItemValue())
value = 0; value = 0;
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
+1 -1
View File
@@ -57,7 +57,7 @@ public class dbNPCHandler extends dbHandlerBase {
runeSets.put(runeSetID, runeList); runeSets.put(runeSetID, runeList);
} else { } else {
ArrayList<Integer> runeList = runeSets.get(runeSetID); ArrayList<Integer> runeList = runeSets.get(runeSetID);
runeList.add(runeSetID); runeList.add(runeBaseID);
runeSets.put(runeSetID, runeList); runeSets.put(runeSetID, runeList);
} }
} }
@@ -50,7 +50,15 @@ public class dbPromotionClassHandler extends dbHandlerBase {
public PromotionClass GET_PROMOTION_CLASS(final int objectUUID) { public PromotionClass GET_PROMOTION_CLASS(final int objectUUID) {
PromotionClass promotionClass = null; PromotionClass promotionClass;
if (objectUUID == 0)
return null;
promotionClass = (PromotionClass) DbManager.getFromCache(mbEnums.GameObjectType.PromotionClass, objectUUID);
if (promotionClass != null)
return promotionClass;
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_promotion` WHERE `ID` = ?")) { PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_promotion` WHERE `ID` = ?")) {
+19 -5
View File
@@ -22,6 +22,7 @@ import engine.objects.Warehouse;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -134,7 +135,6 @@ public class dbWarehouseHandler extends dbHandlerBase {
while (rs.next()) { while (rs.next()) {
int cityUID = rs.getInt("cityUUID"); int cityUID = rs.getInt("cityUUID");
JSONObject jsonObject = new JSONObject(rs.getString("warehouse"));
City city = City.getCity(cityUID); City city = City.getCity(cityUID);
if (city == null) { if (city == null) {
@@ -142,12 +142,18 @@ public class dbWarehouseHandler extends dbHandlerBase {
continue; 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; city.warehouse.city = city;
// Locate warehouse building // Locate warehouse building
for (Building building : city.parentZone.zoneBuildingSet) { 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; city.warehouse.building = building;
break; break;
} }
@@ -205,8 +211,16 @@ public class dbWarehouseHandler extends dbHandlerBase {
ResultSet rs = preparedStatement.executeQuery()) { ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) { while (rs.next()) {
JSONObject jsonObject = new JSONObject(rs.getString("workorder")); int workOrderID = rs.getInt("workorderID");
WorkOrder workOrder = new WorkOrder(jsonObject); 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); submitList.add(workOrder);
} }
} catch (Exception e) { } catch (Exception e) {
+3 -3
View File
@@ -15,8 +15,8 @@ import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.objects.AbstractGameObject; import engine.objects.AbstractGameObject;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.ItemTemplate;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import engine.parsers.objects.CObjectTemplate;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
/** /**
@@ -38,7 +38,7 @@ public class CreateItemCmd extends AbstractDevCmd {
} }
int templateID = Integer.parseInt(words[0]); int templateID = Integer.parseInt(words[0]);
ItemTemplate template = ItemTemplate.templates.get(templateID); CObjectTemplate template = CObjectTemplate.lookup(templateID);
if (template == null) { if (template == null) {
ChatManager.chatSystemInfo(playerCharacter, "No such template found."); ChatManager.chatSystemInfo(playerCharacter, "No such template found.");
@@ -51,7 +51,7 @@ public class CreateItemCmd extends AbstractDevCmd {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
if (!playerCharacter.charItemManager.hasRoomInventory(template.item_wt)) { if (!playerCharacter.charItemManager.hasRoomInventory(template.getItemWt())) {
ChatManager.chatSystemInfo(playerCharacter, "You are encumbered!."); ChatManager.chatSystemInfo(playerCharacter, "You are encumbered!.");
break; break;
} }
+106
View File
@@ -0,0 +1,106 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd;
import engine.gameManager.ChatManager;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.objects.AbstractGameObject;
import engine.objects.Building;
import engine.objects.City;
import engine.objects.PlayerCharacter;
import engine.objects.Warehouse;
import java.util.HashMap;
public class FillCmd extends AbstractDevCmd {
public FillCmd() {
super("fill");
}
@Override
protected void _doCmd(PlayerCharacter pc, String[] words,
AbstractGameObject target) {
Building building;
City city;
Warehouse warehouse;
int amount = Integer.MAX_VALUE;
HashMap<mbEnums.ResourceType, Integer> oldResources;
if (words.length > 1) {
this.sendUsage(pc);
return;
}
if (words.length == 1 && !words[0].isEmpty()) {
try {
amount = Integer.parseInt(words[0]);
} catch (NumberFormatException e) {
throwbackError(pc, "Amount must be a number, " + words[0] + " is invalid");
return;
}
if (amount < 0) {
throwbackError(pc, "Amount must be 0 or more");
return;
}
}
if (target == null || target.getObjectType() != mbEnums.GameObjectType.Building) {
throwbackError(pc, "Select a warehouse");
return;
}
building = (Building) target;
if (building.getBlueprint() == null || building.getBlueprint().getBuildingGroup() != mbEnums.BuildingGroup.WAREHOUSE) {
throwbackError(pc, "Selected building is not a warehouse");
return;
}
city = building.getCity();
if (city == null || city.warehouse == null || city.warehouse.building == null
|| city.warehouse.building.getObjectUUID() != building.getObjectUUID()) {
throwbackError(pc, "Selected warehouse is not the active warehouse of a city");
return;
}
warehouse = city.warehouse;
oldResources = new HashMap<>(warehouse.resources);
for (mbEnums.ResourceType resourceType : mbEnums.ResourceType.values())
warehouse.resources.put(resourceType, Math.min(amount, resourceType.deposit_limit));
if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
warehouse.resources.putAll(oldResources);
throwbackError(pc, "Failed to update the warehouse in the database");
return;
}
ChatManager.chatSayInfo(pc, "Filled the " + city.getCityName() + " warehouse"
+ (amount == Integer.MAX_VALUE ? " to each deposit limit" : " with " + amount + " of each resource (capped at the deposit limit)"));
}
@Override
protected String _getHelpString() {
return "Sets every resource in the selected warehouse";
}
@Override
protected String _getUsageString() {
return "' ./fill [amount]' with a warehouse selected (no amount fills each resource to its deposit limit)";
}
}
+6 -5
View File
@@ -18,6 +18,7 @@ import engine.mbEnums.BuildingGroup;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.mbEnums.TargetColor; import engine.mbEnums.TargetColor;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.util.StringUtils; import engine.util.StringUtils;
import java.text.DecimalFormat; import java.text.DecimalFormat;
@@ -507,17 +508,17 @@ public class InfoCmd extends AbstractDevCmd {
case Item: //intentional passthrough case Item: //intentional passthrough
case MobLoot: case MobLoot:
Item item = (Item) target; Item item = (Item) target;
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
output += StringUtils.addWS("Template: " + template.template_id, 20); output += StringUtils.addWS("Template: " + template.getTemplateId(), 20);
output += "Weight: " + template.item_wt; output += "Weight: " + template.getItemWt();
output += newline; output += newline;
DecimalFormat df = new DecimalFormat("###,###,###,###,##0"); DecimalFormat df = new DecimalFormat("###,###,###,###,##0");
output += StringUtils.addWS("Qty: " output += StringUtils.addWS("Qty: "
+ df.format(item.getNumOfItems()), 20); + df.format(item.getNumOfItems()), 20);
output += "Charges: " + (byte) item.chargesRemaining output += "Charges: " + (byte) item.chargesRemaining
+ '/' + item.template.item_initial_charges; + '/' + item.template.getItemInitialCharges();
output += newline; output += newline;
output += "Name: " + template.item_base_name; output += "Name: " + template.getItemBaseName();
output += newline; output += newline;
output += item.getContainerInfo(); output += item.getContainerInfo();
+16 -9
View File
@@ -11,9 +11,11 @@ package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd; import engine.devcmd.AbstractDevCmd;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums.ItemContainerType;
import engine.mbEnums.ItemType; import engine.mbEnums.ItemType;
import engine.mbEnums.OwnerType; import engine.mbEnums.OwnerType;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
/** /**
@@ -59,18 +61,17 @@ public class MakeItemCmd extends AbstractDevCmd {
} }
int templateID; int templateID;
ItemTemplate template; CObjectTemplate template;
try { try {
templateID = Integer.parseInt(words[0]); templateID = Integer.parseInt(words[0]);
template = ItemTemplate.templates.get(words[0].toLowerCase()); template = CObjectTemplate.lookup(templateID);
if (template == null) { if (template == null) {
throwbackError(pc, "Supplied type " + words[0] throwbackError(pc, "No item template with id " + words[0]);
+ " failed to parse to an Integer");
return; return;
} }
if (template.item_type == ItemType.GOLD) { if (template.getItemType() == ItemType.GOLD) {
this.throwbackInfo(pc, "use /addgold to add gold."); this.throwbackInfo(pc, "use /addgold to add gold.");
return; return;
} }
@@ -132,7 +133,7 @@ public class MakeItemCmd extends AbstractDevCmd {
return; return;
} }
} }
template = ItemTemplate.templates.get(templateID); template = CObjectTemplate.lookup(templateID);
if (template == null) { if (template == null) {
throwbackError(pc, "Unable to find template " + templateID); throwbackError(pc, "Unable to find template " + templateID);
@@ -140,8 +141,8 @@ public class MakeItemCmd extends AbstractDevCmd {
} }
if ((numItems > 1) if ((numItems > 1)
&& (template.item_type.equals(ItemType.RESOURCE) == false) && (template.getItemType().equals(ItemType.RESOURCE) == false)
&& (template.item_type.equals(ItemType.OFFERING)) == false) && (template.getItemType().equals(ItemType.OFFERING)) == false)
numItems = 1; numItems = 1;
CharacterItemManager cim = pc.charItemManager; CharacterItemManager cim = pc.charItemManager;
@@ -155,7 +156,7 @@ public class MakeItemCmd extends AbstractDevCmd {
for (int i = 0; i < quantity; i++) { for (int i = 0; i < quantity; i++) {
int weight = template.item_wt; int weight = template.getItemWt();
if (!cim.hasRoomInventory(weight)) { if (!cim.hasRoomInventory(weight)) {
throwbackError(pc, "Not enough room in inventory for any more of this item. " + i + " produced."); throwbackError(pc, "Not enough room in inventory for any more of this item. " + i + " produced.");
@@ -169,6 +170,7 @@ public class MakeItemCmd extends AbstractDevCmd {
Item item = new Item(templateID); Item item = new Item(templateID);
item.ownerID = pc.getObjectUUID(); item.ownerID = pc.getObjectUUID();
item.ownerType = OwnerType.PlayerCharacter; item.ownerType = OwnerType.PlayerCharacter;
item.containerType = ItemContainerType.INVENTORY;
if (numItems > 1) if (numItems > 1)
item.setNumOfItems(numItems); item.setNumOfItems(numItems);
@@ -180,6 +182,11 @@ public class MakeItemCmd extends AbstractDevCmd {
return; return;
} }
if (item == null) {
throwbackError(pc, "DB error 2: Unable to create item " + templateID + ".");
return;
}
//create prefix //create prefix
if (!prefix.isEmpty()) if (!prefix.isEmpty())
item.addPermanentEnchantmentForDev(prefix, 0); item.addPermanentEnchantmentForDev(prefix, 0);
+3 -2
View File
@@ -11,6 +11,7 @@ package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd; import engine.devcmd.AbstractDevCmd;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import java.util.ArrayList; import java.util.ArrayList;
@@ -52,8 +53,8 @@ public class PrintBankCmd extends AbstractDevCmd {
throwbackInfo(pc, "Bank for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); throwbackInfo(pc, "Bank for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (Item item : list) { for (Item item : list) {
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
throwbackInfo(pc, " " + template.item_base_name + ", count: " + item.getNumOfItems()); throwbackInfo(pc, " " + template.getItemBaseName() + ", count: " + item.getNumOfItems());
} }
Item gold = cim.getGoldBank(); Item gold = cim.getGoldBank();
+3 -2
View File
@@ -13,6 +13,7 @@ import engine.devcmd.AbstractDevCmd;
import engine.gameManager.ChatManager; import engine.gameManager.ChatManager;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.powers.effectmodifiers.AbstractEffectModifier; import engine.powers.effectmodifiers.AbstractEffectModifier;
public class PrintBonusesCmd extends AbstractDevCmd { public class PrintBonusesCmd extends AbstractDevCmd {
@@ -33,8 +34,8 @@ public class PrintBonusesCmd extends AbstractDevCmd {
if (target instanceof Item) { if (target instanceof Item) {
type = "Item"; type = "Item";
tar = (Item) target; tar = (Item) target;
ItemTemplate template = ItemTemplate.templates.get(((Item) tar).templateID); CObjectTemplate template = CObjectTemplate.lookup(((Item) tar).templateID);
name = template.item_base_name; name = template.getItemBaseName();
} else if (target instanceof AbstractCharacter) { } else if (target instanceof AbstractCharacter) {
tar = (AbstractCharacter) target; tar = (AbstractCharacter) target;
name = ((AbstractCharacter) tar).getFirstName(); 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() + ')'); throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) { for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) {
Item equip = tarMob.charItemManager.equipped.get(slot); Item equip = tarMob.charItemManager.equipped.get(slot);
throwbackInfo(pc, equip.templateID + " : " + equip.template.item_base_name + ", slot: " + slot); throwbackInfo(pc, equip.templateID + " : " + equip.template.getItemBaseName() + ", slot: " + slot);
} }
return; return;
} }
@@ -76,7 +76,7 @@ public class PrintEquipCmd extends AbstractDevCmd {
throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) { for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) {
Item equip = tarMob.charItemManager.equipped.get(slot); Item equip = tarMob.charItemManager.equipped.get(slot);
throwbackInfo(pc, equip.templateID + " : " + equip.template.item_base_name + ", slot: " + slot); throwbackInfo(pc, equip.templateID + " : " + equip.template.getItemBaseName() + ", slot: " + slot);
} }
return; return;
} }
@@ -87,7 +87,7 @@ public class PrintEquipCmd extends AbstractDevCmd {
for (mbEnums.EquipSlotType slot : list.keySet()) { for (mbEnums.EquipSlotType slot : list.keySet()) {
Item item = list.get(slot); Item item = list.get(slot);
throwbackInfo(pc, " " + item.template.item_base_name + ", slot: " + slot); throwbackInfo(pc, " " + item.template.getItemBaseName() + ", slot: " + slot);
} }
} }
@@ -12,6 +12,7 @@ package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd; import engine.devcmd.AbstractDevCmd;
import engine.mbEnums.ItemType; import engine.mbEnums.ItemType;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
@@ -75,18 +76,18 @@ public class PrintInventoryCmd extends AbstractDevCmd {
int goldCount = 0; int goldCount = 0;
for (Item item : inventory) { for (Item item : inventory) {
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
if (item.template.item_type.equals(ItemType.GOLD) == false) { if (item.template.getItemType().equals(ItemType.GOLD) == false) {
String chargeInfo = ""; String chargeInfo = "";
int chargeMax = template.item_initial_charges; int chargeMax = template.getItemInitialCharges();
if (chargeMax > 0) { if (chargeMax > 0) {
int charges = item.chargesRemaining; int charges = item.chargesRemaining;
chargeInfo = " charges: " + charges + '/' + chargeMax; chargeInfo = " charges: " + charges + '/' + chargeMax;
} }
throwbackInfo(pc, " " + template.item_base_name + ", count: " + item.getNumOfItems() + chargeInfo); throwbackInfo(pc, " " + template.getItemBaseName() + ", count: " + item.getNumOfItems() + chargeInfo);
} else } else
goldCount += item.getNumOfItems(); goldCount += item.getNumOfItems();
} }
+3 -2
View File
@@ -11,6 +11,7 @@ package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd; import engine.devcmd.AbstractDevCmd;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import java.util.ArrayList; import java.util.ArrayList;
@@ -50,8 +51,8 @@ public class PrintVaultCmd extends AbstractDevCmd {
ArrayList<Item> list = cim.getVault(); ArrayList<Item> list = cim.getVault();
throwbackInfo(pc, "Vault for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); throwbackInfo(pc, "Vault for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (Item item : list) { for (Item item : list) {
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
throwbackInfo(pc, " " + template.item_base_name + ", count: " + item.getNumOfItems()); throwbackInfo(pc, " " + template.getItemBaseName() + ", count: " + item.getNumOfItems());
} }
Item gold = cim.getGoldVault(); Item gold = cim.getGoldVault();
if (gold != null) if (gold != null)
+4 -3
View File
@@ -5,6 +5,7 @@ import engine.gameManager.LootManager;
import engine.gameManager.ZoneManager; import engine.gameManager.ZoneManager;
import engine.loot.BootySetEntry; import engine.loot.BootySetEntry;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@@ -34,10 +35,10 @@ public class SimulateBootyCmd extends AbstractDevCmd {
if (mob.bootySet != 0) { if (mob.bootySet != 0) {
for (BootySetEntry entry : LootManager._bootySetMap.get(mob.bootySet)) { for (BootySetEntry entry : LootManager._bootySetMap.get(mob.bootySet)) {
ItemTemplate template = ItemTemplate.templates.get(entry.templateID); CObjectTemplate template = CObjectTemplate.lookup(entry.templateID);
if (template != null) if (template != null)
output += "[" + entry.bootyType + "] " + template.item_base_name + " [Chance] " + entry.dropChance + newline; output += "[" + entry.bootyType + "] " + template.getItemBaseName() + " [Chance] " + entry.dropChance + newline;
} }
} }
@@ -57,7 +58,7 @@ public class SimulateBootyCmd extends AbstractDevCmd {
try { try {
mob.loadInventory(); mob.loadInventory();
for (Item lootItem : mob.charItemManager.getInventory()) { for (Item lootItem : mob.charItemManager.getInventory()) {
switch (lootItem.template.item_type) { switch (lootItem.template.getItemType()) {
case EMPLOYMENTCONTRACT: //CONTRACT case EMPLOYMENTCONTRACT: //CONTRACT
Contracts.add(lootItem); Contracts.add(lootItem);
break; break;
+14 -10
View File
@@ -22,6 +22,7 @@ import engine.mbEnums.BuildingGroup;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.net.client.msg.ErrorPopupMsg; import engine.net.client.msg.ErrorPopupMsg;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -199,31 +200,31 @@ public enum BuildingManager {
return false; return false;
} }
ItemTemplate template = ItemTemplate.templates.get(1705032); // Elan Stone CObjectTemplate template = CObjectTemplate.lookup(1705032); // Elan Stone
if (!player.charItemManager.hasRoomInventory(template.item_wt)) if (!player.charItemManager.hasRoomInventory(template.getItemWt()))
return false; return false;
if (!ItemManager.MakeItemForPlayer(template.template_id, player, amount)) if (!ItemManager.MakeItemForPlayer(template.getTemplateId(), player, amount))
return false; return false;
shrine.setFavors(0); shrine.setFavors(0);
break; break;
case WAREHOUSE: case WAREHOUSE:
City city = building.getCity(); Warehouse warehouse = Warehouse.destroyedWarehouses.get(building.getObjectUUID());
if (city == null)
return true;
Warehouse warehouse = city.warehouse;
if (warehouse == null) if (warehouse == null)
return false; return false;
for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) { for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) {
if (!player.charItemManager.hasRoomInventory(resourceType.template.item_wt)) { CObjectTemplate resourceTemplate = CObjectTemplate.lookup(resourceType.templateID);
if (resourceTemplate == null)
continue;
if (!player.charItemManager.hasRoomInventory(resourceTemplate.getItemWt())) {
ChatManager.chatSystemInfo(player, "You can not carry any more of that item."); ChatManager.chatSystemInfo(player, "You can not carry any more of that item.");
return false; return false;
} }
@@ -241,6 +242,9 @@ public enum BuildingManager {
} }
} }
if (Warehouse.isEmpty(warehouse))
Warehouse.destroyedWarehouses.remove(building.getObjectUUID());
break; break;
} }
+4 -3
View File
@@ -22,6 +22,7 @@ import engine.net.client.ClientConnection;
import engine.net.client.handlers.ClientAdminCommandMsgHandler; import engine.net.client.handlers.ClientAdminCommandMsgHandler;
import engine.net.client.msg.chat.*; import engine.net.client.msg.chat.*;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.server.world.WorldServer; import engine.server.world.WorldServer;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -535,12 +536,12 @@ public enum ChatManager {
if (item != null) //Steal if (item != null) //Steal
if (success) { if (success) {
String name = ""; String name = "";
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) if (item.template.getItemType().equals(mbEnums.ItemType.GOLD))
name = amount + " gold "; name = amount + " gold ";
else { else {
String vowels = "aeiou"; String vowels = "aeiou";
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
String iName = template.item_base_name; String iName = template.getItemBaseName();
if (iName.length() > 0) if (iName.length() > 0)
if (vowels.indexOf(iName.substring(0, 1).toLowerCase()) >= 0) if (vowels.indexOf(iName.substring(0, 1).toLowerCase()) >= 0)
name = "an " + iName + ' '; name = "an " + iName + ' ';
+33 -30
View File
@@ -18,6 +18,8 @@ import engine.net.client.msg.PowerProjectileMsg;
import engine.net.client.msg.TargetedActionMsg; import engine.net.client.msg.TargetedActionMsg;
import engine.net.client.msg.UpdateStateMsg; import engine.net.client.msg.UpdateStateMsg;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.powers.DamageShield; import engine.powers.DamageShield;
import engine.powers.effectmodifiers.AbstractEffectModifier; import engine.powers.effectmodifiers.AbstractEffectModifier;
import engine.powers.effectmodifiers.WeaponProcEffectModifier; import engine.powers.effectmodifiers.WeaponProcEffectModifier;
@@ -92,25 +94,25 @@ public enum CombatManager {
return; return;
} }
if (mainWeapon == null && offWeapon != null && !offWeapon.template.item_skill_used.equals("Block")) { if (mainWeapon == null && offWeapon != null && !offWeapon.template.getItemSkillUsed().equals("Block")) {
//swing left hand only //swing left hand only
processAttack(attacker, target, mbEnums.EquipSlotType.LHELD); processAttack(attacker, target, mbEnums.EquipSlotType.LHELD);
return; return;
} }
if (mainWeapon == null && offWeapon != null && offWeapon.template.item_skill_used.equals("Block")) { if (mainWeapon == null && offWeapon != null && offWeapon.template.getItemSkillUsed().equals("Block")) {
//no weapon equipped with a shield, punch with one hand //no weapon equipped with a shield, punch with one hand
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD); processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
return; return;
} }
if (mainWeapon != null && offWeapon != null && offWeapon.template.item_skill_used.equals("Block")) { if (mainWeapon != null && offWeapon != null && offWeapon.template.getItemSkillUsed().equals("Block")) {
//one weapon equipped with a shield, swing with one hand //one weapon equipped with a shield, swing with one hand
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD); processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
return; return;
} }
if (mainWeapon != null && offWeapon != null && !offWeapon.template.item_skill_used.equals("Block")) { if (mainWeapon != null && offWeapon != null && !offWeapon.template.getItemSkillUsed().equals("Block")) {
//two weapons equipped, swing both hands //two weapons equipped, swing both hands
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD); processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter))
@@ -146,7 +148,7 @@ public enum CombatManager {
if (bonus != null) if (bonus != null)
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None); rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None);
attackRange += weapon.template.item_weapon_max_range * rangeMod; attackRange += engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_max_range * rangeMod;
} else if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) { } else if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) {
//unarmed mob, add the natural attack range from its rune (0 for most melee mobs) //unarmed mob, add the natural attack range from its rune (0 for most melee mobs)
@@ -211,7 +213,7 @@ public enum CombatManager {
if (weapon != null) { if (weapon != null) {
float wepSpeed = (int) (weapon.template.item_weapon_wepspeed); float wepSpeed = (int) (engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_wepspeed);
if (weapon.getBonusPercent(mbEnums.ModType.WeaponSpeed, mbEnums.SourceType.None) != 0f) //add weapon speed bonus if (weapon.getBonusPercent(mbEnums.ModType.WeaponSpeed, mbEnums.SourceType.None) != 0f) //add weapon speed bonus
wepSpeed *= (1 + weapon.getBonus(mbEnums.ModType.WeaponSpeed, mbEnums.SourceType.None)); wepSpeed *= (1 + weapon.getBonus(mbEnums.ModType.WeaponSpeed, mbEnums.SourceType.None));
@@ -270,13 +272,13 @@ public enum CombatManager {
if (weapon != null && !attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) { if (weapon != null && !attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) {
//check if Out of Stamina //check if Out of Stamina
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) { if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) {
if (attacker.getStamina() < (weapon.template.item_wt / 3f)) { if (attacker.getStamina() < (weapon.template.getItemWt() / 3f)) {
//set auto attack job //set auto attack job
setAutoAttackJob(attacker, slot, delay); setAutoAttackJob(attacker, slot, delay);
return; return;
} }
} }
float stam = weapon.template.item_wt / 3f; float stam = weapon.template.getItemWt() / 3f;
stam = (stam < 1) ? 1 : stam; stam = (stam < 1) ? 1 : stam;
attacker.modifyStamina(-(stam), attacker, true); attacker.modifyStamina(-(stam), attacker, true);
} else } else
@@ -416,7 +418,7 @@ public enum CombatManager {
if (((Mob) attacker).isSiege()) if (((Mob) attacker).isSiege())
damageType = mbEnums.DamageType.SIEGE; damageType = mbEnums.DamageType.SIEGE;
} else { } else {
damageType = (mbEnums.DamageType) attacker.charItemManager.getEquipped().get(slot).template.item_weapon_damage.keySet().toArray()[0]; damageType = engine.parsers.objects.ItemTemplate.getWeapon(attacker.charItemManager.getEquipped().get(slot).templateID).weapon_damage.get(0).damage_type;
} }
//get resists //get resists
@@ -577,13 +579,14 @@ public enum CombatManager {
target.setCombatTarget(attacker); target.setCombatTarget(attacker);
} }
public static int getSwingAnimation(ItemTemplate wb, DeferredPowerJob dpj, mbEnums.EquipSlotType slot) { public static int getSwingAnimation(CObjectTemplate wb, DeferredPowerJob dpj, mbEnums.EquipSlotType slot) {
//No weapon, return default animation //No weapon, return default animation
if (wb == null) if (wb == null)
return 75; return 75;
int token; int token;
engine.parsers.objects.ItemTemplate.Weapon weapon = engine.parsers.objects.ItemTemplate.getWeapon(wb.getTemplateId());
if (dpj != null) { if (dpj != null) {
@@ -594,7 +597,7 @@ public enum CombatManager {
} }
//Item has no equipment slots and should not try to return an animation, return default instead //Item has no equipment slots and should not try to return an animation, return default instead
if (wb.item_eq_slots_or == null || wb.item_eq_slots_or.isEmpty()) { if (wb.getItemEqSlotsOr().isEmpty()) {
return 75; return 75;
} }
@@ -603,18 +606,18 @@ public enum CombatManager {
int random; int random;
//Item can only be equipped in one slot, return animation for that slot //Item can only be equipped in one slot, return animation for that slot
if (wb.item_eq_slots_or.size() == 1) { if (wb.getItemEqSlotsOr().size() == 1) {
if (wb.item_eq_slots_or.iterator().next().equals(mbEnums.EquipSlotType.RHELD)) { if (wb.getItemEqSlotsOr().iterator().next().equals(mbEnums.EquipSlotType.RHELD)) {
anim = wb.weapon_attack_anim_right.get(0)[0]; anim = weapon.weapon_attack_anim_right.get(0)[0];
if (dpj != null) { if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_right.size()); random = ThreadLocalRandom.current().nextInt(weapon.weapon_attack_anim_right.size());
anim = wb.weapon_attack_anim_right.get(random)[0]; anim = weapon.weapon_attack_anim_right.get(random)[0];
} }
} else { } else {
anim = wb.weapon_attack_anim_left.get(0)[0]; anim = weapon.weapon_attack_anim_left.get(0)[0];
if (dpj != null) { if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_left.size()); random = ThreadLocalRandom.current().nextInt(weapon.weapon_attack_anim_left.size());
anim = wb.weapon_attack_anim_left.get(random)[0]; anim = weapon.weapon_attack_anim_left.get(random)[0];
} }
} }
return anim; return anim;
@@ -622,16 +625,16 @@ public enum CombatManager {
//Item can be equipped in either hand, and should have animation sets for each hand //Item can be equipped in either hand, and should have animation sets for each hand
if (slot.equals(mbEnums.EquipSlotType.RHELD)) { if (slot.equals(mbEnums.EquipSlotType.RHELD)) {
anim = wb.weapon_attack_anim_right.get(0)[0]; anim = weapon.weapon_attack_anim_right.get(0)[0];
if (dpj != null) { if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_right.size()); random = ThreadLocalRandom.current().nextInt(weapon.weapon_attack_anim_right.size());
anim = wb.weapon_attack_anim_right.get(random)[0]; anim = weapon.weapon_attack_anim_right.get(random)[0];
} }
} else { } else {
anim = wb.weapon_attack_anim_left.get(0)[0]; anim = weapon.weapon_attack_anim_left.get(0)[0];
if (dpj != null) { if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_left.size()); random = ThreadLocalRandom.current().nextInt(weapon.weapon_attack_anim_left.size());
anim = wb.weapon_attack_anim_left.get(random)[0]; anim = weapon.weapon_attack_anim_left.get(random)[0];
} }
} }
return anim; return anim;
@@ -713,7 +716,7 @@ public enum CombatManager {
// Mobs use their mobbase rune, players use their race rune. // Mobs use their mobbase rune, players use their race rune.
int runeID; int runeID;
ItemTemplate rune; RuneTemplate rune;
int hand = slot.equals(mbEnums.EquipSlotType.LHELD) ? 2 : 1; int hand = slot.equals(mbEnums.EquipSlotType.LHELD) ? 2 : 1;
switch (attacker.getObjectType()) { switch (attacker.getObjectType()) {
@@ -727,18 +730,18 @@ public enum CombatManager {
return null; 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; return null;
for (float[] attack : rune.rune_natural_attacks) for (float[] attack : rune.rune_naturalattacks)
if (attack.length > 3 && (int) attack[3] == hand) if (attack.length > 3 && (int) attack[3] == hand)
return attack; return attack;
// No entry for this hand, fall back to the first natural 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) { public static int calculatePetDamage(AbstractCharacter agent) {
@@ -75,6 +75,7 @@ public enum ConfigManager {
// Mobile AI modifiers // Mobile AI modifiers
MB_AI_CAST_FREQUENCY, MB_AI_CAST_FREQUENCY,
MB_AI_AGGRO_RANGE, MB_AI_AGGRO_RANGE,
MB_MINION_SPAWN_TIME,
//drop rates //drop rates
MB_NORMAL_EXP_RATE, MB_NORMAL_EXP_RATE,
@@ -101,6 +101,7 @@ public enum DevCmdManager {
DevCmdManager.registerDevCmd(new SetAdminRuneCmd()); DevCmdManager.registerDevCmd(new SetAdminRuneCmd());
DevCmdManager.registerDevCmd(new SetInvulCmd()); DevCmdManager.registerDevCmd(new SetInvulCmd());
DevCmdManager.registerDevCmd(new MakeItemCmd()); DevCmdManager.registerDevCmd(new MakeItemCmd());
DevCmdManager.registerDevCmd(new FillCmd());
DevCmdManager.registerDevCmd(new SetSubRaceCmd()); DevCmdManager.registerDevCmd(new SetSubRaceCmd());
// Admin // Admin
+16 -9
View File
@@ -15,8 +15,8 @@ import engine.mbEnums;
import engine.net.client.msg.ItemProductionMsg; import engine.net.client.msg.ItemProductionMsg;
import engine.objects.City; import engine.objects.City;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.ItemTemplate;
import engine.objects.NPC; import engine.objects.NPC;
import engine.parsers.objects.CObjectTemplate;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -49,6 +49,9 @@ public enum ForgeManager implements Runnable {
public static final ConcurrentHashMap<NPC, ConcurrentHashMap.KeySetView<WorkOrder, Boolean>> vendorWorkOrderLookup = new ConcurrentHashMap<>(); public static final ConcurrentHashMap<NPC, ConcurrentHashMap.KeySetView<WorkOrder, Boolean>> vendorWorkOrderLookup = new ConcurrentHashMap<>();
public static final ConcurrentHashMap<Item, WorkOrder> itemWorkOrderLookup = new ConcurrentHashMap<>(); public static final ConcurrentHashMap<Item, WorkOrder> itemWorkOrderLookup = new ConcurrentHashMap<>();
// Server-only data from static_vendor_items; the client templates have no mod table.
public static HashMap<Integer, Integer> vendorModTables = new HashMap<>();
public static void start() { public static void start() {
Thread forgeManager; Thread forgeManager;
@@ -222,12 +225,12 @@ public enum ForgeManager implements Runnable {
// Calculate production cost for a single run of the workOrder // Calculate production cost for a single run of the workOrder
HashMap<mbEnums.ResourceType, Integer> production_cost = new HashMap<>(); HashMap<mbEnums.ResourceType, Integer> production_cost = new HashMap<>();
ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID); CObjectTemplate template = CObjectTemplate.lookup(workOrder.templateID);
// Add gold and resource costs from template // Add gold and resource costs from template
production_cost.put(mbEnums.ResourceType.GOLD, template.item_value); production_cost.put(mbEnums.ResourceType.GOLD, template.getItemValue());
production_cost.putAll(template.item_resource_cost); production_cost.putAll(template.getItemResourceCosts());
// Calculate cost of prefix and suffix // Calculate cost of prefix and suffix
@@ -248,8 +251,8 @@ public enum ForgeManager implements Runnable {
// Create new virtual item from specified template // Create new virtual item from specified template
ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID);
Item forgedItem = new Item(workOrder.templateID); Item forgedItem = new Item(workOrder.templateID);
int modTable = getVendorModTable(workOrder.templateID);
// forgedItem gets a negative id; a virtual item which is not persisted // forgedItem gets a negative id; a virtual item which is not persisted
@@ -264,13 +267,13 @@ public enum ForgeManager implements Runnable {
// Assign a prefix and suffix to this item if random rolled // Assign a prefix and suffix to this item if random rolled
if (workOrder.prefixToken == 0 && workOrder.vendor.getItemModTable().contains((template.modTable))) if (workOrder.prefixToken == 0 && workOrder.vendor.getItemModTable().contains(modTable))
forgedItem.prefixToken = calcRandomMod(workOrder.vendor, mbEnums.ItemModType.PREFIX, template.modTable); forgedItem.prefixToken = calcRandomMod(workOrder.vendor, mbEnums.ItemModType.PREFIX, modTable);
else else
forgedItem.prefixToken = workOrder.prefixToken; forgedItem.prefixToken = workOrder.prefixToken;
if (workOrder.suffixToken == 0 && workOrder.vendor.getItemModTable().contains((template.modTable))) if (workOrder.suffixToken == 0 && workOrder.vendor.getItemModTable().contains(modTable))
forgedItem.suffixToken = calcRandomMod(workOrder.vendor, mbEnums.ItemModType.SUFFIX, template.modTable); forgedItem.suffixToken = calcRandomMod(workOrder.vendor, mbEnums.ItemModType.SUFFIX, modTable);
else else
forgedItem.suffixToken = workOrder.suffixToken; forgedItem.suffixToken = workOrder.suffixToken;
@@ -364,6 +367,10 @@ public enum ForgeManager implements Runnable {
DbManager.WarehouseQueries.WRITE_WORKORDER(workOrder); DbManager.WarehouseQueries.WRITE_WORKORDER(workOrder);
} }
public static int getVendorModTable(int templateID) {
return vendorModTables.getOrDefault(templateID, 0);
}
public static int calcRandomMod(NPC vendor, mbEnums.ItemModType itemModType, int modTable) { public static int calcRandomMod(NPC vendor, mbEnums.ItemModType itemModType, int modTable) {
// Random prefix or suffix token based on item.template.modtable // Random prefix or suffix token based on item.template.modtable
+1 -1
View File
@@ -307,7 +307,7 @@ public enum GroupManager {
return false; return false;
} }
if (!item.template.item_type.equals(mbEnums.ItemType.GOLD)) //only split goldItem if (!item.template.getItemType().equals(mbEnums.ItemType.GOLD)) //only split goldItem
return false; return false;
Group group = getGroup(pc); Group group = getGroup(pc);
+35 -54
View File
@@ -2,6 +2,7 @@ package engine.gameManager;
import engine.mbEnums; import engine.mbEnums;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -21,66 +22,46 @@ public enum ItemManager {
public static final AtomicInteger lastNegativeID = new AtomicInteger(0); public static final AtomicInteger lastNegativeID = new AtomicInteger(0);
// restrict true: the set is a blacklist; false: a whitelist. An empty set allows everyone.
public static Boolean ValidRace(Item item, mbEnums.MonsterType race) { public static Boolean ValidRace(Item item, mbEnums.MonsterType race) {
if (item.template.item_race_req.isEmpty() && item.template.item_race_res.isEmpty()) EnumSet<mbEnums.MonsterType> races = item.template.getItemRaceReq();
boolean restrict = item.template.getItemRaceReqRestrict();
if (races.isEmpty())
return true; return true;
if (item.template.item_race_req.isEmpty() == false) return races.contains(race) != restrict;
if (item.template.item_race_req.contains(race))
return true;
if (item.template.item_race_res.isEmpty() == false)
if (item.template.item_race_res.contains(race) == false)
return true;
return false;
} }
public static Boolean ValidClass(Item item, mbEnums.ClassType base, mbEnums.ClassType profession) { public static Boolean ValidClass(Item item, mbEnums.ClassType base, mbEnums.ClassType profession) {
// Early exit if no entry EnumSet<mbEnums.ClassType> classes = item.template.getItemClassReq();
boolean restrict = item.template.getItemClassReqRestrict();
boolean listed;
if (item.template.item_class_req.isEmpty() && item.template.item_class_res.isEmpty()) if (classes.isEmpty())
return true; return true;
if (item.template.item_class_req.isEmpty() == false) listed = classes.contains(base) || classes.contains(profession);
if (item.template.item_class_req.contains(base) || item.template.item_class_req.contains(profession))
return true;
if (item.template.item_class_res.isEmpty() == false) return listed != restrict;
if (item.template.item_class_res.contains(base) == false && item.template.item_class_res.contains(profession) == false)
return true;
return false;
} }
public static Boolean ValidDiscipline(Item item, EnumSet<mbEnums.DisciplineType> discs) { public static Boolean ValidDiscipline(Item item, EnumSet<mbEnums.DisciplineType> discs) {
// Early exit if no entry EnumSet<mbEnums.DisciplineType> disciplines = item.template.getItemDiscReq();
boolean restrict = item.template.getItemDiscReqRestrict();
EnumSet<mbEnums.DisciplineType> workSet;
if (item.template.item_disc_req.isEmpty() && item.template.item_disc_res.isEmpty()) if (disciplines.isEmpty())
return true; return true;
EnumSet<mbEnums.DisciplineType> workSet = EnumSet.copyOf(discs); workSet = EnumSet.copyOf(discs);
workSet.retainAll(disciplines);
if (item.template.item_disc_req.isEmpty() == false) { return workSet.isEmpty() == restrict;
workSet.retainAll(item.template.item_disc_req);
if (workSet.isEmpty() == false)
return true;
}
if (item.template.item_disc_res.isEmpty() == false) {
workSet.retainAll(item.template.item_disc_res);
if (workSet.isEmpty() == false)
return true;
}
return false;
} }
public static Boolean canCharacterEquip(Item item, AbstractCharacter character) { public static Boolean canCharacterEquip(Item item, AbstractCharacter character) {
@@ -91,12 +72,12 @@ public enum ItemManager {
CharacterSkill characterSkill; CharacterSkill characterSkill;
if (item.template.item_skill_required.isEmpty()) if (item.template.getItemSkillReq().isEmpty())
return true; return true;
for (String skillRequired : item.template.item_skill_required.keySet()) { for (String skillRequired : item.template.getItemSkillReq().keySet()) {
int required_value = item.template.item_skill_required.get(skillRequired); int required_value = item.template.getItemSkillReq().get(skillRequired);
characterSkill = skills.get(skillRequired); characterSkill = skills.get(skillRequired);
if (characterSkill == null) if (characterSkill == null)
@@ -112,31 +93,31 @@ public enum ItemManager {
public static boolean isTwoHanded(Item item) { public static boolean isTwoHanded(Item item) {
if (!item.template.item_type.equals(mbEnums.ItemType.WEAPON)) if (!item.template.getItemType().equals(mbEnums.ItemType.WEAPON))
return false; return false;
return item.template.item_eq_slots_and.contains(EnumSet.of(mbEnums.EquipSlotType.LHELD, mbEnums.EquipSlotType.RHELD)); return item.template.getItemEqSlotsAnd().containsAll(EnumSet.of(mbEnums.EquipSlotType.LHELD, mbEnums.EquipSlotType.RHELD));
} }
public static boolean isTwoHanded(ItemTemplate template) { public static boolean isTwoHanded(CObjectTemplate template) {
if (!template.item_type.equals(mbEnums.ItemType.WEAPON)) if (!template.getItemType().equals(mbEnums.ItemType.WEAPON))
return false; return false;
return template.item_eq_slots_and.contains(EnumSet.of(mbEnums.EquipSlotType.LHELD, mbEnums.EquipSlotType.RHELD)); return template.getItemEqSlotsAnd().containsAll(EnumSet.of(mbEnums.EquipSlotType.LHELD, mbEnums.EquipSlotType.RHELD));
} }
public static boolean isShield(Item item) { public static boolean isShield(Item item) {
if (item.template.item_skill_required.containsKey("Block")) if (item.template.getItemSkillReq().containsKey("Block"))
return true; return true;
return false; return false;
} }
public static boolean isShield(ItemTemplate template) { public static boolean isShield(CObjectTemplate template) {
if (template.item_skill_required.containsKey("Block")) if (template.getItemSkillReq().containsKey("Block"))
return true; return true;
return false; return false;
@@ -150,7 +131,7 @@ public enum ItemManager {
return false; return false;
//Item not valid for slot //Item not valid for slot
if (item.template.item_eq_slots_or.contains(slot) == false) if (item.template.getItemEqSlotsOr().contains(slot) == false)
return false; return false;
// Slot is taken // Slot is taken
@@ -165,7 +146,7 @@ public enum ItemManager {
(slot == mbEnums.EquipSlotType.RHELD && equipped.get(mbEnums.EquipSlotType.LHELD) != null))) (slot == mbEnums.EquipSlotType.RHELD && equipped.get(mbEnums.EquipSlotType.LHELD) != null)))
return false; return false;
if (item.template.item_type.equals(mbEnums.ItemType.WEAPON)) if (item.template.getItemType().equals(mbEnums.ItemType.WEAPON))
if (equipped.get(slot) != null && equipped.get(slot).equals(item) == false) if (equipped.get(slot) != null && equipped.get(slot).equals(item) == false)
return false; return false;
@@ -197,7 +178,7 @@ public enum ItemManager {
//players can't wear 0 value items. //players can't wear 0 value items.
return item.template.item_value != 0 || Kit.IsNoobGear(item.templateID); return item.template.getItemValue() != 0 || Kit.IsNoobGear(item.templateID);
} }
+9 -8
View File
@@ -13,6 +13,7 @@ import engine.mbEnums;
import engine.net.client.msg.ErrorPopupMsg; import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.chat.ChatSystemMsg; import engine.net.client.msg.chat.ChatSystemMsg;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.util.ArrayList; import java.util.ArrayList;
@@ -91,8 +92,8 @@ public enum LootManager {
//lastly, check mobs inventory for godly or disc runes to send a server announcement //lastly, check mobs inventory for godly or disc runes to send a server announcement
for (Item item : mob.getInventory()) { for (Item item : mob.getInventory()) {
if ((item.templateID > 2499 && item.templateID <= 3050) || item.template.item_base_name.toLowerCase().contains("of the gods")) { if ((item.templateID > 2499 && item.templateID <= 3050) || item.template.getItemBaseName().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?"); ChatSystemMsg chatMsg = new ChatSystemMsg(null, mob.getName() + " in " + mob.getParentZone().zoneName + " has found the " + item.template.getItemBaseName() + ". Are you tough enough to take it?");
chatMsg.setMessageType(10); chatMsg.setMessageType(10);
chatMsg.setChannel(mbEnums.ChatChannelType.SYSTEM.getChannelID()); chatMsg.setChannel(mbEnums.ChatChannelType.SYSTEM.getChannelID());
DispatchManager.dispatchMsgToAll(chatMsg); DispatchManager.dispatchMsgToAll(chatMsg);
@@ -179,12 +180,12 @@ public enum LootManager {
if (itemUUID == 0) if (itemUUID == 0)
return null; return null;
if (ItemTemplate.templates.get(itemUUID).item_type.equals(mbEnums.ItemType.RESOURCE)) { if (CObjectTemplate.lookup(itemUUID).getItemType().equals(mbEnums.ItemType.RESOURCE)) {
int amount = ThreadLocalRandom.current().nextInt(tableRow.minSpawn, tableRow.maxSpawn + 1); int amount = ThreadLocalRandom.current().nextInt(tableRow.minSpawn, tableRow.maxSpawn + 1);
return new MobLoot(mob, ItemTemplate.templates.get(itemUUID), amount, false); return new MobLoot(mob, CObjectTemplate.lookup(itemUUID), amount, false);
} }
outItem = new MobLoot(mob, ItemTemplate.templates.get(itemUUID), false); outItem = new MobLoot(mob, CObjectTemplate.lookup(itemUUID), false);
if (selectedRow.pModTable != 0) if (selectedRow.pModTable != 0)
try { try {
@@ -374,7 +375,7 @@ public enum LootManager {
if (chanceRoll > bse.dropChance) if (chanceRoll > bse.dropChance)
return; return;
MobLoot lootItem = new MobLoot(mob, ItemTemplate.templates.get(bse.templateID), true); MobLoot lootItem = new MobLoot(mob, CObjectTemplate.lookup(bse.templateID), true);
if (lootItem != null) if (lootItem != null)
mob.charItemManager.addItemToInventory(lootItem); mob.charItemManager.addItemToInventory(lootItem);
@@ -422,14 +423,14 @@ public enum LootManager {
//create the item from the table, quantity is always 1 //create the item from the table, quantity is always 1
MobLoot winnings = new MobLoot(playerCharacter, ItemTemplate.templates.get(selectedItem.templateID), 1, false); MobLoot winnings = new MobLoot(playerCharacter, CObjectTemplate.lookup(selectedItem.templateID), 1, false);
if (winnings == null) if (winnings == null)
return; return;
//early exit if the inventory of the player will not old the item //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.getItemWt()) == false) {
ErrorPopupMsg.sendErrorPopup(playerCharacter, 21); ErrorPopupMsg.sendErrorPopup(playerCharacter, 21);
return; return;
} }
+31 -21
View File
@@ -19,12 +19,14 @@ import engine.net.client.ClientConnection;
import engine.net.client.msg.ErrorPopupMsg; import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.PetMsg; import engine.net.client.msg.PetMsg;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
import engine.powers.RuneSkillAdjustEntry; import engine.powers.RuneSkillAdjustEntry;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@@ -315,26 +317,26 @@ public enum NPCManager {
//has main hand weapon //has main hand weapon
Item weapon = guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD); 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(); primaryStat = guard.getStatStrCurrent();
else else
primaryStat = guard.getStatDexCurrent(); primaryStat = guard.getStatDexCurrent();
guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin() + weapon.template.item_weapon_damage.values().iterator().next()[0]) * rankModifier) + primaryStat; guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin() + engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_damage.get(0).damage_min) * rankModifier) + primaryStat;
guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax() + weapon.template.item_weapon_damage.values().iterator().next()[1]) * rankModifier) + primaryStat; guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax() + engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_damage.get(0).damage_max) * rankModifier) + primaryStat;
guard.speedHandOne = weapon.template.item_weapon_wepspeed; guard.speedHandOne = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_wepspeed;
guard.rangeHandOne = weapon.template.item_weapon_max_range; guard.rangeHandOne = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_max_range;
} else if (guard.charItemManager.equipped.containsKey(mbEnums.EquipSlotType.LHELD) && !ItemManager.isShield(guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template)) { } else if (guard.charItemManager.equipped.containsKey(mbEnums.EquipSlotType.LHELD) && !ItemManager.isShield(guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template)) {
//has off hand weapon //has off hand weapon
Item weapon = guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD); 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(); primaryStat = guard.getStatStrCurrent();
else else
primaryStat = guard.getStatDexCurrent(); primaryStat = guard.getStatDexCurrent();
guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin() + weapon.template.item_weapon_damage.values().iterator().next()[0]) * rankModifier) + primaryStat; guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin() + engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_damage.get(0).damage_min) * rankModifier) + primaryStat;
guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax() + weapon.template.item_weapon_damage.values().iterator().next()[1]) * rankModifier) + primaryStat; guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax() + engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_damage.get(0).damage_max) * rankModifier) + primaryStat;
guard.speedHandOne = weapon.template.item_weapon_wepspeed; guard.speedHandOne = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_wepspeed;
guard.rangeHandOne = weapon.template.item_weapon_max_range; guard.rangeHandOne = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_max_range;
} else { } else {
primaryStat = guard.getStatStrCurrent(); primaryStat = guard.getStatStrCurrent();
guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin()) * rankModifier) + primaryStat; guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin()) * rankModifier) + primaryStat;
@@ -352,8 +354,8 @@ public enum NPCManager {
int baseDef = guard.mobBase.getDefenseRating(); int baseDef = guard.mobBase.getDefenseRating();
int armorDefense = 0; int armorDefense = 0;
for (Item equipped : guard.charItemManager.equipped.values()) for (Item equipped : guard.charItemManager.equipped.values())
if (equipped.template.item_type.equals(mbEnums.ItemType.ARMOR) || ItemManager.isShield(equipped.template)) if (equipped.template.getItemType().equals(mbEnums.ItemType.ARMOR) || ItemManager.isShield(equipped.template))
armorDefense += equipped.template.item_defense_rating; armorDefense += engine.parsers.objects.ItemTemplate._items.get(equipped.templateID).item_defense_rating;
guard.defenseRating = dexterity + baseDef + armorDefense; guard.defenseRating = dexterity + baseDef + armorDefense;
} }
@@ -361,9 +363,9 @@ public enum NPCManager {
int strength = guard.getStatStrCurrent(); int strength = guard.getStatStrCurrent();
int baseAtr = guard.mobBase.getAttackRating(); int baseAtr = guard.mobBase.getAttackRating();
if (guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD) != null) if (guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD) != null)
guard.atrHandOne = baseAtr + (int) ((strength * 0.5f) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD).template.item_skill_required.values().toArray()[0] * 4) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD).template.item_skill_required.values().toArray()[0] * 3)); guard.atrHandOne = baseAtr + (int) ((strength * 0.5f) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD).template.getItemSkillReq().values().toArray()[0] * 4) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD).template.getItemSkillReq().values().toArray()[0] * 3));
else if (guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD) != null && !ItemManager.isShield(guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template)) else if (guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD) != null && !ItemManager.isShield(guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template))
guard.atrHandTwo = baseAtr + (int) ((strength * 0.5f) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template.item_skill_required.values().toArray()[0] * 4) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template.item_skill_required.values().toArray()[0] * 3)); guard.atrHandTwo = baseAtr + (int) ((strength * 0.5f) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template.getItemSkillReq().values().toArray()[0] * 4) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template.getItemSkillReq().values().toArray()[0] * 3));
else else
guard.atrHandOne = baseAtr; guard.atrHandOne = baseAtr;
} }
@@ -438,14 +440,17 @@ public enum NPCManager {
public static void applyEquipmentResists(Mob mob) { public static void applyEquipmentResists(Mob mob) {
EnumMap<mbEnums.DamageType, Float> armorResists;
if (mob.charItemManager.equipped.isEmpty()) if (mob.charItemManager.equipped.isEmpty())
return; return;
for (Item equipped : mob.charItemManager.equipped.values()) { for (Item equipped : mob.charItemManager.equipped.values()) {
if (equipped.template.item_type.equals(mbEnums.ItemType.ARMOR)) { if (equipped.template.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")); armorResists = equipped.template.getCombatAttackResist();
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.SLASHING, mob.resists.getResist(mbEnums.DamageType.SLASHING, 0) + armorResists.getOrDefault(mbEnums.DamageType.SLASHING, 0f));
mob.resists.setResist(mbEnums.DamageType.PIERCING, mob.resists.getResist(mbEnums.DamageType.PIERCING, 0) + equipped.template.combat_attack_resist.get("PIERCING")); mob.resists.setResist(mbEnums.DamageType.CRUSHING, mob.resists.getResist(mbEnums.DamageType.CRUSHING, 0) + armorResists.getOrDefault(mbEnums.DamageType.CRUSHING, 0f));
mob.resists.setResist(mbEnums.DamageType.PIERCING, mob.resists.getResist(mbEnums.DamageType.PIERCING, 0) + armorResists.getOrDefault(mbEnums.DamageType.PIERCING, 0f));
} }
} }
} }
@@ -480,6 +485,11 @@ public enum NPCManager {
npc.runes.add(shopkeeper); npc.runes.add(shopkeeper);
if (NPCManager._runeSetMap.containsKey(npc.runeSetID)) { if (NPCManager._runeSetMap.containsKey(npc.runeSetID)) {
for (int runeID : _runeSetMap.get(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); RuneBase rb = RuneBase.getRuneBase(runeID);
if (rb != null) { if (rb != null) {
CharacterRune toApply = new CharacterRune(rb, npc.getObjectUUID()); CharacterRune toApply = new CharacterRune(rb, npc.getObjectUUID());
@@ -536,14 +546,14 @@ public enum NPCManager {
public static long calcRollingDuration(NPC vendor, int templateID) { public static long calcRollingDuration(NPC vendor, int templateID) {
ItemTemplate template = ItemTemplate.templates.get(templateID); CObjectTemplate template = CObjectTemplate.lookup(templateID);
float rollingDuration; float rollingDuration;
if (template == null) if (template == null)
return 0; return 0;
if (template.item_bane_rank > 0) if (template.getItemBaneRank() > 0)
return (long) (template.item_bane_rank * 60 * 60 * 3 * 1000 * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue())); return (long) (template.getItemBaneRank() * 60 * 60 * 3 * 1000 * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue()));
if (vendor.building == null) if (vendor.building == null)
return 600; return 600;
+3 -3
View File
@@ -2255,12 +2255,12 @@ public enum PowersManager {
if (pb.targetItem()) if (pb.targetItem())
return true; return true;
// TODO add these checks later // TODO add these checks later
else if (pb.targetArmor() && item.template.item_type.equals(ItemType.ARMOR)) else if (pb.targetArmor() && item.template.getItemType().equals(ItemType.ARMOR))
return true; return true;
else if (pb.targetJewelry() && item.template.item_type.equals(ItemType.JEWELRY)) else if (pb.targetJewelry() && item.template.getItemType().equals(ItemType.JEWELRY))
return true; return true;
else else
return pb.targetWeapon() && item.template.item_type.equals(ItemType.WEAPON); return pb.targetWeapon() && item.template.getItemType().equals(ItemType.WEAPON);
} // How did we get here? all valid targets have been covered } // How did we get here? all valid targets have been covered
else else
return false; return false;
+4 -4
View File
@@ -12,9 +12,9 @@ import engine.gameManager.DbManager;
import engine.gameManager.ForgeManager; import engine.gameManager.ForgeManager;
import engine.mbEnums; import engine.mbEnums;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.ItemTemplate;
import engine.objects.NPC; import engine.objects.NPC;
import engine.objects.Warehouse; import engine.objects.Warehouse;
import engine.parsers.objects.CObjectTemplate;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@@ -127,16 +127,16 @@ public class WorkOrder implements Delayed {
int validation_result = 0; int validation_result = 0;
ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID); CObjectTemplate template = CObjectTemplate.lookup(workOrder.templateID);
if (workOrder.vendor.getBuilding() == null) if (workOrder.vendor.getBuilding() == null)
return 58; //58: The formula is beyond the means of this facility 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.getItemWt()))
return 30; //30: That person cannot carry that item return 30; //30: That person cannot carry that item
if ((workOrder.prefixToken != 0 || workOrder.suffixToken != 0) && if ((workOrder.prefixToken != 0 || workOrder.suffixToken != 0) &&
!workOrder.vendor.getItemModTable().contains((template.modTable))) !workOrder.vendor.getItemModTable().contains(ForgeManager.getVendorModTable(workOrder.templateID)))
return 59; //59: This hireling does not have this formula return 59; //59: This hireling does not have this formula
if (!Warehouse.calcCostOverrun(workOrder).isEmpty()) if (!Warehouse.calcCostOverrun(workOrder).isEmpty())
-30
View File
@@ -583,36 +583,6 @@ final public class FastMath {
return ret.normalize(); 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 * 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.Vector3f;
import engine.math.Vector3fImmutable; import engine.math.Vector3fImmutable;
import engine.objects.AbstractCharacter; import engine.objects.AbstractCharacter;
import engine.objects.ItemTemplate;
import engine.objects.Shrine; import engine.objects.Shrine;
import engine.objects.Zone; import engine.objects.Zone;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
@@ -824,6 +823,7 @@ public class mbEnums {
ACID, ACID,
DISEASE, DISEASE,
UNKNOWN, UNKNOWN,
ANTISIEGE,
// these added for immunities // these added for immunities
ATTACK, ATTACK,
POWERS, POWERS,
@@ -842,34 +842,26 @@ public class mbEnums {
public static DamageType getDamageType(String modName) { public static DamageType getDamageType(String modName) {
String name;
if (modName.isEmpty()) if (modName.isEmpty())
return DamageType.NONE; return DamageType.NONE;
if(modName.toLowerCase().equals("blind")) // Client and DB strings vary in case and punctuation ("Fire", "Anti-Siege")
modName = "BLINDNESS";
if(modName.toLowerCase().equals("powerblock")) name = modName.replaceAll("[\\s,-]", "").toUpperCase();
modName = "POWERINHIBITOR";
//validity check for not looking up damage type that doesn't exist if (name.equals("BLIND"))
boolean valid = false; name = "BLINDNESS";
for(DamageType type : DamageType.values()){
if(type.name().equals(modName))
valid = true;
}
if(!valid) if (name.equals("POWERBLOCK"))
return DamageType.NONE; name = "POWERINHIBITOR";
DamageType damageType; for (DamageType type : DamageType.values())
if (type.name().equals(name))
return type;
try { return DamageType.NONE;
damageType = DamageType.valueOf(modName.replace(",", "").toUpperCase());
} catch (Exception e) {
Logger.error(e);
return DamageType.NONE;
}
return damageType;
} }
} }
@@ -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 { public enum ShrineType {
Aelfborn(1701900, -75506007, true), Aelfborn(1701900, -75506007, true),
@@ -2761,7 +2762,6 @@ public class mbEnums {
public static HashMap<Integer, ResourceType> templateHashLookup = new HashMap<>(); public static HashMap<Integer, ResourceType> templateHashLookup = new HashMap<>();
public static HashMap<Integer, ResourceType> resourceHashLookup = new HashMap<>(); public static HashMap<Integer, ResourceType> resourceHashLookup = new HashMap<>();
public int templateID; public int templateID;
public ItemTemplate template;
public int resourceHash; public int resourceHash;
public int templateHash; public int templateHash;
public int deposit_limit; public int deposit_limit;
@@ -2769,7 +2769,6 @@ public class mbEnums {
ResourceType(int templateID, int resourceHash, int templateHash, int deposit_limit, int mine_production) { ResourceType(int templateID, int resourceHash, int templateHash, int deposit_limit, int mine_production) {
this.templateID = templateID; this.templateID = templateID;
this.template = ItemTemplate.templates.get(this.templateID);
this.resourceHash = resourceHash; this.resourceHash = resourceHash;
this.templateHash = templateHash; this.templateHash = templateHash;
this.deposit_limit = deposit_limit; this.deposit_limit = deposit_limit;
@@ -2908,6 +2907,14 @@ public class mbEnums {
EMPTY_NEWITEM; 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 { public enum AttributeType {
None, None,
Strength, Strength,
@@ -46,7 +46,7 @@ public class ActivateNPCMsgHandler extends AbstractClientMsgHandler {
// Filter hirelings by slot type // Filter hirelings by slot type
for (Item hirelings : player.getInventory()) { for (Item hirelings : player.getInventory()) {
if (hirelings.template.item_type.equals(ItemType.EMPLOYMENTCONTRACT)) { if (hirelings.template.getItemType().equals(ItemType.EMPLOYMENTCONTRACT)) {
contract = DbManager.ContractQueries.GET_CONTRACT(hirelings.templateID); contract = DbManager.ContractQueries.GET_CONTRACT(hirelings.templateID);
@@ -78,7 +78,7 @@ public class AddItemToTradeWindowMsgHandler extends AbstractClientMsgHandler {
if (!item.validForInventory(source.getClientConnection(), source, source.charItemManager)) if (!item.validForInventory(source.getClientConnection(), source, source.charItemManager))
return false; return false;
if (!tradingWith.hasRoomTrade(item.template.item_wt)) { if (!tradingWith.hasRoomTrade(item.template.getItemWt())) {
dispatch = Dispatch.borrow(source, msg); dispatch = Dispatch.borrow(source, msg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY); DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY);
return false; return false;
@@ -97,9 +97,13 @@ public class DestroyBuildingHandler extends AbstractClientMsgHandler {
if (city == null) if (city == null)
return true; 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.Guild;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import engine.parsers.objects.DeedTemplate;
import engine.util.StringUtils; import engine.util.StringUtils;
public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler { public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
@@ -42,6 +43,7 @@ public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
Guild playerGuild; Guild playerGuild;
Item charter; Item charter;
DeedTemplate charterDeed;
Dispatch dispatch; Dispatch dispatch;
msg = (GuildCreationFinalizeMsg) baseMsg; msg = (GuildCreationFinalizeMsg) baseMsg;
@@ -65,7 +67,9 @@ public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
// Item must be a valid charterType (type 10 in db) // 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); ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND);
return true; return true;
} }
@@ -238,10 +238,10 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (targetItem == null) if (targetItem == null)
return; return;
if (targetItem.template.item_type.equals(ItemType.GOLD)) if (targetItem.template.getItemType().equals(ItemType.GOLD))
return; return;
if (!vendor.charItemManager.hasRoomInventory(targetItem.template.item_wt)) { if (!vendor.charItemManager.hasRoomInventory(targetItem.template.getItemWt())) {
ErrorPopupMsg.sendErrorPopup(player, 21); ErrorPopupMsg.sendErrorPopup(player, 21);
return; return;
} }
@@ -319,7 +319,7 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (targetItem == null) if (targetItem == null)
continue; continue;
if (targetItem.template.item_type.equals(ItemType.GOLD)) if (targetItem.template.getItemType().equals(ItemType.GOLD))
return; return;
if (!vendor.charItemManager.doesCharOwnThisItem(targetItem.getObjectUUID())) if (!vendor.charItemManager.doesCharOwnThisItem(targetItem.getObjectUUID()))
@@ -328,14 +328,14 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (!vendor.charItemManager.inventoryContains(targetItem)) if (!vendor.charItemManager.inventoryContains(targetItem))
continue; continue;
itemValue = targetItem.template.item_value; itemValue = targetItem.template.getItemValue();
if (vendor.getBuilding().getStrongboxValue() + itemValue > vendor.getBuilding().getMaxGold()) { if (vendor.getBuilding().getStrongboxValue() + itemValue > vendor.getBuilding().getMaxGold()) {
ErrorPopupMsg.sendErrorPopup(player, 201); ErrorPopupMsg.sendErrorPopup(player, 201);
break; break;
} }
switch (targetItem.template.item_type) { switch (targetItem.template.getItemType()) {
case EMPLOYMENTCONTRACT: case EMPLOYMENTCONTRACT:
case CHARTER: case CHARTER:
case DEED: case DEED:
@@ -465,13 +465,13 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (targetItem == null) if (targetItem == null)
return; return;
if (targetItem.template.item_type.equals(ItemType.GOLD)) if (targetItem.template.getItemType().equals(ItemType.GOLD))
return; return;
if (!vendor.charItemManager.inventoryContains(targetItem)) if (!vendor.charItemManager.inventoryContains(targetItem))
return; return;
if (!player.charItemManager.hasRoomInventory(targetItem.template.item_wt)) if (!player.charItemManager.hasRoomInventory(targetItem.template.getItemWt()))
return; return;
player.charItemManager.buyFromNPC(targetItem, vendor); player.charItemManager.buyFromNPC(targetItem, vendor);
@@ -161,7 +161,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
if (itemRet == null) if (itemRet == null)
return true; return true;
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) { if (item.template.getItemType().equals(mbEnums.ItemType.GOLD)) {
// this is done to prevent the temporary goldItem item // this is done to prevent the temporary goldItem item
// (from the mob) from appearing in player's inventory. // (from the mob) from appearing in player's inventory.
// It also updates the goldItem quantity display // 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 //TODO send group loot message if player is grouped and visible
Group group = GroupManager.getGroup(player); 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.getItemType().equals(mbEnums.ItemType.GOLD) == false)) {
String name = item.getName(); String name = item.getName();
String text = player.getFirstName() + " has looted " + name + '.'; String text = player.getFirstName() + " has looted " + name + '.';
ChatManager.chatGroupInfoCanSee(player, text); ChatManager.chatGroupInfoCanSee(player, text);
@@ -202,7 +202,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
if (itemRet == null) if (itemRet == null)
return true; return true;
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) { if (item.template.getItemType().equals(mbEnums.ItemType.GOLD)) {
// this is done to prevent the temporary goldItem item // this is done to prevent the temporary goldItem item
// (from the mob) from appearing in player's inventory. // (from the mob) from appearing in player's inventory.
// It also updates the goldItem quantity display // 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 //TODO send group loot message if player is grouped and visible
Group group = GroupManager.getGroup(player); 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.getItemType().equals(mbEnums.ItemType.GOLD) == false)) {
String name = item.getName(); String name = item.getName();
String text = player.getFirstName() + " has looted " + name + '.'; String text = player.getFirstName() + " has looted " + name + '.';
ChatManager.chatGroupInfoCanSee(player, text); ChatManager.chatGroupInfoCanSee(player, text);
@@ -141,17 +141,9 @@ public class ManageCityAssetMsgHandler extends AbstractClientMsgHandler {
return true; return true;
} }
City city = building.getCity(); Warehouse warehouse = Warehouse.destroyedWarehouses.get(building.getObjectUUID());
if (city == null) if (warehouse == null || Warehouse.isEmpty(warehouse)) {
return true;
Warehouse warehouse = city.warehouse;
if (warehouse == null)
return true;
if (Warehouse.isEmpty(warehouse)) {
ErrorPopupMsg.sendErrorPopup(player, 167); // no more resources. ErrorPopupMsg.sendErrorPopup(player, 167); // no more resources.
return true; return true;
} }
@@ -8,6 +8,7 @@ import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.*; import engine.net.client.msg.*;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.DeedTemplate;
import engine.powers.PowersBase; import engine.powers.PowersBase;
import java.util.ArrayList; import java.util.ArrayList;
@@ -78,7 +79,7 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
int uuid = item.templateID; int uuid = item.templateID;
switch (item.template.item_type) { switch (item.template.getItemType()) {
case DEED: //buildings case DEED: //buildings
//Call add building screen here, ib.getUseID() get's building ID //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.setX(loc.getX() + 64); //offset grid from tol
pam.setY(loc.getY()); pam.setY(loc.getY());
pam.setZ(loc.getZ() + 64); //offset grid from tol 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); dispatch = Dispatch.borrow(player, pam);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY); 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 if (uuid > 680069 && uuid < 680074) //Handle Charter, Deed, Petition, Warrant here
break; break;
if (item.template.item_bane_rank > 0) { if (item.template.getItemBaneRank() > 0) {
// Only one banestone at a time // Only one banestone at a time
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
if (Bane.summonBanestone(player, origin, item.template.item_bane_rank) == true) if (Bane.summonBanestone(player, origin, item.template.getItemBaneRank()) == true)
itemMan.consume(item); itemMan.consume(item);
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
@@ -225,7 +226,7 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
// Bypass for waterbuckets // Bypass for waterbuckets
if (item.template.item_type.equals(mbEnums.ItemType.BUCKET)) { if (item.template.getItemType().equals(mbEnums.ItemType.BUCKET)) {
// test for valid target type // test for valid target type
if (target.getObjectType() == mbEnums.GameObjectType.PlayerCharacter) if (target.getObjectType() == mbEnums.GameObjectType.PlayerCharacter)
@@ -250,10 +251,9 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
} }
if (waterbucketBypass == false) { if (waterbucketBypass == false) {
String powerString = item.template.item_power_grant.keySet().iterator().next(); engine.parsers.objects.PowerGrant powerGrant = item.template.getItemPowerGrant().get(0);
PowersBase powerAction = PowersManager.powersBaseByIDString.get(powerString); PowersBase powerAction = PowersManager.powersBaseByIDString.get(powerGrant.power_type);
int powerValue = item.template.item_power_grant.get(powerString); PowersManager.applyPower(player, target, Vector3fImmutable.ZERO, powerAction.getToken(), powerGrant.power_value, true);
PowersManager.applyPower(player, target, Vector3fImmutable.ZERO, powerAction.getToken(), powerValue, true);
} }
itemMan.consume(item); itemMan.consume(item);
} else //just remove the item at this point } else //just remove the item at this point
@@ -266,7 +266,8 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
case RUNE: case RUNE:
if(ApplyRuneMsg.applyRune(uuid, origin, player)) { if(ApplyRuneMsg.applyRune(uuid, origin, player)) {
itemMan.consume(item); itemMan.consume(item);
} } else
ErrorPopupMsg.sendErrorPopup(player, 161);
break; break;
default: //shouldn't be here, consume item default: //shouldn't be here, consume item
dispatch = Dispatch.borrow(player, msg); dispatch = Dispatch.borrow(player, msg);
@@ -18,6 +18,7 @@ import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.PlaceAssetMsg; import engine.net.client.msg.PlaceAssetMsg;
import engine.net.client.msg.PlaceAssetMsg.PlacementInfo; import engine.net.client.msg.PlaceAssetMsg.PlacementInfo;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.DeedTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.pmw.tinylog.Logger; 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) { private static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) {
Bane bane;
if (serverZone.guild_zone == false) { if (serverZone.guild_zone == false) {
PlaceAssetMsg.sendPlaceAssetError(origin, 52, player.getName()); PlaceAssetMsg.sendPlaceAssetError(origin, 52, player.getName());
return false; return false;
@@ -205,6 +208,15 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
return false; 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)) { if (placementCollisionCheck(serverZone, origin, placementInfo)) {
PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets
return false; return false;
@@ -345,8 +357,8 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
Item contract = null; Item contract = null;
for (Item inventoryItem : playerCharacter.getInventory()) { for (Item inventoryItem : playerCharacter.getInventory()) {
if (inventoryItem.template.item_type.equals(ItemType.DEED) && if (inventoryItem.template.getItemType().equals(ItemType.DEED) &&
inventoryItem.template.deed_structure_id == buildingList.getBlueprintUUID()) { DeedTemplate._deeds.get(inventoryItem.templateID).deed_structure_id == buildingList.getBlueprintUUID()) {
contract = inventoryItem; contract = inventoryItem;
break; break;
} }
@@ -1341,6 +1353,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
} }
Warehouse warehouse = new Warehouse(warehouseBuilding); Warehouse warehouse = new Warehouse(warehouseBuilding);
Warehouse.destroyedWarehouses.values().removeIf(destroyed -> destroyed.city == city);
city.warehouse = warehouse; city.warehouse = warehouse;
DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse); DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse);
@@ -85,7 +85,7 @@ public class RepairMsgHandler extends AbstractClientMsgHandler {
//make sure item is damaged and not destroyed //make sure item is damaged and not destroyed
short dur = (short) toRepair.combat_health_current; short dur = (short) toRepair.combat_health_current;
short max = (short) toRepair.template.combat_health_full; short max = (short) toRepair.template.getCombatHealthFull();
//account for durability modifications //account for durability modifications
@@ -68,7 +68,7 @@ public class TransferItemFromBankMsgHandler extends AbstractClientMsgHandler {
return true; return true;
; ;
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) { if (item.template.getItemType().equals(mbEnums.ItemType.GOLD)) {
if (!itemManager.moveGoldToInventory(item, msg.getNumItems())) if (!itemManager.moveGoldToInventory(item, msg.getNumItems()))
return true; return true;
@@ -85,7 +85,7 @@ public class TransferItemFromBankMsgHandler extends AbstractClientMsgHandler {
// Not gold, process update here // Not gold, process update here
if (!itemManager.hasRoomInventory(item.template.item_wt)) if (!itemManager.hasRoomInventory(item.template.getItemWt()))
return true; return true;
if (itemManager.moveItemToInventory(item) == false) if (itemManager.moveItemToInventory(item) == false)
@@ -65,7 +65,7 @@ public class TransferItemToBankMsgHandler extends AbstractClientMsgHandler {
return true; return true;
if (item.containerType == mbEnums.ItemContainerType.INVENTORY && itemManager.isBankOpen()) if (item.containerType == mbEnums.ItemContainerType.INVENTORY && itemManager.isBankOpen())
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) { if (item.template.getItemType().equals(mbEnums.ItemType.GOLD)) {
if (!itemManager.moveGoldToBank(item, msg.getNumItems())) if (!itemManager.moveGoldToBank(item, msg.getNumItems()))
return true; return true;
UpdateGoldMsg goldMes = new UpdateGoldMsg(player); UpdateGoldMsg goldMes = new UpdateGoldMsg(player);
@@ -76,7 +76,7 @@ public class TransferItemToBankMsgHandler extends AbstractClientMsgHandler {
} else { } else {
if (!itemManager.hasRoomBank(item.template.item_wt)) if (!itemManager.hasRoomBank(item.template.getItemWt()))
return true; return true;
if (!itemManager.moveItemToBank(item)) if (!itemManager.moveItemToBank(item))
@@ -86,7 +86,7 @@ public class VendorBuyMsgHandler extends AbstractClientMsgHandler {
if (me.objectUUID == vendorBuyMsg.getItemID()) { if (me.objectUUID == vendorBuyMsg.getItemID()) {
//test room available for item //test room available for item
if (!itemMan.hasRoomInventory(me.template.item_wt)) if (!itemMan.hasRoomInventory(me.template.getItemWt()))
return true; return true;
int cost = me.getValue(); int cost = me.getValue();
@@ -148,10 +148,10 @@ public class VendorBuyMsgHandler extends AbstractClientMsgHandler {
//test room available for item //test room available for item
if (!itemMan.hasRoomInventory(vendorItem.template.item_wt)) if (!itemMan.hasRoomInventory(vendorItem.template.getItemWt()))
return true; return true;
int cost = vendorItem.template.item_value; int cost = vendorItem.template.getItemValue();
if (vendorItem.flags.contains(mbEnums.ItemFlags.Identified) || vendorItem.isCustomValue()) if (vendorItem.flags.contains(mbEnums.ItemFlags.Identified) || vendorItem.isCustomValue())
cost = vendorItem.magicValue; cost = vendorItem.magicValue;
@@ -94,7 +94,7 @@ public class VendorSellMsgHandler extends AbstractClientMsgHandler {
return true; return true;
if (npc.getParentZone() != null && npc.getParentZone().playerCityUUID != 0) if (npc.getParentZone() != null && npc.getParentZone().playerCityUUID != 0)
if (!npc.charItemManager.hasRoomInventory(sell.template.item_wt)) { if (!npc.charItemManager.hasRoomInventory(sell.template.getItemWt())) {
ErrorPopupMsg.sendErrorPopup(player, 21); ErrorPopupMsg.sendErrorPopup(player, 21);
return true; return true;
} }
@@ -104,7 +104,7 @@ public class VendorSellMsgHandler extends AbstractClientMsgHandler {
//get goldItem cost to sell //get goldItem cost to sell
cost = (int) (sell.template.item_value * (sell.combat_health_current / sell.template.combat_health_full)); cost = (int) (sell.template.getItemValue() * (sell.combat_health_current / sell.template.getCombatHealthFull()));
float bargain = player.getBargain(); float bargain = player.getBargain();
float profit = npc.getBuyPercent(player) + bargain; float profit = npc.getBuyPercent(player) + bargain;
+73 -72
View File
@@ -22,10 +22,17 @@ import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class ApplyRuneMsg extends ClientNetMsg { public class ApplyRuneMsg extends ClientNetMsg {
// Client ArcCharacter_AddRune (0x4695D0)
private static final int MAX_TALENTS = 8;
private int targetType; private int targetType;
private int targetID; private int targetID;
private int removeRuneBase; private int removeRuneBase;
@@ -78,67 +85,28 @@ public class ApplyRuneMsg extends ClientNetMsg {
return false; return false;
} }
//Check race is met
ConcurrentHashMap<Integer, Boolean> races = rb.getRace();
if (races.size() > 0) {
int raceID = playerCharacter.getRaceID();
boolean valid = false;
for (int validID : races.keySet()) {
if (validID == raceID) {
valid = true;
break;
}
}
if (!valid) {
return false;
}
}
//Check base class is met
ConcurrentHashMap<Integer, Boolean> baseClasses = rb.getBaseClass();
if (baseClasses.size() > 0) {
int baseClassID = playerCharacter.getBaseClassID();
boolean valid = false;
for (int validID : baseClasses.keySet()) {
if (validID == baseClassID) {
valid = true;
break;
}
}
if (!valid) {
return false;
}
}
//Check promotion class is met
ConcurrentHashMap<Integer, Boolean> promotionClasses = rb.getPromotionClass();
if (promotionClasses.size() > 0) {
int promotionClassID = playerCharacter.getPromotionClassID();
boolean valid = false;
for (int validID : promotionClasses.keySet()) {
if (validID == promotionClassID) {
valid = true;
break;
}
}
if (!valid) {
return false;
}
}
//Check disciplines are met
ArrayList<CharacterRune> runes = playerCharacter.getRunes(); ArrayList<CharacterRune> runes = playerCharacter.getRunes();
ConcurrentHashMap<Integer, Boolean> disciplines = rb.getDiscipline(); HashSet<Integer> classIDs = new HashSet<>();
if (disciplines.size() > 0) { HashSet<Integer> heldDiscIDs = new HashSet<>();
for (CharacterRune cr : runes) {
int runeBaseID = cr.getRuneBaseID(); // Client ArcCharacter_MeetsItemReqs (0x475F40)
for (Integer prohID : disciplines.keySet()) { if (!meetsListReq(rb.getRace().keySet(), rb.isRaceRestrict(), Collections.singleton(playerCharacter.getRaceID())))
if (runeBaseID == prohID) { return false;
return false; //Prohibited rune
} // A listed base class covers its promotions: base or promotion listed is enough
} classIDs.addAll(rb.getBaseClass().keySet());
} classIDs.addAll(rb.getPromotionClass().keySet());
}
if (!meetsListReq(classIDs, rb.isClassRestrict(), Arrays.asList(playerCharacter.getBaseClassID(), playerCharacter.getPromotionClassID())))
return false;
// Only the new rune's list is checked, not the held discs' lists
for (CharacterRune cr : runes)
if (isDiscipline(cr.getRuneBaseID()))
heldDiscIDs.add(cr.getRuneBaseID());
if (!meetsListReq(rb.getDiscipline().keySet(), rb.isDiscRestrict(), heldDiscIDs))
return false;
int discCount = 0; int discCount = 0;
for (CharacterRune cr : runes) { for (CharacterRune cr : runes) {
@@ -158,6 +126,32 @@ public class ApplyRuneMsg extends ClientNetMsg {
return false; return false;
} }
// Client 0x4768D0 refuses a talent unless its rank beats the held one with the same sub type (rank 0 included);
// AddRune (0x4695D0) then replaces the held talent
CharacterRune replacedRune = null;
if (rb.getRuneType() == mbEnums.RuneType.TALENT) {
int talentCount = 0;
for (CharacterRune cr : runes) {
RuneBase heldRuneBase = cr.getRuneBase();
if (heldRuneBase == null || heldRuneBase.getRuneType() != mbEnums.RuneType.TALENT)
continue;
talentCount++;
if (rb.getSubTypeToken() != 0 && heldRuneBase.getSubTypeToken() == rb.getSubTypeToken())
replacedRune = cr;
}
if (replacedRune != null) {
if (rb.getRank() <= replacedRune.getRuneBase().getRank())
return false;
} else if (talentCount >= MAX_TALENTS)
return false;
}
int strTotal = 0; int strTotal = 0;
int dexTotal = 0; int dexTotal = 0;
int conTotal = 0; int conTotal = 0;
@@ -227,11 +221,6 @@ public class ApplyRuneMsg extends ClientNetMsg {
} }
} }
//Check if max number runes already reached
if (runes.size() > 12) {
return false;
}
//if discipline, check number applied //if discipline, check number applied
if (isDiscipline(runeID)) { if (isDiscipline(runeID)) {
if (playerCharacter.getLevel() < 70) { if (playerCharacter.getLevel() < 70) {
@@ -259,13 +248,10 @@ public class ApplyRuneMsg extends ClientNetMsg {
return false; return false;
} }
//remove any overridden runes from player //remove the replaced talent from player
ArrayList<Integer> overwrite = rb.getOverwrite();
CharacterRune toRemove = null; CharacterRune toRemove = null;
if (overwrite.size() > 0) { if (replacedRune != null) {
for (int overwriteID : overwrite) { toRemove = playerCharacter.removeRune(replacedRune.getRuneBaseID());
toRemove = playerCharacter.removeRune(overwriteID);
}
} }
//add rune to player //add rune to player
@@ -331,6 +317,21 @@ public class ApplyRuneMsg extends ClientNetMsg {
return runeID > 3000 && runeID < 3050; return runeID > 3000 && runeID < 3050;
} }
// Client ArcItemRestrict_Check (0x4BAC60). An empty list has no requirement; no rune has an empty whitelist.
private static boolean meetsListReq(Set<Integer> listedIDs, boolean restrict, Collection<Integer> heldIDs) {
boolean listed = false;
if (listedIDs.isEmpty())
return true;
for (int heldID : heldIDs)
if (listedIDs.contains(heldID))
listed = true;
return listed != restrict;
}
/** /**
* Serializes the subclass specific items to the supplied NetMsgWriter. * Serializes the subclass specific items to the supplied NetMsgWriter.
*/ */
+9 -7
View File
@@ -19,6 +19,7 @@ import engine.net.ByteBufferReader;
import engine.net.ByteBufferWriter; import engine.net.ByteBufferWriter;
import engine.net.Protocol; import engine.net.Protocol;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Period; import org.joda.time.Period;
import org.joda.time.Seconds; import org.joda.time.Seconds;
@@ -420,17 +421,18 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(rollableSet.size()); writer.putInt(rollableSet.size());
for (int templateID : rollableSet) { for (int templateID : rollableSet) {
ItemTemplate template = ItemTemplate.templates.get(templateID); CObjectTemplate template = CObjectTemplate.lookup(templateID);
int modTable = ForgeManager.getVendorModTable(templateID);
writer.put((byte) 1); writer.put((byte) 1);
writer.putInt(0); writer.putInt(0);
writer.putInt(templateID); //itemID writer.putInt(templateID); //itemID
writer.putInt(template.item_value); writer.putInt(template.getItemValue());
writer.putInt(600); writer.putInt(600);
writer.put((byte) 1); writer.put((byte) 1);
writer.put((byte) template.modTable); writer.put((byte) modTable);
writer.put((byte) template.modTable); writer.put((byte) modTable);
writer.put((byte) template.modTable); writer.put((byte) modTable);
writer.put((byte) template.modTable);//EffectItemType writer.put((byte) modTable);//EffectItemType
} }
ArrayList<Item> cooking = NPCManager.getAllCookingForVendor(npc); ArrayList<Item> cooking = NPCManager.getAllCookingForVendor(npc);
@@ -444,7 +446,7 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(0); writer.putInt(0);
writer.putInt(item.templateID); writer.putInt(item.templateID);
writer.putInt(item.template.item_value); writer.putInt(item.template.getItemValue());
long timeLife = item.getDateToUpgrade() - System.currentTimeMillis(); long timeLife = item.getDateToUpgrade() - System.currentTimeMillis();
@@ -54,7 +54,7 @@ public class UpdateInventoryMsg extends ClientNetMsg {
for (Item item : list) { for (Item item : list) {
if (item.template.item_type.equals(ItemType.GOLD)) { if (item.template.getItemType().equals(ItemType.GOLD)) {
if (item.getNumOfItems() == 0) if (item.getNumOfItems() == 0)
continue; continue;
} }
+6 -1
View File
@@ -16,6 +16,7 @@ import engine.net.ByteBufferWriter;
import engine.net.Protocol; import engine.net.Protocol;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import engine.parsers.objects.DeedTemplate;
public class UseCharterMsg extends ClientNetMsg { public class UseCharterMsg extends ClientNetMsg {
@@ -61,9 +62,13 @@ public class UseCharterMsg extends ClientNetMsg {
public void configure() { public void configure() {
DeedTemplate charterDeed;
if (close) { if (close) {
for (Item i : player.getInventory()) { 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(); charterUUID = i.getObjectUUID();
break; break;
} }
@@ -156,7 +156,7 @@ public class VendorBuyWindowMsg extends ClientNetMsg {
for (Item item : inventory) { for (Item item : inventory) {
if (item.getOwnerID() != ownerID) if (item.getOwnerID() != ownerID)
continue; continue;
if (item.template.item_type.equals(ItemType.GOLD)) { if (item.template.getItemType().equals(ItemType.GOLD)) {
if (item.getNumOfItems() == 0) if (item.getNumOfItems() == 0)
continue; continue;
} }
+41 -40
View File
@@ -29,6 +29,8 @@ import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.ApplyRuneMsg; import engine.net.client.msg.ApplyRuneMsg;
import engine.net.client.msg.UpdateStateMsg; import engine.net.client.msg.UpdateStateMsg;
import engine.parsers.objects.CObjectTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
import engine.powers.PowersBase; import engine.powers.PowersBase;
import engine.powers.effectmodifiers.AbstractEffectModifier; import engine.powers.effectmodifiers.AbstractEffectModifier;
@@ -652,11 +654,11 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
AssignDefenseValue(abstractCharacter); AssignDefenseValue(abstractCharacter);
// calculate atr and damage for each hand // calculate atr and damage for each hand
ConcurrentHashMap<EquipSlotType, Item> equipped = abstractCharacter.charItemManager.getEquipped(); ConcurrentHashMap<EquipSlotType, Item> equipped = abstractCharacter.charItemManager.getEquipped();
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.RHELD), true, equipped.get(EquipSlotType.RHELD)); AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.RHELD), true, equipped.get(EquipSlotType.LHELD));
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.LHELD)); AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.RHELD));
if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) { if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
Mob mob = (Mob) abstractCharacter; Mob mob = (Mob) abstractCharacter;
ItemTemplate rune = ItemTemplate.templates.get(mob.loadID); RuneTemplate rune = RuneTemplate._runes.get(mob.loadID);
float minDamage = mob.mobBase.getDamageMin(); float minDamage = mob.mobBase.getDamageMin();
float maxDamage = mob.mobBase.getDamageMax(); float maxDamage = mob.mobBase.getDamageMax();
int attackRating = mob.mobBase.getAttackRating(); int attackRating = mob.mobBase.getAttackRating();
@@ -691,16 +693,17 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
// make sure weapon exists // make sure weapon exists
boolean noWeapon = false; boolean noWeapon = false;
ItemTemplate weaponTemplate = null; CObjectTemplate weaponTemplate = null;
engine.parsers.objects.ItemTemplate.Damage damage;
if (weapon == null) if (weapon == null)
noWeapon = true; noWeapon = true;
else { else {
ItemTemplate itemTemplate = weapon.template; CObjectTemplate itemTemplate = weapon.template;
if (itemTemplate == null) if (itemTemplate == null)
noWeapon = true; noWeapon = true;
else if (!weapon.template.item_type.equals(ItemType.WEAPON)) { else if (!weapon.template.getItemType().equals(ItemType.WEAPON)) {
defaultAtrAndDamage(abstractCharacter, mainHand); defaultAtrAndDamage(abstractCharacter, mainHand);
return; return;
} else } else
@@ -717,7 +720,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
if (noWeapon) { if (noWeapon) {
if (mainHand) { if (mainHand) {
Item off = abstractCharacter.charItemManager.getEquipped().get(EquipSlotType.LHELD); 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.getItemType().equals(ItemType.WEAPON))
abstractCharacter.rangeHandOne = 10 * (1 + (abstractCharacter.statStrBase / 600)); // Set abstractCharacter.rangeHandOne = 10 * (1 + (abstractCharacter.statStrBase / 600)); // Set
// to // to
// no // no
@@ -750,9 +753,9 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
} else { } else {
if (mainHand) if (mainHand)
abstractCharacter.rangeHandOne = weapon.template.item_weapon_max_range * (1 + (abstractCharacter.statStrBase / 600)); abstractCharacter.rangeHandOne = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_max_range * (1 + (abstractCharacter.statStrBase / 600));
else else
abstractCharacter.rangeHandTwo = weapon.template.item_weapon_max_range * (1 + (abstractCharacter.statStrBase / 600)); abstractCharacter.rangeHandTwo = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_max_range * (1 + (abstractCharacter.statStrBase / 600));
if (abstractCharacter.bonuses != null) { if (abstractCharacter.bonuses != null) {
float range_bonus = 1 + abstractCharacter.bonuses.getFloatPercentAll(ModType.WeaponRange, SourceType.None); float range_bonus = 1 + abstractCharacter.bonuses.getFloatPercentAll(ModType.WeaponRange, SourceType.None);
@@ -764,20 +767,20 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
} }
skillPercentage = getModifiedAmount(abstractCharacter.skills.get(weapon.template.item_skill_used)); skillPercentage = getModifiedAmount(abstractCharacter.skills.get(weapon.template.getItemSkillUsed()));
masteryPercentage = getModifiedAmount(abstractCharacter.skills.get(weaponTemplate.item_skill_mastery_used)); masteryPercentage = getModifiedAmount(abstractCharacter.skills.get(weaponTemplate.getItemSkillMasteryUsed()));
if (masteryPercentage == 0f) if (masteryPercentage == 0f)
mastDam = 0f; mastDam = 0f;
// mastDam = CharacterSkill.getQuickMastery(this, wb.getMastery()); // mastDam = CharacterSkill.getQuickMastery(this, wb.getMastery());
else else
mastDam = masteryPercentage; mastDam = masteryPercentage;
int[] damages = (int[]) weaponTemplate.item_weapon_damage.values().toArray()[0]; damage = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_damage.get(0);
min = damages[0]; min = damage.damage_min;
max = damages[1]; max = damage.damage_max;
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")) if (abstractCharacter.effects != null && abstractCharacter.effects.containsKey("DeathShroud"))
@@ -826,7 +829,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
//calculate speed //calculate speed
if (weaponTemplate != null) if (weaponTemplate != null)
speed = weaponTemplate.item_weapon_wepspeed; speed = engine.parsers.objects.ItemTemplate.getWeapon(weaponTemplate.getTemplateId()).weapon_wepspeed;
else else
speed = 20f; //unarmed attack speed speed = 20f; //unarmed attack speed
if (weapon != null) if (weapon != null)
@@ -881,15 +884,12 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
max *= percentMaxDamage; max *= percentMaxDamage;
} }
//if duel wielding, cut damage by 30% // Client (0x466EB0 sets char+0xD0C, 0x46D200 applies it): 30% cut only when both hands hold a WEAPON.
if (otherHand != null) { if (!noWeapon && otherHand != null && otherHand.template != null
ItemTemplate ibo = otherHand.template; && otherHand.template.getItemType().equals(ItemType.WEAPON)) {
min *= 0.7f;
if (ibo != null && otherHand.template.equals(ItemType.WEAPON)) { max *= 0.7f;
min *= 0.7f;
max *= 0.7f;
}
} }
// calculate damage // calculate damage
@@ -955,7 +955,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
if (ItemManager.isShield(shield) == false) if (ItemManager.isShield(shield) == false)
return 0; return 0;
ItemTemplate template = shield.template; CObjectTemplate template = shield.template;
if (template == null) if (template == null)
return 0; return 0;
@@ -969,7 +969,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
} else } else
skillMod = blockSkill.getModifiedAmount(); 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 //apply item defense bonuses
@@ -991,29 +991,30 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
if (armor == null) if (armor == null)
return 0; return 0;
ItemTemplate template = armor.template; CObjectTemplate template = armor.template;
if (template == null) if (template == null)
return 0; return 0;
if (!armor.template.item_type.equals(ItemType.ARMOR)) if (!armor.template.getItemType().equals(ItemType.ARMOR))
return 0; return 0;
if (armor.template.item_skill_used.isEmpty()) // "Cloth" is a placeholder in Skills.cfg (token 0) that no character learns
return template.item_defense_rating; if (armor.template.getItemSkillUsed().isEmpty() || armor.template.getItemSkillUsed().equals("Cloth"))
return engine.parsers.objects.ItemTemplate._items.get(armor.templateID).item_defense_rating;
CharacterSkill armorSkill = abstractCharacter.skills.get(armor.template.item_skill_used); CharacterSkill armorSkill = abstractCharacter.skills.get(armor.template.getItemSkillUsed());
if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) && armorSkill == null) { if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) && armorSkill == null) {
Logger.error("Player " + abstractCharacter.getObjectUUID() Logger.error("Player " + abstractCharacter.getObjectUUID()
+ " has armor equipped without the nescessary skill to equip it"); + " 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) 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 //apply item defense bonuses
@@ -1031,7 +1032,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
private static float getWeaponDefense(AbstractCharacter abstractCharacter, ConcurrentHashMap<EquipSlotType, Item> equipped) { private static float getWeaponDefense(AbstractCharacter abstractCharacter, ConcurrentHashMap<EquipSlotType, Item> equipped) {
Item weapon = equipped.get(EquipSlotType.RHELD); Item weapon = equipped.get(EquipSlotType.RHELD);
ItemTemplate weaponTemplate = null; CObjectTemplate weaponTemplate = null;
CharacterSkill skill, mastery; CharacterSkill skill, mastery;
float val = 0; float val = 0;
boolean unarmed = false; boolean unarmed = false;
@@ -1052,8 +1053,8 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
skill = abstractCharacter.skills.get("Unarmed Combat"); skill = abstractCharacter.skills.get("Unarmed Combat");
mastery = abstractCharacter.skills.get("Unarmed Combat Mastery"); mastery = abstractCharacter.skills.get("Unarmed Combat Mastery");
} else { } else {
skill = abstractCharacter.skills.get(weapon.template.item_skill_used); skill = abstractCharacter.skills.get(weapon.template.getItemSkillUsed());
mastery = abstractCharacter.skills.get(weaponTemplate.item_skill_mastery_used); mastery = abstractCharacter.skills.get(weaponTemplate.getItemSkillMasteryUsed());
} }
if (skill != null) if (skill != null)
@@ -1088,7 +1089,7 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
for (Item item : equipped.values()) { for (Item item : equipped.values()) {
ItemTemplate ib = item.template; CObjectTemplate ib = item.template;
if (ib == null) if (ib == null)
continue; continue;
@@ -1507,9 +1508,9 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
} }
float range = 8; float range = 8;
if ((this).charItemManager.equipped.get(EquipSlotType.RHELD) != null) { if ((this).charItemManager.equipped.get(EquipSlotType.RHELD) != null) {
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.RHELD).template.item_weapon_max_range; range = engine.parsers.objects.ItemTemplate.getWeapon(((Mob) this).charItemManager.equipped.get(EquipSlotType.RHELD).templateID).weapon_max_range;
} else if (((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD) != null) { } else if (((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD) != null) {
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD).template.item_weapon_max_range; range = engine.parsers.objects.ItemTemplate.getWeapon(((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD).templateID).weapon_max_range;
} else { } else {
// Unarmed: use the rune natural attack range when it has one (e.g. Gasser 150), not clamped. // Unarmed: use the rune natural attack range when it has one (e.g. Gasser 150), not clamped.
+3 -3
View File
@@ -172,7 +172,7 @@ public class Account extends AbstractGameObject {
this.vault = DbManager.ItemQueries.GET_ITEMS_FOR_ACCOUNT(this.getObjectUUID()); this.vault = DbManager.ItemQueries.GET_ITEMS_FOR_ACCOUNT(this.getObjectUUID());
for (Item item : this.vault) { for (Item item : this.vault) {
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) { if (item.template.getItemType().equals(mbEnums.ItemType.GOLD)) {
this.vaultGold = item; this.vaultGold = item;
} }
} }
@@ -216,7 +216,7 @@ public class Account extends AbstractGameObject {
return; return;
if (item.containerType == mbEnums.ItemContainerType.INVENTORY && player.charItemManager.isVaultOpen()) { if (item.containerType == mbEnums.ItemContainerType.INVENTORY && player.charItemManager.isVaultOpen()) {
if (!player.charItemManager.hasRoomVault(item.template.item_wt)) { if (!player.charItemManager.hasRoomVault(item.template.getItemWt())) {
ItemToVaultMsgHandler.forceTransferFromVaultToInventory(msg, origin, "There is no room in your vault."); ItemToVaultMsgHandler.forceTransferFromVaultToInventory(msg, origin, "There is no room in your vault.");
return; return;
} }
@@ -262,7 +262,7 @@ public class Account extends AbstractGameObject {
return; return;
if (item.containerType == mbEnums.ItemContainerType.VAULT && itemManager.isVaultOpen()) { if (item.containerType == mbEnums.ItemContainerType.VAULT && itemManager.isVaultOpen()) {
if (!itemManager.hasRoomInventory(item.template.item_wt)) { if (!itemManager.hasRoomInventory(item.template.getItemWt())) {
ItemFromVaultMsgHandler.forceTransferFromInventoryToVault(msg, origin, "There is no room in your inventory."); ItemFromVaultMsgHandler.forceTransferFromInventoryToVault(msg, origin, "There is no room in your inventory.");
return; return;
} }
+39 -1
View File
@@ -10,7 +10,11 @@
package engine.objects; package engine.objects;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.net.ByteBufferWriter; import engine.net.ByteBufferWriter;
import engine.parsers.objects.RuneTemplate;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -104,8 +108,42 @@ public class BaseClass extends AbstractGameObject {
} }
/**
* Rune Template Constructor
*/
public BaseClass(RuneTemplate rune) {
super(rune.template_id);
this.name = rune.obj_name;
this.description = rune.rune_dsc;
this.strMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Strength, 0).byteValue();
this.dexMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Dexterity, 0).byteValue();
this.conMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Constitution, 0).byteValue();
this.intMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Intelligence, 0).byteValue();
this.spiMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Spirit, 0).byteValue();
this.token = Hasher.SBStringHash(rune.rune_sub_type);
this.healthMod = rune.rune_health;
this.manaMod = rune.rune_mana;
this.staminaMod = rune.rune_stamina;
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.getObjectUUID());
this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID());
this.effectsList = (DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()));
}
public static void LoadAllBaseClasses() { public static void LoadAllBaseClasses() {
DbManager.BaseClassQueries.GET_ALL_BASE_CLASSES();
int loadedCount = 0;
// Promotion classes and the Pet class are also CLASS runes, but not standard creation runes
for (RuneTemplate rune : RuneTemplate._runes.values()) {
if (rune.rune_type != mbEnums.RuneType.CLASS || !rune.rune_is_standard_character_creation)
continue;
DbManager.addToCache(new BaseClass(rune));
loadedCount++;
}
Logger.info("Loaded " + loadedCount + " base classes from runes");
} }
public static void serializeForClientMsg(BaseClass baseClass, ByteBufferWriter writer) { public static void serializeForClientMsg(BaseClass baseClass, ByteBufferWriter writer) {
+1
View File
@@ -433,6 +433,7 @@ public class Building extends AbstractWorldObject {
// Special handling of warehouses // Special handling of warehouses
if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) { if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) {
Warehouse.destroyedWarehouses.put(this.getObjectUUID(), city.warehouse);
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse); DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
city.warehouse = null; city.warehouse = null;
} }
+34 -33
View File
@@ -17,6 +17,7 @@ import engine.mbEnums.ItemType;
import engine.net.Dispatch; import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.*; import engine.net.client.msg.*;
import engine.parsers.objects.CObjectTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -130,19 +131,19 @@ public class CharacterItemManager {
this.equipped.put(i.equipSlot, i); this.equipped.put(i.equipSlot, i);
break; break;
case BANK: case BANK:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldBank = i; this.goldBank = i;
else if (this.bank.contains(i) == false) else if (this.bank.contains(i) == false)
this.bank.add(i); this.bank.add(i);
break; break;
case INVENTORY: case INVENTORY:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldInventory = i; this.goldInventory = i;
else if (this.inventory.contains(i) == false) else if (this.inventory.contains(i) == false)
this.inventory.add(i); this.inventory.add(i);
break; break;
case VAULT: case VAULT:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldVault = i; this.goldVault = i;
else if (this.vault.contains(i) == false) else if (this.vault.contains(i) == false)
this.vault.add(i); this.vault.add(i);
@@ -175,13 +176,13 @@ public class CharacterItemManager {
this.equipped.put(i.equipSlot, i); this.equipped.put(i.equipSlot, i);
break; break;
case BANK: case BANK:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldBank = i; this.goldBank = i;
else if (this.bank.contains(i) == false) else if (this.bank.contains(i) == false)
this.bank.add(i); this.bank.add(i);
break; break;
case INVENTORY: case INVENTORY:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldInventory = i; this.goldInventory = i;
else if (this.inventory.contains(i) == false) else if (this.inventory.contains(i) == false)
this.inventory.add(i); this.inventory.add(i);
@@ -511,7 +512,7 @@ public class CharacterItemManager {
if (i == null) if (i == null)
return false; return false;
if (i.template.item_type.equals(ItemType.GOLD)) { if (i.template.getItemType().equals(ItemType.GOLD)) {
if (this.getGoldInventory() != null) { if (this.getGoldInventory() != null) {
if (i.getObjectUUID() == this.getGoldInventory().getObjectUUID()) if (i.getObjectUUID() == this.getGoldInventory().getObjectUUID())
this.goldInventory = null; this.goldInventory = null;
@@ -541,7 +542,7 @@ public class CharacterItemManager {
} }
private synchronized boolean junk(Item i, boolean updateInventory) { private synchronized boolean junk(Item i, boolean updateInventory) {
if (i.template.item_type.equals(ItemType.GOLD)) { if (i.template.getItemType().equals(ItemType.GOLD)) {
if (this.getGoldInventory().getObjectUUID() == i.getObjectUUID()) if (this.getGoldInventory().getObjectUUID() == i.getObjectUUID())
if (DbManager.ItemQueries.UPDATE_GOLD(i, 0)) { if (DbManager.ItemQueries.UPDATE_GOLD(i, 0)) {
this.getGoldInventory().setNumOfItems(0); this.getGoldInventory().setNumOfItems(0);
@@ -596,7 +597,7 @@ public class CharacterItemManager {
if (equippedContains(item)) { if (equippedContains(item)) {
fromEquip = true; fromEquip = true;
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
this.absCharacter.cancelOnUnEquip(); this.absCharacter.cancelOnUnEquip();
} }
@@ -814,7 +815,7 @@ public class CharacterItemManager {
// This removes ingame item from inventory for loot. // This removes ingame item from inventory for loot.
private synchronized boolean removeItemFromInventory(Item i) { private synchronized boolean removeItemFromInventory(Item i) {
if (i.template.item_type.equals(ItemType.GOLD)) { if (i.template.getItemType().equals(ItemType.GOLD)) {
if (i.getObjectUUID() != this.getGoldInventory().getObjectUUID()) if (i.getObjectUUID() != this.getGoldInventory().getObjectUUID())
return false; return false;
if (!DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) { if (!DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) {
@@ -837,7 +838,7 @@ public class CharacterItemManager {
// This adds item to inventory for loot. Validity checks already handled // This adds item to inventory for loot. Validity checks already handled
public synchronized boolean addItemToInventory(Item i) { public synchronized boolean addItemToInventory(Item i) {
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
if (this.absCharacter.getObjectType() == GameObjectType.Mob) { if (this.absCharacter.getObjectType() == GameObjectType.Mob) {
if (this.goldInventory == null) if (this.goldInventory == null)
loadGoldItems(); loadGoldItems();
@@ -856,7 +857,7 @@ public class CharacterItemManager {
this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal()); this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal());
if (i.template != null) if (i.template != null)
this.inventoryWeight += i.template.item_wt; this.inventoryWeight += i.template.getItemWt();
return true; return true;
} }
@@ -1041,7 +1042,7 @@ public class CharacterItemManager {
if (npc.getContractID() >= 1900 && npc.getContractID() <= 1906) if (npc.getContractID() >= 1900 && npc.getContractID() <= 1906)
eatItem = true; eatItem = true;
if (itemToSell.template.item_type.equals(ItemType.POTION)) if (itemToSell.template.getItemType().equals(ItemType.POTION))
eatItem = true; eatItem = true;
if (eatItem) { if (eatItem) {
@@ -1085,14 +1086,14 @@ public class CharacterItemManager {
synchronized (this) { synchronized (this) {
synchronized (itemMan) { synchronized (itemMan) {
ItemTemplate template = purchasedItem.template; CObjectTemplate template = purchasedItem.template;
if (template == null) if (template == null)
return false; return false;
//test inventory is not full //test inventory is not full
if (!hasRoomInventory(template.item_wt)) if (!hasRoomInventory(template.getItemWt()))
return false; return false;
if (!itemMan.inventory.contains(purchasedItem)) if (!itemMan.inventory.contains(purchasedItem))
@@ -1105,8 +1106,8 @@ public class CharacterItemManager {
// Reset value // Reset value
purchasedItem.value = (int) (purchasedItem.template.item_value * (purchasedItem.combat_health_current / purchasedItem.template.combat_health_full)); purchasedItem.value = (int) (purchasedItem.template.getItemValue() * (purchasedItem.combat_health_current / purchasedItem.template.getCombatHealthFull()));
purchasedItem.magicValue = purchasedItem.template.item_value + purchasedItem.calcMagicValue(); purchasedItem.magicValue = purchasedItem.template.getItemValue() + purchasedItem.calcMagicValue();
// db transfer successfully, remove from this character // db transfer successfully, remove from this character
// skip this check if this is a mobLoot item (which is not in any inventory) // skip this check if this is a mobLoot item (which is not in any inventory)
@@ -1184,9 +1185,9 @@ public class CharacterItemManager {
synchronized (lockFirst) { synchronized (lockFirst) {
synchronized (lockSecond) { synchronized (lockSecond) {
// make sure current player has item in inventory // 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.getItemType().equals(ItemType.GOLD) && lootItem.getObjectUUID() != this.getGoldInventory().getObjectUUID() && !(this.absCharacter.getObjectType().equals(GameObjectType.Mob)))
return null; 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.getItemType().equals(ItemType.GOLD))
return null; return null;
// get weight of item // get weight of item
@@ -1194,13 +1195,13 @@ public class CharacterItemManager {
if (lootItem.template == null) if (lootItem.template == null)
return null; return null;
int weight = lootItem.template.item_wt; int weight = lootItem.template.getItemWt();
// make sure lootingPlayer has room for item // make sure lootingPlayer has room for item
if (!lootItem.template.item_type.equals(ItemType.GOLD) && !looterItems.hasRoomInventory(weight)) if (!lootItem.template.getItemType().equals(ItemType.GOLD) && !looterItems.hasRoomInventory(weight))
return null; return null;
if (lootItem.template.item_type.equals(ItemType.GOLD)) if (lootItem.template.getItemType().equals(ItemType.GOLD))
if (amount != -1) { //from steal if (amount != -1) { //from steal
int total = lootItem.getNumOfItems(); int total = lootItem.getNumOfItems();
amount = (amount > total) ? total : amount; amount = (amount > total) ? total : amount;
@@ -1263,19 +1264,19 @@ public class CharacterItemManager {
* Delegates * Delegates
*/ */
public synchronized boolean bankContains(Item item) { public synchronized boolean bankContains(Item item) {
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
return (this.getGoldBank() != null && this.goldBank.getObjectUUID() == item.getObjectUUID()); return (this.getGoldBank() != null && this.goldBank.getObjectUUID() == item.getObjectUUID());
return bank.contains(item); return bank.contains(item);
} }
public synchronized boolean inventoryContains(Item item) { public synchronized boolean inventoryContains(Item item) {
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
return (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == item.getObjectUUID()); return (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == item.getObjectUUID());
return inventory.contains(item); return inventory.contains(item);
} }
public synchronized boolean vaultContains(Item item) { public synchronized boolean vaultContains(Item item) {
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
return (this.getGoldVault() != null && this.goldVault.getObjectUUID() == item.getObjectUUID()); return (this.getGoldVault() != null && this.goldVault.getObjectUUID() == item.getObjectUUID());
return this.account.getVault().contains(item); return this.account.getVault().contains(item);
} }
@@ -1518,13 +1519,13 @@ public class CharacterItemManager {
item.zeroItem(); item.zeroItem();
item.containerType = mbEnums.ItemContainerType.INVENTORY; item.containerType = mbEnums.ItemContainerType.INVENTORY;
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
//only add gold item once //only add gold item once
if (!corpse.hasGold()) if (!corpse.hasGold())
corpse.setHasGold(true); corpse.setHasGold(true);
newInventory.add(item); newInventory.add(item);
} else //item } else //item
if (item.template.item_type.equals(ItemType.GOLD)) { if (item.template.getItemType().equals(ItemType.GOLD)) {
int amt = item.getNumOfItems(); int amt = item.getNumOfItems();
item.setNumOfItems(0); item.setNumOfItems(0);
MobLoot ml = new MobLoot(this.absCharacter, amt); MobLoot ml = new MobLoot(this.absCharacter, amt);
@@ -1676,7 +1677,7 @@ public class CharacterItemManager {
if (item == null) if (item == null)
continue; continue;
weight += item.template.item_wt; weight += item.template.getItemWt();
} }
return weight; return weight;
} }
@@ -1696,7 +1697,7 @@ public class CharacterItemManager {
this.bankWeight = 0; this.bankWeight = 0;
for (Item item : this.bank) { for (Item item : this.bank) {
if (item.template != null) if (item.template != null)
this.bankWeight += item.template.item_wt; this.bankWeight += item.template.getItemWt();
} }
} }
@@ -1707,7 +1708,7 @@ public class CharacterItemManager {
while (it.hasNext()) { while (it.hasNext()) {
Item item = it.next(); Item item = it.next();
if (item.template != null) if (item.template != null)
this.equipWeight += item.template.item_wt; this.equipWeight += item.template.getItemWt();
} }
} }
@@ -1715,7 +1716,7 @@ public class CharacterItemManager {
this.inventoryWeight = 0; this.inventoryWeight = 0;
for (Item item : this.inventory) { for (Item item : this.inventory) {
if (item.template != null) if (item.template != null)
this.inventoryWeight += item.template.item_wt; this.inventoryWeight += item.template.getItemWt();
} }
} }
@@ -1723,7 +1724,7 @@ public class CharacterItemManager {
this.vaultWeight = 0; this.vaultWeight = 0;
for (Item item : this.vault) { for (Item item : this.vault) {
if (item.template != null) if (item.template != null)
this.vaultWeight += item.template.item_wt; this.vaultWeight += item.template.getItemWt();
} }
} }
@@ -1755,7 +1756,7 @@ public class CharacterItemManager {
return; return;
if (!item.moveItemToInventory(player)) if (!item.moveItemToInventory(player))
Logger.error("templateL " + item.template.template_id); Logger.error("templateL " + item.template.getTemplateId());
// remove it from other lists: // remove it from other lists:
this.remItemFromLists(item); this.remItemFromLists(item);
@@ -1932,7 +1933,7 @@ public class CharacterItemManager {
return; return;
//don't damage noob gear, hair or beards. //don't damage noob gear, hair or beards.
if (item.template.combat_health_full == 0) if (item.template.getCombatHealthFull() == 0)
return; return;
if (!item.isCanDestroy()) if (!item.isCanDestroy())
+3 -3
View File
@@ -296,14 +296,14 @@ public class Corpse extends AbstractWorldObject {
//get weight of item //get weight of item
int weight = item.template.item_wt; int weight = item.template.getItemWt();
//make sure looter has room for item //make sure looter has room for item
if (item.template.item_type.equals(ItemType.GOLD) == false && !looterItems.hasRoomInventory(weight)) if (item.template.getItemType().equals(ItemType.GOLD) == false && !looterItems.hasRoomInventory(weight))
return null; return null;
//attempt to transfer item in db //attempt to transfer item in db
if (item.template.item_type.equals(ItemType.GOLD)) { if (item.template.getItemType().equals(ItemType.GOLD)) {
if (!looterItems.moveGoldToInventory(item, item.getNumOfItems())) if (!looterItems.moveGoldToInventory(item, item.getNumOfItems()))
return null; return null;
} else if (!item.moveItemToInventory(looter)) } else if (!item.moveItemToInventory(looter))
+30 -23
View File
@@ -20,6 +20,8 @@ import engine.net.ByteBufferWriter;
import engine.net.Dispatch; import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.DeleteItemMsg; import engine.net.client.msg.DeleteItemMsg;
import engine.parsers.objects.CObjectTemplate;
import engine.parsers.objects.UserPowerAction;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
import engine.powers.effectmodifiers.AbstractEffectModifier; import engine.powers.effectmodifiers.AbstractEffectModifier;
import engine.powers.poweractions.AbstractPowerAction; import engine.powers.poweractions.AbstractPowerAction;
@@ -31,6 +33,7 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -55,7 +58,7 @@ public class Item extends AbstractWorldObject {
public int templateID; public int templateID;
private long dateToUpgrade; private long dateToUpgrade;
public int magicValue; public int magicValue;
public ItemTemplate template; public CObjectTemplate template;
public String name = ""; public String name = "";
public int prefixToken; // Forge support public int prefixToken; // Forge support
public int suffixToken; // Forge support public int suffixToken; // Forge support
@@ -68,14 +71,14 @@ public class Item extends AbstractWorldObject {
public Item(int templateID) { public Item(int templateID) {
super(); super();
this.templateID = templateID; this.templateID = templateID;
this.template = ItemTemplate.templates.get(templateID); this.template = CObjectTemplate.lookup(templateID);
this.chargesRemaining = this.template.item_initial_charges; this.chargesRemaining = this.template.getItemInitialCharges();
this.combat_health_current = this.template.combat_health_full; this.combat_health_current = this.template.getCombatHealthFull();
this.equipSlot = EquipSlotType.NONE; this.equipSlot = EquipSlotType.NONE;
this.containerType = ItemContainerType.NONE; this.containerType = ItemContainerType.NONE;
this.numberOfItems = 1; this.numberOfItems = 1;
this.flags.addAll(this.template.item_flags); this.flags.addAll(this.template.getItemFlags());
this.value = this.template.item_value; this.value = this.template.getItemValue();
this.magicValue = this.value + calcMagicValue(); this.magicValue = this.value + calcMagicValue();
loadTemplateEnchantments(); loadTemplateEnchantments();
} }
@@ -87,7 +90,7 @@ public class Item extends AbstractWorldObject {
super(rs); super(rs);
this.templateID = rs.getInt("templateID"); this.templateID = rs.getInt("templateID");
this.template = ItemTemplate.templates.get(this.templateID); this.template = CObjectTemplate.lookup(this.templateID);
if (this.template == null) if (this.template == null)
Logger.error("Null template of " + this.templateID); Logger.error("Null template of " + this.templateID);
@@ -166,7 +169,7 @@ public class Item extends AbstractWorldObject {
this.value = rs.getInt("value"); this.value = rs.getInt("value");
if (this.value == 0) { if (this.value == 0) {
this.value = this.template.item_value; this.value = this.template.getItemValue();
this.magicValue = this.value + calcMagicValue(); this.magicValue = this.value + calcMagicValue();
} else } else
this.magicValue = this.value; // Value was overridden in vendor inventory this.magicValue = this.value; // Value was overridden in vendor inventory
@@ -238,7 +241,7 @@ public class Item extends AbstractWorldObject {
writer.putString(item.name); // Unknown. pad? writer.putString(item.name); // Unknown. pad?
writer.put((byte) 1); // End Datablock byte writer.put((byte) 1); // End Datablock byte
writer.putFloat(item.template.combat_health_full); writer.putFloat(item.template.getCombatHealthFull());
writer.putFloat((float) item.combat_health_current); writer.putFloat((float) item.combat_health_current);
writer.put((byte) 1); // End Datablock byte writer.put((byte) 1); // End Datablock byte
@@ -255,7 +258,7 @@ public class Item extends AbstractWorldObject {
} else } else
writer.putInt(item.numberOfItems); // Amount of gold writer.putInt(item.numberOfItems); // Amount of gold
} else } else
writer.putInt(item.template.item_value); writer.putInt(item.template.getItemValue());
writer.putInt(item.getValue()); writer.putInt(item.getValue());
@@ -288,7 +291,7 @@ public class Item extends AbstractWorldObject {
writer.putInt(36); //Magical, blue name writer.putInt(36); //Magical, blue name
else else
writer.putInt(40); //Magical, unidentified writer.putInt(40); //Magical, unidentified
else if (item.template.item_user_power_action.size() > 0) else if (item.template.getItemUserPowerAction().isEmpty() == false)
writer.putInt(36); //Magical, blue name writer.putInt(36); //Magical, blue name
else else
writer.putInt(4); //Non-Magical, grey name writer.putInt(4); //Non-Magical, grey name
@@ -298,7 +301,7 @@ public class Item extends AbstractWorldObject {
writer.put((byte) 0); writer.put((byte) 0);
if (item.template.item_type.equals(ItemType.EMPLOYMENTCONTRACT) == false) { if (item.template.getItemType().equals(ItemType.EMPLOYMENTCONTRACT) == false) {
writer.putShort((short) 0); writer.putShort((short) 0);
return; return;
} }
@@ -463,7 +466,7 @@ public class Item extends AbstractWorldObject {
int serialized = 0; int serialized = 0;
for (Item item : list) { for (Item item : list) {
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
if (item.numberOfItems == 0) if (item.numberOfItems == 0)
continue; continue;
try { try {
@@ -497,7 +500,7 @@ public class Item extends AbstractWorldObject {
int serialized = 0; int serialized = 0;
for (Item item : list) { for (Item item : list) {
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
if (item.numberOfItems == 0) if (item.numberOfItems == 0)
continue; continue;
try { try {
@@ -931,12 +934,16 @@ public class Item extends AbstractWorldObject {
private void loadTemplateEnchantments() { private void loadTemplateEnchantments() {
EffectsBase effect; EffectsBase effect;
LinkedHashMap<String, int[]> templateEnchantments = new LinkedHashMap<>();
if (ConfigManager.serverType.equals(mbEnums.ServerType.LOGINSERVER)) if (ConfigManager.serverType.equals(mbEnums.ServerType.LOGINSERVER))
return; return;
// Duplicate powers collapse to the last entry; applying one twice would double its magic value.
for (UserPowerAction userPowerAction : this.template.getItemUserPowerAction())
templateEnchantments.put(userPowerAction.power, userPowerAction.arguments);
for (String effectID : this.template.item_user_power_action.keySet()) { for (String effectID : templateEnchantments.keySet()) {
effect = PowersManager.getEffectByIDString(effectID); effect = PowersManager.getEffectByIDString(effectID);
@@ -946,7 +953,7 @@ public class Item extends AbstractWorldObject {
} }
AbstractPowerAction apa = PowersManager.getPowerActionByIDString(effect.getIDString()); AbstractPowerAction apa = PowersManager.getPowerActionByIDString(effect.getIDString());
apa.applyBakedInStatsForItem(this, this.template.item_user_power_action.get(effectID)[0]); apa.applyBakedInStatsForItem(this, templateEnchantments.get(effectID)[0]);
} }
} }
@@ -955,7 +962,7 @@ public class Item extends AbstractWorldObject {
// No enchantments for negative id items in the db. Early exit. // No enchantments for negative id items in the db. Early exit.
if (this.objectUUID < 0) { if (this.objectUUID < 0) {
this.magicValue = this.template.item_value + calcMagicValue(); this.magicValue = this.template.getItemValue() + calcMagicValue();
return; return;
} }
@@ -971,8 +978,8 @@ public class Item extends AbstractWorldObject {
// Value is overridden the db // Value is overridden the db
if (this.value == this.template.item_value) if (this.value == this.template.getItemValue())
this.magicValue = this.template.item_value + calcMagicValue(); this.magicValue = this.template.getItemValue() + calcMagicValue();
} }
public void addPermanentEnchantment(String enchantID, int rank) { public void addPermanentEnchantment(String enchantID, int rank) {
@@ -1116,11 +1123,11 @@ public class Item extends AbstractWorldObject {
else else
modifiedValue = this.value; modifiedValue = this.value;
if (this.template.item_initial_charges > 0) if (this.template.getItemInitialCharges() > 0)
modifiedValue = modifiedValue * (this.chargesRemaining / this.template.item_initial_charges); modifiedValue = modifiedValue * (this.chargesRemaining / this.template.getItemInitialCharges());
if (this.template.combat_health_full > 0) if (this.template.getCombatHealthFull() > 0)
modifiedValue = (int) (modifiedValue * (this.combat_health_current / this.template.combat_health_full)); modifiedValue = (int) (modifiedValue * (this.combat_health_current / this.template.getCombatHealthFull()));
return modifiedValue; return modifiedValue;
} }
-643
View File
@@ -1,643 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
public class ItemTemplate {
// MB Dev Notes:
// This class parses JSON data generated by mbEditor Pro 2.1.
// Cache definitions are loaded for ItemType.ITEM, ItemType,DEED
// and ItemType.RUNE.
//
// Exported JSON data is stored in db table static_item_templates
// and can be easily mass updated using tooling available on the Wiki.
//
// Always read never write!
// Use copies of fields to avoid side effects.
// Global template lookup
public static HashMap<Integer, ItemTemplate> templates = new HashMap<>();
public int template_id;
// Template Properties
public String obj_name;
public boolean obj_pickable;
public Vector3fImmutable obj_scale;
public Vector3fImmutable obj_forward_vector;
public Vector3fImmutable obj_default_alignment;
public int obj_render_object;
public int obj_icon;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public float combat_health_current;
public float combat_health_full;
public final HashMap<String, Float> combat_attack_resist = new HashMap<>();
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 float item_bulk_factor;
public final HashMap<mbEnums.ShrineType, Integer> item_offering_info = new HashMap<>();
public int item_defense_rating;
public float item_weapon_wepspeed;
public float item_weapon_max_range;
public int item_weapon_projectile_id;
public float item_weapon_projectile_speed;
public int item_weapon_combat_idle_anim;
public final HashMap<mbEnums.DamageType, int[]> item_weapon_damage = new HashMap<>();
public final ArrayList<int[]> weapon_attack_anim_right = new ArrayList<>();
public final ArrayList<int[]> weapon_attack_anim_left = new ArrayList<>();
public mbEnums.AttributeType item_primary_attr = mbEnums.AttributeType.None;
public mbEnums.AttributeType item_secondary_attr = mbEnums.AttributeType.None;
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_initial_charges;
public final HashMap<String, Integer> item_skill_required = new HashMap<>();
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public final EnumSet<mbEnums.MonsterType> item_race_res = EnumSet.noneOf(mbEnums.MonsterType.class);
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public final EnumSet<mbEnums.ClassType> item_class_res = EnumSet.noneOf(mbEnums.ClassType.class);
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumSet<mbEnums.DisciplineType> item_disc_res = EnumSet.noneOf(mbEnums.DisciplineType.class);
public int item_level_req;
public mbEnums.SexType item_sex_req;
public final HashMap<String, int[]> item_user_power_action = new HashMap<>();
public final HashMap<String, Integer> item_power_grant = new HashMap<>();
public final HashMap<String, int[]> item_power_action = new HashMap<>();
public final HashMap<mbEnums.ResourceType, Integer> item_resource_cost = new HashMap<>();
public int modTable;
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;
public final HashMap<mbEnums.AttributeType, Integer> rune_attr_adj = new HashMap<>();
public final HashMap<mbEnums.AttributeType, Integer> rune_max_attr_adj = new HashMap<>();
public final HashMap<String, Integer> rune_skill_grant = new HashMap<>();
public final HashMap<String, Integer> skill_granted_skills = new HashMap<>();
public final HashMap<String, Integer> power_granted_skills = new HashMap<>();
public String rune_type;
public String rune_sub_type;
public boolean rune_is_standard_character_creation = false;
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 HashMap<mbEnums.MovementType, Float> rune_speed = new HashMap<>();
public int rune_group_type;
public boolean rune_group_is_faction = false;
public boolean rune_group_is_guild = false;
public String rune_dsc;
public String rune_fx_txt;
public final EnumSet<mbEnums.MobBehaviourType> rune_group_tactics = EnumSet.noneOf(mbEnums.MobBehaviourType.class);
public final EnumSet<mbEnums.MobBehaviourType> rune_group_role_set = EnumSet.noneOf(mbEnums.MobBehaviourType.class);
public boolean rune_renderable = false;
public int rune_natural_power_attack;
// One float[] per rune_naturalattacks entry. Same "attack info" struct as a weapon (verified in IDA, see
// MBDATA/docs/client-map.md): [0] attack speed (WEPSPEED), [1] range (MAXRANGE), [2] unknown int,
// [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_natural_attacks = new ArrayList<>();
public final HashMap<String, String> rune_sparse_data = new HashMap<>();
public final HashMap<String, int[]> rune_skill_adj = new HashMap<>();
public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
public final EnumSet<mbEnums.MonsterType> rune_not_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 int item_bane_rank;
public ItemTemplate(JSONObject jsonObject) {
try {
// Reading a String
obj_name = jsonObject.getString("obj_name");
// Reading a boolean
obj_pickable = jsonObject.getBoolean("obj_pickable");
// Reading floats from an array (note always check for empty arrays)
JSONArray scaleData = jsonObject.getJSONArray("obj_scale");
if (scaleData.isEmpty() == false)
obj_scale = new Vector3fImmutable(scaleData.getFloat(0), scaleData.getFloat(1),
scaleData.getFloat(2));
JSONArray forwardVector = jsonObject.getJSONArray("obj_forward_vector");
if (forwardVector.isEmpty() == false)
obj_forward_vector = new Vector3fImmutable(forwardVector.getFloat(0), forwardVector.getFloat(1),
forwardVector.getFloat(2));
JSONArray defaultAlignment = jsonObject.getJSONArray("obj_default_alignment");
if (defaultAlignment.isEmpty() == false)
obj_default_alignment = new Vector3fImmutable(defaultAlignment.getFloat(0), defaultAlignment.getFloat(1),
defaultAlignment.getFloat(2));
// Reading an integer value
obj_render_object = jsonObject.getInt("obj_render_object");
obj_icon = jsonObject.getInt("obj_icon");
JSONObject obj_sparse_json = jsonObject.getJSONObject("obj_sparse_data");
for (String sparseType : obj_sparse_json.keySet()) {
Object sparseValue = obj_sparse_json.get(sparseType);
obj_sparse_data.put(sparseType, sparseValue.toString());
}
// Banes are defined by their sparse data field
if (obj_sparse_data.get("ACTIONRESPONSE") != null)
if (obj_sparse_data.get("ACTIONRESPONSE").equals("4250517122"))
item_bane_rank = jsonObject.getInt("item_bane_rank");
// Reading float values
combat_health_current = jsonObject.getFloat("combat_health_current");
combat_health_full = jsonObject.getFloat("combat_health_full");
// Reading a hashmap of floats
JSONObject resist_json = jsonObject.getJSONObject("combat_attack_resist");
for (String resistType : resist_json.keySet()) {
float resistValue = resist_json.getFloat(resistType);
combat_attack_resist.put(resistType, resistValue);
}
// Parsing an enum
item_type = mbEnums.ItemType.valueOf(jsonObject.getString("item_type"));
item_eq_slots_value = jsonObject.getInt("item_eq_slots_value");
item_eq_slots_type = jsonObject.getBoolean("item_eq_slots_type");
// Parsing an enumset
JSONArray eq_slots_or = jsonObject.getJSONArray("item_eq_slots_or");
if (eq_slots_or.isEmpty() == false)
for (Object o : eq_slots_or)
item_eq_slots_or.add(mbEnums.EquipSlotType.valueOf((String) o));
JSONArray eq_slots_and = jsonObject.getJSONArray("item_eq_slots_and");
if (eq_slots_and.isEmpty() == false)
for (Object o : eq_slots_and)
item_eq_slots_and.add(mbEnums.EquipSlotType.valueOf((String) o));
item_takeable = jsonObject.getBoolean("item_takeable");
item_value = (jsonObject.getInt("item_value"));
item_wt = jsonObject.getInt("item_wt");
item_passive_defense_mod = jsonObject.getFloat("item_passive_defense_mod");
item_base_name = jsonObject.getString("item_base_name");
item_dsc = jsonObject.getString("item_dsc");
item_render_object_female = jsonObject.getInt("item_render_object_female");
item_health_full = jsonObject.getFloat("item_health_full");
Object skill_used = jsonObject.get("item_skill_used");
if (skill_used instanceof String) {
if (skill_used.equals("Cloth") == false)
item_skill_used = (String) skill_used;
}
Object mastery_used = jsonObject.get("item_skill_mastery_used");
if (mastery_used instanceof String)
item_skill_mastery_used = (String) mastery_used;
item_parry_anim_id = jsonObject.getInt("item_parry_anim_id");
// Reading offering data
JSONArray offering_data = jsonObject.getJSONArray("item_offering_info");
for (Object entry : offering_data) {
JSONObject offering_entry = (JSONObject) entry;
String offering_type = offering_entry.getString("offering_type").replaceAll("-", "");
int offering_value = offering_entry.getInt("offering_value");
item_offering_info.put(mbEnums.ShrineType.valueOf(offering_type), offering_value);
}
// Fields only present for ARMOR
if (item_type.equals(mbEnums.ItemType.ARMOR)) {
item_bulk_factor = jsonObject.getFloat("item_bulk_factor");
item_defense_rating = jsonObject.getInt("item_defense_rating");
}
// Fields only present for WEAPON
if (item_type.equals(mbEnums.ItemType.WEAPON)) {
JSONObject item_weapon = jsonObject.getJSONObject("item_weapon");
item_weapon_wepspeed = item_weapon.getFloat("weapon_wepspeed");
item_weapon_max_range = item_weapon.getFloat("weapon_max_range");
item_weapon_projectile_id = item_weapon.getInt("weapon_projectile_id");
item_weapon_projectile_speed = item_weapon.getFloat("weapon_projectile_speed");
item_weapon_combat_idle_anim = item_weapon.getInt("weapon_combat_idle_anim");
JSONArray weapon_damage = item_weapon.getJSONArray("weapon_damage");
if (weapon_damage.isEmpty() == false)
for (Object o : weapon_damage) {
JSONObject damage_entry = (JSONObject) o;
mbEnums.DamageType damageType = mbEnums.DamageType.getDamageType(damage_entry.getString("damage_type"));
int min = damage_entry.getInt("damage_min");
int max = damage_entry.getInt("damage_max");
int[] minMax = {min, max};
item_weapon_damage.put(damageType, minMax);
}
JSONArray attack_anim_right = item_weapon.getJSONArray("weapon_attack_anim_right");
if (attack_anim_right.isEmpty() == false)
for (Object o : attack_anim_right) {
JSONArray animationEntry = (JSONArray) o;
int animation = animationEntry.getInt(0);
int duration = animationEntry.getInt(1);
weapon_attack_anim_right.add(new int[]{animation, duration});
}
JSONArray attack_anim_left = item_weapon.getJSONArray("weapon_attack_anim_left");
if (attack_anim_left.isEmpty() == false)
for (Object o : attack_anim_left) {
JSONArray animationEntry = (JSONArray) o;
int animation = animationEntry.getInt(0);
int duration = animationEntry.getInt(1);
weapon_attack_anim_left.add(new int[]{animation, duration});
}
item_primary_attr = mbEnums.AttributeType.valueOf(jsonObject.getString("item_primary_attr"));
item_secondary_attr = mbEnums.AttributeType.valueOf(jsonObject.getString("item_secondary_attr"));
}
JSONArray itemflags = jsonObject.getJSONArray("item_flags");
if (itemflags.isEmpty() == false)
for (Object o : itemflags) {
String flag = (String) o;
if (flag.equals("None") == false)
item_flags.add(mbEnums.ItemFlags.valueOf((String) o));
}
JSONArray itemUseflags = jsonObject.getJSONArray("item_use_flags");
if (itemUseflags.isEmpty() == false)
for (Object o : itemUseflags)
item_use_flags.add(mbEnums.ItemUseFlags.valueOf((String) o));
item_initial_charges = jsonObject.getInt("item_initial_charges");
JSONArray skill_required = jsonObject.getJSONArray("item_skill_req");
if (skill_required.isEmpty() == false)
for (Object o : skill_required) {
JSONObject skill_req = (JSONObject) o;
String skill_type = skill_req.getString("skill_type");
int skill_level = skill_req.getInt("skill_level");
if (skill_type.equals("Cloth") == false)
item_skill_required.put(skill_type, skill_level);
}
JSONObject race_required = jsonObject.getJSONObject("item_race_req");
boolean restrict = race_required.getBoolean("restrict");
JSONArray races = race_required.getJSONArray("races");
if (races.isEmpty() == false)
for (Object o : races) {
String race = (String) o;
race = race.replaceAll("\\s", "");
race = race.replaceAll(",", "");
race = race.replaceAll("-", "");
if (restrict)
item_race_res.add(mbEnums.MonsterType.valueOf(race));
else
item_race_req.add(mbEnums.MonsterType.valueOf(race));
}
JSONObject class_required = jsonObject.getJSONObject("item_class_req");
restrict = class_required.getBoolean("restrict");
JSONArray classes = class_required.getJSONArray("classes");
if (classes.isEmpty() == false)
for (Object o : classes) {
String classEntry = (String) o;
classEntry = classEntry.replaceAll("\\s", "");
classEntry = classEntry.replaceAll(",", "");
if (restrict)
item_class_res.add(mbEnums.ClassType.valueOf(classEntry));
else
item_class_req.add(mbEnums.ClassType.valueOf(classEntry));
}
JSONObject disc_required = jsonObject.getJSONObject("item_disc_req");
restrict = disc_required.getBoolean("restrict");
JSONArray discs = disc_required.getJSONArray("discs");
if (discs.isEmpty() == false)
for (Object o : discs) {
String disc = (String) o;
disc = disc.replaceAll("\\s", "");
disc = disc.replaceAll(",", "");
if (restrict)
item_disc_res.add(mbEnums.DisciplineType.valueOf(disc));
else
item_disc_req.add(mbEnums.DisciplineType.valueOf(disc));
}
item_level_req = jsonObject.getInt("item_level_req");
item_sex_req = mbEnums.SexType.valueOf(jsonObject.getString("item_sex_req"));
JSONArray userPowerActions = jsonObject.getJSONArray("item_user_power_action");
if (userPowerActions.isEmpty() == false)
for (Object o : userPowerActions) {
JSONObject powerActionEntry = (JSONObject) o;
String power = powerActionEntry.getString("power");
JSONArray args = powerActionEntry.getJSONArray("arguments");
int[] arguments = {args.getInt(0), args.getInt(1)};
item_user_power_action.put(power, arguments);
}
JSONArray powerGrantsArray = jsonObject.getJSONArray("item_power_grant");
if (powerGrantsArray.isEmpty() == false) {
for (Object grantArrayEntry : powerGrantsArray) {
JSONObject powerGrantEntry = (JSONObject) grantArrayEntry;
String power_type = powerGrantEntry.getString("power_type");
int power_value = powerGrantEntry.getInt("power_value");
item_power_grant.put(power_type, power_value);
JSONArray skill_granted_array = powerGrantEntry.getJSONArray("power_granted_skills");
for (Object skillGrantEntry : skill_granted_array) {
JSONArray skill_entry = (JSONArray) skillGrantEntry;
String skill_type = skill_entry.getString(0);
int skill_level = skill_entry.getInt(1);
power_granted_skills.put(skill_type, skill_level);
}
}
}
JSONArray item_power_actions = jsonObject.getJSONArray("item_power_action");
if (item_power_actions.isEmpty() == false)
for (Object o : item_power_actions) {
JSONObject powerActionEntry = (JSONObject) o;
String power = powerActionEntry.getString("power_type");
JSONArray power_actions = powerActionEntry.getJSONArray("power_actions");
JSONObject argument_entry = power_actions.getJSONObject(0);
JSONArray power_args = argument_entry.getJSONArray("power_arguments");
int[] power_arguments = {power_args.getInt(0), power_args.getInt(1)};
item_power_action.put(power, power_arguments);
}
JSONArray resource_costs = jsonObject.getJSONArray("item_resource_costs");
if (resource_costs.isEmpty() == false)
for (Object o : resource_costs) {
JSONObject resource_entry = (JSONObject) o;
mbEnums.ResourceType resource_type = mbEnums.ResourceType.valueOf(resource_entry.getString("resource_type").toUpperCase());
int resource_value = resource_entry.getInt("resource_value");
item_resource_cost.put(resource_type, resource_value);
}
// Deed related fields
if (item_type.equals(mbEnums.ItemType.DEED)) {
deed_type = jsonObject.getInt("deed_type");
deed_furniture_id = jsonObject.getInt("deed_furniture_id");
deed_target_id = jsonObject.getInt("deed_target_id");
deed_employment = jsonObject.getInt("deed_employment");
deed_start_rank = jsonObject.getInt("deed_start_rank");
deed_name_lookup = jsonObject.getInt("deed_name_lookup");
deed_indoors = jsonObject.getBoolean("deed_indoors");
deed_is_fortress = jsonObject.getBoolean("deed_is_fortress");
deed_namelookup_val = jsonObject.getFloat("deed_namelookup_val");
deed_custom_city = jsonObject.getBoolean("deed_custom_city");
deed_structure_id = jsonObject.getInt("deed_structure_id");
}
if (item_type.equals(mbEnums.ItemType.RUNE)) {
rune_type = jsonObject.getString("rune_type");
Object subType = jsonObject.get("rune_sub_type");
if (subType instanceof String)
rune_sub_type = (String) subType;
rune_is_standard_character_creation = jsonObject.getBoolean("rune_is_standard_character_creation");
rune_creation_cost = jsonObject.getInt("rune_creation_cost");
rune_rank = jsonObject.getInt("rune_rank");
rune_pracs_per_level = jsonObject.getInt("rune_pracs_per_level");
rune_exp_req_to_level = jsonObject.getFloat("rune_exp_req_to_level");
rune_sex = mbEnums.SexType.valueOf(jsonObject.getString("rune_sex"));
rune_class_icon = jsonObject.getInt("rune_class_icon");
rune_health = jsonObject.getInt("rune_health");
rune_mana = jsonObject.getInt("rune_mana");
rune_stamina = jsonObject.getInt("rune_stamina");
rune_min_damage = jsonObject.getFloat("rune_min_damage");
rune_max_damage = jsonObject.getFloat("rune_max_damage");
rune_attack = jsonObject.getInt("rune_attack");
rune_defense = jsonObject.getInt("rune_defense");
rune_level = jsonObject.getInt("rune_level");
JSONObject rune_speed_json = jsonObject.getJSONObject("rune_speed");
for (String key : rune_speed_json.keySet()) {
mbEnums.MovementType movementType = mbEnums.MovementType.valueOf(key);
float speed = rune_speed_json.getFloat(key);
rune_speed.put(movementType, speed);
}
JSONObject rune_group = jsonObject.getJSONObject("rune_group");
rune_group_type = rune_group.getInt("group_type");
rune_group_is_faction = rune_group.getBoolean("group_is_faction");
rune_group_is_guild = rune_group.getBoolean("group_is_guild");
rune_dsc = jsonObject.getString("rune_dsc");
rune_fx_txt = jsonObject.getString("rune_fx_txt");
long group_tactics_bitvector = jsonObject.getLong("rune_group_tactics");
rune_group_tactics.addAll(mbEnums.fromLong(group_tactics_bitvector, mbEnums.MobBehaviourType.class));
long group_role_bitvector = jsonObject.getLong("rune_group_role_set");
rune_group_role_set.addAll(mbEnums.fromLong(group_role_bitvector, mbEnums.MobBehaviourType.class));
JSONArray enemy_types_json = jsonObject.getJSONArray("rune_enemy_monster_types");
for (Object o : enemy_types_json) {
String enemy = (String) o;
enemy = enemy.replaceAll("-", "");
mbEnums.MonsterType monsterType = mbEnums.MonsterType.valueOf(enemy);
rune_enemy_monster_types.add(monsterType);
}
JSONArray not_enemy_types_json = jsonObject.getJSONArray("rune_not_enemy_monster_types");
for (Object o : not_enemy_types_json) {
String notenemy = (String) o;
notenemy = notenemy.replaceAll("-", "");
mbEnums.MonsterType monsterType = mbEnums.MonsterType.valueOf(notenemy);
rune_not_enemy_monster_types.add(monsterType);
}
JSONArray groupee_types_json = jsonObject.getJSONArray("rune_groupee_monster_types");
for (Object o : groupee_types_json) {
int groupeeId = (int) o;
rune_groupee_monster_types.add(groupeeId);
}
JSONArray helper_types_json = jsonObject.getJSONArray("rune_helper_monster_types");
for (Object o : helper_types_json) {
int helperId = (int) o;
rune_helper_monster_types.add(helperId);
}
rune_renderable = jsonObject.getBoolean("rune_renderable");
rune_natural_power_attack = jsonObject.getInt("rune_natural_power_attack");
if (jsonObject.has("rune_naturalattacks")) {
JSONArray natural_attacks_json = jsonObject.getJSONArray("rune_naturalattacks");
for (Object attackEntry : natural_attacks_json) {
JSONArray attackArray = (JSONArray) attackEntry;
float[] attack = new float[attackArray.length()];
for (int i = 0; i < attackArray.length(); i++)
attack[i] = attackArray.getFloat(i);
rune_natural_attacks.add(attack);
}
}
JSONObject rune_sparse_json = jsonObject.getJSONObject("rune_sparse_data");
for (String sparseType : rune_sparse_json.keySet()) {
Object sparseValue = rune_sparse_json.get(sparseType);
rune_sparse_data.put(sparseType, sparseValue.toString());
}
JSONArray skill_adj_json = (JSONArray) jsonObject.get("rune_skill_adj");
for (Object skillEntry : skill_adj_json) {
JSONObject skill = (JSONObject) skillEntry;
String typeString = skill.getString("skill_type");
JSONArray adjArray = skill.getJSONArray("skill_adjusts");
for (Object adjustList : adjArray) {
JSONArray adjustEntry = (JSONArray) adjustList;
int[] adjust = {adjustEntry.getInt(0), adjustEntry.getInt(1)};
rune_skill_adj.put(typeString, adjust);
}
}
JSONArray attr_adj_json = jsonObject.getJSONArray("rune_attr_adj");
for (Object attributeEntry : attr_adj_json) {
JSONObject attribute = (JSONObject) attributeEntry;
String typeString = attribute.getString("attr_type");
mbEnums.AttributeType attributeType = mbEnums.AttributeType.valueOf(typeString);
int attributeValue = attribute.getInt("attr_value");
rune_attr_adj.put(attributeType, attributeValue);
}
JSONArray max_attr_adj_json = jsonObject.getJSONArray("rune_max_attr_adj");
for (Object attributeEntry : max_attr_adj_json) {
JSONObject attribute = (JSONObject) attributeEntry;
String typeString = attribute.getString("attr_type");
mbEnums.AttributeType attributeType = mbEnums.AttributeType.valueOf(typeString);
int attributeValue = attribute.getInt("attr_value");
rune_max_attr_adj.put(attributeType, attributeValue);
}
JSONArray skill_grant_json = jsonObject.getJSONArray("rune_skill_grant");
if (skill_grant_json.isEmpty() == false)
for (Object runeSkillEntry : skill_grant_json) {
JSONObject skill_granted = (JSONObject) runeSkillEntry;
String typeString = skill_granted.getString("skill_type");
int skill_level = skill_granted.getInt("skill_value");
rune_skill_grant.put(typeString, skill_level);
JSONArray skill_granted_array = skill_granted.getJSONArray("skill_granted_skills");
for (Object skillGrantEntry : skill_granted_array) {
JSONArray skill_entry = (JSONArray) skillGrantEntry;
typeString = skill_entry.getString(0);
skill_level = skill_entry.getInt(1);
skill_granted_skills.put(typeString, skill_level);
}
}
}
} catch (Exception e) {
Logger.error(obj_name + ":" + e);
}
}
}
+26 -2
View File
@@ -436,6 +436,30 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
return mobile; 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) { public static synchronized Mob createGuardMinion(Mob guardCaptain, short level, String minionName) {
Mob minionMobile; Mob minionMobile;
@@ -455,7 +479,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
minionMobile.deathTime = System.currentTimeMillis(); minionMobile.deathTime = System.currentTimeMillis();
minionMobile.guardCaptain = guardCaptain; 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.behaviourType = mbEnums.MobBehaviourType.GuardMinion;
minionMobile.agentType = AIAgentType.GUARDMINION; minionMobile.agentType = AIAgentType.GUARDMINION;
minionMobile.guardedCity = guardCaptain.guardedCity; minionMobile.guardedCity = guardCaptain.guardedCity;
@@ -514,7 +538,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
siegeMinion.behaviourType = MobBehaviourType.SiegeEngine; siegeMinion.behaviourType = MobBehaviourType.SiegeEngine;
siegeMinion.agentType = AIAgentType.SIEGEENGINE; siegeMinion.agentType = AIAgentType.SIEGEENGINE;
siegeMinion.bindLoc = Vector3fImmutable.ZERO; siegeMinion.bindLoc = Vector3fImmutable.ZERO;
siegeMinion.spawnDelay = (60 * 15); siegeMinion.spawnDelay = minionSpawnDelay(60 * 15);
siegeMinion.runAfterLoad(); siegeMinion.runAfterLoad();
+5 -1
View File
@@ -127,9 +127,13 @@ public class MobBase extends AbstractGameObject {
for (BootySetEntry equipmentSetEntry : equipList) { for (BootySetEntry equipmentSetEntry : equipList) {
Item item = new Item(equipmentSetEntry.templateID); Item item = new Item(equipmentSetEntry.templateID);
if (item.template.getItemEqSlotsOr().isEmpty())
continue;
item.objectUUID = ItemManager.lastNegativeID.decrementAndGet(); item.objectUUID = ItemManager.lastNegativeID.decrementAndGet();
item.drop_chance = equipmentSetEntry.dropChance; item.drop_chance = equipmentSetEntry.dropChance;
item.equipSlot = item.template.item_eq_slots_or.iterator().next(); item.equipSlot = item.template.getItemEqSlotsOr().iterator().next();
equip.put(item.equipSlot, item); equip.put(item.equipSlot, item);
} }
+7 -6
View File
@@ -15,6 +15,7 @@ import engine.gameManager.PowersManager;
import engine.mbEnums; import engine.mbEnums;
import engine.mbEnums.ItemType; import engine.mbEnums.ItemType;
import engine.mbEnums.OwnerType; import engine.mbEnums.OwnerType;
import engine.parsers.objects.CObjectTemplate;
import engine.powers.poweractions.AbstractPowerAction; import engine.powers.poweractions.AbstractPowerAction;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -30,22 +31,22 @@ public final class MobLoot extends Item {
private String prefix = ""; private String prefix = "";
private String suffix = ""; private String suffix = "";
public MobLoot(AbstractCharacter mob, ItemTemplate template, boolean noSteal) { public MobLoot(AbstractCharacter mob, CObjectTemplate template, boolean noSteal) {
this(mob, template, 0, noSteal); this(mob, template, 0, noSteal);
} }
public MobLoot(AbstractCharacter mob, int qtyOfGold) { public MobLoot(AbstractCharacter mob, int qtyOfGold) {
this(mob, ItemTemplate.templates.get(mbEnums.ResourceType.GOLD.templateID), qtyOfGold, false); this(mob, CObjectTemplate.lookup(mbEnums.ResourceType.GOLD.templateID), qtyOfGold, false);
} }
public MobLoot(AbstractCharacter mob, ItemTemplate template, int quantity, boolean noSteal) { public MobLoot(AbstractCharacter mob, CObjectTemplate template, int quantity, boolean noSteal) {
super(template.template_id); super(template.getTemplateId());
this.ownerType = OwnerType.Mob; this.ownerType = OwnerType.Mob;
this.ownerID = mob.getObjectUUID(); this.ownerID = mob.getObjectUUID();
this.objectUUID = generateId(); this.objectUUID = generateId();
if (quantity == 0 && template.item_type == ItemType.RESOURCE) if (quantity == 0 && template.getItemType() == ItemType.RESOURCE)
quantity = 1; quantity = 1;
if (quantity > 0) if (quantity > 0)
@@ -105,7 +106,7 @@ public final class MobLoot extends Item {
if (isDeleted) if (isDeleted)
return null; return null;
if (this.template.item_type.equals(ItemType.GOLD)) if (this.template.getItemType().equals(ItemType.GOLD))
return null; return null;
Item item = this; Item item = this;
+6 -5
View File
@@ -23,6 +23,7 @@ import engine.net.ByteBufferWriter;
import engine.net.Dispatch; import engine.net.Dispatch;
import engine.net.client.msg.PetMsg; import engine.net.client.msg.PetMsg;
import engine.net.client.msg.PlaceAssetMsg; import engine.net.client.msg.PlaceAssetMsg;
import engine.parsers.objects.CObjectTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -961,15 +962,15 @@ public class NPC extends AbstractCharacter {
maxSkill = 110; maxSkill = 110;
break; break;
} }
ItemTemplate itemTemplate; CObjectTemplate itemTemplate;
int skillRequired = 0; int skillRequired = 0;
for (Integer itemID : fullItemList) { for (Integer itemID : fullItemList) {
itemTemplate = ItemTemplate.templates.get(itemID); itemTemplate = CObjectTemplate.lookup(itemID);
if (itemTemplate.item_skill_required.values().iterator().hasNext()) if (itemTemplate.getItemSkillReq().values().iterator().hasNext())
skillRequired = itemTemplate.item_skill_required.values().iterator().next(); skillRequired = itemTemplate.getItemSkillReq().values().iterator().next();
boolean exclude = skillRequired == 0 && ItemTemplate.templates.get(itemID).item_type.equals(ItemType.WEAPON); boolean exclude = skillRequired == 0 && CObjectTemplate.lookup(itemID).getItemType().equals(ItemType.WEAPON);
if (skillRequired <= maxSkill && !exclude) if (skillRequired <= maxSkill && !exclude)
filteredItemList.add(itemID); filteredItemList.add(itemID);
} }
+12 -11
View File
@@ -32,6 +32,7 @@ import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.*; import engine.net.client.msg.*;
import engine.net.client.msg.login.CommitNewCharacterMsg; import engine.net.client.msg.login.CommitNewCharacterMsg;
import engine.parsers.objects.CObjectTemplate;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.server.login.LoginServer; import engine.server.login.LoginServer;
@@ -1353,10 +1354,10 @@ public class PlayerCharacter extends AbstractCharacter {
public static float getDexPenalty(Item armor) { public static float getDexPenalty(Item armor) {
if (armor == null) if (armor == null)
return 0f; return 0f;
ItemTemplate ab = armor.template; CObjectTemplate ab = armor.template;
if (ab == null) if (ab == null)
return 0f; 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) { public static void InitializeSkillsOnLoad(PlayerCharacter pc) {
@@ -2816,15 +2817,15 @@ public class PlayerCharacter extends AbstractCharacter {
if (item == null) if (item == null)
return false; return false;
ItemTemplate ib = item.template; CObjectTemplate ib = item.template;
if (ib != null) { if (ib != null) {
if ((item.template.item_type.equals(ItemType.WEAPON)) if ((item.template.getItemType().equals(ItemType.WEAPON))
&& (item.template.item_skill_used.equals(type) || ib.item_skill_mastery_used.equals(type))) && (item.template.getItemSkillUsed().equals(type) || ib.getItemSkillMasteryUsed().equals(type)))
return true; return true;
return (item.template.item_type.equals(ItemType.ARMOR)) return (item.template.getItemType().equals(ItemType.ARMOR))
&& (item.template.item_skill_used.equals(type)); && (item.template.getItemSkillUsed().equals(type));
} }
return false; return false;
@@ -3768,8 +3769,8 @@ public class PlayerCharacter extends AbstractCharacter {
ConcurrentHashMap<EquipSlotType, Item> equipped = this.charItemManager.getEquipped(); ConcurrentHashMap<EquipSlotType, Item> equipped = this.charItemManager.getEquipped();
Item off = equipped.get(EquipSlotType.LHELD); Item off = equipped.get(EquipSlotType.LHELD);
Item main = equipped.get(EquipSlotType.RHELD); Item main = equipped.get(EquipSlotType.RHELD);
ItemTemplate wbMain = null; CObjectTemplate wbMain = null;
ItemTemplate wbOff = null; CObjectTemplate wbOff = null;
if (main != null) if (main != null)
wbMain = main.template; wbMain = main.template;
if (off != null) if (off != null)
@@ -3791,8 +3792,8 @@ public class PlayerCharacter extends AbstractCharacter {
this.bonuses.setBool(ModType.Parry, SourceType.None, false); this.bonuses.setBool(ModType.Parry, SourceType.None, false);
if ((this.baseClass != null && this.baseClass.getObjectUUID() == 2500) if ((this.baseClass != null && this.baseClass.getObjectUUID() == 2500)
|| (this.promotionClass != null && this.promotionClass.getObjectUUID() == 2520)) || (this.promotionClass != null && this.promotionClass.getObjectUUID() == 2520))
if (wbMain == null || wbMain.item_weapon_max_range < MBServerStatics.RANGED_WEAPON_RANGE) if (wbMain == null || engine.parsers.objects.ItemTemplate.getWeapon(wbMain.getTemplateId()).weapon_max_range < MBServerStatics.RANGED_WEAPON_RANGE)
if (wbOff == null || wbOff.item_weapon_max_range < MBServerStatics.RANGED_WEAPON_RANGE) if (wbOff == null || engine.parsers.objects.ItemTemplate.getWeapon(wbOff.getTemplateId()).weapon_max_range < MBServerStatics.RANGED_WEAPON_RANGE)
this.bonuses.setBool(ModType.Parry, SourceType.None, true); this.bonuses.setBool(ModType.Parry, SourceType.None, true);
} }
+78
View File
@@ -10,8 +10,13 @@
package engine.objects; package engine.objects;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.net.ByteBufferWriter; import engine.net.ByteBufferWriter;
import engine.parsers.cache.CObjectIndex;
import engine.parsers.objects.RuneTemplate;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -103,6 +108,79 @@ public class PromotionClass extends AbstractGameObject {
} }
/**
* Rune Template Constructor
*/
public PromotionClass(RuneTemplate rune) {
super(rune.template_id);
this.name = rune.obj_name;
this.description = rune.rune_dsc;
this.token = Hasher.SBStringHash(rune.rune_sub_type);
this.healthMod = rune.rune_health;
this.manaMod = rune.rune_mana;
this.staminaMod = rune.rune_stamina;
this.allowedRunes = getAllowedRuneTokens(rune);
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.getObjectUUID());
this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID());
this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.getObjectUUID());
this.effectsGrantedFighter = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2500);
this.effectsGrantedHealer = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2501);
this.effectsGrantedRogue = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2502);
this.effectsGrantedMage = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2503);
this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID());
}
// Promotions are the CLASS runes that require a base class; this skips the creation classes and Pet 2522.
public static boolean isPromotionRune(RuneTemplate rune) {
return rune.rune_type == mbEnums.RuneType.CLASS && !rune.rune_is_standard_character_creation
&& !rune.item_class_req.isEmpty();
}
// Race and base class tokens are SBStringHash(rune_sub_type) of the runes named in the req lists.
// Every promotion rune's lists are whitelists (restrict false).
private static ArrayList<Integer> getAllowedRuneTokens(RuneTemplate rune) {
ArrayList<Integer> tokens = new ArrayList<>();
Integer classRuneId;
int token;
for (mbEnums.MonsterType race : rune.item_race_req)
for (int raceRuneId : CObjectIndex.getRaceRuneIds(race)) {
token = Hasher.SBStringHash(RuneTemplate._runes.get(raceRuneId).rune_sub_type);
if (!tokens.contains(token))
tokens.add(token);
}
for (mbEnums.ClassType classType : rune.item_class_req) {
classRuneId = CObjectIndex.getClassRuneId(classType);
if (classRuneId != null)
tokens.add(Hasher.SBStringHash(RuneTemplate._runes.get(classRuneId).rune_sub_type));
}
return tokens;
}
public static void LoadAllPromotions() {
int loadedCount = 0;
for (RuneTemplate rune : RuneTemplate._runes.values()) {
if (!isPromotionRune(rune))
continue;
DbManager.addToCache(new PromotionClass(rune));
loadedCount++;
}
Logger.info("Loaded " + loadedCount + " promotion classes from runes");
// The CSR/admin rows 2901+ have no promotion rune; the DB loader skips ids already cached
DbManager.PromotionQueries.GET_ALL_PROMOTIONS();
}
public static void serializeForClientMsg(PromotionClass promotionClass, ByteBufferWriter writer) { public static void serializeForClientMsg(PromotionClass promotionClass, ByteBufferWriter writer) {
writer.putInt(3); // For BaseClass writer.putInt(3); // For BaseClass
writer.putInt(0); // Pad writer.putInt(0); // Pad
+106 -1
View File
@@ -13,7 +13,11 @@ import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.mbEnums.RaceType; import engine.mbEnums.RaceType;
import engine.net.ByteBufferWriter; import engine.net.ByteBufferWriter;
import engine.parsers.cache.CacheParserUtil;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -27,6 +31,9 @@ public class Race {
private static ConcurrentHashMap<Integer, Race> _raceByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private static ConcurrentHashMap<Integer, Race> _raceByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
// Creation points before the race rune's rune_creation_cost is taken off
private static final int STARTING_POINT_POOL = 30;
private final String name; private final String name;
private final String description; private final String description;
private final int raceRuneID; private final int raceRuneID;
@@ -107,8 +114,106 @@ public class Race {
} }
/**
* Rune Template Constructor
* minHeight and strHeightMod are server-only and come from the static_rune_race row.
*/
public Race(RuneTemplate rune, Race dbRace) {
this.raceRuneID = rune.template_id;
this.raceType = mbEnums.RaceType.getRaceTypebyRuneID(raceRuneID);
this.name = rune.obj_name;
this.description = rune.rune_dsc;
this.strStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Strength, 0).shortValue();
this.strMin = 0;
this.strMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Strength, 0).shortValue();
this.dexStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Dexterity, 0).shortValue();
this.dexMin = 0;
this.dexMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Dexterity, 0).shortValue();
this.conStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Constitution, 0).shortValue();
this.conMin = 0;
this.conMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Constitution, 0).shortValue();
this.intStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Intelligence, 0).shortValue();
this.intMin = 0;
this.intMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Intelligence, 0).shortValue();
this.spiStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Spirit, 0).shortValue();
this.spiMin = 0;
this.spiMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Spirit, 0).shortValue();
this.token = Hasher.SBStringHash(rune.rune_sub_type);
this.healthBonus = (short) rune.rune_health;
this.manaBonus = (short) rune.rune_mana;
this.staminaBonus = (short) rune.rune_stamina;
this.startingPoints = (byte) (STARTING_POINT_POOL - rune.rune_creation_cost);
this.minHeight = dbRace.minHeight;
this.strHeightMod = dbRace.strHeightMod;
this.hairStyles = new HashSet<>(rune.rune_hair);
this.beardStyles = new HashSet<>(rune.rune_beard);
this.skinColors = DbManager.RaceQueries.SKIN_COLOR_FOR_RACE(raceRuneID);
this.beardColors = DbManager.RaceQueries.BEARD_COLORS_FOR_RACE(raceRuneID);
this.hairColors = DbManager.RaceQueries.HAIR_COLORS_FOR_RACE(raceRuneID);
this.baseClasses = getBaseClassesForRace(rune);
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(raceRuneID);
this.powersGranted = PowerReq.getPowerReqsForRune(raceRuneID);
this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.raceRuneID);
// Races without hair styles (Aracoix, Shade) select style 0, which creation validates
if (this.hairStyles.isEmpty())
this.hairStyles.add(0);
}
// Base classes are the creation class runes whose item_race_req names this race's sub type.
// Every creation class rune's race list is a whitelist (restrict false).
private static ArrayList<BaseClass> getBaseClassesForRace(RuneTemplate rune) {
ArrayList<BaseClass> baseClasses = new ArrayList<>();
mbEnums.MonsterType monsterType;
BaseClass baseClass;
monsterType = CacheParserUtil.readEnum(mbEnums.MonsterType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type");
for (RuneTemplate classRune : RuneTemplate._runes.values()) {
if (classRune.rune_type != mbEnums.RuneType.CLASS || !classRune.rune_is_standard_character_creation)
continue;
if (!classRune.item_race_req.contains(monsterType))
continue;
baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(classRune.template_id);
if (baseClass != null)
baseClasses.add(baseClass);
}
return baseClasses;
}
// Iterates the static_rune_race rows: no rune field separates 2018-2021 from the mob race runes,
// and each row still holds the server-only heights.
public static void loadAllRaces() { public static void loadAllRaces() {
Race._raceByID = DbManager.RaceQueries.LOAD_ALL_RACES();
ConcurrentHashMap<Integer, Race> dbRaces;
ConcurrentHashMap<Integer, Race> races;
RuneTemplate rune;
dbRaces = DbManager.RaceQueries.LOAD_ALL_RACES();
races = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
for (Race dbRace : dbRaces.values()) {
rune = RuneTemplate._runes.get(dbRace.raceRuneID);
if (rune == null || rune.rune_type != mbEnums.RuneType.RACE) {
Logger.warn("Race " + dbRace.raceRuneID + " has no race rune; loaded from static_rune_race");
races.put(dbRace.raceRuneID, dbRace);
continue;
}
races.put(dbRace.raceRuneID, new Race(rune, dbRace));
}
Race._raceByID = races;
Logger.info("Loaded " + races.size() + " races from runes");
} }
public static Race getRace(int id) { public static Race getRace(int id) {
+8 -4
View File
@@ -19,6 +19,7 @@ import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.EnumMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -159,13 +160,16 @@ public class Resists {
private static float[] getArmorResists(Item armor, float[] phys) { private static float[] getArmorResists(Item armor, float[] phys) {
EnumMap<mbEnums.DamageType, Float> armorResists;
if (armor == null) if (armor == null)
return phys; return phys;
if (armor.template.item_type.equals(mbEnums.ItemType.ARMOR)) { if (armor.template.getItemType().equals(mbEnums.ItemType.ARMOR)) {
phys[0] += armor.template.combat_attack_resist.get("SLASHING"); armorResists = armor.template.getCombatAttackResist();
phys[1] += armor.template.combat_attack_resist.get("CRUSHING"); phys[0] += armorResists.getOrDefault(mbEnums.DamageType.SLASHING, 0f);
phys[2] += armor.template.combat_attack_resist.get("PIERCING"); phys[1] += armorResists.getOrDefault(mbEnums.DamageType.CRUSHING, 0f);
phys[2] += armorResists.getOrDefault(mbEnums.DamageType.PIERCING, 0f);
} }
return phys; return phys;
+139 -1
View File
@@ -12,7 +12,11 @@ package engine.objects;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.net.ByteBufferWriter; import engine.net.ByteBufferWriter;
import engine.parsers.cache.CObjectIndex;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -36,6 +40,12 @@ public class RuneBase extends AbstractGameObject {
private final ArrayList<Integer> overwrite = new ArrayList<>(); private final ArrayList<Integer> overwrite = new ArrayList<>();
private int levelRequired = 1; private int levelRequired = 1;
private ArrayList<MobBaseEffects> effectsList = new ArrayList<>(); private ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
private mbEnums.RuneType runeType;
private int subTypeToken;
private int rank;
private boolean raceRestrict;
private boolean classRestrict;
private boolean discRestrict;
/** /**
* No Table ID Constructor * No Table ID Constructor
@@ -74,10 +84,82 @@ public class RuneBase extends AbstractGameObject {
this.type = rs.getInt("type"); this.type = rs.getInt("type");
this.subtype = rs.getByte("subtype"); this.subtype = rs.getByte("subtype");
// static_rune_runereq race/class rows are whitelists, its discipline rows prohibitions
this.discRestrict = true;
DbManager.RuneBaseQueries.GET_RUNE_REQS(this); DbManager.RuneBaseQueries.GET_RUNE_REQS(this);
this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()); this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID());
} }
/**
* Rune Template Constructor
* type and subtype are server enums that don't map cleanly from rune_type/rune_sub_type, so they
* come from the static_rune_runebase row, as do the effects until 2.10.
* Talents with the same sub type token replace each other by rank (client 0x4695D0), so no overwrite list.
*/
public RuneBase(RuneTemplate rune, RuneBase dbRuneBase) {
super(rune.template_id);
Integer classRuneID;
Integer discRuneID;
ArrayList<Integer> raceRuneIDs;
RuneTemplate classRune;
this.name = rune.item_base_name;
this.description = rune.rune_dsc;
this.type = dbRuneBase.type;
this.subtype = dbRuneBase.subtype;
this.effectsList = dbRuneBase.effectsList;
this.runeType = rune.rune_type;
this.rank = rune.rune_rank;
this.levelRequired = rune.item_level_req;
this.raceRestrict = rune.item_race_req_restrict;
this.classRestrict = rune.item_class_req_restrict;
this.discRestrict = rune.item_disc_req_restrict;
for (mbEnums.MonsterType raceType : rune.item_race_req) {
raceRuneIDs = CObjectIndex.getRaceRuneIds(raceType);
if (raceRuneIDs.isEmpty())
Logger.warn("RuneBase " + rune.template_id + ": race " + raceType + " has no race rune");
for (int raceRuneID : raceRuneIDs)
this.race.put(raceRuneID, true);
}
for (mbEnums.ClassType classType : rune.item_class_req) {
classRuneID = CObjectIndex.getClassRuneId(classType);
if (classRuneID == null) {
Logger.warn("RuneBase " + rune.template_id + ": class " + classType + " has no class rune");
continue;
}
classRune = RuneTemplate._runes.get(classRuneID);
if (classRune.rune_is_standard_character_creation)
this.baseClass.put(classRuneID, true);
else
this.promotionClass.put(classRuneID, true);
}
for (mbEnums.DisciplineType discType : rune.item_disc_req) {
discRuneID = CObjectIndex.getDisciplineRuneId(discType);
if (discRuneID == null) {
Logger.warn("RuneBase " + rune.template_id + ": discipline " + discType + " has no discipline rune");
continue;
}
this.discipline.put(discRuneID, true);
}
// Same token as CObjectIndex: unresolved sub types are stored as their hash
if (!rune.rune_sub_type.isEmpty())
this.subTypeToken = Hasher.SBStringHash(rune.rune_sub_type);
else
this.subTypeToken = rune.rune_sub_type_hash;
}
public static RuneBase getRuneBase(int tableId) { public static RuneBase getRuneBase(int tableId) {
if (tableId == 0) if (tableId == 0)
@@ -100,9 +182,39 @@ public class RuneBase extends AbstractGameObject {
} }
// Iterates the static_rune_runebase rows: no rune field separates the player runes from the mob
// talents, and each row still holds the server type/subtype.
public static void LoadAllRuneBases() { public static void LoadAllRuneBases() {
DbManager.RuneBaseQueries.LOAD_ALL_RUNEBASES(); ArrayList<RuneBase> dbRuneBases;
ArrayList<RuneBaseAttribute> attributes;
RuneTemplate rune;
int loadedCount = 0;
dbRuneBases = DbManager.RuneBaseQueries.LOAD_ALL_RUNEBASES();
RuneBaseAttribute.runeBaseAttributeMap.clear();
for (RuneBase dbRuneBase : dbRuneBases) {
rune = RuneTemplate._runes.get(dbRuneBase.getObjectUUID());
if (rune == null) {
Logger.warn("RuneBase " + dbRuneBase.getObjectUUID() + " has no rune; loaded from static_rune_runebase without attributes");
continue;
}
DbManager.addToCache(new RuneBase(rune, dbRuneBase));
attributes = RuneBaseAttribute.getAttributesForRune(rune);
// getAttrs() returns null for a rune without attributes, as the DB map did
if (!attributes.isEmpty())
RuneBaseAttribute.runeBaseAttributeMap.put(rune.template_id, attributes);
loadedCount++;
}
Logger.info("Loaded " + loadedCount + " rune bases from runes");
RuneBase.AllowedBaseClassRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_BASECLASS(); RuneBase.AllowedBaseClassRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_BASECLASS();
RuneBase.AllowedRaceRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_RACE(); RuneBase.AllowedRaceRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_RACE();
} }
@@ -188,6 +300,32 @@ public class RuneBase extends AbstractGameObject {
return this.overwrite; return this.overwrite;
} }
public mbEnums.RuneType getRuneType() {
return this.runeType;
}
// 0 when the rune has no sub type; talents sharing a non-zero token replace each other
public int getSubTypeToken() {
return this.subTypeToken;
}
public int getRank() {
return this.rank;
}
// restrict true: the list is a blacklist; false: a whitelist
public boolean isRaceRestrict() {
return this.raceRestrict;
}
public boolean isClassRestrict() {
return this.classRestrict;
}
public boolean isDiscRestrict() {
return this.discRestrict;
}
/* /*
* Serializing * Serializing
*/ */
+31
View File
@@ -10,11 +10,15 @@
package engine.objects; package engine.objects;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
@@ -57,6 +61,33 @@ public class RuneBaseAttribute extends AbstractGameObject {
this.runeBaseID = rs.getInt("RuneBaseID"); this.runeBaseID = rs.getInt("RuneBaseID");
} }
// Attribute ids: 0 = rune_creation_cost, 1-5 = rune_attr_adj, 6-10 = rune_max_attr_adj,
// 11-15 = item_attr_req; the AttributeType ordinals Strength..Spirit are 1-5.
public static ArrayList<RuneBaseAttribute> getAttributesForRune(RuneTemplate rune) {
ArrayList<RuneBaseAttribute> attributes = new ArrayList<>();
if (rune.rune_creation_cost != 0)
attributes.add(new RuneBaseAttribute((short) MBServerStatics.RUNE_COST_ATTRIBUTE_ID, (short) rune.rune_creation_cost));
addAttributes(attributes, rune.rune_attr_adj, 0);
addAttributes(attributes, rune.rune_max_attr_adj, 5);
addAttributes(attributes, rune.item_attr_req, 10);
return attributes;
}
private static void addAttributes(ArrayList<RuneBaseAttribute> attributes, EnumMap<mbEnums.AttributeType, Integer> values, int idOffset) {
for (mbEnums.AttributeType attributeType : values.keySet()) {
if (values.get(attributeType) == 0)
continue;
attributes.add(new RuneBaseAttribute((short) (attributeType.ordinal() + idOffset), values.get(attributeType).shortValue()));
}
}
public static void LoadAllAttributes() { public static void LoadAllAttributes() {
DbManager.RuneBaseAttributeQueries.GET_ATTRIBUTES_FOR_RUNEBASE(); DbManager.RuneBaseAttributeQueries.GET_ATTRIBUTES_FOR_RUNEBASE();
+2 -2
View File
@@ -256,13 +256,13 @@ public class Shrine extends AbstractWorldObject implements Comparable<Shrine> {
if (!boonOwner.charItemManager.doesCharOwnThisItem(boonItem.getObjectUUID())) if (!boonOwner.charItemManager.doesCharOwnThisItem(boonItem.getObjectUUID()))
return false; return false;
for (ShrineType boonShrineType : boonItem.template.item_offering_info.keySet()) { for (ShrineType boonShrineType : boonItem.template.getItemOfferingInfo().keySet()) {
if (boonShrineType != shrineType) if (boonShrineType != shrineType)
continue; continue;
//Same Shrine Type, add favors and stop loop. //Same Shrine Type, add favors and stop loop.
int amount = boonItem.template.item_offering_info.get(boonShrineType) * boonItem.getNumOfItems(); int amount = boonItem.template.getItemOfferingInfo().get(boonShrineType) * boonItem.getNumOfItems();
int oldAmount = this.favors; int oldAmount = this.favors;
if (!DbManager.ShrineQueries.updateFavors(this, this.favors + amount, oldAmount)) { if (!DbManager.ShrineQueries.updateFavors(this, this.favors + amount, oldAmount)) {
+10 -6
View File
@@ -17,6 +17,7 @@ import engine.mbEnums;
import engine.net.Dispatch; import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.*; import engine.net.client.msg.*;
import engine.parsers.objects.CObjectTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.json.JSONArray; import org.json.JSONArray;
@@ -31,6 +32,9 @@ import java.util.concurrent.ConcurrentHashMap;
public class Warehouse { 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 EnumSet<mbEnums.ResourceType> locked = EnumSet.noneOf(mbEnums.ResourceType.class);
public Building building; public Building building;
public City city; public City city;
@@ -301,10 +305,10 @@ public class Warehouse {
return false; return false;
} }
if (resource.template.item_type.equals(mbEnums.ItemType.GOLD)) if (resource.template.getItemType().equals(mbEnums.ItemType.GOLD))
resourceType = mbEnums.ResourceType.GOLD; resourceType = mbEnums.ResourceType.GOLD;
else else
resourceType = mbEnums.ResourceType.valueOf(ItemTemplate.templates.get(resource.templateID).item_base_name.toUpperCase()); resourceType = mbEnums.ResourceType.valueOf(CObjectTemplate.lookup(resource.templateID).getItemBaseName().toUpperCase());
if (transaction) if (transaction)
AddTransactionToWarehouse(warehouse, playerCharacter.getObjectType(), playerCharacter.getObjectUUID(), mbEnums.TransactionType.DEPOSIT, resourceType, amount); AddTransactionToWarehouse(warehouse, playerCharacter.getObjectType(), playerCharacter.getObjectUUID(), mbEnums.TransactionType.DEPOSIT, resourceType, amount);
@@ -415,7 +419,7 @@ public class Warehouse {
if (playerCharacter == null) if (playerCharacter == null)
return false; return false;
ItemTemplate template = ItemTemplate.templates.get(resourceType.templateID); CObjectTemplate template = CObjectTemplate.lookup(resourceType.templateID);
if (warehouse.resources.get(resourceType) == null) if (warehouse.resources.get(resourceType) == null)
return false; return false;
@@ -429,7 +433,7 @@ public class Warehouse {
return false; return false;
if (addToInventory) if (addToInventory)
if (!itemMan.hasRoomInventory(template.item_wt)) { if (!itemMan.hasRoomInventory(template.getItemWt())) {
ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item."); ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item.");
return false; return false;
} }
@@ -498,7 +502,7 @@ public class Warehouse {
if (playerCharacter == null) if (playerCharacter == null)
return false; return false;
ItemTemplate template = ItemTemplate.templates.get(resourceType); CObjectTemplate template = CObjectTemplate.lookup(resourceType.templateID);
if (template == null) if (template == null)
return false; return false;
@@ -514,7 +518,7 @@ public class Warehouse {
if (itemMan == null) if (itemMan == null)
return false; return false;
if (!itemMan.hasRoomInventory(template.item_wt)) { if (!itemMan.hasRoomInventory(template.getItemWt())) {
ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item."); ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item.");
return false; 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;
}
}
+186
View File
@@ -0,0 +1,186 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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");
// The exporter writes ["NONE"] for an empty slot mask
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_or.remove(mbEnums.EquipSlotType.NONE);
deed.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, deedJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
deed.item_eq_slots_and.remove(mbEnums.EquipSlotType.NONE);
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;
}
}
+84
View File
@@ -0,0 +1,84 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import org.pmw.tinylog.Logger;
import java.util.LinkedHashMap;
import java.util.Objects;
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;
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);
}
}
+338
View File
@@ -0,0 +1,338 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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");
// The exporter writes ["NONE"] for an empty slot mask
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_or.remove(mbEnums.EquipSlotType.NONE);
rune.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
rune.item_eq_slots_and.remove(mbEnums.EquipSlotType.NONE);
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,501 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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.
// Read only once loaded.
public class ItemTemplate implements CObjectTemplate {
public static HashMap<Integer, ItemTemplate> _items = new HashMap<>();
// Legacy templates zero-filled the weapon block on non-weapons; held shields and the like rely on it.
private static final Weapon NO_WEAPON = new Weapon();
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;
}
public static Weapon getWeapon(int templateID) {
ItemTemplate template = _items.get(templateID);
if (template == null || template.item_weapon == null)
return NO_WEAPON;
return template.item_weapon;
}
// 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 the old static_item_templates export, 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 old static_item_templates export decoded 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 //called to get amount of gold to steal between 0 and max gold
protected static int getAmountToSteal(Item item) { protected static int getAmountToSteal(Item item) {
if (item.template.item_type.equals(ItemType.GOLD)) { if (item.template.getItemType().equals(ItemType.GOLD)) {
int amount = item.getNumOfItems(); int amount = item.getNumOfItems();
@@ -159,7 +159,7 @@ public class StealPowerAction extends AbstractPowerAction {
if (myCIM == null || ownerCIM == null) if (myCIM == null || ownerCIM == null)
return; return;
if (tar.template.item_type.equals(ItemType.GOLD)) { if (tar.template.getItemType().equals(ItemType.GOLD)) {
//stealing gold //stealing gold
if (!myCIM.transferGoldToMyInventory((AbstractCharacter) owner, amount)) if (!myCIM.transferGoldToMyInventory((AbstractCharacter) owner, amount))
return; return;
+34 -3
View File
@@ -21,6 +21,13 @@ import engine.net.client.ClientConnection;
import engine.net.client.ClientConnectionManager; import engine.net.client.ClientConnectionManager;
import engine.net.client.msg.login.*; import engine.net.client.msg.login.*;
import engine.objects.*; 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.ItemTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.server.world.WorldServer; import engine.server.world.WorldServer;
import engine.session.Session; import engine.session.Session;
@@ -281,8 +288,32 @@ public class LoginServer {
Logger.info("Initializing Database layer"); Logger.info("Initializing Database layer");
initDatabaseLayer(); initDatabaseLayer();
Logger.info("Loading Item Templates"); Logger.info("Loading Deed Templates");
DbManager.ItemQueries.LOAD_ITEM_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);
}
Logger.info("Loading Cache Item Templates");
ItemTemplate._items = ItemParser.parseItemFolder();
if (ItemTemplate._items == null || ItemTemplate._items.isEmpty()) {
Logger.error("ABORT! Cache item templates failed to load!");
exit(1);
}
Logger.info("Indexing Cache Templates");
CObjectIndex.build();
Logger.info("Initializing Network"); Logger.info("Initializing Network");
Network.init(); Network.init();
@@ -300,9 +331,9 @@ public class LoginServer {
Logger.info("Initializing PowersManager."); Logger.info("Initializing PowersManager.");
PowersManager.initPowersManager(false); PowersManager.initPowersManager(false);
RuneBaseAttribute.LoadAllAttributes();
RuneBase.LoadAllRuneBases(); RuneBase.LoadAllRuneBases();
BaseClass.LoadAllBaseClasses(); BaseClass.LoadAllBaseClasses();
PromotionClass.LoadAllPromotions();
Race.loadAllRaces(); Race.loadAllRaces();
RuneBaseEffect.LoadRuneBaseEffects(); RuneBaseEffect.LoadRuneBaseEffects();
+42 -6
View File
@@ -31,6 +31,14 @@ import engine.net.client.ClientConnectionManager;
import engine.net.client.msg.UpdateStateMsg; import engine.net.client.msg.UpdateStateMsg;
import engine.net.client.msg.chat.ChatSystemMsg; import engine.net.client.msg.chat.ChatSystemMsg;
import engine.objects.*; 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.ItemTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.util.MapLoader; import engine.util.MapLoader;
import engine.util.ThreadUtils; import engine.util.ThreadUtils;
@@ -297,14 +305,38 @@ public class WorldServer {
Logger.info("Initializing MeshBounds"); Logger.info("Initializing MeshBounds");
MeshBounds.InitializeBuildingBounds(); MeshBounds.InitializeBuildingBounds();
Logger.info("Loading Item Templates"); Logger.info("Loading vendor modTables");
DbManager.ItemQueries.LOAD_ITEM_TEMPLATES(); ForgeManager.vendorModTables = DbManager.ItemQueries.LOAD_TEMPLATE_MODTABLES();
Logger.info("Back fill template modTables"); Logger.info("Loading Deed Templates");
DbManager.ItemQueries.LOAD_TEMPLATE_MODTABLES(); 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);
}
Logger.info("Loading Cache Item Templates");
ItemTemplate._items = ItemParser.parseItemFolder();
if (ItemTemplate._items == null || ItemTemplate._items.isEmpty()) {
Logger.error("ABORT! Cache item templates failed to load!");
exit(1);
}
Logger.info("Indexing Cache Templates");
CObjectIndex.build();
Logger.info("Loading PromotionClasses"); Logger.info("Loading PromotionClasses");
DbManager.PromotionQueries.GET_ALL_PROMOTIONS(); PromotionClass.LoadAllPromotions();
Logger.info("Loading NPC and Mob Rune Sets"); Logger.info("Loading NPC and Mob Rune Sets");
NPCManager._runeSetMap = dbNPCHandler.LOAD_RUNES_FOR_NPC_AND_MOBS(); NPCManager._runeSetMap = dbNPCHandler.LOAD_RUNES_FOR_NPC_AND_MOBS();
@@ -316,7 +348,6 @@ public class WorldServer {
Logger.info("Initializing Loot Manager"); Logger.info("Initializing Loot Manager");
LootManager.init(); LootManager.init();
RuneBaseAttribute.LoadAllAttributes();
RuneBase.LoadAllRuneBases(); RuneBase.LoadAllRuneBases();
BaseClass.LoadAllBaseClasses(); BaseClass.LoadAllBaseClasses();
Race.loadAllRaces(); Race.loadAllRaces();
@@ -361,6 +392,11 @@ public class WorldServer {
mbEnums.RaceType.initRaceTypeTables(); mbEnums.RaceType.initRaceTypeTables();
Race.loadAllRaces(); Race.loadAllRaces();
if (ParityCheck.isEnabled()) {
Logger.info("Running DB/JSON parity check");
ParityCheck.run();
}
Logger.info("Loading building slot/stuck location data."); Logger.info("Loading building slot/stuck location data.");
BuildingLocation.loadBuildingLocations(); BuildingLocation.loadBuildingLocations();
-57
View File
@@ -57,63 +57,6 @@ public class StringUtils {
return Long.toHexString(l).toUpperCase(); 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) { public static String wordWrap(String text, int LineWidth) {
StringTokenizer st = new StringTokenizer(text); StringTokenizer st = new StringTokenizer(text);
int SpaceLeft = LineWidth; int SpaceLeft = LineWidth;