Compare commits

..
Author SHA1 Message Date
MagicBotandClaude Opus 5.5 0c7b6f6c46 Compute XP thresholds from the client formula instead of the LevelToExp table
Client ArcCharacter_ExpForLevelUp (0x4715D0): XP to reach level L is
(L-1)^3 * max(1, 1 + (L-40) * 0.0512f) * class * race rune_exp_req_to_level,
rounded half up. getLevel steps levels with it up to LEVELCAP. Both methods
now take the PlayerCharacter so the factors come from its runes; the CSR
promotions 2901+ have no rune and use the base class factor.

Same values: every player race is 10 and every class rune 15, and the formula
equals the removed table for all 76 levels. Levels above the table no longer
use the old approximate fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:42:52 -04:00
MagicBotandClaude Opus 5.5 db67e56302 Write static_rune_lookup from the loaded race and class caches at world boot
The DB views char_perAccount and dyn_trash_detail lost their race and class
names when 2.11 dropped the Phase 2 tables. The world server now rewrites the
generated static_rune_lookup table (ID, type, name) after the second
Race.loadAllRaces(): 26 player races, 4 base classes, the promotion runes and
the CSR promotion rows 2901-2911. One transaction (delete, batch insert,
commit); on any failure it rolls back and logs a warning, the boot continues.

Names come from the runes: races use obj_name (as in 2.3), classes the rune
names, so DB typos such as "Theif" become "Thief" in the views. No gameplay
change. Needs JSON-Refactor-Plan/sql/2.12-rune-lookup.sql (table, level
functions, views) run before boot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:35:58 -04:00
MagicBotandClaude Opus 5.5 8b9aa43c1b Remove the dead skill and power DB loaders
Deletes the uncalled static_rune_skill_adjusts, static_rune_maxskills,
static_skill_skillsgranted, static_skill_skillreq and static_power_powergrant
loaders and their ResultSet constructors. LOAD_RUNE_POWERS stays for the 14
Magister Captain guard contract ids. No value changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:42:19 -04:00
MagicBotandClaude Opus 5.5 d576ed1468 Stop reading the replaced race, base class and promotion columns
static_rune_race is read only for the race ids and the server-only heights;
the hair/beard style, racebaseclass, baseclass and promotionrunereq queries
are gone. GET_BASE_CLASS is cache-only, which also drops the obj_account
cache-miss query. CSR/admin promotions 2901+ (no promotionrunereq rows) get an
empty allowed list. No value changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:41:06 -04:00
MagicBotandClaude Opus 5.5 658d52cf41 Remove the dead static_rune_baseeffect path
RuneBaseEffect, its handler, every effectsGranted field and getter (including
the PromotionClass promoID*10+2500..2503 lookups) and the empty
PlayerBonuses.grantEffect had no reader (2.10a). The GameObjectType constant
stays so later ordinals are unchanged. No value changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:40:12 -04:00
MagicBotandClaude Opus 5.5 64a86152d3 Load rune bases from all non-RACE runes, dropping static_rune_runebase reads
The 315 static_rune_runebase rows are exactly the non-RACE runes, so the loader
selects by rune_type. Removes the server type/subtype columns (no live reader),
the runereq/overwrite path, the GET_RUNEBASE cache-miss query, the runebase
attribute handler, the racerune/baseclassrune loaders and the 111111 checks.
No value changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:36:34 -04:00
MagicBotandClaude Opus 5.5 13e02707eb Load player rune effects from item_user_power_action instead of static_npc_mobbase_effects
MobBaseEffects.getEffectsForRune builds each rune's list from the RUNE JSON
on first use and caches it: exact repeats are dropped (54 rune files list
their effects twice), the action id resolves to its effect through
PowerActions.cfg, token = SBStringHash(effect id), rank = arg 0,
reqLvl = arg 1. Used by Race, BaseClass, PromotionClass and RuneBase;
MobBase stays on GET_RUNEBASE_EFFECTS until Task 3.4.
Parity (scripts/p210b.py, token level): 1074 runes match. Differences:
- Tireless, Tough as Nails, Taint of Madness: JSON action TRT-* resolves
  to the same effect the DB stores; no change
- CSR 2901-2904, 2906, 2908, 2910, 2911, Non-Aggro Rune 252624: DB token
  was the NoNPCAggro action (no effect found, skipped); now NONPCAGGRO,
  which has no mods; no gameplay change
- Mob-only 12021/12022 NECROPETSTATS and 13236 PEACE are server rows on
  the MobBase path, untouched

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:10:29 -04:00
MagicBotandClaude Opus 5.5 098f5d2c38 Read wpak cfgs from one folder per archive
Files now load from mb.data/wpak/<archive>/<file>, matching MBDATA's
data/wpak layout and the client's Config/Config.wpak naming, so more
archives can sit beside Config/ later. All nine current files are in
wpak/Config/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:38:07 -04:00
MagicBotandClaude Opus 5.5 c63212b900 Remove the old engine.wpak parser package
Replaced by engine.parsers.wpak. Nothing referenced it except the
commented-out calls and the "Loading WPAK data" log line in
ConfigManager, which are removed too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:30:47 -04:00
MagicBotandClaude Opus 5.5 3ae1c72f46 Load Skills, StartingKitTable, CharCreateRuneList and PowActionCostInfo
SkillParser: uid, name, icon, attribute weights and description per
Skills.cfg line (94 skills; the server's ids 1-63 stay in the DB).
StartingKitParser: each kit line is a list of up to 10 item ids under
its RACE= line, as the client reads it; the file's tab columns are not
used (451 kits, same count as static_rune_validkit).
CharCreateRuneParser: race (with sex), class, promotion and attribute
creation rune lists. PowActionCostParser: resource costs per id, the
source of static_effect_costmaps (524 entries). No consumers change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:22:27 -04:00
MagicBotandClaude Opus 5.5 bd145b1e55 Load Powers.cfg at world boot
PowerParser loads Powers.cfg into PowerTemplate._powers. The 22 header
fields are named after the client loader's error messages (0x56E410);
ACTION= lines keep trains, duration, curve and stack fields; prereqs,
curves, flags, messages and CONDITION blocks are kept. Keys compare
case-insensitively, as in the client. Four keys the client ignores
(APPLYDAMAGESELF=, APPLYEFFECTCASTER=, APPLYEFFECTTARGET= and a
POWEREND= typo) are skipped; any other unknown key fails the load.

Numbers are read like the client's atoi/atof with a warning when a
token isn't clean: PRL-033 init time "2.9.0" -> 2.9 and SGE-001
precision "10.0" -> 10, which matches the DB. Ten EQPREREQ lines end
without a required flag, which the client reads as 0.

Needs the fixed Powers.cfg (SRDX-DB header had "SPELL 15 Unknown"
pasted twice; fixed in MBDATA, findings section 6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:19:14 -04:00
MagicBotandClaude Opus 5.5 623f7c4ae6 Load Effects.cfg, ModTypes.cfg and CompoundCurves.cfg at world boot
CompoundCurveParser keeps every curve segment (the CompoundCurveType enum
holds one value per curve). ModTypeParser keeps each mod type's Behavior,
which decides the argument layout of a MODS line. EffectParser loads
Effects.cfg into EffectTemplate._effects: header, the four flag keys
(case-insensitive, as in the client), SOURCE lines, every MODS line in
order, and CONDITION lines. Mod layouts follow the client's ArcMod*_Parse
functions; curve names in effects and power actions must exist in
CompoundCurves.cfg. Boot order: curves, mod types, power actions, effects.
No consumers change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:13:26 -04:00
MagicBotandClaude Opus 5.5 6c820940d3 Add wpak cfg reader and PowerActions.cfg loader
New engine.parsers.wpak importer (replaces engine.wpak, which stays unused).
WpakFileReader tokenizes cfg lines (quotes, '#'/'*' comments, trailing '#')
and builds XxxBEGIN/XxxEND blocks; every error names file:line.

PowerActionParser loads PowerActions.cfg into PowerActionTemplate._powerActions,
keyed by action id, at world boot. Header layouts per action type come from
the client's ArcPowerAction*_ParseHeader functions: the trailing 0/1 on
ApplyEffect and friends is the aggressive flag, not a level. effect_ids holds
the action -> effect map needed by Task 2.10b. Three ids (PRE-235..237)
repeat identically; the later entry wins, as in the client. No consumers change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 11:08:09 -04:00
MagicBotandClaude Opus 5.5 76dcdc0f72 Collapse a rune's skipped non-skill grant warnings into one line
Archon 2018 logged 12 lines at every boot, one per resistance grant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 09:27:34 -04:00
MagicBotandClaude Opus 5.5 4170749a59 Load rune skill reqs from rune_skill_grant instead of static_skill_skillreq
SkillReq.getSkillReqsForRune builds each rune's list from the RUNE JSON
on first use and caches it: skill_value is the grant level and
skill_granted_skills are the prereqs. The prereq amounts (80, Warrior 50,
Sun Dancer 60) are loaded but not yet read; CharacterSkill keeps its
hard-coded 80 and validForWarrior.
Parity (scripts/p29.py): 47 of 79 DB runes match. Value changes (JSON
wins, Task 2.9 decisions):
- Thief Dagger Mastery, Throwing, Unarmed Combat Mastery: level 15 -> 20
- Sentinel Axe/Great Axe/Hammer/Great Hammer/Sword Mastery: 20 -> 15;
  gains Great Sword Mastery (15, Sword); loses Wear Armor, Medium
- Channeler loses Sword; Storm Lord loses Stormcalling
- Blood Horn gains Hammer Mastery (20, Hammer)
- No weapon prereq any more on Archer Archery, Blade Weaver Blade Weaving,
  Sun Dancer Sun Dancing and the 11 Master of X talents
- No effect: Druid/Prelate Sword and Fury Spear 10 -> 1 (promotion is at
  10), discipline/talent level changes (they need level 20), Throwing
  prereqs (always granted), Blade Master Sword prereq (granted at 1)
- JSON-only runes (Archon, Duelist, War Correspondent, Pet, mob/guard
  races) gain lists; Archon resistances and Duelist Dueling are not
  skills and are skipped with a warning

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 09:19:38 -04:00
MagicBotandClaude Opus 5.5 d6df01f8f3 Skip mob race runes when loading power granted trains
Their item_power_action entries are mob power ranks by level, not trains,
and getGrantedTrains only looks up a player's race, class, promotion and
runes. RACE runes load only when they are a player Race (static_rune_race).
Cuts the table from 379384 entries / 969 powers to 2596 / 925; no value a
player can read changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:31:49 -04:00
MagicBotandClaude Opus 5.5 7c12d5bde0 Load power granted trains from item_power_action instead of static_power_powergrant
Amount = power_arguments[0] of each power type's first action, keyed by
SBStringHash(power_type). Parity (scripts/p28.py): 69 of the 70 DB runes
match; Wizard 2521 WIZ-064 Conjure Familiar goes from 1 to 20 (decided JSON).
The JSON adds grants on 2189 runes: mob and guard races (2100+), Archon 2018
(ARCHON-001..005 40, CSR-UNINVIS 30), and NPC trainer/guard talents
(252569-252655, 253002). Only Archon is a player race; the talents are not
player runes. The DB loader stays until 2.11.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:19:38 -04:00
MagicBotandClaude Opus 5.5 3c57a8bb0e Rename legacy PowerGrant to PowerGrantedTrains
Ends the name clash with the parser's engine.parsers.objects.PowerGrant.
It no longer extends AbstractGameObject: it was never cached or sent as a
game object, and the renamed class has no GameObjectType entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:19:14 -04:00
MagicBotandClaude Opus 5.5 fae15889f7 Load rune powers from item_power_grant instead of static_rune_powers
Parity (scripts/p28.py): all 1066 DB runes with a JSON file match in value
and order; token = SBStringHash(power_type), rank = power_value, name = obj_name.
The 14 Magister Captain guard contract ids (848, 952, 1052, 1182, 1252, 1352,
1452, 1502, 1527, 1552, 1557, 1602, 1652, 980102) have no rune file; guard
spells are server-made, so they keep their DB rows.
The JSON adds 170 class/discipline/talent runes; none is a mobbase, and the
trainer contracts among them never reach the guard contract path in MobAI,
so no reachable value change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:18:51 -04:00
MagicBotandClaude Opus 5.5 11cffedd62 Stack a mob's rune skill adjusts by skill type instead of replacing them
applyRuneSkills checked mob.skills for the rune name rather than the skill
type, so every qualifying [rank, level] pair replaced the skill and only
the last one counted. Pairs at or below the mob's level now add up, the
same way trainer caps sum their pairs.

Value changes: only mobbases with several pairs for one skill, Guard Dog
12017 and Guard 13170. Only Guard Dog is in use (contract 910 Personal
Guard Dog); Guard 13170 has no spawns or contracts on dev. Example at
level 50: Dodge and Unarmed Combat 30 -> 110, Unarmed Combat Mastery
20 -> 80. Guard ATR/defense/damage formulas don't read these skills.
No mobbase has a base skill matching a rune skill, so applyMobbaseSkill
values are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:48:53 -04:00
MagicBotandClaude Opus 5.5 865949fd1c Load rune granted skills from level-0 rune_skill_adj instead of static_skill_skillsgranted
Load moved after the rune templates are parsed.
Parity (scripts/p27.py): 76 of 107 DB runes match. Value changes (JSON
wins, Task 2.7 decisions):
- Blade Master 3005: Sword +10 removed (no rune_skill_adj in the JSON;
  Blade Weaver 3006 keeps Sword +10). Reaches players via PlayerBonuses.
- Pet 2522: Dodge and Unarmed Combat 1 -> 22. Not reachable; players
  never hold the Pet rune.
- 29 Master X / trainer runes lose their rows (no level-0 pairs in the
  JSON). Not reachable; players never hold trainer runes. Their caps
  still load into MaxSkills.
- 2243 mob/guard race runes gain rows. Not reachable; PlayerBonuses
  only reads player race/class/promotion/held runes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:46:48 -04:00
MagicBotandClaude Opus 5.5 e3aae0af5c Load trainer max skills from talent rune_skill_adj instead of static_rune_maxskills
Parity (scripts/p27.py): all 78 DB runes match (token, skillLevel = pair
level, maxSkillPercent = pair rank). Talents only, as decided in Task 2.7:
class/discipline/race adjusts are skill grants, and contracts whose class
ID is such a rune (Thief, Huntress, Warlock, Fighter, 8 disciplines,
Vampire) would otherwise gain trainer caps.
The selector adds Brawler 250054 and guard talents 252621-252623; no
contract keys on them, so no reachable value change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:46:18 -04:00
MagicBotandClaude Opus 5.5 0921b031ea Load rune skill adjusts from rune_skill_adj instead of static_rune_skill_adjusts
Parity (scripts/p27.py): all 2259 DB runes and 69290 rows match in value
and order; token = SBStringHash(skill_type), name = obj_name.
The JSON adds 95 class/discipline/talent runes; applyRuneSkills reads by
mobbase ID and none of them is a mobbase, so no reachable value change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:45:59 -04:00
MagicBotandClaude Opus 5.5 2798d29d0a Log every refused character creation with the account and IP
All validation refusals in generatePCFromCommitNewCharacterMsg now go
through refuseCreation, which warns with the reason, account and IP. The
negative stat line used to be the only one that named the account.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:11:02 -04:00
MagicBotandClaude Opus 5.5 6a4e7f0e1b Refuse creation stats below 1 or above the racial maximum
Follows CharCreate_AdjustAttribute (0xBAC140): a stat must stay at 1 or
more and can't be raised past the racial maximum. Checked after the rune
bonuses and max adjustments but before the rune penalties, since Giant's
Blood lowers the INT/DEX max by 10 but the stat by only 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:10:29 -04:00
MagicBotandClaude Opus 5.5 d6bda1fff0 Cap creation at 10 runes and 8 talents like the client
The rune count limit drops from 16 to the client's 10 slots, race and class
included (CharCreate_ApplyRune 0xBA8C80), and more than 8 talents is
refused (ArcCharacter_AddRune 0x4695D0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:10:03 -04:00
MagicBotandClaude Opus 5.5 bd57c2a3f9 Refuse duplicate creation runes by id and talents sharing a client sub type
Character creation now checks duplicates by rune id instead of relying on
RuneBase object identity, and groups talents by rune_sub_type
(RuneBase.getSubTypeToken) instead of the DB SubType byte. Creation talents
are rank 0, so two in the same group block each other, as in
ArcCharacter_AddRune (0x4695D0).

New groups over the DB (scripts/p26b.py):
- weapon proficiencies 250080-250087: only one at creation
- Blood Gift: Blood of the Dragon, Giant's Blood, Taint of Madness, Taint of Chaos
- Juggernaut + Tough Hide; Tireless + Tough as Nails
- Childhood gains Witch Sight 250112; Dark Whispers and Divine Touch join
  the Background group

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:09:38 -04:00
MagicBotandClaude Opus 5.5 bd04f4d692 Build the non-creation talent set without Set.of for the Java 8 build
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 06:51:39 -04:00
MagicBotandClaude Opus 5.5 85bc807260 Build the creation talent maps from the runes
AllowedRaceRunesMap and AllowedBaseClassRunesMap now come from TALENT runes
with rune_is_standard_character_creation, using item_race_req and
item_class_req (restrict true = blacklist, false = whitelist) over the
creation race runes (2000-2029) and class runes (2500-2503). Every creation
race and class gets an explicit list, so the 111111 sentinel is no longer
used. static_rune_racerune / baseclassrune loaders stay until 2.11.

Eight flagged runes that are not in CharCreateRuneList.cfg ATTRRUNES are left
out: 2910 Wolfpack Developer, 2911 QA Test Rune, the unused duplicates
250116/250117/250123/250124/250125, and 250081 Proficient with Bows
(commented out in the cfg).

Parity (offline, scripts/p26.py): 84 talents, the same as the DB apart from
these value changes, all decided JSON (2026-10-22):
- 250081 Proficient with Bows: no longer accepted at creation (DB: any race/class)
- 250088 Raised by Barbarians: Elf, Irekei lose it
- 250089 Raised by Centaurs: Elf loses it
- 250092 Raised by Thieves' Guild: Dwarf loses it
- 250093 Raised in the Woods: female Irekei loses it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 06:49:55 -04:00
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
125 changed files with 4839 additions and 3599 deletions

No files matched your search

+1 -60
View File
@@ -13,13 +13,7 @@ import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.objects.BaseClass; import engine.objects.BaseClass;
import org.pmw.tinylog.Logger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
public class dbBaseClassHandler extends dbHandlerBase { public class dbBaseClassHandler extends dbHandlerBase {
@@ -33,59 +27,6 @@ public class dbBaseClassHandler extends dbHandlerBase {
if (id == 0) if (id == 0)
return null; return null;
BaseClass baseClass = (BaseClass) DbManager.getFromCache(GameObjectType.BaseClass, id); return (BaseClass) DbManager.getFromCache(GameObjectType.BaseClass, id);
if (baseClass != null)
return baseClass;
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `obj_account` WHERE `UID`=?")) {
preparedStatement.setLong(1, id);
ResultSet rs = preparedStatement.executeQuery();
baseClass = (BaseClass) getObjectFromRs(rs);
} catch (SQLException e) {
Logger.error(e);
}
return baseClass;
}
public ArrayList<BaseClass> GET_BASECLASS_FOR_RACE(final int id) {
ArrayList<BaseClass> baseClasses = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT b.* FROM `static_rune_baseclass` b, `static_rune_racebaseclass` r WHERE b.`ID` = r.`BaseClassID` && r.`RaceID` = ?")) {
preparedStatement.setInt(1, id);
ResultSet rs = preparedStatement.executeQuery();
baseClasses = getObjectsFromRs(rs, 20);
} catch (SQLException e) {
Logger.error(e);
}
return baseClasses;
}
public ArrayList<BaseClass> GET_ALL_BASE_CLASSES() {
ArrayList<BaseClass> baseClasses = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_baseclass`;")) {
ResultSet rs = preparedStatement.executeQuery();
baseClasses = getObjectsFromRs(rs, 20);
} catch (SQLException e) {
Logger.error(e);
}
return baseClasses;
} }
} }
+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();
@@ -27,30 +27,17 @@ public class dbPromotionClassHandler extends dbHandlerBase {
this.localObjectType = mbEnums.GameObjectType.valueOf(this.localClass.getSimpleName()); this.localObjectType = mbEnums.GameObjectType.valueOf(this.localClass.getSimpleName());
} }
public ArrayList<Integer> GET_ALLOWED_RUNES(final PromotionClass pc) {
ArrayList<Integer> runeList = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_promotionrunereq` WHERE `promoID`=?")) {
preparedStatement.setInt(1, pc.getObjectUUID());
ResultSet rs = preparedStatement.executeQuery();
while (rs.next())
runeList.add(rs.getInt("runereqID"));
} catch (SQLException e) {
Logger.error(e);
}
return runeList;
}
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` = ?")) {
+7 -60
View File
@@ -10,15 +10,14 @@
package engine.db.handlers; package engine.db.handlers;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.objects.Race;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
public class dbRaceHandler extends dbHandlerBase { public class dbRaceHandler extends dbHandlerBase {
@@ -46,27 +45,6 @@ public class dbRaceHandler extends dbHandlerBase {
return integerSet; return integerSet;
} }
public HashSet<Integer> BEARD_STYLES_FOR_RACE(final int id) {
HashSet<Integer> integerSet = new HashSet<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `beardStyle` FROM `static_rune_racebeardstyle` WHERE `RaceID` = ?")) {
preparedStatement.setInt(1, id);
ResultSet rs = preparedStatement.executeQuery();
while (rs.next())
integerSet.add(rs.getInt(1));
} catch (SQLException e) {
Logger.error(e);
}
return integerSet;
}
public HashSet<Integer> HAIR_COLORS_FOR_RACE(final int id) { public HashSet<Integer> HAIR_COLORS_FOR_RACE(final int id) {
HashSet<Integer> integerSet = new HashSet<>(); HashSet<Integer> integerSet = new HashSet<>();
@@ -89,28 +67,6 @@ public class dbRaceHandler extends dbHandlerBase {
} }
public HashSet<Integer> HAIR_STYLES_FOR_RACE(final int id) {
HashSet<Integer> integerSet = new HashSet<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT `hairStyle` FROM `static_rune_racehairstyle` WHERE `RaceID` = ?")) {
preparedStatement.setInt(1, id);
ResultSet rs = preparedStatement.executeQuery();
while (rs.next())
integerSet.add(rs.getInt(1));
} catch (SQLException e) {
Logger.error(e);
}
return integerSet;
}
public HashSet<Integer> SKIN_COLOR_FOR_RACE(final int id) { public HashSet<Integer> SKIN_COLOR_FOR_RACE(final int id) {
HashSet<Integer> integerSet = new HashSet<>(); HashSet<Integer> integerSet = new HashSet<>();
@@ -132,32 +88,23 @@ public class dbRaceHandler extends dbHandlerBase {
return integerSet; return integerSet;
} }
public ConcurrentHashMap<Integer, Race> LOAD_ALL_RACES() { // Rune id -> {minHeight, strHeightMod}; the rows also decide which race runes are player races
public HashMap<Integer, float[]> LOAD_RACE_HEIGHTS() {
ConcurrentHashMap<Integer, Race> races; HashMap<Integer, float[]> raceHeights = new HashMap<>();
Race thisRace;
races = new ConcurrentHashMap<>();
int recordsRead = 0;
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_rune_race")) { PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_rune_race")) {
ResultSet rs = preparedStatement.executeQuery(); ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) { while (rs.next())
raceHeights.put(rs.getInt("ID"), new float[]{rs.getFloat("minHeight"), rs.getFloat("strHeightMod")});
recordsRead++;
thisRace = new Race(rs);
races.put(thisRace.getRaceRuneID(), thisRace);
}
} catch (SQLException e) { } catch (SQLException e) {
Logger.error(e); Logger.error(e);
} }
Logger.info("read: " + recordsRead + " cached: " + races.size()); return raceHeights;
return races;
} }
} }
@@ -1,48 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.db.handlers;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.objects.RuneBaseAttribute;
import org.pmw.tinylog.Logger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
public class dbRuneBaseAttributeHandler extends dbHandlerBase {
public dbRuneBaseAttributeHandler() {
this.localClass = RuneBaseAttribute.class;
this.localObjectType = mbEnums.GameObjectType.valueOf(this.localClass.getSimpleName());
}
public ArrayList<RuneBaseAttribute> GET_ATTRIBUTES_FOR_RUNEBASE() {
ArrayList<RuneBaseAttribute> runeBaseAttributesList = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_runebaseattribute`")) {
ResultSet rs = preparedStatement.executeQuery();
runeBaseAttributesList = getObjectsFromRs(rs, 10);
} catch (SQLException e) {
Logger.error(e);
}
return runeBaseAttributesList;
}
}
@@ -1,92 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.db.handlers;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.mbEnums.GameObjectType;
import engine.objects.AbstractGameObject;
import engine.objects.RuneBaseEffect;
import org.pmw.tinylog.Logger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
public class dbRuneBaseEffectHandler extends dbHandlerBase {
public dbRuneBaseEffectHandler() {
this.localClass = RuneBaseEffect.class;
this.localObjectType = mbEnums.GameObjectType.valueOf(this.localClass.getSimpleName());
}
public ArrayList<RuneBaseEffect> GET_EFFECTS_FOR_RUNEBASE(int id) {
ArrayList<RuneBaseEffect> runeBaseEffectsList = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_baseeffect` WHERE `runeID`=?")) {
preparedStatement.setInt(1, id);
ResultSet rs = preparedStatement.executeQuery();
runeBaseEffectsList = getObjectsFromRs(rs, 250);
} catch (SQLException e) {
Logger.error(e);
}
return runeBaseEffectsList;
}
public ArrayList<RuneBaseEffect> GET_ALL_RUNEBASE_EFFECTS() {
ArrayList<RuneBaseEffect> runeBaseEffectsList = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_baseeffect`;")) {
ResultSet rs = preparedStatement.executeQuery();
runeBaseEffectsList = getObjectsFromRs(rs, 250);
} catch (SQLException e) {
Logger.error(e);
}
return runeBaseEffectsList;
}
//This calls from cache only. Call this AFTER caching all runebase effects;
public HashMap<Integer, ArrayList<RuneBaseEffect>> LOAD_BASEEFFECTS_FOR_RUNEBASE() {
HashMap<Integer, ArrayList<RuneBaseEffect>> runeBaseEffectSet;
runeBaseEffectSet = new HashMap<>();
for (AbstractGameObject runeBaseEffect : DbManager.getList(GameObjectType.RuneBaseEffect)) {
int runeBaseID = ((RuneBaseEffect) runeBaseEffect).getRuneBaseID();
if (runeBaseEffectSet.get(runeBaseID) == null) {
ArrayList<RuneBaseEffect> runeBaseEffectList = new ArrayList<>();
runeBaseEffectList.add((RuneBaseEffect) runeBaseEffect);
runeBaseEffectSet.put(runeBaseID, runeBaseEffectList);
} else {
ArrayList<RuneBaseEffect> runeBaseEffectList = runeBaseEffectSet.get(runeBaseID);
runeBaseEffectList.add((RuneBaseEffect) runeBaseEffect);
runeBaseEffectSet.put(runeBaseID, runeBaseEffectList);
}
}
return runeBaseEffectSet;
}
}
+41 -173
View File
@@ -11,9 +11,12 @@ package engine.db.handlers;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.objects.AbstractGameObject;
import engine.objects.BaseClass;
import engine.objects.PromotionClass;
import engine.objects.Race;
import engine.objects.RuneBase; import engine.objects.RuneBase;
import engine.powers.RunePowerEntry; import engine.powers.RunePowerEntry;
import engine.powers.RuneSkillAdjustEntry;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.Connection; import java.sql.Connection;
@@ -69,192 +72,57 @@ public class dbRuneBaseHandler extends dbHandlerBase {
return mobPowers; return mobPowers;
} }
public static HashMap<Integer, ArrayList<RuneSkillAdjustEntry>> LOAD_RUNE_SKILL_ADJUSTS() { // static_rune_lookup is generated for the DB views; a failed write must never stop the boot
public static void WRITE_RUNE_LOOKUP() {
HashMap<Integer, ArrayList<RuneSkillAdjustEntry>> runeSkillAdjusts = new HashMap<>(); int rowsWritten = 0;
RuneSkillAdjustEntry runeSkillAdjustEntry;
int rune_id;
int recordsRead = 0;
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_rune_skill_adjusts")) { PreparedStatement deleteStatement = connection.prepareStatement("DELETE FROM `static_rune_lookup`");
PreparedStatement insertStatement = connection.prepareStatement("INSERT INTO `static_rune_lookup` (`ID`, `type`, `name`) VALUES (?, ?, ?)")) {
ResultSet rs = preparedStatement.executeQuery(); connection.setAutoCommit(false);
while (rs.next()) { try {
deleteStatement.executeUpdate();
recordsRead++; for (Race race : Race.getAllRaces()) {
addRuneLookupRow(insertStatement, race.getRaceRuneID(), "RACE", race.getName());
rune_id = rs.getInt("rune_id"); rowsWritten++;
runeSkillAdjustEntry = new RuneSkillAdjustEntry(rs);
if (runeSkillAdjusts.get(rune_id) == null) {
ArrayList<RuneSkillAdjustEntry> skillAdjustList = new ArrayList<>();
skillAdjustList.add(runeSkillAdjustEntry);
runeSkillAdjusts.put(rune_id, skillAdjustList);
} else {
ArrayList<RuneSkillAdjustEntry> powerList = runeSkillAdjusts.get(rune_id);
powerList.add(runeSkillAdjustEntry);
runeSkillAdjusts.put(rune_id, powerList);
} }
}
} catch (SQLException e) {
Logger.error(e);
return runeSkillAdjusts;
}
Logger.info("read: " + recordsRead + " cached: " + runeSkillAdjusts.size()); for (AbstractGameObject baseClass : DbManager.getMap(mbEnums.GameObjectType.BaseClass).values()) {
return runeSkillAdjusts; addRuneLookupRow(insertStatement, baseClass.getObjectUUID(), "BASECLASS", ((BaseClass) baseClass).getName());
} rowsWritten++;
public void GET_RUNE_REQS(final RuneBase rb) {
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_runereq` WHERE `runeID` = ?")) {
preparedStatement.setInt(1, rb.getObjectUUID());
ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) {
int type = rs.getInt("type");
switch (type) {
case 1:
rb.getRace().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed"));
break;
case 2:
rb.getBaseClass().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed"));
break;
case 3:
rb.getPromotionClass().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed"));
break;
case 4:
rb.getDiscipline().put(rs.getInt("requiredRuneID"), rs.getBoolean("isAllowed"));
break;
case 5:
rb.getOverwrite().add(rs.getInt("requiredRuneID"));
break;
case 6:
rb.setLevelRequired(rs.getInt("requiredRuneID"));
break;
} }
for (AbstractGameObject promotionClass : DbManager.getMap(mbEnums.GameObjectType.PromotionClass).values()) {
addRuneLookupRow(insertStatement, promotionClass.getObjectUUID(), "PROMOTION", ((PromotionClass) promotionClass).getName());
rowsWritten++;
}
insertStatement.executeBatch();
connection.commit();
} catch (SQLException | RuntimeException e) {
// Roll back before setAutoCommit(true), which would otherwise commit the delete
connection.rollback();
throw e;
} finally {
connection.setAutoCommit(true);
} }
} catch (SQLException e) { } catch (Exception e) {
Logger.error(e); Logger.warn("static_rune_lookup not written; DB views keep their old names: " + e.getMessage());
return;
} }
Logger.info("static_rune_lookup: wrote " + rowsWritten + " rows");
} }
public RuneBase GET_RUNEBASE(final int id) { private static void addRuneLookupRow(PreparedStatement insertStatement, int runeID, String type, String name) throws SQLException {
insertStatement.setInt(1, runeID);
RuneBase runeBase = null; insertStatement.setString(2, type);
insertStatement.setString(3, name);
try (Connection connection = DbManager.getConnection(); insertStatement.addBatch();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_runebase` WHERE `ID` = ?")) {
preparedStatement.setInt(1, id);
ResultSet rs = preparedStatement.executeQuery();
runeBase = (RuneBase) getObjectFromRs(rs);
} catch (SQLException e) {
Logger.error(e);
}
return runeBase;
}
public ArrayList<RuneBase> LOAD_ALL_RUNEBASES() {
ArrayList<RuneBase> runeBasesList = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_runebase`;")) {
ResultSet rs = preparedStatement.executeQuery();
runeBasesList = getObjectsFromRs(rs, 1000);
} catch (SQLException e) {
Logger.error(e);
}
return runeBasesList;
}
public HashMap<Integer, ArrayList<Integer>> LOAD_ALLOWED_STARTING_RUNES_FOR_BASECLASS() {
HashMap<Integer, ArrayList<Integer>> runeSets = new HashMap<>();
int recordsRead = 0;
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_rune_baseclassrune")) {
ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) {
recordsRead++;
int baseClassID = rs.getInt("BaseClassesID");
int runeBaseID = rs.getInt("RuneBaseID");
if (runeSets.get(baseClassID) == null) {
ArrayList<Integer> runeList = new ArrayList<>();
runeList.add(runeBaseID);
runeSets.put(baseClassID, runeList);
} else {
ArrayList<Integer> runeList = runeSets.get(baseClassID);
runeList.add(runeBaseID);
runeSets.put(baseClassID, runeList);
}
}
} catch (SQLException e) {
Logger.error(e);
}
Logger.info("read: " + recordsRead + " cached: " + runeSets.size());
return runeSets;
}
public HashMap<Integer, ArrayList<Integer>> LOAD_ALLOWED_STARTING_RUNES_FOR_RACE() {
HashMap<Integer, ArrayList<Integer>> runeSets;
runeSets = new HashMap<>();
int recordsRead = 0;
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_rune_racerune")) {
ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) {
recordsRead++;
int raceID = rs.getInt("RaceID");
int runeBaseID = rs.getInt("RuneBaseID");
if (runeSets.get(raceID) == null) {
ArrayList<Integer> runeList = new ArrayList<>();
runeList.add(runeBaseID);
runeSets.put(raceID, runeList);
} else {
ArrayList<Integer> runeList = runeSets.get(raceID);
runeList.add(runeBaseID);
runeSets.put(raceID, runeList);
}
}
} catch (SQLException e) {
Logger.error(e);
}
Logger.info("read: " + recordsRead + " cached: " + runeSets.size());
return runeSets;
} }
} }
@@ -12,7 +12,6 @@ package engine.db.handlers;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.objects.MaxSkills;
import engine.objects.SkillsBase; import engine.objects.SkillsBase;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -20,8 +19,6 @@ import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
public class dbSkillBaseHandler extends dbHandlerBase { public class dbSkillBaseHandler extends dbHandlerBase {
@@ -98,55 +95,4 @@ public class dbSkillBaseHandler extends dbHandlerBase {
SkillsBase.putInCache(skillsBase); SkillsBase.putInCache(skillsBase);
return skillsBase; return skillsBase;
} }
public void LOAD_ALL_MAX_SKILLS_FOR_CONTRACT() {
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_rune_maxskills`")) {
ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) {
MaxSkills maxSKills = new MaxSkills(rs);
if (MaxSkills.MaxSkillsSet.get(maxSKills.getRuneID()) == null) {
ArrayList<MaxSkills> newMaxSkillsList = new ArrayList<>();
newMaxSkillsList.add(maxSKills);
MaxSkills.MaxSkillsSet.put(maxSKills.getRuneID(), newMaxSkillsList);
} else
MaxSkills.MaxSkillsSet.get(maxSKills.getRuneID()).add(maxSKills);
}
} catch (SQLException e) {
Logger.error(e);
}
}
public void LOAD_ALL_RUNE_SKILLS() {
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_skill_skillsgranted`")) {
ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) {
int runeID = rs.getInt("runeID");
int token = rs.getInt("token");
int amount = rs.getInt("amount");
if (SkillsBase.runeSkillsCache.get(runeID) == null)
SkillsBase.runeSkillsCache.put(runeID, new HashMap<>());
SkillsBase.runeSkillsCache.get(runeID).put(token, amount);
}
} catch (SQLException e) {
Logger.error(e);
}
}
} }
@@ -76,24 +76,4 @@ public class dbSkillReqHandler extends dbHandlerBase {
Logger.error(e); Logger.error(e);
} }
} }
public ArrayList<SkillReq> GET_REQS_FOR_RUNE(final int objectUUID) {
ArrayList<SkillReq> skillReqsList = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_skill_skillreq` WHERE `runeID`=?")) {
preparedStatement.setInt(1, objectUUID);
ResultSet rs = preparedStatement.executeQuery();
skillReqsList = getObjectsFromRs(rs, 5);
} catch (SQLException e) {
Logger.error(e);
}
return skillReqsList;
}
} }
+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();
+15 -7
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,17 +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(templateID); template = CObjectTemplate.lookup(templateID);
if (template == null) { if (template == null) {
throwbackError(pc, "No item template with id " + words[0]); throwbackError(pc, "No item template with id " + words[0]);
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;
} }
@@ -131,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);
@@ -139,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;
@@ -154,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.");
@@ -168,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);
@@ -179,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;
+6 -5
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,12 +200,12 @@ 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);
@@ -218,12 +219,12 @@ public enum BuildingManager {
for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) { for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) {
ItemTemplate resourceTemplate = ItemTemplate.templates.get(resourceType.templateID); CObjectTemplate resourceTemplate = CObjectTemplate.lookup(resourceType.templateID);
if (resourceTemplate == null) if (resourceTemplate == null)
continue; continue;
if (!player.charItemManager.hasRoomInventory(resourceTemplate.item_wt)) { 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;
} }
+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) {
@@ -166,14 +166,6 @@ public enum ConfigManager {
regex.put(MB_LOGIN_FNAME_REGEX, Pattern.compile(MB_LOGIN_FNAME_REGEX.getValue())); regex.put(MB_LOGIN_FNAME_REGEX, Pattern.compile(MB_LOGIN_FNAME_REGEX.getValue()));
Logger.info("Loading WPAK data");
// *** Needs powermanager collection
// defined before activation.
// EffectsParser.parseWpakFile();
// PowersParser.parseWpakFile();
// PowerActionParser.parseWpakFile();
return true; return true;
} }
-2
View File
@@ -58,8 +58,6 @@ public enum DbManager {
public static final dbPromotionClassHandler PromotionQueries = new dbPromotionClassHandler(); public static final dbPromotionClassHandler PromotionQueries = new dbPromotionClassHandler();
public static final dbRaceHandler RaceQueries = new dbRaceHandler(); public static final dbRaceHandler RaceQueries = new dbRaceHandler();
public static final dbResistHandler ResistQueries = new dbResistHandler(); public static final dbResistHandler ResistQueries = new dbResistHandler();
public static final dbRuneBaseAttributeHandler RuneBaseAttributeQueries = new dbRuneBaseAttributeHandler();
public static final dbRuneBaseEffectHandler RuneBaseEffectQueries = new dbRuneBaseEffectHandler();
public static final dbRuneBaseHandler RuneBaseQueries = new dbRuneBaseHandler(); public static final dbRuneBaseHandler RuneBaseQueries = new dbRuneBaseHandler();
public static final dbSkillBaseHandler SkillsBaseQueries = new dbSkillBaseHandler(); public static final dbSkillBaseHandler SkillsBaseQueries = new dbSkillBaseHandler();
public static final dbSkillReqHandler SkillReqQueries = new dbSkillReqHandler(); public static final dbSkillReqHandler SkillReqQueries = new dbSkillReqHandler();
@@ -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;
} }
+27 -22
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));
} }
} }
} }
@@ -466,7 +471,7 @@ public enum NPCManager {
if (skillBase == null) if (skillBase == null)
continue; continue;
if (entry.level <= mob.level) if (entry.level <= mob.level)
if (mob.skills.containsKey(entry.name) == false) if (mob.skills.containsKey(entry.skill_type) == false)
mob.skills.put(entry.skill_type, new CharacterSkill(skillBase, mob, entry.rank)); mob.skills.put(entry.skill_type, new CharacterSkill(skillBase, mob, entry.rank));
else else
mob.skills.put(entry.skill_type, new CharacterSkill(skillBase, mob, entry.rank + mob.skills.get(entry.skill_type).getNumTrains())); mob.skills.put(entry.skill_type, new CharacterSkill(skillBase, mob, entry.rank + mob.skills.get(entry.skill_type).getNumTrains()));
@@ -541,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())
@@ -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;
@@ -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);
@@ -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;
@@ -356,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;
} }
@@ -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;
} }
+33 -28
View File
@@ -10,10 +10,12 @@
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.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -82,30 +84,41 @@ public class BaseClass extends AbstractGameObject {
/** /**
* ResultSet Constructor * Rune Template Constructor
*/ */
public BaseClass(ResultSet rs) throws SQLException { public BaseClass(RuneTemplate rune) {
super(rs); super(rune.template_id);
this.name = rs.getString("name"); this.name = rune.obj_name;
this.description = rs.getString("description"); this.description = rune.rune_dsc;
this.strMod = rs.getByte("strMod"); this.strMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Strength, 0).byteValue();
this.dexMod = rs.getByte("dexMod"); this.dexMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Dexterity, 0).byteValue();
this.conMod = rs.getByte("conMod"); this.conMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Constitution, 0).byteValue();
this.intMod = rs.getByte("intMod"); this.intMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Intelligence, 0).byteValue();
this.spiMod = rs.getByte("spiMod"); this.spiMod = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Spirit, 0).byteValue();
this.token = rs.getInt("token"); this.token = Hasher.SBStringHash(rune.rune_sub_type);
this.healthMod = rs.getInt("healthMod"); this.healthMod = rune.rune_health;
this.manaMod = rs.getInt("manaMod"); this.manaMod = rune.rune_mana;
this.staminaMod = rs.getInt("staminaMod"); this.staminaMod = rune.rune_stamina;
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.getObjectUUID()); this.skillsGranted = SkillReq.getSkillReqsForRune(this.getObjectUUID());
this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID()); this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID());
this.effectsList = (DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID())); this.effectsList = MobBaseEffects.getEffectsForRune(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) {
@@ -187,10 +200,6 @@ public class BaseClass extends AbstractGameObject {
return this.powersGranted; return this.powersGranted;
} }
public ArrayList<RuneBaseEffect> getEffectsGranted() {
return RuneBaseEffect.RuneIDBaseEffectMap.get(this.getObjectUUID());
}
/* /*
* Utils * Utils
*/ */
@@ -199,10 +208,6 @@ public class BaseClass extends AbstractGameObject {
if (this.getRuneList().contains(rb.getObjectUUID())) if (this.getRuneList().contains(rb.getObjectUUID()))
return true; return true;
if (RuneBase.AllowedBaseClassRunesMap.containsKey(111111)) {
if (RuneBase.AllowedBaseClassRunesMap.get(111111).contains(rb.getObjectUUID()))
return true;
}
return false; return false;
} }
+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())
+1 -1
View File
@@ -334,7 +334,7 @@ public class CharacterPower extends AbstractGameObject {
// return pc.getBonuses().getByte("gt." + this.power.getToken()); // return pc.getBonuses().getByte("gt." + this.power.getToken());
// } else // } else
// return pc.getBonuses().getByte("gt." + this.power.getToken()); // return pc.getBonuses().getByte("gt." + this.power.getToken());
return PowerGrant.getGrantedTrains(this.power.getToken(), pc); return PowerGrantedTrains.getGrantedTrains(this.power.getToken(), pc);
} else } else
return 0; return 0;
} }
+3 -18
View File
@@ -27,7 +27,6 @@ public class CharacterRune extends AbstractGameObject {
private final int player; private final int player;
private final ArrayList<SkillReq> skillsGranted; private final ArrayList<SkillReq> skillsGranted;
private final ArrayList<PowerReq> powersGranted; private final ArrayList<PowerReq> powersGranted;
private final ArrayList<RuneBaseEffect> effectsGranted;
/** /**
* No Table ID Constructor * No Table ID Constructor
@@ -37,16 +36,12 @@ public class CharacterRune extends AbstractGameObject {
this.runeBase = runeBase; this.runeBase = runeBase;
this.player = characterID; this.player = characterID;
if (this.runeBase != null) { if (this.runeBase != null) {
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); this.skillsGranted = SkillReq.getSkillReqsForRune(this.runeBase.getObjectUUID());
this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID()); this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID());
} else { } else {
this.skillsGranted = new ArrayList<>(); this.skillsGranted = new ArrayList<>();
this.powersGranted = new ArrayList<>(); this.powersGranted = new ArrayList<>();
} }
if (this.runeBase != null)
this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID());
else
this.effectsGranted = new ArrayList<>();
} }
/** /**
@@ -57,16 +52,12 @@ public class CharacterRune extends AbstractGameObject {
this.runeBase = runeBase; this.runeBase = runeBase;
this.player = characterID; this.player = characterID;
if (this.runeBase != null) { if (this.runeBase != null) {
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); this.skillsGranted = SkillReq.getSkillReqsForRune(this.runeBase.getObjectUUID());
this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID()); this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID());
} else { } else {
this.skillsGranted = new ArrayList<>(); this.skillsGranted = new ArrayList<>();
this.powersGranted = new ArrayList<>(); this.powersGranted = new ArrayList<>();
} }
if (this.runeBase != null)
this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID());
else
this.effectsGranted = new ArrayList<>();
} }
/** /**
@@ -78,14 +69,12 @@ public class CharacterRune extends AbstractGameObject {
this.runeBase = RuneBase.getRuneBase(rs.getInt("RuneBaseID")); this.runeBase = RuneBase.getRuneBase(rs.getInt("RuneBaseID"));
this.player = rs.getInt("CharacterID"); this.player = rs.getInt("CharacterID");
if (this.runeBase != null) { if (this.runeBase != null) {
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); this.skillsGranted = SkillReq.getSkillReqsForRune(this.runeBase.getObjectUUID());
this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID()); this.powersGranted = PowerReq.getPowerReqsForRune(this.runeBase.getObjectUUID());
this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID());
} else { } else {
Logger.error("Failed to find RuneBase for CharacterRune " + this.getObjectUUID()); Logger.error("Failed to find RuneBase for CharacterRune " + this.getObjectUUID());
this.skillsGranted = new ArrayList<>(); this.skillsGranted = new ArrayList<>();
this.powersGranted = new ArrayList<>(); this.powersGranted = new ArrayList<>();
this.effectsGranted = new ArrayList<>();
} }
} }
@@ -200,10 +189,6 @@ public class CharacterRune extends AbstractGameObject {
return this.powersGranted; return this.powersGranted;
} }
public ArrayList<RuneBaseEffect> getEffectsGranted() {
return this.effectsGranted;
}
@Override @Override
public void updateDatabase() { public void updateDatabase() {
DbManager.CharacterRuneQueries.updateDatabase(this); DbManager.CharacterRuneQueries.updateDatabase(this);
+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))
+41 -121
View File
@@ -12,114 +12,17 @@ package engine.objects;
import engine.gameManager.ZoneManager; import engine.gameManager.ZoneManager;
import engine.math.Vector3fImmutable; import engine.math.Vector3fImmutable;
import engine.mbEnums.TargetColor; import engine.mbEnums.TargetColor;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.TreeMap;
import static engine.gameManager.LootManager.LOOTMANAGER; import static engine.gameManager.LootManager.LOOTMANAGER;
public class Experience { public class Experience {
private static final TreeMap<Integer, Integer> ExpToLevel; // Client ArcCharacter_ExpForLevelUp (0x4715D0) float constant at 0x154195C
private static final int[] LevelToExp = {Integer.MIN_VALUE, // Pad private static final float EXP_LEVEL_SLOPE = 0.0512f;
// everything
// over 1
// R0
0, // Level 1
150, // Level 2
1200, // Level 3
4050, // Level 4
9600, // Level 5
18750, // Level 6
32400, // Level 7
51450, // Level 8
76800, // Level 9
// R1
109350, // Level 10
150000, // Level 11
199650, // Level 12
259200, // Level 13
329550, // Level 14
411600, // Level 15
506250, // Level 16
614400, // Level 17
736950, // Level 18
874800, // Level 19
// R2
1028850, // Level 20
1200000, // Level 21
1389150, // Level 22
1597200, // Level 23
1825050, // Level 24
2073600, // Level 25
2343750, // Level 26
2636400, // Level 27
2952450, // Level 28
3292800, // Level 29
// R3
3658350, // Level 30
4050000, // Level 31
4468650, // Level 32
4915200, // Level 33
5390550, // Level 34
5895600, // Level 35
6431250, // Level 36
6998400, // Level 37
7597950, // Level 38
8230800, // Level 39
// R4
8897850, // Level 40
10091520, // Level 41
11396777, // Level 42
12820187, // Level 43
14368505, // Level 44
16048666, // Level 45
17867790, // Level 46
19833183, // Level 47
21952335, // Level 48
24232919, // Level 49
// R5
26682793, // Level 50
29310000, // Level 51
32122766, // Level 52
35129502, // Level 53
38338805, // Level 54
41759452, // Level 55
45400409, // Level 56
49270824, // Level 57
53380030, // Level 58
57737542, // Level 59
// R6
62353064, // Level 60
67236479, // Level 61
72397859, // Level 62
77847457, // Level 63
83595712, // Level 64
89653247, // Level 65
96030869, // Level 66
102739569, // Level 67
109790524, // Level 68
117195093, // Level 69
// R7
124964822, // Level 70
133111438, // Level 71
141646855, // Level 72
150583171, // Level 73
159932666, // Level 74
169707808, // Level 75
179921247, // Level 76
};
private static final float[] MaxExpPerLevel = {Float.MIN_VALUE, // Pad private static final float[] MaxExpPerLevel = {Float.MIN_VALUE, // Pad
// everything // everything
// over // over
@@ -227,39 +130,56 @@ public class Experience {
// (expGranted / group.size()) * (groupBonus * (group.size()-1) +1) // (expGranted / group.size()) * (groupBonus * (group.size()-1) +1)
private static final float GROUP_BONUS = 0.5f; // 0.2 grants (20%) addtional private static final float GROUP_BONUS = 0.5f; // 0.2 grants (20%) addtional
static {
ExpToLevel = new TreeMap<>();
// flip keys and values for other Map
for (int i = 1; i < LevelToExp.length; i++) {
ExpToLevel.put(LevelToExp[i], i);
}
} // end Static block
// exp per group member // exp per group member
// called to determine current level based on xp // called to determine current level based on xp
public static int getLevel(int experience) { public static int getLevel(PlayerCharacter player, int experience) {
int expKey = ExpToLevel.floorKey(experience); int level = 1;
int level = ExpToLevel.get(expKey);
if (level > MBServerStatics.LEVELCAP) while (level < MBServerStatics.LEVELCAP && experience >= getBaseExperience(player, level + 1))
level = MBServerStatics.LEVELCAP; level++;
return level; return level;
} }
// Get the base xp for a level // The client function returns the XP to reach level + 1, so it is called with level - 1.
public static int getBaseExperience(int level) { // Arithmetic order and the half-up rounding follow the client so every threshold matches.
public static int getBaseExperience(PlayerCharacter player, int level) {
if (level < LevelToExp.length) int clientLevel = level - 1;
return LevelToExp[level]; double levelScale;
int fLevel = level - 1; levelScale = Math.max(1.0, (clientLevel - 39) * (double) EXP_LEVEL_SLOPE + 1.0);
int baseXP = fLevel * fLevel * fLevel;
return (int) ((fLevel < 40) ? (baseXP * 150) return (int) (long) ((double) clientLevel * clientLevel * clientLevel * levelScale
: (baseXP * (150 + (7.6799998 * (level - 40))))); * getClassExpFactor(player) * getRaceExpFactor(player) + 0.5);
}
// rune_exp_req_to_level; the client uses 1.0 when the rune is missing
private static double getRaceExpFactor(PlayerCharacter player) {
RuneTemplate rune = null;
if (player.race != null)
rune = RuneTemplate._runes.get(player.race.getRaceRuneID());
return (rune == null) ? 1.0 : rune.rune_exp_req_to_level;
}
// GUESS: the client's class rune (0x45EAD0) is the promotion once taken; every CLASS rune is 15 either way.
// The CSR promotions 2901+ have no rune, so they fall back to the base class.
private static double getClassExpFactor(PlayerCharacter player) {
RuneTemplate rune = null;
if (player.promotionClass != null)
rune = RuneTemplate._runes.get(player.promotionClass.getObjectUUID());
if (rune == null && player.baseClass != null)
rune = RuneTemplate._runes.get(player.baseClass.getObjectUUID());
return (rune == null) ? 1.0 : rune.rune_exp_req_to_level;
} }
// Max XP granted for killing a blue, yellow or orange mob // Max XP granted for killing a blue, yellow or orange mob
+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;
} }
-649
View File
@@ -1,649 +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;
// Lossy: some runes list the same power several times with different
// arguments (a step table, e.g. PET_DISC). Only the last entry is kept.
// engine.parsers.objects.RuneTemplate keeps the full list.
public final HashMap<String, int[]> item_user_power_action = new HashMap<>();
public final HashMap<String, Integer> item_power_grant = new HashMap<>();
// Lossy: some runes carry up to 35 power_actions entries per power
// (a step table, e.g. HNT-031). Only the first entry is kept.
// engine.parsers.objects.RuneTemplate keeps the full list.
public final HashMap<String, int[]> item_power_action = new HashMap<>();
public final HashMap<mbEnums.ResourceType, Integer> item_resource_cost = new HashMap<>();
public int modTable;
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);
}
}
}
+29 -13
View File
@@ -9,10 +9,14 @@
package engine.objects; package engine.objects;
import java.sql.ResultSet; import engine.mbEnums;
import java.sql.SQLException; import engine.parsers.objects.RuneTemplate;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
public class MaxSkills { public class MaxSkills {
@@ -23,17 +27,6 @@ public class MaxSkills {
private int maxSkillPercent; private int maxSkillPercent;
/**
* ResultSet Constructor
*/
public MaxSkills(ResultSet rs) throws SQLException {
this.runeID = rs.getInt("runeID");
this.skillToken = rs.getInt("skillToken");
this.skillLevel = rs.getInt("skillLevel");
this.maxSkillPercent = rs.getInt("maxSkillPercent");
}
public MaxSkills(int runeID, int skillToken, int skillLevel, int maxSkillPercent) { public MaxSkills(int runeID, int skillToken, int skillLevel, int maxSkillPercent) {
super(); super();
this.runeID = runeID; this.runeID = runeID;
@@ -42,6 +35,29 @@ public class MaxSkills {
this.maxSkillPercent = maxSkillPercent; this.maxSkillPercent = maxSkillPercent;
} }
public static void LoadAllMaxSkills() {
ArrayList<MaxSkills> maxSkillsList;
int entryCount = 0;
// Talents only: class/discipline/race adjusts are skill grants, and contracts that key on those runes would read them as caps
for (RuneTemplate rune : RuneTemplate._runes.values()) {
if (rune.rune_type != mbEnums.RuneType.TALENT || rune.rune_skill_adj.isEmpty())
continue;
maxSkillsList = new ArrayList<>();
for (Map.Entry<String, ArrayList<int[]>> skillAdjust : rune.rune_skill_adj.entrySet())
for (int[] adjust : skillAdjust.getValue())
maxSkillsList.add(new MaxSkills(rune.template_id, Hasher.SBStringHash(skillAdjust.getKey()), adjust[1], adjust[0]));
MaxSkillsSet.put(rune.template_id, maxSkillsList);
entryCount += maxSkillsList.size();
}
Logger.info("Loaded " + entryCount + " max skills for " + MaxSkillsSet.size() + " runes");
}
public int getRuneID() { public int getRuneID() {
return runeID; return runeID;
} }
+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);
} }
+57
View File
@@ -9,12 +9,23 @@
package engine.objects; package engine.objects;
import engine.parsers.objects.RuneTemplate;
import engine.parsers.objects.UserPowerAction;
import engine.parsers.objects.wpak.PowerActionTemplate;
import engine.server.MBServerStatics;
import engine.util.Hasher;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
public class MobBaseEffects { public class MobBaseEffects {
private static final ConcurrentHashMap<Integer, ArrayList<MobBaseEffects>> effectsByRune = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private int mobBaseID; private int mobBaseID;
private int token; private int token;
private int rank; private int rank;
@@ -30,6 +41,52 @@ public class MobBaseEffects {
this.reqLvl = rs.getInt("reqLvl"); this.reqLvl = rs.getInt("reqLvl");
} }
public MobBaseEffects(int token, int rank, int reqLvl) {
this.token = token;
this.rank = rank;
this.reqLvl = reqLvl;
}
public static ArrayList<MobBaseEffects> getEffectsForRune(int runeID) {
return effectsByRune.computeIfAbsent(runeID, MobBaseEffects::loadEffectsForRune);
}
// item_user_power_action entries are {action id, [rank, reqLvl]}; the action names the effect to apply
private static ArrayList<MobBaseEffects> loadEffectsForRune(int runeID) {
ArrayList<MobBaseEffects> runeEffects = new ArrayList<>();
HashSet<String> seenEntries = new HashSet<>();
RuneTemplate rune = RuneTemplate._runes.get(runeID);
PowerActionTemplate powerAction;
String effectID;
int rank;
int reqLvl;
if (rune == null)
return runeEffects;
for (UserPowerAction userPowerAction : rune.item_user_power_action) {
rank = userPowerAction.arguments.length > 0 ? userPowerAction.arguments[0] : 0;
reqLvl = userPowerAction.arguments.length > 1 ? userPowerAction.arguments[1] : 0;
// 54 rune files repeat their whole list; the DB kept each entry once
if (!seenEntries.add(userPowerAction.power + ":" + rank + ":" + reqLvl))
continue;
powerAction = PowerActionTemplate._powerActions.get(userPowerAction.power);
if (powerAction != null && !powerAction.effect_ids.isEmpty())
effectID = powerAction.effect_ids.get(0);
else
effectID = userPowerAction.power;
runeEffects.add(new MobBaseEffects(Hasher.SBStringHash(effectID), rank, reqLvl));
}
return runeEffects;
}
/** /**
* @return the mobBaseID * @return the mobBaseID
+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);
} }
+3 -27
View File
@@ -9,8 +9,6 @@
package engine.objects; package engine.objects;
import engine.gameManager.DbManager;
import engine.net.ByteBufferWriter;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
@@ -23,7 +21,6 @@ public class NPCRune extends AbstractGameObject {
private final RuneBase runeBase; private final RuneBase runeBase;
private final int player; private final int player;
private final ArrayList<SkillReq> skillsGranted; private final ArrayList<SkillReq> skillsGranted;
private final ArrayList<RuneBaseEffect> effectsGranted;
/** /**
* No Table ID Constructor * No Table ID Constructor
@@ -33,10 +30,9 @@ public class NPCRune extends AbstractGameObject {
this.runeBase = runeBase; this.runeBase = runeBase;
this.player = characterID; this.player = characterID;
if (this.runeBase != null) if (this.runeBase != null)
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); this.skillsGranted = SkillReq.getSkillReqsForRune(this.runeBase.getObjectUUID());
else else
this.skillsGranted = new ArrayList<>(); this.skillsGranted = new ArrayList<>();
this.effectsGranted = new ArrayList<>();
} }
/** /**
@@ -47,10 +43,9 @@ public class NPCRune extends AbstractGameObject {
this.runeBase = runeBase; this.runeBase = runeBase;
this.player = characterID; this.player = characterID;
if (this.runeBase == null) if (this.runeBase == null)
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); this.skillsGranted = SkillReq.getSkillReqsForRune(this.runeBase.getObjectUUID());
else else
this.skillsGranted = new ArrayList<>(); this.skillsGranted = new ArrayList<>();
this.effectsGranted = new ArrayList<>();
} }
/** /**
@@ -62,25 +57,10 @@ public class NPCRune extends AbstractGameObject {
this.runeBase = RuneBase.getRuneBase(rs.getInt("RuneBaseID")); this.runeBase = RuneBase.getRuneBase(rs.getInt("RuneBaseID"));
this.player = rs.getInt("NpcID"); this.player = rs.getInt("NpcID");
if (this.runeBase != null) { if (this.runeBase != null) {
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.runeBase.getObjectUUID()); this.skillsGranted = SkillReq.getSkillReqsForRune(this.runeBase.getObjectUUID());
this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.runeBase.getObjectUUID());
} else { } else {
Logger.error("Failed to find RuneBase for NPCRune " + this.getObjectUUID()); Logger.error("Failed to find RuneBase for NPCRune " + this.getObjectUUID());
this.skillsGranted = new ArrayList<>(); this.skillsGranted = new ArrayList<>();
this.effectsGranted = new ArrayList<>();
}
}
public static void serializeForClientMsg(NPCRune npcRune, ByteBufferWriter writer) {
if (npcRune.runeBase != null) {
writer.putInt(npcRune.runeBase.getType());
writer.putInt(0);
writer.putInt(npcRune.runeBase.getObjectUUID());
writer.putInt(npcRune.getObjectType().ordinal());
writer.putInt(npcRune.getObjectUUID());
} else {
for (int i = 0; i < 5; i++)
writer.putInt(0);
} }
} }
@@ -109,10 +89,6 @@ public class NPCRune extends AbstractGameObject {
* Serializing * Serializing
*/ */
public ArrayList<RuneBaseEffect> getEffectsGranted() {
return this.effectsGranted;
}
@Override @Override
public void updateDatabase() { public void updateDatabase() {
-4
View File
@@ -236,10 +236,6 @@ public class PlayerBonuses {
} }
public void grantEffect(RuneBaseEffect rbe) {
}
public void setFloat(AbstractEffectModifier mod, float val) { public void setFloat(AbstractEffectModifier mod, float val) {
if (val != 0) if (val != 0)
this.bonusFloats.put(mod, val); this.bonusFloats.put(mod, val);
+78 -81
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;
@@ -729,6 +730,14 @@ public class PlayerCharacter extends AbstractCharacter {
} }
// The client enforces these rules, so a refusal usually means a modified client.
private static PlayerCharacter refuseCreation(Account account, ClientConnection clientConnection, String reason) {
Logger.warn("Character creation refused: " + reason + " ACCOUNT: " + account.getUname() + "(" + account.getObjectUUID()
+ ") IP ADDRESS: " + clientConnection.getClientIpAddress());
return null;
}
public static PlayerCharacter generatePCFromCommitNewCharacterMsg(Account a, CommitNewCharacterMsg msg, ClientConnection clientConnection) { public static PlayerCharacter generatePCFromCommitNewCharacterMsg(Account a, CommitNewCharacterMsg msg, ClientConnection clientConnection) {
String firstName = msg.getFirstName().trim(); String firstName = msg.getFirstName().trim();
@@ -767,8 +776,7 @@ public class PlayerCharacter extends AbstractCharacter {
Race race = Race.getRace(raceID); Race race = Race.getRace(raceID);
if (race == null) { if (race == null) {
Logger.info("Invalid RaceID: " + raceID); return refuseCreation(a, clientConnection, "Invalid RaceID: " + raceID);
return null;
} }
// Verify BaseClass Object. // Verify BaseClass Object.
@@ -776,8 +784,7 @@ public class PlayerCharacter extends AbstractCharacter {
BaseClass baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID); BaseClass baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID);
if (baseClass == null) { if (baseClass == null) {
Logger.info("Invalid BaseClasID: " + baseClassID); return refuseCreation(a, clientConnection, "Invalid BaseClasID: " + baseClassID);
return null;
} }
// Verify Race/baseClass combo. // Verify Race/baseClass combo.
@@ -792,8 +799,7 @@ public class PlayerCharacter extends AbstractCharacter {
} }
if (!valid) { if (!valid) {
Logger.info("Invalid BaseClass/Race Combo"); return refuseCreation(a, clientConnection, "Invalid BaseClass/Race Combo");
return null;
} }
// Verify HairStyle/BeardStyle/SkinColor/HairColor/BeardColor // Verify HairStyle/BeardStyle/SkinColor/HairColor/BeardColor
@@ -804,23 +810,19 @@ public class PlayerCharacter extends AbstractCharacter {
int beardColorID = msg.getBeardColor(); int beardColorID = msg.getBeardColor();
if (!race.isValidHairStyle(hairStyleID)) { if (!race.isValidHairStyle(hairStyleID)) {
Logger.info("Invalid HairStyleID: " + hairStyleID + " for race: " + race.getName()); return refuseCreation(a, clientConnection, "Invalid HairStyleID: " + hairStyleID + " for race: " + race.getName());
return null;
} }
if (!race.isValidSkinColor(skinColorID)) { if (!race.isValidSkinColor(skinColorID)) {
Logger.info("Invalid skinColorID: " + skinColorID + " for race: " + race.getName()); return refuseCreation(a, clientConnection, "Invalid skinColorID: " + skinColorID + " for race: " + race.getName());
return null;
} }
if (!race.isValidHairColor(hairColorID)) { if (!race.isValidHairColor(hairColorID)) {
Logger.info("Invalid hairColorID: " + hairColorID + " for race: " + race.getName()); return refuseCreation(a, clientConnection, "Invalid hairColorID: " + hairColorID + " for race: " + race.getName());
return null;
} }
if (!race.isValidBeardColor(beardColorID)) { if (!race.isValidBeardColor(beardColorID)) {
Logger.info("Invalid beardColorID: " + beardColorID + " for race: " + race.getName()); return refuseCreation(a, clientConnection, "Invalid beardColorID: " + beardColorID + " for race: " + race.getName());
return null;
} }
// Get stat modifiers // Get stat modifiers
@@ -832,8 +834,7 @@ public class PlayerCharacter extends AbstractCharacter {
if (intMod < -5 || dexMod < -5 || conMod < -5 || strMod < -5 || spiMod < -5) { if (intMod < -5 || dexMod < -5 || conMod < -5 || strMod < -5 || spiMod < -5) {
Logger.error("NEGATIVE STAT CHEAT ATTEMPTED! ACCOUNT: " + a.getUname() + "(" + a.getObjectUUID() + ") IP ADDRESS: " + clientConnection.getClientIpAddress()); return refuseCreation(a, clientConnection, "Negative stat mod");
return null;
} }
// calculate current stats: // calculate current stats:
@@ -850,12 +851,11 @@ public class PlayerCharacter extends AbstractCharacter {
short intMax = race.getIntMax(); short intMax = race.getIntMax();
short spiMax = race.getSpiMax(); short spiMax = race.getSpiMax();
// Verify not too many runes applied // 10 rune slots including race and class (client 0xBA8C80)
int numRunes = msg.getNumRunes(); int numRunes = msg.getNumRunes();
if (numRunes > 16) { if (numRunes > 10) {
Logger.info("Too many Runes applied"); return refuseCreation(a, clientConnection, "Too many Runes applied");
return null;
} }
// Get Runes // Get Runes
@@ -865,7 +865,8 @@ public class PlayerCharacter extends AbstractCharacter {
int[] characterRunes = msg.getRunes(); int[] characterRunes = msg.getRunes();
HashSet<Byte> usedRunesSubType = new HashSet<>(); HashSet<Integer> usedRuneIDs = new HashSet<>();
HashSet<Integer> usedSubTypeTokens = new HashSet<>();
HashSet<RuneBase> usedRunes = new HashSet<>(); HashSet<RuneBase> usedRunes = new HashSet<>();
// So that all the penalties can be added at the end. // So that all the penalties can be added at the end.
@@ -905,38 +906,33 @@ public class PlayerCharacter extends AbstractCharacter {
// Null check // Null check
if (runeBase == null) { if (runeBase == null) {
Logger.info("GOM returned NULL RuneBase"); return refuseCreation(a, clientConnection, "GOM returned NULL RuneBase");
return null;
} }
// Validate Rune against Race // Validate Rune against Race
if (!race.isAllowedRune(runeBase)) { if (!race.isAllowedRune(runeBase)) {
Logger.info("Trait Not valid for Race"); return refuseCreation(a, clientConnection, "Trait Not valid for Race");
return null;
} }
// Validate BaseClass against Race // Validate BaseClass against Race
if (!baseClass.isAllowedRune(runeBase)) { if (!baseClass.isAllowedRune(runeBase)) {
Logger.info("Trait Not valid for BaseClass"); return refuseCreation(a, clientConnection, "Trait Not valid for BaseClass");
return null;
} }
int previous_size = usedRunes.size(); if (!usedRuneIDs.add(runeBase.getObjectUUID())) {
int previous_subtype = usedRunesSubType.size(); return refuseCreation(a, clientConnection, "Duplicate RuneBase");
}
// Creation talents are rank 0, so two of the same sub type block each other (client 0x4695D0)
if (runeBase.getSubTypeToken() != 0 && !usedSubTypeTokens.add(runeBase.getSubTypeToken())) {
return refuseCreation(a, clientConnection, "Duplicate RuneBase Subtype");
}
usedRunes.add(runeBase); usedRunes.add(runeBase);
usedRunesSubType.add(runeBase.getSubtype());
// Duplicate Rune check // Talent cap of 8 (client 0x4695D0)
if (usedRunes.size() <= previous_size) { if (usedRunes.size() > 8) {
Logger.info("Duplicate RuneBase"); return refuseCreation(a, clientConnection, "Too many talents applied");
return null;
}
// Duplicate Subtype check
if (runeBase.getSubtype() != 0 && usedRunesSubType.size() <= previous_subtype) {
Logger.info("Duplicate RuneBase Subtype");
return null;
} }
int maxValue = 0; int maxValue = 0;
@@ -981,43 +977,37 @@ public class PlayerCharacter extends AbstractCharacter {
remainingPoints -= value; remainingPoints -= value;
continue; continue;
} }
Logger.info("Not enough points left"); return refuseCreation(a, clientConnection, "Not enough points left");
return null;
case MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID:
if (strCur >= value) if (strCur >= value)
continue; continue;
Logger.info("STR fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "STR fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID:
if (dexCur >= value) if (dexCur >= value)
continue; continue;
Logger.info("DEX fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "DEX fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID:
if (conCur >= value) if (conCur >= value)
continue; continue;
Logger.info("CON fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "CON fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID:
if (intCur >= value) if (intCur >= value)
continue; continue;
Logger.info("INT fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "INT fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID: case MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID:
if (spiCur >= value) if (spiCur >= value)
continue; continue;
Logger.info("SPI fails to meet Rune Minimum --> " + rb.getName()); return refuseCreation(a, clientConnection, "SPI fails to meet Rune Minimum --> " + rb.getName());
return null;
case MBServerStatics.RUNE_STR_ATTRIBUTE_ID: case MBServerStatics.RUNE_STR_ATTRIBUTE_ID:
if (value < 0) if (value < 0)
@@ -1082,12 +1072,20 @@ public class PlayerCharacter extends AbstractCharacter {
continue; continue;
default: default:
Logger.info("Unknown ATTRIBUTE_ID while checking RuneBaseAttributes: " + attrID); return refuseCreation(a, clientConnection, "Unknown ATTRIBUTE_ID while checking RuneBaseAttributes: " + attrID);
return null;
} }
} }
} }
// Checked before the penalties, as the client's +/- does (0xBAC140): Giant's Blood cuts a max below its stat
if (strCur < 1 || dexCur < 1 || conCur < 1 || intCur < 1 || spiCur < 1) {
return refuseCreation(a, clientConnection, "Attribute below 1");
}
if (strCur > strMax || dexCur > dexMax || conCur > conMax || intCur > intMax || spiCur > spiMax) {
return refuseCreation(a, clientConnection, "Attribute above racial maximum");
}
// Add in all of the penalties. // Add in all of the penalties.
strCur += penalties.get("StrCur"); strCur += penalties.get("StrCur");
strMax += penalties.get("StrMax"); strMax += penalties.get("StrMax");
@@ -1116,8 +1114,7 @@ public class PlayerCharacter extends AbstractCharacter {
} }
if (kit == null) { if (kit == null) {
Logger.info("Unable to find matching kitID: " + kitID); return refuseCreation(a, clientConnection, "Unable to find matching kitID: " + kitID);
return null;
} }
byte runningTrains = 0; byte runningTrains = 0;
@@ -1270,7 +1267,7 @@ public class PlayerCharacter extends AbstractCharacter {
player.endLoc = Vector3fImmutable.ZERO; player.endLoc = Vector3fImmutable.ZERO;
//get level based on experience //get level based on experience
player.level = (short) Experience.getLevel(player.exp); player.level = (short) Experience.getLevel(player, player.exp);
player.setHealth(999999f); player.setHealth(999999f);
player.mana.set(999999f); player.mana.set(999999f);
@@ -1353,10 +1350,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,22 +2813,22 @@ 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;
} }
public short getPCLevel() { public short getPCLevel() {
short level = (short) Experience.getLevel(this.exp); short level = (short) Experience.getLevel(this, this.exp);
if (this.promotionClass == null && level >= 10) if (this.promotionClass == null && level >= 10)
return (short) 10; return (short) 10;
@@ -2876,8 +2873,8 @@ public class PlayerCharacter extends AbstractCharacter {
public synchronized void grantXP(int xp) { public synchronized void grantXP(int xp) {
// Stop players from getting experience past the cap // Stop players from getting experience past the cap
if (this.exp + xp >= Experience.getBaseExperience(MBServerStatics.LEVELCAP)) if (this.exp + xp >= Experience.getBaseExperience(this, MBServerStatics.LEVELCAP))
xp = Experience.getBaseExperience(MBServerStatics.LEVELCAP) - this.exp + 1; xp = Experience.getBaseExperience(this, MBServerStatics.LEVELCAP) - this.exp + 1;
if (xp == 0) if (xp == 0)
xp = 1; xp = 1;
@@ -2893,7 +2890,7 @@ public class PlayerCharacter extends AbstractCharacter {
//not promoted at level 10, start checking for negative EXP //not promoted at level 10, start checking for negative EXP
if (this.promotionClass == null && this.getLevel() == 10) { if (this.promotionClass == null && this.getLevel() == 10) {
if (this.getExp() == Experience.getBaseExperience(11)) { if (this.getExp() == Experience.getBaseExperience(this, 11)) {
if (this.overFlowEXP == 110000) if (this.overFlowEXP == 110000)
return; return;
@@ -2916,10 +2913,10 @@ public class PlayerCharacter extends AbstractCharacter {
} else { } else {
//Player exp begins negative exp, add remaing exp after level 11 to overflow //Player exp begins negative exp, add remaing exp after level 11 to overflow
if (this.getExp() + remainingXP >= Experience.getBaseExperience(11)) { if (this.getExp() + remainingXP >= Experience.getBaseExperience(this, 11)) {
this.overFlowEXP = remainingXP - (Experience.getBaseExperience(11) - this.getExp()); this.overFlowEXP = remainingXP - (Experience.getBaseExperience(this, 11) - this.getExp());
this.exp = Experience.getBaseExperience(11); this.exp = Experience.getBaseExperience(this, 11);
GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP); GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP);
Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg); Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
@@ -2951,7 +2948,7 @@ public class PlayerCharacter extends AbstractCharacter {
else else
nextLevel = level + 2; nextLevel = level + 2;
int nextLevelEXP = Experience.getBaseExperience(nextLevel); int nextLevelEXP = Experience.getBaseExperience(this, nextLevel);
// if overflow > 0, u have level 11 experience + overflow, but level is still 10 due to just promoting. // if overflow > 0, u have level 11 experience + overflow, but level is still 10 due to just promoting.
//Use level + 2 experience for next level. //Use level + 2 experience for next level.
@@ -2974,8 +2971,8 @@ public class PlayerCharacter extends AbstractCharacter {
checkGuildStatus(); checkGuildStatus();
this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES); this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
// double overflow exp used up, remaining overflow will just add to level + 1. // double overflow exp used up, remaining overflow will just add to level + 1.
} else if (this.getExp() + this.overFlowEXP >= Experience.getBaseExperience(level + 1)) { } else if (this.getExp() + this.overFlowEXP >= Experience.getBaseExperience(this, level + 1)) {
int nextExperience = Experience.getBaseExperience(level + 1) + this.overFlowEXP; int nextExperience = Experience.getBaseExperience(this, level + 1) + this.overFlowEXP;
this.exp = nextExperience; this.exp = nextExperience;
this.level++; this.level++;
charReloadRequired = true; charReloadRequired = true;
@@ -2991,10 +2988,10 @@ public class PlayerCharacter extends AbstractCharacter {
} else { } else {
// Hand out each Level one at a time. // Hand out each Level one at a time.
isNewLevel = Experience.getLevel(exp + remainingXP) > this.getLevel(); isNewLevel = Experience.getLevel(this, exp + remainingXP) > this.getLevel();
if (isNewLevel) { if (isNewLevel) {
neededXP = Experience.getBaseExperience(this.getLevel() + 1) - this.exp; neededXP = Experience.getBaseExperience(this, this.getLevel() + 1) - this.exp;
charReloadRequired = true; charReloadRequired = true;
this.exp += neededXP; this.exp += neededXP;
@@ -3768,8 +3765,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 +3788,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);
} }
@@ -4091,7 +4088,7 @@ public class PlayerCharacter extends AbstractCharacter {
tmpLevel = (short) Math.min(tmpLevel, 75); tmpLevel = (short) Math.min(tmpLevel, 75);
while (this.level < tmpLevel) { while (this.level < tmpLevel) {
grantXP(Experience.getBaseExperience(tmpLevel) - this.exp); grantXP(Experience.getBaseExperience(this, tmpLevel) - this.exp);
} }
} }
@@ -9,31 +9,25 @@
package engine.objects; package engine.objects;
import engine.gameManager.DbManager; import engine.mbEnums;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.util.Hasher;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class PowerGrant extends AbstractGameObject { public class PowerGrantedTrains {
private static ConcurrentHashMap<Integer, PowerGrant> grantedPowers = null; private static ConcurrentHashMap<Integer, PowerGrantedTrains> grantedPowers = null;
private int token; private int token;
private ConcurrentHashMap<Integer, Short> runeGrants = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private ConcurrentHashMap<Integer, Short> runeGrants = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
/** public PowerGrantedTrains(int token) {
* ResultSet Constructor this.token = token;
*/
public PowerGrant(ResultSet rs) throws SQLException {
super();
this.token = rs.getInt("powerToken");
runeGrants.put(rs.getInt("runeID"), rs.getShort("grantAmount"));
} }
/* /*
@@ -44,11 +38,11 @@ public class PowerGrant extends AbstractGameObject {
if (pc == null) if (pc == null)
return (short) 0; return (short) 0;
if (PowerGrant.grantedPowers == null) if (PowerGrantedTrains.grantedPowers == null)
fillGrantedPowers(); loadGrantedPowers();
if (PowerGrant.grantedPowers.containsKey(token)) { if (PowerGrantedTrains.grantedPowers.containsKey(token)) {
PowerGrant pg = PowerGrant.grantedPowers.get(token); PowerGrantedTrains pg = PowerGrantedTrains.grantedPowers.get(token);
ConcurrentHashMap<Integer, Short> runeGrants = pg.runeGrants; ConcurrentHashMap<Integer, Short> runeGrants = pg.runeGrants;
ArrayList<Integer> toks = new ArrayList<>(); ArrayList<Integer> toks = new ArrayList<>();
@@ -88,39 +82,33 @@ public class PowerGrant extends AbstractGameObject {
return (short) 0; return (short) 0;
} }
public static void fillGrantedPowers() { // Trains are power_arguments[0] of the first action. Mob race runes are skipped:
// their power actions are mob power ranks by level, not trains.
public static void loadGrantedPowers() {
PowerGrant.grantedPowers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); ConcurrentHashMap<Integer, PowerGrantedTrains> powerGrants = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
int token;
int grantCount = 0;
try (Connection connection = DbManager.getConnection(); for (RuneTemplate rune : RuneTemplate._runes.values()) {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM static_power_powergrant")) {
if (rune.rune_type == mbEnums.RuneType.RACE && Race.getRace(rune.template_id) == null)
continue;
ResultSet rs = preparedStatement.executeQuery(); for (Map.Entry<String, ArrayList<int[]>> powerAction : rune.item_power_action.entrySet()) {
if (PowerGrant.grantedPowers.size() > 0) { if (powerAction.getValue().isEmpty())
rs.close(); continue;
return;
token = Hasher.SBStringHash(powerAction.getKey());
powerGrants.computeIfAbsent(token, PowerGrantedTrains::new)
.addRuneGrant(rune.template_id, (short) powerAction.getValue().get(0)[0]);
grantCount++;
} }
while (rs.next()) {
int token = rs.getInt("powerToken");
PowerGrant pg;
if (PowerGrant.grantedPowers.containsKey(token)) {
pg = PowerGrant.grantedPowers.get(token);
pg.addRuneGrant(rs.getInt("runeID"), rs.getShort("grantAmount"));
} else {
pg = new PowerGrant(rs);
PowerGrant.grantedPowers.put(token, pg);
}
}
rs.close();
} catch (SQLException e) {
Logger.error("SQL Error number: " + e.getErrorCode(), e);
} }
PowerGrantedTrains.grantedPowers = powerGrants;
Logger.info("Loaded " + grantCount + " power train grants for " + powerGrants.size() + " powers");
} }
/* /*
@@ -134,8 +122,4 @@ public class PowerGrant extends AbstractGameObject {
private void addRuneGrant(int runeID, short amount) { private void addRuneGrant(int runeID, short amount) {
this.runeGrants.put(runeID, amount); this.runeGrants.put(runeID, amount);
} }
@Override
public void updateDatabase() {
}
} }
+77 -40
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;
@@ -28,11 +33,6 @@ public class PromotionClass extends AbstractGameObject {
private final ArrayList<Integer> allowedRunes; private final ArrayList<Integer> allowedRunes;
private final ArrayList<SkillReq> skillsGranted; private final ArrayList<SkillReq> skillsGranted;
private final ArrayList<PowerReq> powersGranted; private final ArrayList<PowerReq> powersGranted;
private final ArrayList<RuneBaseEffect> effectsGranted;
private final ArrayList<RuneBaseEffect> effectsGrantedFighter;
private final ArrayList<RuneBaseEffect> effectsGrantedHealer;
private final ArrayList<RuneBaseEffect> effectsGrantedRogue;
private final ArrayList<RuneBaseEffect> effectsGrantedMage;
private int token = 0; private int token = 0;
private ArrayList<MobBaseEffects> effectsList = new ArrayList<>(); private ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
@@ -50,11 +50,6 @@ public class PromotionClass extends AbstractGameObject {
this.healthMod = 0f; this.healthMod = 0f;
this.manaMod = 0f; this.manaMod = 0f;
this.staminaMod = 0f; this.staminaMod = 0f;
this.effectsGranted = new ArrayList<>();
this.effectsGrantedFighter = new ArrayList<>();
this.effectsGrantedHealer = new ArrayList<>();
this.effectsGrantedRogue = new ArrayList<>();
this.effectsGrantedMage = new ArrayList<>();
} }
/** /**
@@ -71,15 +66,11 @@ public class PromotionClass extends AbstractGameObject {
this.healthMod = 0f; this.healthMod = 0f;
this.manaMod = 0f; this.manaMod = 0f;
this.staminaMod = 0f; this.staminaMod = 0f;
this.effectsGranted = new ArrayList<>();
this.effectsGrantedFighter = new ArrayList<>();
this.effectsGrantedHealer = new ArrayList<>();
this.effectsGrantedRogue = new ArrayList<>();
this.effectsGrantedMage = new ArrayList<>();
} }
/** /**
* ResultSet Constructor * ResultSet Constructor
* Only the CSR/admin rows 2901+ load from the DB; they have no promotion rune and allow no runes.
*/ */
public PromotionClass(ResultSet rs) throws SQLException { public PromotionClass(ResultSet rs) throws SQLException {
super(rs); super(rs);
@@ -90,19 +81,82 @@ public class PromotionClass extends AbstractGameObject {
this.healthMod = rs.getFloat("healthMod"); this.healthMod = rs.getFloat("healthMod");
this.manaMod = rs.getFloat("manaMod"); this.manaMod = rs.getFloat("manaMod");
this.staminaMod = rs.getFloat("staminaMod"); this.staminaMod = rs.getFloat("staminaMod");
this.allowedRunes = DbManager.PromotionQueries.GET_ALLOWED_RUNES(this); this.allowedRunes = new ArrayList<>();
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.getObjectUUID()); this.skillsGranted = SkillReq.getSkillReqsForRune(this.getObjectUUID());
this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID()); this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID());
this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.getObjectUUID()); this.effectsList = MobBaseEffects.getEffectsForRune(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());
} }
/**
* 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 = SkillReq.getSkillReqsForRune(this.getObjectUUID());
this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID());
this.effectsList = MobBaseEffects.getEffectsForRune(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
@@ -169,23 +223,6 @@ public class PromotionClass extends AbstractGameObject {
* Serializing * Serializing
*/ */
public ArrayList<RuneBaseEffect> getEffectsGranted() {
return this.effectsGranted;
}
public ArrayList<RuneBaseEffect> getEffectsGranted(int baseClassID) {
if (baseClassID == 2500)
return this.effectsGrantedFighter;
else if (baseClassID == 2501)
return this.effectsGrantedHealer;
else if (baseClassID == 2502)
return this.effectsGrantedRogue;
else if (baseClassID == 2503)
return this.effectsGrantedMage;
else
return new ArrayList<>();
}
@Override @Override
public void updateDatabase() { public void updateDatabase() {
// TODO Create update logic. // TODO Create update logic.
+101 -45
View File
@@ -11,14 +11,18 @@ package engine.objects;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
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.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class Race { public class Race {
@@ -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;
@@ -64,57 +71,114 @@ public class Race {
private ArrayList<MobBaseEffects> effectsList = new ArrayList<>(); private ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
/** /**
* ResultSet Constructor * Rune Template Constructor
* minHeight and strHeightMod are server-only and come from the static_rune_race row.
*/ */
public Race(ResultSet rs) throws SQLException { public Race(RuneTemplate rune, float[] heights) {
this.raceRuneID = rs.getInt("ID"); this.raceRuneID = rune.template_id;
this.raceType = mbEnums.RaceType.getRaceTypebyRuneID(raceRuneID); this.raceType = mbEnums.RaceType.getRaceTypebyRuneID(raceRuneID);
this.name = rs.getString("name"); this.name = rune.obj_name;
this.description = rs.getString("description"); this.description = rune.rune_dsc;
this.strStart = rs.getShort("strStart"); this.strStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Strength, 0).shortValue();
this.strMin = rs.getShort("strMin"); this.strMin = 0;
this.strMax = rs.getShort("strMax"); this.strMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Strength, 0).shortValue();
this.dexStart = rs.getShort("dexStart"); this.dexStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Dexterity, 0).shortValue();
this.dexMin = rs.getShort("dexMin"); this.dexMin = 0;
this.dexMax = rs.getShort("dexMax"); this.dexMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Dexterity, 0).shortValue();
this.conStart = rs.getShort("conStart"); this.conStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Constitution, 0).shortValue();
this.conMin = rs.getShort("conMin"); this.conMin = 0;
this.conMax = rs.getShort("conMax"); this.conMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Constitution, 0).shortValue();
this.intStart = rs.getShort("intStart"); this.intStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Intelligence, 0).shortValue();
this.intMin = rs.getShort("intMin"); this.intMin = 0;
this.intMax = rs.getShort("intMax"); this.intMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Intelligence, 0).shortValue();
this.spiStart = rs.getShort("spiStart"); this.spiStart = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Spirit, 0).shortValue();
this.spiMin = rs.getShort("spiMin"); this.spiMin = 0;
this.spiMax = rs.getShort("spiMax"); this.spiMax = rune.rune_max_attr_adj.getOrDefault(mbEnums.AttributeType.Spirit, 0).shortValue();
this.token = rs.getInt("token"); this.token = Hasher.SBStringHash(rune.rune_sub_type);
this.healthBonus = rs.getShort("healthBonus"); this.healthBonus = (short) rune.rune_health;
this.manaBonus = rs.getShort("manaBonus"); this.manaBonus = (short) rune.rune_mana;
this.staminaBonus = rs.getShort("staminaBonus"); this.staminaBonus = (short) rune.rune_stamina;
this.startingPoints = rs.getByte("startingPoints"); this.startingPoints = (byte) (STARTING_POINT_POOL - rune.rune_creation_cost);
this.raceType = RaceType.getRaceTypebyRuneID(this.raceRuneID); this.minHeight = heights[0];
this.minHeight = rs.getFloat("minHeight"); this.strHeightMod = heights[1];
this.strHeightMod = rs.getFloat("strHeightMod"); this.hairStyles = new HashSet<>(rune.rune_hair);
this.hairStyles = DbManager.RaceQueries.HAIR_STYLES_FOR_RACE(raceRuneID); this.beardStyles = new HashSet<>(rune.rune_beard);
this.beardStyles = DbManager.RaceQueries.BEARD_STYLES_FOR_RACE(raceRuneID);
this.skinColors = DbManager.RaceQueries.SKIN_COLOR_FOR_RACE(raceRuneID); this.skinColors = DbManager.RaceQueries.SKIN_COLOR_FOR_RACE(raceRuneID);
this.beardColors = DbManager.RaceQueries.BEARD_COLORS_FOR_RACE(raceRuneID); this.beardColors = DbManager.RaceQueries.BEARD_COLORS_FOR_RACE(raceRuneID);
this.hairColors = DbManager.RaceQueries.HAIR_COLORS_FOR_RACE(raceRuneID); this.hairColors = DbManager.RaceQueries.HAIR_COLORS_FOR_RACE(raceRuneID);
this.baseClasses = DbManager.BaseClassQueries.GET_BASECLASS_FOR_RACE(raceRuneID); this.baseClasses = getBaseClassesForRace(rune);
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(raceRuneID); this.skillsGranted = SkillReq.getSkillReqsForRune(raceRuneID);
this.powersGranted = PowerReq.getPowerReqsForRune(raceRuneID); this.powersGranted = PowerReq.getPowerReqsForRune(raceRuneID);
this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.raceRuneID); this.effectsList = MobBaseEffects.getEffectsForRune(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();
HashMap<Integer, float[]> raceHeights;
ConcurrentHashMap<Integer, Race> races;
RuneTemplate rune;
raceHeights = DbManager.RaceQueries.LOAD_RACE_HEIGHTS();
races = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
for (Map.Entry<Integer, float[]> raceRow : raceHeights.entrySet()) {
rune = RuneTemplate._runes.get(raceRow.getKey());
if (rune == null || rune.rune_type != mbEnums.RuneType.RACE) {
Logger.warn("Race " + raceRow.getKey() + " in static_rune_race has no race rune; skipped");
continue;
}
races.put(rune.template_id, new Race(rune, raceRow.getValue()));
}
Race._raceByID = races;
Logger.info("Loaded " + races.size() + " races from runes");
} }
public static Race getRace(int id) { public static Race getRace(int id) {
return _raceByID.get(id); return _raceByID.get(id);
} }
public static Collection<Race> getAllRaces() {
return _raceByID.values();
}
@Override @Override
public boolean equals(Object object) { public boolean equals(Object object) {
@@ -258,10 +322,6 @@ public class Race {
return this.powersGranted; return this.powersGranted;
} }
public ArrayList<RuneBaseEffect> getEffectsGranted() {
return RuneBaseEffect.RuneIDBaseEffectMap.get(this.raceRuneID);
}
/* /*
* Validators * Validators
*/ */
@@ -290,10 +350,6 @@ public class Race {
if (this.getAllowedRunes() != null) if (this.getAllowedRunes() != null)
if (this.getAllowedRunes().contains(rb.getObjectUUID())) if (this.getAllowedRunes().contains(rb.getObjectUUID()))
return true; return true;
if (RuneBase.AllowedBaseClassRunesMap.containsKey(111111)) {
if (RuneBase.AllowedRaceRunesMap.get(111111).contains(rb.getObjectUUID()))
return true;
}
return false; return false;
} }
+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;
+196 -74
View File
@@ -11,13 +11,17 @@ package engine.objects;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
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.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -25,57 +29,88 @@ public class RuneBase extends AbstractGameObject {
public static HashMap<Integer, ArrayList<Integer>> AllowedBaseClassRunesMap = new HashMap<>(); public static HashMap<Integer, ArrayList<Integer>> AllowedBaseClassRunesMap = new HashMap<>();
public static HashMap<Integer, ArrayList<Integer>> AllowedRaceRunesMap = new HashMap<>(); public static HashMap<Integer, ArrayList<Integer>> AllowedRaceRunesMap = new HashMap<>();
// Flagged rune_is_standard_character_creation but missing from CharCreateRuneList.cfg ATTRRUNES:
// admin runes 2910/2911, unused duplicates, and Proficient with Bows (commented out in the cfg).
private static final Set<Integer> NON_CREATION_TALENTS = new HashSet<>(Arrays.asList(2910, 2911, 250081, 250116, 250117, 250123, 250124, 250125));
private final String name; private final String name;
private final String description; private final String description;
private final int type;
private final byte subtype;
private final ConcurrentHashMap<Integer, Boolean> race = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private final ConcurrentHashMap<Integer, Boolean> race = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private final ConcurrentHashMap<Integer, Boolean> baseClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private final ConcurrentHashMap<Integer, Boolean> baseClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private final ConcurrentHashMap<Integer, Boolean> promotionClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private final ConcurrentHashMap<Integer, Boolean> promotionClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private final ConcurrentHashMap<Integer, Boolean> discipline = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private final ConcurrentHashMap<Integer, Boolean> discipline = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
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 * Rune Template Constructor
* Talents with the same sub type token replace each other by rank (client 0x4695D0), so no overwrite list.
*/ */
public RuneBase(String name, String description, int type, byte subtype, ArrayList<RuneBaseAttribute> attrs) { public RuneBase(RuneTemplate rune) {
super(); super(rune.template_id);
this.name = name; Integer classRuneID;
this.description = description; Integer discRuneID;
this.type = type; ArrayList<Integer> raceRuneIDs;
this.subtype = subtype; RuneTemplate classRune;
} this.name = rune.item_base_name;
this.description = rune.rune_dsc;
this.effectsList = MobBaseEffects.getEffectsForRune(rune.template_id);
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) {
* Normal Constructor raceRuneIDs = CObjectIndex.getRaceRuneIds(raceType);
*/
public RuneBase(String name, String description, int type, byte subtype, ArrayList<RuneBaseAttribute> attrs, int newUUID) {
super(newUUID);
this.name = name; if (raceRuneIDs.isEmpty())
this.description = description; Logger.warn("RuneBase " + rune.template_id + ": race " + raceType + " has no race rune");
this.type = type;
this.subtype = subtype;
} for (int raceRuneID : raceRuneIDs)
this.race.put(raceRuneID, true);
}
/** for (mbEnums.ClassType classType : rune.item_class_req) {
* ResultSet Constructor classRuneID = CObjectIndex.getClassRuneId(classType);
*/
public RuneBase(ResultSet rs) throws SQLException {
super(rs);
this.name = rs.getString("name"); if (classRuneID == null) {
this.description = rs.getString("description"); Logger.warn("RuneBase " + rune.template_id + ": class " + classType + " has no class rune");
this.type = rs.getInt("type"); continue;
this.subtype = rs.getByte("subtype"); }
DbManager.RuneBaseQueries.GET_RUNE_REQS(this); classRune = RuneTemplate._runes.get(classRuneID);
this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID());
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) {
@@ -83,28 +118,113 @@ public class RuneBase extends AbstractGameObject {
if (tableId == 0) if (tableId == 0)
return null; return null;
RuneBase rb = (RuneBase) DbManager.getFromCache(mbEnums.GameObjectType.RuneBase, tableId); return (RuneBase) DbManager.getFromCache(mbEnums.GameObjectType.RuneBase, tableId);
if (rb != null)
return rb;
return DbManager.RuneBaseQueries.GET_RUNEBASE(tableId);
} }
public static void serializeForClientMsg(RuneBase runeBase, ByteBufferWriter writer) { // Rune bases are every non-RACE rune: the classes, disciplines and talents (the 315 former static_rune_runebase rows).
writer.putInt(runeBase.type);
writer.putInt(0); // Pad
writer.putInt(runeBase.getObjectUUID());
writer.putInt(runeBase.getObjectType().ordinal());
writer.putInt(runeBase.getObjectUUID());
}
public static void LoadAllRuneBases() { public static void LoadAllRuneBases() {
DbManager.RuneBaseQueries.LOAD_ALL_RUNEBASES(); ArrayList<RuneBaseAttribute> attributes;
RuneBase.AllowedBaseClassRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_BASECLASS(); int loadedCount = 0;
RuneBase.AllowedRaceRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_RACE();
RuneBaseAttribute.runeBaseAttributeMap.clear();
for (RuneTemplate rune : RuneTemplate._runes.values()) {
if (rune.rune_type == mbEnums.RuneType.RACE)
continue;
DbManager.addToCache(new RuneBase(rune));
attributes = RuneBaseAttribute.getAttributesForRune(rune);
// getAttrs() returns null for a rune without attributes
if (!attributes.isEmpty())
RuneBaseAttribute.runeBaseAttributeMap.put(rune.template_id, attributes);
loadedCount++;
}
Logger.info("Loaded " + loadedCount + " rune bases from runes");
loadCreationTalents();
}
// Every creation race and class gets an explicit list; there is no "any" entry.
private static void loadCreationTalents() {
ArrayList<Integer> creationRaceIDs = new ArrayList<>();
ArrayList<Integer> creationClassIDs = new ArrayList<>();
ArrayList<Integer> listedRaceIDs;
ArrayList<Integer> listedClassIDs;
Integer classRuneID;
boolean listed;
boolean allowed;
int talentCount = 0;
AllowedRaceRunesMap = new HashMap<>();
AllowedBaseClassRunesMap = new HashMap<>();
for (RuneTemplate rune : RuneTemplate._runes.values()) {
if (!rune.rune_is_standard_character_creation)
continue;
if (rune.rune_type == mbEnums.RuneType.RACE) {
creationRaceIDs.add(rune.template_id);
AllowedRaceRunesMap.put(rune.template_id, new ArrayList<>());
}
if (rune.rune_type == mbEnums.RuneType.CLASS) {
creationClassIDs.add(rune.template_id);
AllowedBaseClassRunesMap.put(rune.template_id, new ArrayList<>());
}
}
for (RuneTemplate rune : RuneTemplate._runes.values()) {
if (!rune.rune_is_standard_character_creation || rune.rune_type != mbEnums.RuneType.TALENT)
continue;
if (NON_CREATION_TALENTS.contains(rune.template_id))
continue;
listedRaceIDs = new ArrayList<>();
listedClassIDs = new ArrayList<>();
for (mbEnums.MonsterType raceType : rune.item_race_req)
listedRaceIDs.addAll(CObjectIndex.getRaceRuneIds(raceType));
for (mbEnums.ClassType classType : rune.item_class_req) {
classRuneID = CObjectIndex.getClassRuneId(classType);
if (classRuneID != null)
listedClassIDs.add(classRuneID);
}
// restrict true = blacklist, false = whitelist (ArcItemRestrict_Check 0x4BAC60)
for (int raceRuneID : creationRaceIDs) {
listed = listedRaceIDs.contains(raceRuneID);
allowed = rune.item_race_req_restrict ? !listed : listed;
if (allowed)
AllowedRaceRunesMap.get(raceRuneID).add(rune.template_id);
}
for (int baseClassID : creationClassIDs) {
listed = listedClassIDs.contains(baseClassID);
allowed = rune.item_class_req_restrict ? !listed : listed;
if (allowed)
AllowedBaseClassRunesMap.get(baseClassID).add(rune.template_id);
}
talentCount++;
}
Logger.info("Loaded " + talentCount + " creation talents for " + creationRaceIDs.size() + " races and "
+ creationClassIDs.size() + " classes from runes");
} }
@Override @Override
@@ -124,15 +244,6 @@ public class RuneBase extends AbstractGameObject {
return false; return false;
} }
if (this.type != rbObj.type) {
return false;
}
if (this.subtype != rbObj.subtype) {
return false;
}
return true; return true;
} }
@@ -150,17 +261,6 @@ public class RuneBase extends AbstractGameObject {
return description; return description;
} }
public int getType() {
return type;
}
/**
* @return the subtype
*/
public byte getSubtype() {
return subtype;
}
/** /**
* @return the attrs * @return the attrs
*/ */
@@ -184,8 +284,30 @@ public class RuneBase extends AbstractGameObject {
return this.discipline; return this.discipline;
} }
public ArrayList<Integer> getOverwrite() { public mbEnums.RuneType getRuneType() {
return this.overwrite; 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;
} }
/* /*
+23 -32
View File
@@ -9,12 +9,12 @@
package engine.objects; package engine.objects;
import engine.gameManager.DbManager; import engine.mbEnums;
import engine.mbEnums.GameObjectType; import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
@@ -23,7 +23,6 @@ public class RuneBaseAttribute extends AbstractGameObject {
public static HashMap<Integer, ArrayList<RuneBaseAttribute>> runeBaseAttributeMap = new HashMap<>(); public static HashMap<Integer, ArrayList<RuneBaseAttribute>> runeBaseAttributeMap = new HashMap<>();
private short attributeID; private short attributeID;
private short modValue; private short modValue;
private int runeBaseID;
/** /**
@@ -46,39 +45,31 @@ public class RuneBaseAttribute extends AbstractGameObject {
this.modValue = modValue; this.modValue = modValue;
} }
/** // Attribute ids: 0 = rune_creation_cost, 1-5 = rune_attr_adj, 6-10 = rune_max_attr_adj,
* ResultSet Constructor // 11-15 = item_attr_req; the AttributeType ordinals Strength..Spirit are 1-5.
*/
public RuneBaseAttribute(ResultSet rs) throws SQLException {
super(rs);
this.attributeID = rs.getShort("attributeID"); public static ArrayList<RuneBaseAttribute> getAttributesForRune(RuneTemplate rune) {
this.modValue = rs.getShort("modValue");
this.runeBaseID = rs.getInt("RuneBaseID"); 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;
} }
public static void LoadAllAttributes() { private static void addAttributes(ArrayList<RuneBaseAttribute> attributes, EnumMap<mbEnums.AttributeType, Integer> values, int idOffset) {
DbManager.RuneBaseAttributeQueries.GET_ATTRIBUTES_FOR_RUNEBASE();
for (mbEnums.AttributeType attributeType : values.keySet()) {
if (values.get(attributeType) == 0)
continue;
//cache attributeLists for rune. attributes.add(new RuneBaseAttribute((short) (attributeType.ordinal() + idOffset), values.get(attributeType).shortValue()));
for (AbstractGameObject ago : DbManager.getList(GameObjectType.RuneBaseAttribute)) {
RuneBaseAttribute runeBaseAttribute = (RuneBaseAttribute) ago;
int runeBaseID = ((RuneBaseAttribute) runeBaseAttribute).runeBaseID;
if (runeBaseAttributeMap.get(runeBaseID) == null) {
ArrayList<RuneBaseAttribute> attributeList = new ArrayList<>();
attributeList.add(runeBaseAttribute);
runeBaseAttributeMap.put(runeBaseID, attributeList);
} else {
ArrayList<RuneBaseAttribute> attributeList = runeBaseAttributeMap.get(runeBaseID);
attributeList.add(runeBaseAttribute);
runeBaseAttributeMap.put(runeBaseID, attributeList);
}
} }
} }
/* /*
-71
View File
@@ -1,71 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.objects;
import engine.gameManager.DbManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
public class RuneBaseEffect extends AbstractGameObject {
public static HashMap<Integer, ArrayList<RuneBaseEffect>> RuneIDBaseEffectMap = new HashMap<>();
private byte type;
private String name;
private short amount;
private int runeBaseID;
/**
* ResultSet Constructor
*/
public RuneBaseEffect(ResultSet rs) throws SQLException {
super(rs);
this.type = rs.getByte("type");
this.name = rs.getString("name");
this.amount = rs.getShort("amount");
this.runeBaseID = rs.getInt("runeID");
}
/*
* Getters
*/
public static void LoadRuneBaseEffects() {
//cache runebase effects.
DbManager.RuneBaseEffectQueries.GET_ALL_RUNEBASE_EFFECTS();
//store runebase effects in new hashmap.
RuneBaseEffect.RuneIDBaseEffectMap = DbManager.RuneBaseEffectQueries.LOAD_BASEEFFECTS_FOR_RUNEBASE();
}
public int getType() {
return this.type;
}
public String getName() {
return this.name;
}
public short getAmount() {
return this.amount;
}
@Override
public void updateDatabase() {
}
public int getRuneBaseID() {
return runeBaseID;
}
}
+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)) {
+68 -20
View File
@@ -10,18 +10,25 @@
package engine.objects; package engine.objects;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SkillReq extends AbstractGameObject { public class SkillReq extends AbstractGameObject {
private static final int DEFAULT_SKILL_REQ_AMOUNT = 80;
private static final ConcurrentHashMap<Integer, ArrayList<SkillReq>> skillReqsByRune = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private int skillID; private int skillID;
private short level; private short level;
private ArrayList<Byte> skillReqs; private ArrayList<Byte> skillReqs;
private HashMap<Byte, Integer> skillReqAmounts;
/* This shouldn't be used /* This shouldn't be used
public SkillReq(SkillsBase skillsBase, short level, ArrayList<Byte>skillReqs) { public SkillReq(SkillsBase skillsBase, short level, ArrayList<Byte>skillReqs) {
@@ -52,25 +59,62 @@ public class SkillReq extends AbstractGameObject {
} }
*/ */
/** // skill_value is the level the skill is granted at; skill_granted_skills are prereq skills with amounts
* ResultSet Constructor public SkillReq(RuneTemplate.SkillGrant skillGrant, SkillsBase skillsBase, int runeID) {
*/ super();
public SkillReq(ResultSet rs) throws SQLException {
super(rs, 0);
this.skillID = rs.getInt("skillID");
this.level = rs.getShort("level");
skillReqs = new ArrayList<>(0);
int skillReq; SkillsBase reqSkillsBase;
skillReq = rs.getInt("skillreq1");
if (skillReq > 0) this.skillID = skillsBase.getObjectUUID();
skillReqs.add((byte) skillReq); this.level = (short) skillGrant.skill_value;
skillReq = rs.getInt("skillreq2"); this.skillReqs = new ArrayList<>(0);
if (skillReq > 0) this.skillReqAmounts = new HashMap<>();
skillReqs.add((byte) skillReq);
skillReq = rs.getInt("skillreq3"); for (Map.Entry<String, Integer> reqSkill : skillGrant.skill_granted_skills.entrySet()) {
if (skillReq > 0)
skillReqs.add((byte) skillReq); reqSkillsBase = DbManager.SkillsBaseQueries.GET_BASE_BY_NAME(reqSkill.getKey());
if (reqSkillsBase == null) {
Logger.warn("Rune " + runeID + " skill grant " + skillGrant.skill_type + ": unknown prereq skill " + reqSkill.getKey());
continue;
}
this.skillReqs.add((byte) reqSkillsBase.getObjectUUID());
this.skillReqAmounts.put((byte) reqSkillsBase.getObjectUUID(), reqSkill.getValue());
}
}
public static ArrayList<SkillReq> getSkillReqsForRune(int runeID) {
return skillReqsByRune.computeIfAbsent(runeID, SkillReq::loadSkillReqsForRune);
}
private static ArrayList<SkillReq> loadSkillReqsForRune(int runeID) {
ArrayList<SkillReq> runeSkillReqs = new ArrayList<>();
ArrayList<String> skippedGrants = new ArrayList<>();
RuneTemplate rune = RuneTemplate._runes.get(runeID);
SkillsBase skillsBase;
if (rune == null)
return runeSkillReqs;
for (RuneTemplate.SkillGrant skillGrant : rune.rune_skill_grant) {
skillsBase = DbManager.SkillsBaseQueries.GET_BASE_BY_NAME(skillGrant.skill_type);
if (skillsBase == null) {
skippedGrants.add(skillGrant.skill_type);
continue;
}
runeSkillReqs.add(new SkillReq(skillGrant, skillsBase, runeID));
}
// Expected at every boot: Archon 2018 lists resistances as skill grants (Duelist 3010 lists Dueling); not a bug
if (!skippedGrants.isEmpty())
Logger.warn("Rune " + runeID + ": skipped non-skill grants " + skippedGrants);
return runeSkillReqs;
} }
/* /*
@@ -92,6 +136,10 @@ public class SkillReq extends AbstractGameObject {
return this.skillReqs; return this.skillReqs;
} }
public int getSkillReqAmount(byte reqSkillID) {
return this.skillReqAmounts.getOrDefault(reqSkillID, DEFAULT_SKILL_REQ_AMOUNT);
}
@Override @Override
public void updateDatabase() { public void updateDatabase() {
+30
View File
@@ -11,11 +11,16 @@ package engine.objects;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums.SourceType; import engine.mbEnums.SourceType;
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;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -102,6 +107,31 @@ public class SkillsBase extends AbstractGameObject {
return null; return null;
} }
// Level 0 adjusts are the skills a rune grants outright; higher levels are trainer caps
public static void LoadAllRuneSkills() {
HashMap<Integer, Integer> grantedSkills;
int entryCount = 0;
for (RuneTemplate rune : RuneTemplate._runes.values()) {
grantedSkills = new HashMap<>();
for (Map.Entry<String, ArrayList<int[]>> skillAdjust : rune.rune_skill_adj.entrySet())
for (int[] adjust : skillAdjust.getValue())
if (adjust[1] == 0)
grantedSkills.put(Hasher.SBStringHash(skillAdjust.getKey()), adjust[0]);
if (grantedSkills.isEmpty())
continue;
runeSkillsCache.put(rune.template_id, grantedSkills);
entryCount += grantedSkills.size();
}
Logger.info("Loaded " + entryCount + " granted skills for " + runeSkillsCache.size() + " runes");
}
public static void putInCache(SkillsBase sb) { public static void putInCache(SkillsBase sb) {
if (sb == null) if (sb == null)
+7 -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;
@@ -304,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);
@@ -418,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;
@@ -432,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;
} }
@@ -501,7 +502,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 (template == null) if (template == null)
return false; return false;
@@ -517,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;
} }
+6
View File
@@ -97,8 +97,14 @@ public class DeedParser {
deed.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, deedJson.getString("item_type"), templateId, "item_type"); 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_value = deedJson.getInt("item_eq_slots_value");
deed.item_eq_slots_type = deedJson.getBoolean("item_eq_slots_type"); 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.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.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_takeable = deedJson.getBoolean("item_takeable");
deed.item_value = deedJson.getInt("item_value"); deed.item_value = deedJson.getInt("item_value");
deed.item_wt = deedJson.getInt("item_wt"); deed.item_wt = deedJson.getInt("item_wt");
-34
View File
@@ -9,16 +9,10 @@
package engine.parsers.cache; package engine.parsers.cache;
import engine.gameManager.ConfigManager; import engine.gameManager.ConfigManager;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.objects.AbstractGameObject;
import engine.objects.BaseClass;
import engine.parsers.objects.RuneTemplate;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer; import java.util.function.Consumer;
// Dev-only check that compares DB-built objects with the cache JSON before a loader is switched. // Dev-only check that compares DB-built objects with the cache JSON before a loader is switched.
@@ -34,10 +28,6 @@ public class ParityCheck {
private int differenceCount = 0; private int differenceCount = 0;
private int missingCount = 0; private int missingCount = 0;
static {
register("static_rune_baseclass", ParityCheck::compareBaseClasses);
}
private ParityCheck(String table) { private ParityCheck(String table) {
this.table = table; this.table = table;
} }
@@ -91,28 +81,4 @@ public class ParityCheck {
differenceCount++; differenceCount++;
Logger.warn("Parity: " + table + "/" + id + "/" + field + ": db=" + dbValue + " json=" + jsonValue); Logger.warn("Parity: " + table + "/" + id + "/" + field + ": db=" + dbValue + " json=" + jsonValue);
} }
private static void compareBaseClasses(ParityCheck check) {
ConcurrentHashMap<Integer, AbstractGameObject> cache;
BaseClass baseClass;
RuneTemplate rune;
cache = DbManager.getMap(mbEnums.GameObjectType.BaseClass);
if (cache == null) {
Logger.warn("Parity: static_rune_baseclass: no BaseClass objects loaded");
return;
}
for (AbstractGameObject cached : cache.values()) {
baseClass = (BaseClass) cached;
rune = RuneTemplate._runes.get(baseClass.getObjectUUID());
if (!check.compareRow(baseClass.getObjectUUID(), rune))
continue;
check.compareField(baseClass.getObjectUUID(), "name", baseClass.getName(), rune.obj_name);
}
}
} }
+6
View File
@@ -112,8 +112,14 @@ public class RuneParser {
rune.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, runeJson.getString("item_type"), templateId, "item_type"); 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_value = runeJson.getInt("item_eq_slots_value");
rune.item_eq_slots_type = runeJson.getBoolean("item_eq_slots_type"); 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.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.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_takeable = runeJson.getBoolean("item_takeable");
rune.item_value = runeJson.getInt("item_value"); rune.item_value = runeJson.getInt("item_value");
rune.item_wt = runeJson.getInt("item_wt"); rune.item_wt = runeJson.getInt("item_wt");
@@ -8,8 +8,92 @@
package engine.parsers.objects; 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. // Common type for ITEM, DEED and RUNE cache templates, which share one id space.
// Shared obj_/combat_/item_ getters arrive with JSON-Refactor-Plan Task 1.1. // 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 { 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();
} }
@@ -119,4 +119,329 @@ public class DeedTemplate implements CObjectTemplate {
public float deed_namelookup_val; public float deed_namelookup_val;
public boolean deed_custom_city; public boolean deed_custom_city;
public int deed_structure_id; 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;
}
} }
+340 -4
View File
@@ -25,14 +25,15 @@ import java.util.LinkedHashMap;
// Values are kept as the client defines them: no req/res splitting, // Values are kept as the client defines them: no req/res splitting,
// no defaults substituted. Strings with a matching enum in mbEnums // no defaults substituted. Strings with a matching enum in mbEnums
// are stored as that enum; everything else stays a String. // are stored as that enum; everything else stays a String.
// // Read only once loaded.
// Not to be confused with engine.objects.ItemTemplate, which is loaded
// from the database. Read only once loaded.
public class ItemTemplate implements CObjectTemplate { public class ItemTemplate implements CObjectTemplate {
public static HashMap<Integer, ItemTemplate> _items = new HashMap<>(); 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; public int template_id;
// obj_ // obj_
@@ -138,6 +139,341 @@ public class ItemTemplate implements CObjectTemplate {
public int item_bane_rank; public int item_bane_rank;
public boolean item_ignore_saved_actions; 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). // item_weapon (ArcAttackInfo_LoadWeapon, sb.exe 0x61FA60).
public static class Weapon { public static class Weapon {
@@ -149,7 +485,7 @@ public class ItemTemplate implements CObjectTemplate {
public int weapon_combat_idle_anim; public int weapon_combat_idle_anim;
public final ArrayList<Damage> weapon_damage = new ArrayList<>(); public final ArrayList<Damage> weapon_damage = new ArrayList<>();
// [animation, duration] pairs in file order; names from engine.objects.ItemTemplate, not the client. // [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_right = new ArrayList<>();
public final ArrayList<int[]> weapon_attack_anim_left = new ArrayList<>(); public final ArrayList<int[]> weapon_attack_anim_left = new ArrayList<>();
} }
+326 -1
View File
@@ -141,7 +141,7 @@ public class RuneTemplate implements CObjectTemplate {
public String rune_dsc; public String rune_dsc;
public String rune_fx_txt; public String rune_fx_txt;
// Bit vectors. The existing ItemTemplate decodes these as MobBehaviourType. // Bit vectors. The old static_item_templates export decoded these as MobBehaviourType.
public long rune_group_tactics; public long rune_group_tactics;
public long rune_group_role_set; public long rune_group_role_set;
@@ -191,6 +191,331 @@ public class RuneTemplate implements CObjectTemplate {
public int rune_natural_power_attack; public int rune_natural_power_attack;
public final HashMap<String, String> rune_sparse_data = new HashMap<>(); 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. // 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. // skill_monster_types is empty in every RUNE file, so it is kept as raw JSON.
@@ -0,0 +1,27 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.ArrayList;
import java.util.LinkedHashMap;
// CharCreateRuneList.cfg: the rune ids the client offers at character creation,
// in file order. Race runes carry the sex they are offered to (NONE = both).
public class CharCreateRuneList {
public static CharCreateRuneList _runeList;
public final LinkedHashMap<Integer, mbEnums.SexType> race_runes = new LinkedHashMap<>();
public final ArrayList<Integer> class_runes = new ArrayList<>();
public final ArrayList<Integer> promotion_runes = new ArrayList<>();
public final ArrayList<Integer> attr_runes = new ArrayList<>();
}
@@ -0,0 +1,31 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import java.util.ArrayList;
import java.util.HashMap;
// One row of CompoundCurves.cfg: <name> then segments of <DownperRange> <SimpleCurveName> <params>.
// Most curves are one SlopedLine segment; DefaultCurveUp/Down have two, which the
// CompoundCurveType enum (one value per curve) can't hold.
public class CompoundCurveTemplate {
public static HashMap<String, CompoundCurveTemplate> _curves = new HashMap<>();
public String name;
public final ArrayList<Segment> segments = new ArrayList<>();
public static class Segment {
public int down_per_range;
public String simple_curve;
public float[] params;
}
}
@@ -0,0 +1,76 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.ArrayList;
import java.util.HashMap;
// One EFFECTBEGIN block of Effects.cfg (client ArcEffect_ParseBlock 0x555540).
// Header: <effect_id> <effect_name> <icon>. Keys are flags; SOURCE, MODS and
// CONDITION are nested blocks, kept in file order.
//
// Read only once loaded.
public class EffectTemplate {
public static HashMap<String, EffectTemplate> _effects = new HashMap<>();
public String effect_id;
public String effect_name;
public int icon;
public boolean isitemeffect;
public boolean dontsave;
public boolean ignorenomod;
public boolean isspireeffect;
public final ArrayList<String> sources = new ArrayList<>();
public final ArrayList<ModEntry> mods = new ArrayList<>();
public final ArrayList<ConditionEntry> conditions = new ArrayList<>();
// One MODS line. Which fields are set depends on the mod type's behavior in ModTypes.cfg;
// the layouts are from the client's ArcMod*_Parse functions. args keeps the line as written.
public static class ModEntry {
public mbEnums.ModType mod_type;
public String behavior;
public final ArrayList<String> args = new ArrayList<>();
public float min_mod;
public float max_mod;
// Standard and FPSubType*: the client reads this with String::ToInt, so 16.8 acts as 16 (0x55A6C0)
public float percent_mod;
public String curve;
// The behavior's subtype: attribute, skill, damage type, source type, power type, mod type or monster type
public String type;
// SubTypeSourceType/SubTypePowerType display text, or the two ItemName strings
public String string1;
public String string2;
public String proc_power_id;
public int map_key;
public final ArrayList<Integer> map_values = new ArrayList<>();
}
// One CONDITION line: <condition> <amount> [curve] [damage type ...] (0x588320)
public static class ConditionEntry {
public mbEnums.PowerFailCondition condition;
public float amount;
public String curve;
public final ArrayList<mbEnums.DamageType> damage_types = new ArrayList<>();
}
}
@@ -0,0 +1,25 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import java.util.HashMap;
// One row of ModTypes.cfg: <Type> <Behavior> <DisplayCategory> <DisplayString>.
// Behavior names the client ArcMod class that parses the mod's arguments in Effects.cfg.
// Types are kept as strings: the cfg has types the ModType enum doesn't (AttrReq, BulkFactor, ...).
public class ModTypeTemplate {
public static HashMap<String, ModTypeTemplate> _modTypes = new HashMap<>();
public String type;
public String behavior;
public String display_category;
public String display_string;
}
@@ -0,0 +1,26 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.HashMap;
import java.util.LinkedHashMap;
// One line of PowActionCostInfo.cfg: <id> ("<resource>" <amount>)*.
// The resource cost of a prefix/suffix (client EffectCostMap 0x6E00C0); the source of
// the server's static_effect_costmaps.
public class PowActionCostTemplate {
public static HashMap<String, PowActionCostTemplate> _costs = new HashMap<>();
public String id;
public final LinkedHashMap<mbEnums.ResourceType, Integer> costs = new LinkedHashMap<>();
}
@@ -0,0 +1,86 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.ArrayList;
import java.util.HashMap;
// One POWERACTIONBEGIN block of PowerActions.cfg.
//
// Header: <action_id> <action_type> <type-specific args>. The per-type layouts
// come from the client's ArcPowerAction*_ParseHeader functions. header_args keeps
// every arg as written, including ones the client reads but nothing names yet.
// KEY= lines map to the lower-case fields named after them.
//
// Read only once loaded.
public class PowerActionTemplate {
public static HashMap<String, PowerActionTemplate> _powerActions = new HashMap<>();
public String action_id;
public mbEnums.PowerActionType action_type;
public final ArrayList<String> header_args = new ArrayList<>();
// Header
// ApplyEffect, ApplyEffects, Block, Charm, DamageOverTime, DeferredPower,
// DirectDamage, Invis, SpireDisable, Track, Transform, TransferStatOT
public final ArrayList<String> effect_ids = new ArrayList<>();
public String deferred_power_id;
public int num_iterations;
public int mob_id;
public int mob_level;
public String effect_source_to_remove;
public mbEnums.ItemFlags item_flag;
public boolean item_flag_value;
public String track_filter;
public int max_track;
public mbEnums.CostType transfer_from_type;
public mbEnums.CostType transfer_to_type;
// The client reads both with String::ToInt, so 31.5 acts as 31 (0x56ADF0)
public float transfer_amount;
public String transfer_amount_curve;
public float transfer_efficiency;
public String transfer_efficiency_curve;
// Keys, plus the header's trailing aggressive flag (both set the same client field)
public boolean isaggressive;
public boolean bladetrails;
public boolean isresistable;
public String applyeffectself;
public String applyeffectother;
public String wearoffeffectself;
public String wearoffeffectother;
public boolean applyeffectblank;
public boolean wearoffeffectblank;
public boolean is_available_for_formulae;
public boolean removeall;
public final ArrayList<Integer> bodyparts = new ArrayList<>();
public final ArrayList<Integer> femalebodyparts = new ArrayList<>();
public boolean shouldshowweapons;
public boolean shouldshowarmor;
public final ArrayList<Integer> attackanims = new ArrayList<>();
public float[] scalefactor;
public String effectid;
public int levelcap;
public String levelcap_curve;
public boolean clearaggro;
public boolean targetbecomespet;
public boolean destroyoldpet;
public String rootfsmid;
public mbEnums.DamageType damagetype;
public final ArrayList<Integer> splashdamage = new ArrayList<>();
}
@@ -0,0 +1,132 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
// One POWERBEGIN block of Powers.cfg (client loader 0x56E410).
// Header field names follow the client's "<field> Error with line" messages.
// KEY= lines map to the lower-case fields named after them.
//
// Read only once loaded.
public class PowerTemplate {
public static HashMap<String, PowerTemplate> _powers = new HashMap<>();
// Header: 22 tokens
public String power_id;
public String power_name;
public mbEnums.PowerType power_type;
public int icon_number;
public String focus;
public mbEnums.PowerTargetType target_type;
public float range;
public mbEnums.AreaType area_type;
public float area_range;
public mbEnums.ExcludeType exclude_type;
public mbEnums.CostType cost_type;
public float cost_amt;
public int difficulty;
public int precision;
public float init_time;
public float release_time;
public float recycle_time;
public boolean hit_roll_yn;
public mbEnums.CastingModeType casting_mode;
public int init_anim;
public int release_anim;
public mbEnums.TargetSelectType target_sel;
// Keys
public final ArrayList<ActionEntry> actions = new ArrayList<>();
public int maxlevel;
public float hatevalue;
public String hatevalue_curve;
public String grantoverridevar;
public mbEnums.PowerCategoryType category;
public final ArrayList<EquipPrereq> eqprereqs = new ArrayList<>();
public final ArrayList<mbEnums.MonsterType> monstertypeprereqs = new ArrayList<>();
public final ArrayList<mbEnums.MonsterType> monstertyperestricts = new ArrayList<>();
public final ArrayList<EffectPrereq> targeteffectprereqs_anded = new ArrayList<>();
public final ArrayList<EffectPrereq> targeteffectprereqs_ored = new ArrayList<>();
public final ArrayList<EffectPrereq> effectprereqs = new ArrayList<>();
// CURVE= <COSTAMT|INITTIME|RECYCLETIME> <curve>: the curve applied to that header value
public final LinkedHashMap<String, String> curves = new LinkedHashMap<>();
public final ArrayList<String> descriptions = new ArrayList<>();
public int loopanimid;
public boolean bladetrails;
public boolean cancastwhilemoving;
public boolean cancastwhileflying;
public boolean shouldcheckpath;
public boolean isadminpower;
public boolean cantargetadmin;
public boolean isprojectile;
public boolean sticky;
public int maxnummobtargets;
public int maxnumplayertargets;
public float pulseinfo_duration;
public int pulseinfo_iterations;
public final ArrayList<Integer> sounds = new ArrayList<>();
public int casterspulseparticle;
public final ArrayList<EffectTemplate.ConditionEntry> conditions = new ArrayList<>();
public String initstring;
public String successself;
public String successother;
public String fizzleself;
public String fizzleother;
public String applyeffectself;
public String applyeffectother;
public String wearoffeffectself;
public String wearoffeffectother;
public String applydamagecaster;
public String applydamagetarget;
public String applydamageother;
// ACTION= <action_id> <min_trains> <max_trains> <duration> <duration_curve> <stack_type>
// <stack_order> <stack_priority>, then up to four optional pairs kept only in args
public static class ActionEntry {
public String action_id;
public int min_trains;
public int max_trains;
public float duration;
public String duration_curve;
public String stack_type;
public int stack_order;
public mbEnums.CategoryToPowerType stack_priority;
public final ArrayList<String> args = new ArrayList<>();
}
// EQPREREQ= repeats <slot> <skill> <required>
public static class EquipPrereq {
public mbEnums.EquipSlotType slot;
public String skill;
public boolean required;
}
public static class EffectPrereq {
public String effect_id;
public int value;
public String message;
}
}
@@ -0,0 +1,28 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.HashMap;
import java.util.LinkedHashMap;
// One line of Skills.cfg: "<uid>" "<name>" <icon> (<attribute> <weight>)* = "<description>".
// The cfg has no numeric ids; the server's skill ids 1-63 stay in static_skill_skillsbase.
public class SkillTemplate {
public static HashMap<String, SkillTemplate> _skills = new HashMap<>();
public String skill_uid;
public String skill_name;
public int icon_number;
public final LinkedHashMap<mbEnums.AttributeType, Integer> attr_weights = new LinkedHashMap<>();
public String description;
}
@@ -0,0 +1,29 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects.wpak;
import engine.mbEnums;
import java.util.ArrayList;
// One kit of StartingKitTable.cfg: a kit line under RACE= <race> <sex> "<class>".
// The client reads a kit as a plain list of up to 10 item template ids; the tab
// columns in the file (Legs, Torso, ...) are not used (0x620250).
// kit_number counts the kits of one race/sex/class from 0.
public class StartingKitTemplate {
public static ArrayList<StartingKitTemplate> _kits = new ArrayList<>();
public String race;
public mbEnums.SexType sex;
public String base_class;
public int kit_number;
public final ArrayList<Integer> item_ids = new ArrayList<>();
}
@@ -0,0 +1,90 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.mbEnums;
import engine.parsers.objects.wpak.CharCreateRuneList;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
// Loads CharCreateRuneList.cfg (client loaders at 0xBA4C20 and three siblings, one per section).
public class CharCreateRuneParser {
private static final String fileName = "Config/CharCreateRuneList.cfg";
public static CharCreateRuneList parseCharCreateRuneFile() {
CharCreateRuneList runeList = new CharCreateRuneList();
WpakBlock root;
int failedCount = 0;
try {
root = WpakFileReader.readBlocks(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : root.lines()) {
Logger.error(line.location() + ": line outside a XxxRUNESBEGIN/END section");
failedCount++;
}
for (WpakBlock block : root.blocks()) {
try {
switch (block.name) {
case "RACERUNES":
for (WpakLine line : block.lines())
runeList.race_runes.put(line.intToken(0), parseSex(line));
break;
case "CLASSRUNES":
readRuneIds(block, runeList.class_runes);
break;
case "PROMOTIONRUNES":
readRuneIds(block, runeList.promotion_runes);
break;
case "ATTRRUNES":
readRuneIds(block, runeList.attr_runes);
break;
default:
throw block.beginLine.error("unexpected " + block.name + "BEGIN");
}
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
}
}
Logger.info("Loaded " + runeList.race_runes.size() + " race, " + runeList.class_runes.size() + " class, "
+ runeList.promotion_runes.size() + " promotion and " + runeList.attr_runes.size() + " attribute creation runes");
if (failedCount > 0)
return null;
return runeList;
}
// The client keeps the previous line's sex when one is missing; every race line in the file has one
private static mbEnums.SexType parseSex(WpakLine line) {
if (!line.has(1))
return mbEnums.SexType.NONE;
return line.enumToken(mbEnums.SexType.class, 1);
}
private static void readRuneIds(WpakBlock block, ArrayList<Integer> runeIds) {
for (WpakLine line : block.lines())
runeIds.add(line.intToken(0));
}
}
@@ -0,0 +1,101 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.parsers.objects.wpak.CompoundCurveTemplate;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.HashMap;
// Loads CompoundCurves.cfg.
public class CompoundCurveParser {
private static final String fileName = "Config/CompoundCurves.cfg";
// Segments run back to back on one line, so the param count per simple curve
// is what tells where the next segment starts.
private static final HashMap<String, Integer> paramCounts = new HashMap<>();
static {
paramCounts.put("FlatLine", 0);
paramCounts.put("SlopedLine", 1);
paramCounts.put("SlopedInitValLine", 1);
}
public static HashMap<String, CompoundCurveTemplate> parseCompoundCurvesFile() {
HashMap<String, CompoundCurveTemplate> curves = new HashMap<>();
ArrayList<WpakLine> lines;
CompoundCurveTemplate curve;
int failedCount = 0;
try {
lines = WpakFileReader.readLines(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : lines) {
try {
curve = parseCurveLine(line);
if (curves.containsKey(curve.name))
throw line.error("duplicate curve " + curve.name);
curves.put(curve.name, curve);
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
}
}
Logger.info("Loaded " + curves.size() + " of " + lines.size() + " compound curves");
if (failedCount > 0)
return null;
return curves;
}
private static CompoundCurveTemplate parseCurveLine(WpakLine line) {
CompoundCurveTemplate curve = new CompoundCurveTemplate();
CompoundCurveTemplate.Segment segment;
Integer paramCount;
int index = 1;
curve.name = line.token(0);
while (line.has(index)) {
segment = new CompoundCurveTemplate.Segment();
segment.down_per_range = line.intToken(index);
segment.simple_curve = line.token(index + 1);
paramCount = paramCounts.get(segment.simple_curve);
if (paramCount == null)
throw line.error("unknown simple curve " + segment.simple_curve);
segment.params = new float[paramCount];
for (int i = 0; i < paramCount; i++)
segment.params[i] = line.floatToken(index + 2 + i);
curve.segments.add(segment);
index += 2 + paramCount;
}
if (curve.segments.isEmpty())
throw line.error("curve " + curve.name + " has no segments");
return curve;
}
}
+224
View File
@@ -0,0 +1,224 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.mbEnums;
import engine.parsers.objects.wpak.EffectTemplate;
import engine.parsers.objects.wpak.ModTypeTemplate;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.HashMap;
// Loads Effects.cfg. ModTypes.cfg and CompoundCurves.cfg must be loaded first:
// a mod's argument layout comes from its behavior, and curve names are checked.
public class EffectParser {
private static final String fileName = "Config/Effects.cfg";
public static HashMap<String, EffectTemplate> parseEffectsFile() {
HashMap<String, EffectTemplate> effects = new HashMap<>();
WpakBlock root;
EffectTemplate effect;
int blockCount = 0;
int failedCount = 0;
try {
root = WpakFileReader.readBlocks(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : root.lines()) {
Logger.error(line.location() + ": line outside EFFECTBEGIN/END");
failedCount++;
}
for (WpakBlock block : root.blocks()) {
try {
if (!block.name.equals("EFFECT"))
throw block.beginLine.error("unexpected " + block.name + "BEGIN");
blockCount++;
effect = parseEffectBlock(block);
if (effects.containsKey(effect.effect_id))
Logger.warn(block.location() + ": duplicate effect " + effect.effect_id + "; the later entry wins");
effects.put(effect.effect_id, effect);
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
} catch (Exception e) {
Logger.error(block.location() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + effects.size() + " of " + blockCount + " effects");
if (failedCount > 0)
return null;
return effects;
}
private static EffectTemplate parseEffectBlock(WpakBlock block) {
EffectTemplate effect = new EffectTemplate();
ArrayList<WpakLine> lines = block.lines();
WpakLine header;
if (lines.isEmpty())
throw block.beginLine.error("effect has no header line");
header = lines.get(0);
effect.effect_id = header.token(0);
effect.effect_name = header.token(1);
effect.icon = header.optionalIntToken(2);
for (int i = 1; i < lines.size(); i++)
parseKey(effect, lines.get(i));
for (WpakBlock child : block.blocks()) {
switch (child.name) {
case "SOURCE":
for (WpakLine line : child.lines())
effect.sources.add(line.token(0));
break;
case "MODS":
for (WpakLine line : child.lines())
effect.mods.add(parseMod(line));
break;
case "CONDITION":
for (WpakLine line : child.lines())
effect.conditions.add(parseCondition(line));
break;
default:
throw child.beginLine.error("unexpected " + child.name + "BEGIN in an effect");
}
if (!child.blocks().isEmpty())
throw child.blocks().get(0).beginLine.error("nested block in " + child.name);
}
return effect;
}
// The client compares these case-insensitively (cfg has IsItemEffect, client ISITEMEFFECT)
private static void parseKey(EffectTemplate effect, WpakLine line) {
switch (line.token(0).toUpperCase()) {
case "ISITEMEFFECT":
effect.isitemeffect = true;
break;
case "DONTSAVE":
effect.dontsave = true;
break;
case "IGNORENOMOD":
effect.ignorenomod = true;
break;
case "ISSPIREEFFECT":
effect.isspireeffect = true;
break;
default:
throw line.error("unknown effect key " + line.token(0));
}
}
private static EffectTemplate.ModEntry parseMod(WpakLine line) {
EffectTemplate.ModEntry mod = new EffectTemplate.ModEntry();
ModTypeTemplate modType = ModTypeTemplate._modTypes.get(line.token(0));
if (modType == null)
throw line.error("mod type " + line.token(0) + " is not in ModTypes.cfg");
mod.mod_type = line.enumToken(mbEnums.ModType.class, 0);
mod.behavior = modType.behavior;
mod.args.addAll(line.tokens.subList(1, line.size()));
switch (mod.behavior) {
case "Flag":
break;
case "Standard":
parseStandard(mod, line);
break;
case "FPSubTypeAttr":
case "FPSubTypeSkill":
case "FPSubTypeDmg":
case "FPSubTypeMonster":
parseStandard(mod, line);
mod.type = line.token(4);
break;
case "DD":
mod.min_mod = line.floatToken(1);
mod.max_mod = line.floatToken(2);
mod.percent_mod = line.floatToken(3);
mod.curve = WpakParserUtil.curveToken(line, 5);
mod.type = line.token(6);
break;
case "ProcInfo":
mod.min_mod = line.floatToken(1);
mod.proc_power_id = line.token(2);
mod.percent_mod = line.floatToken(3);
break;
case "SubTypeSourceType":
case "SubTypePowerType":
mod.type = line.token(1);
mod.string1 = line.has(2) ? String.join(" ", line.tokens.subList(2, line.size())) : null;
break;
case "SubTypeSkill":
case "SubTypeDmg":
case "SubTypeMod":
case "SubTypePower":
mod.type = line.token(1);
break;
case "MapIntToInts":
mod.map_key = line.intToken(1);
mod.map_values.addAll(line.intTokensFrom(2));
break;
case "String":
mod.string1 = line.token(1);
mod.string2 = line.token(2);
break;
default:
throw line.error("unknown mod behavior " + mod.behavior + " for " + line.token(0));
}
return mod;
}
// Tokens after the curve are ignored by the client (some lines end in "Unknown")
private static void parseStandard(EffectTemplate.ModEntry mod, WpakLine line) {
mod.min_mod = line.floatToken(1);
mod.percent_mod = line.floatToken(2);
mod.curve = WpakParserUtil.curveToken(line, 3);
}
static EffectTemplate.ConditionEntry parseCondition(WpakLine line) {
EffectTemplate.ConditionEntry condition = new EffectTemplate.ConditionEntry();
condition.condition = line.enumToken(mbEnums.PowerFailCondition.class, 0);
condition.amount = line.floatToken(1);
condition.curve = line.has(2) ? WpakParserUtil.curveToken(line, 2) : null;
for (int i = 3; i < line.size(); i++)
condition.damage_types.add(line.enumToken(mbEnums.DamageType.class, i));
return condition;
}
}
@@ -0,0 +1,65 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.parsers.objects.wpak.ModTypeTemplate;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.HashMap;
// Loads ModTypes.cfg. Must load before Effects.cfg, whose mod arguments depend on the behavior.
public class ModTypeParser {
private static final String fileName = "Config/ModTypes.cfg";
public static HashMap<String, ModTypeTemplate> parseModTypesFile() {
HashMap<String, ModTypeTemplate> modTypes = new HashMap<>();
ArrayList<WpakLine> lines;
ModTypeTemplate modType;
int failedCount = 0;
try {
lines = WpakFileReader.readLines(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : lines) {
try {
if (line.size() != 4)
throw line.error("expected 4 tokens, found " + line.size());
modType = new ModTypeTemplate();
modType.type = line.token(0);
modType.behavior = line.token(1);
modType.display_category = line.token(2);
modType.display_string = line.token(3);
if (modTypes.containsKey(modType.type))
throw line.error("duplicate mod type " + modType.type);
modTypes.put(modType.type, modType);
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
}
}
Logger.info("Loaded " + modTypes.size() + " of " + lines.size() + " mod types");
if (failedCount > 0)
return null;
return modTypes;
}
}
@@ -0,0 +1,59 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.mbEnums;
import engine.parsers.objects.wpak.PowActionCostTemplate;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.HashMap;
// Loads PowActionCostInfo.cfg (client loader EffectCostMap_LoadPowActionCostInfo 0x6E00C0).
public class PowActionCostParser {
private static final String fileName = "Config/PowActionCostInfo.cfg";
public static HashMap<String, PowActionCostTemplate> parsePowActionCostFile() {
HashMap<String, PowActionCostTemplate> costs = new HashMap<>();
ArrayList<WpakLine> lines;
PowActionCostTemplate cost;
int failedCount = 0;
try {
lines = WpakFileReader.readLines(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : lines) {
try {
// A repeated id adds its pairs to the existing entry, as the client does
cost = costs.computeIfAbsent(line.token(0), id -> new PowActionCostTemplate());
cost.id = line.token(0);
for (int i = 1; i < line.size(); i += 2)
cost.costs.put(line.enumToken(mbEnums.ResourceType.class, i), line.intToken(i + 1));
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
}
}
Logger.info("Loaded " + costs.size() + " power action resource costs from " + lines.size() + " lines");
if (failedCount > 0)
return null;
return costs;
}
}
@@ -0,0 +1,258 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.wpak;
import engine.mbEnums;
import engine.parsers.objects.wpak.PowerActionTemplate;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.HashMap;
// Loads PowerActions.cfg (client loader PowerActions_LoadCfg 0x578EC0).
// CompoundCurves.cfg must be loaded first.
public class PowerActionParser {
private static final String fileName = "Config/PowerActions.cfg";
public static HashMap<String, PowerActionTemplate> parsePowerActionsFile() {
HashMap<String, PowerActionTemplate> powerActions = new HashMap<>();
WpakBlock root;
PowerActionTemplate powerAction;
int blockCount = 0;
int failedCount = 0;
try {
root = WpakFileReader.readBlocks(fileName);
} catch (Exception e) {
Logger.error(fileName + ": " + e.getMessage());
return null;
}
for (WpakLine line : root.lines()) {
Logger.error(line.location() + ": line outside POWERACTIONBEGIN/END");
failedCount++;
}
for (WpakBlock block : root.blocks()) {
try {
if (!block.name.equals("POWERACTION"))
throw block.beginLine.error("unexpected " + block.name + "BEGIN");
blockCount++;
powerAction = parsePowerActionBlock(block);
// The client's map assigns on insert, so a repeated id keeps the later entry
if (powerActions.containsKey(powerAction.action_id))
Logger.warn(block.location() + ": duplicate power action " + powerAction.action_id + "; the later entry wins");
powerActions.put(powerAction.action_id, powerAction);
} catch (WpakFormatException e) {
Logger.error(e.getMessage());
failedCount++;
} catch (Exception e) {
Logger.error(block.location() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + powerActions.size() + " of " + blockCount + " power actions");
if (failedCount > 0)
return null;
return powerActions;
}
private static PowerActionTemplate parsePowerActionBlock(WpakBlock block) {
PowerActionTemplate powerAction = new PowerActionTemplate();
ArrayList<WpakLine> lines = block.lines();
WpakLine header;
if (!block.blocks().isEmpty())
throw block.blocks().get(0).beginLine.error("nested block in a power action");
if (lines.isEmpty())
throw block.beginLine.error("empty power action");
header = lines.get(0);
powerAction.action_id = header.token(0);
powerAction.action_type = header.enumToken(mbEnums.PowerActionType.class, 1);
powerAction.header_args.addAll(header.tokens.subList(2, header.size()));
parseHeader(powerAction, header);
for (int i = 1; i < lines.size(); i++)
parseKey(powerAction, lines.get(i));
return powerAction;
}
// Layouts from the ArcPowerAction*_ParseHeader functions; args start at token 2
private static void parseHeader(PowerActionTemplate powerAction, WpakLine header) {
switch (powerAction.action_type) {
case ApplyEffect:
case Block:
case Charm:
case DirectDamage:
case Invis:
case SpireDisable:
powerAction.effect_ids.add(header.token(2));
powerAction.isaggressive = header.optionalIntToken(3) != 0;
break;
case ApplyEffects:
powerAction.isaggressive = header.intToken(2) != 0;
for (int i = 3; i < header.size(); i++)
powerAction.effect_ids.add(header.token(i));
break;
case DeferredPower:
powerAction.effect_ids.add(header.token(2));
powerAction.isaggressive = header.intToken(3) != 0;
powerAction.deferred_power_id = header.token(4);
break;
case DamageOverTime:
powerAction.effect_ids.add(header.token(2));
powerAction.num_iterations = header.intToken(3);
powerAction.isaggressive = header.optionalIntToken(4) != 0;
break;
case CreateMob:
powerAction.mob_id = header.intToken(2);
powerAction.mob_level = header.intToken(3);
break;
case RemoveEffect:
powerAction.effect_source_to_remove = header.token(2);
break;
case SetItemFlag:
powerAction.item_flag = header.enumToken(mbEnums.ItemFlags.class, 2);
powerAction.item_flag_value = header.optionalBoolToken(3);
break;
case Track:
powerAction.effect_ids.add(header.token(2));
powerAction.track_filter = header.token(5);
powerAction.max_track = header.intToken(7);
break;
case Transform:
powerAction.effect_ids.add(header.token(2));
break;
case TransferStat:
parseTransferStat(powerAction, header);
break;
case TransferStatOT:
parseTransferStat(powerAction, header);
powerAction.effect_ids.add(header.token(10));
powerAction.num_iterations = header.intToken(11);
break;
default:
break;
}
}
private static void parseTransferStat(PowerActionTemplate powerAction, WpakLine header) {
powerAction.transfer_from_type = header.enumToken(mbEnums.CostType.class, 2);
powerAction.transfer_to_type = header.enumToken(mbEnums.CostType.class, 3);
powerAction.transfer_amount = header.floatToken(4);
powerAction.transfer_amount_curve = WpakParserUtil.curveToken(header, 5);
powerAction.transfer_efficiency = header.floatToken(6);
powerAction.transfer_efficiency_curve = WpakParserUtil.curveToken(header, 7);
powerAction.isaggressive = header.optionalIntToken(9) != 0;
}
// Shared keys from ArcPowerAction_ParseKey 0x579820; the rest are per action type.
// The client logs an unknown key and goes on; here it fails the load so no data is dropped silently.
private static void parseKey(PowerActionTemplate powerAction, WpakLine line) {
switch (line.token(0)) {
case "ISAGGRESSIVE=":
powerAction.isaggressive = line.optionalBoolToken(1);
break;
case "BLADETRAILS=":
powerAction.bladetrails = line.optionalBoolToken(1);
break;
case "ISRESISTABLE=":
powerAction.isresistable = line.optionalBoolToken(1);
break;
case "APPLYEFFECTSELF=":
powerAction.applyeffectself = line.token(1);
break;
case "APPLYEFFECTOTHER=":
powerAction.applyeffectother = line.token(1);
break;
case "WEAROFFEFFECTSELF=":
powerAction.wearoffeffectself = line.token(1);
break;
case "WEAROFFEFFECTOTHER=":
powerAction.wearoffeffectother = line.token(1);
break;
case "APPLYEFFECTBLANK=":
powerAction.applyeffectblank = line.optionalBoolToken(1);
break;
case "WEAROFFEFFECTBLANK=":
powerAction.wearoffeffectblank = line.optionalBoolToken(1);
break;
case "IS_AVAILABLE_FOR_FORMULAE=":
powerAction.is_available_for_formulae = line.optionalBoolToken(1);
break;
case "REMOVEALL=":
powerAction.removeall = line.optionalBoolToken(1);
break;
case "BODYPARTS=":
powerAction.bodyparts.addAll(line.intTokensFrom(1));
break;
case "FEMALEBODYPARTS=":
powerAction.femalebodyparts.addAll(line.intTokensFrom(1));
break;
case "SHOULDSHOWWEAPONS=":
powerAction.shouldshowweapons = line.optionalBoolToken(1);
break;
case "SHOULDSHOWARMOR=":
powerAction.shouldshowarmor = line.optionalBoolToken(1);
break;
case "ATTACKANIMS=":
powerAction.attackanims.addAll(line.intTokensFrom(1));
break;
case "SCALEFACTOR=":
powerAction.scalefactor = new float[]{line.floatToken(1), line.floatToken(2), line.floatToken(3)};
break;
case "EFFECTID=":
powerAction.effectid = line.token(1);
break;
case "LEVELCAP=":
powerAction.levelcap = line.intToken(1);
powerAction.levelcap_curve = line.has(2) ? WpakParserUtil.curveToken(line, 2) : null;
break;
case "CLEARAGGRO=":
powerAction.clearaggro = line.optionalBoolToken(1);
break;
case "TARGETBECOMESPET=":
powerAction.targetbecomespet = line.optionalBoolToken(1);
break;
case "DESTROYOLDPET=":
powerAction.destroyoldpet = line.optionalBoolToken(1);
break;
case "ROOTFSMID=":
powerAction.rootfsmid = line.token(1);
break;
case "DAMAGETYPE=":
powerAction.damagetype = line.enumToken(mbEnums.DamageType.class, 1);
break;
case "SPLASHDAMAGE=":
powerAction.splashdamage.addAll(line.intTokensFrom(1));
break;
default:
throw line.error("unknown power action key " + line.token(0));
}
}
}
Loaded 100 of 125 files, more files were not shown because too many files have changed in this diff. Show more