Compare commits

...
Author SHA1 Message Date
MagicBotandClaude Opus 5.5 b2730794a2 Show effect details in /printeffects
Each line now has the effects map key (effect UUID or stack type such as
PetBuff), the effect id and name, rank, time left or static, and the
power that applied it. Help and usage strings no longer describe
/printstats.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:27:27 -04:00
MagicBotandClaude Opus 5.5 c8653626a2 Use a Java 8 set for the DB target type powers
The dev build compiles as Java 8, which has no Set.of.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:03:29 -04:00
MagicBotandClaude Opus 5.5 a6f2495e57 Compare monster type restrictions as MonsterType
PowersBase.monsterTypeRestrictions is now an EnumSet<MonsterType>
(DB values Siege/Centaur/Dwarf/Vampire read with CacheParserUtil.readEnum).
MobBase gets monsterType from its race rune's rune_sub_type (all 2380
RACE runes convert); players use RaceType.getMonsterType(). No string
compares and no rune lookup per cast. Same results as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:59:37 -04:00
MagicBotandClaude Opus 5.5 7b58211386 Enforce MONSTERTYPERESTRICTS on player targets
Players use their race rune's rune_sub_type, like mobs. Only GOR-003
Crimson Pact (single-target ghoul transformation) restricts player
types: it no longer lands on Centaur 2004/2005, Dwarf 2006 or Vampire
2028/2029, matching its description ("Cannot be cast on a vampire,
centaur, or dwarf"). No player race rune has sub-type Siege, so the
Siege-restricted PC/GROUP powers are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:57:04 -04:00
MagicBotandClaude Opus 5.5 86d78a55a9 Enforce MONSTERTYPERESTRICTS on mob targets
static_power_powerbase monsterTypeRestrict1-3 were loaded into
PowersBase.monsterTypeRestrictions but never read. getAllTargets now
drops a Mob whose race rune rune_sub_type is restricted (the rune
sub-type is the client's monster type, g_MonsterTypeEnum at rune +0xC).

86 powers restrict Siege (DB and Powers.cfg agree). Value changes:
- Imbue Golem, Dark Lord's Boon, Strengthen Beast no longer land on the
  Siege-type summons Siege Golem 11999 (ANM-004) and Ice Fiend 11998
  (SUM-005); they still buff the other pets.
- The 83 Siege-restricted harmful powers (bleeds, DOTs, procs, debuffs,
  Challenge, The Lesser Kiss, ...) no longer affect Trebuchet 13171,
  Mangonel 13757, Ballista 13758 or the two Siege summons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:56:22 -04:00
MagicBotandClaude Opus 5.5 3a9fc2607a Accept single-target pet powers on pets
validateTarget tested MASK_MOB before MASK_PET, and summoned pets carry
both, so every single-target PET power (pet heals and buffs such as
Repair Golem, Dark Lord's Favor, Strengthen Beast, Vashteera's Favor)
was rejected on any pet. Pets are now tested first and accept PET or
MOBILE powers, matching the client, where Mobile covers every ArcMobile
and Pet is left to the server (0x49E440). Non-pet mobs are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:52:57 -04:00
MagicBotandClaude Opus 5.5 f7324831b8 Remove the NECROPET power target type
No power uses it after DRU-066 moved to the Powers.cfg Pet type, and its
area query (WorldGrid.getObjectsInRangePartialNecroPets) was an empty
stub. The DB rows are aligned with sql/necro-pet-targeting.sql.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:51:41 -04:00
MagicBotandClaude Opus 5.5 fe5c69cc09 Give necro pets MASK_PET
DRU-066 Braialla's Judgement targets Pet and requires NECROPETSTATS, so
necro pets must be found by the Pet AoE query; the client leaves Pet
targeting to the server (0x49E440 has no Pet case). Reverts the necro
pet exclusion from 40799f96.

Other MASK_PET readers: MobAI pet aggro scan is still off
(PET_AGGRO_ENABLED = false); InterestManager and validateTarget test
MASK_MOB first, so they don't change. DRU-066 is the only AoE PET power.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:50:34 -04:00
MagicBotandClaude Opus 5.5 72dcf07aab Take power target types from Powers.cfg
PowersBase gets setTargetType (the old inline parse, now resetting the
flags first) and the world server re-applies every power's target type
from PowerTemplate after the wpak load. The login server only uses power
tokens and keeps the DB value.

Parity vs static_power_powerbase (1465 powers): 1460 identical, area,
radius type, exclude and target effect prereqs identical for all.
Value changes:
- NEC-018 Sepulchre's Bounty: PET -> MOBILE. Its AoE now collects mobs
  and pets; the NECROPETSTATS target prereq keeps only necro pets.
- DRU-066 Braialla's Judgement: NECROPET -> PET.
Kept on the DB (user, behaviour change, for the Phase 4 power task):
HLR-025 Succor PC (cfg CORPSE), HNT-023 Phaedra's Fury PC (cfg SELF),
RNG-033 Fleet of Foot GROUP (cfg SELF).

Client: ArcPower_IsValidTargetForType 0x49E440 accepts any live
ArcMobile for Mobile and has no case for Pet (server decides).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:49:39 -04:00
MagicBotandClaude Opus 5.5 ad3e616cb7 Remove the static_npc_mobbase_effects loader
Mob effects come from the race rune (2.10b builder) and necro pets get
NECROPETSTATS in code, so GET_RUNEBASE_EFFECTS, the MobBaseEffects
ResultSet constructor, its unused mobBaseID and the equals/hashCode used
for the merge are gone. The table is now unread; drop it with
sql/3.4-mobbase-effects-drop.sql.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:16:19 -04:00
MagicBotandClaude Opus 5.5 fb7e641d9c Grant NECROPETSTATS to necro pets in code
No client data grants NECROPETSTATS (no rune, summon power or item; no
reference in sb.exe), but NEC-018 Sepulchre's Bounty and DRU-066
Braialla's Judgement require it on the target, so the server adds it to
Grobones 12021 and Wight 12022 in applyMobbaseEffects.

Rank is the highest rank of the pet's own ladder (PET_NECRO /
PET_NECRO_HARDHIT) at or below its level, which is exactly what the 100
server-made static_npc_mobbase_effects rows held (same 50 rank/reqLvl
pairs). Tying it to that ladder is a guess at the official behaviour.
No value changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:15:32 -04:00
MagicBotandClaude Opus 5.5 2593e397a9 Note why the necro pets keep their DB effects rows
static_npc_mobbase_effects is shrunk to the 100 NECROPETSTATS rows
(sql/3.4-mobbase-effects-shrink.sql); the Wild Boar 13236 PEACE row is
dropped as unused (no spawns, contracts or summon power).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:07:45 -04:00
MagicBotandClaude Opus 5.5 4bf3cf9a27 Load mob effects from the race rune's item_user_power_action
MobBase now takes its effects from MobBaseEffects.getEffectsForRune (the
2.10b builder) and adds the DB rows the rune doesn't have on top.

Parity (scripts/p34.py, 2375 mobbases): 1028 identical, 1344 empty on
both sides, 3 with server-only DB rows that stay in the DB:
- 12021 Grobones, 12022 Wight: 50 NECROPETSTATS rows each
- 13236 Wild Boar: PEACE rank 40, reqLvl 1
No value changes. Effect order does not matter: applyMobbaseEffects keeps
the highest eligible rank per effect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:54:19 -04:00
MagicBotandClaude Opus 5.5 063ebb9717 Load mob speeds from the race rune's rune_speed
MobBase walk/walkCombat/run/runCombat now come from rune_speed
WALK/COMBATWALK/RUN/COMBATRUN instead of static_npc_mobbase_race.

Parity (JSON-Refactor-Plan/scripts/p33.py, compared as Java floats):
2375/2375 loaded mobbases identical, every one has a race row on dev.
No value changes. The 253 unused race rows (no mobbase) and the
fly/swim columns had no reader.

LOAD_ALL_MOBBASE_SPEEDS and static_npc_mobbase_race are now unread;
they go in Task 3.6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:44:32 -04:00
MagicBotandClaude Opus 5.5 12fa84d379 Remove the dead mobbase base-skill path
static_npc_mobbase_stats.baseSkills never matches a static_skill_skillsbase
token on dev (2007496699 on 2208 rows is no known skill hash and is absent
from the client), so applyMobbaseSkill never added a skill and only cost a
skillsbase query per mob spawn. No value changes. Nothing reads the
baseSkills/skillAmount columns now; the table goes in 3.6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:33:34 -04:00
MagicBotandClaude Opus 5.5 06a421de0f Load MobBaseStats from the race rune's rune_attr_adj
Parity (scripts/p32.py, static_npc_mobbase_stats by loadID vs rune_attr_adj):
2301 of 2375 mobbases identical. 74 have no stats row and went from all-zero
stats to the rune values: CSR 2020/2021, summoner pets 12024-12035 and
13266-13301, clay constructs 12200-12202/12375/12382, Guard 13170 (not spawned
or contracted), necro undead 13339-13348, and 13961, 14167, 14168, 14260-14262,
14285, 14292. On dev 47 obj_mob spawns use these (13343, 14167, 14168, 14285,
14292). Effect: higher rune skill values for these mobs via CharacterSkill.
LOAD_STATS stays until the table is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:32:59 -04:00
MagicBotandClaude Opus 5.5 ccc9d9f0ed Drop the server-made mobbase rows and the dead mobbase columns
The 406 ID <> loadID mobbases (15000-17266) were junk: no RUNE file, nothing
on dev references them. MobBase now always reads its core fields from the
race rune and logs an error if the rune is missing; the loader skips any
ID <> loadID rows still present. GetClassType only matched those IDs, so it
always returned 2518 and is replaced by the constant.

DB: MBDATA/JSON-Refactor-Plan/sql/3.6-mobbase-cleanup.sql deletes the rows,
the 10 orphan static_npc_mobbase_effects rows (17061/17122/17154) and drops
13 columns no longer read: lootTableID, name, level, health, atr, defense,
minDmg, maxDmg, noaggro, raceType, fsm, notEnemy, enemy.

Value changes: none for the 2375 loaded mobbases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:03:27 -04:00
MagicBotandClaude Opus 5.5 c811ba9f4e Log the loaded mobbase count, split into rune and server-made rows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:31:49 -04:00
MagicBotandClaude Opus 5.5 c74cb127dc Load MobBase core fields from the race rune
Mobbases whose ID is their own race rune (2375 of 2781) now take name,
level, health, atr, defense, min/max damage, fsm and enemy/notEnemy from
the rune; the 406 server-made mobbases keep their DB row. Server-only
columns (bootySet, flags, seeInvis, spawnTime, goldMod, scale,
attackRange) stay DB.

Parity (MBDATA scripts/p31.py vs magicbane.sql), JSON for all (D2,
user decision 2026-10-23):
- fsm 84: DB None where the rune has no ROOTFSMID; no change
- enemy/notEnemy: no change
- level 95: rune 0 (player races, guards, pets, golems, necro pets)
- level 8: 13314 53->42, 13581 53->40, 13758 78->40, 14034 54->40,
  14172 54->40, 14174 55->40, 14177 54->40, 13171 10->1
- health/atr/def/dmg 61: DB defaults 100/10/20 -> 0 (tombstones
  2030-2040, pets 12024-12035 and 13266-13301, CSR 2020/2021 atr/def/dmg)
- defense 27: DB 100 -> rune 140-780 (clay, risen/doom, skeletal lines,
  12011, 13961, 14167, 14168, 14285, 14292); damage/atr there and on
  14037/14087/14296/14303 already came from the rune at runtime
- name 33: DB names -> obj_name (e.g. Sharduk -> Hell Hound)

No ParityCheck comparator: p31.py covers it.

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

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

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

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

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

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

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

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

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

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

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

No consumers yet; no values change.

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

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

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

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

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

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

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

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

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

Not wired into server startup yet.

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

No files matched your search

@@ -118,11 +118,6 @@ public class WorldGrid {
return outbound; return outbound;
} }
public static HashSet<AbstractWorldObject> getObjectsInRangePartialNecroPets(Vector3fImmutable loc, double r) {
HashSet<AbstractWorldObject> outbound = new HashSet<>();
return outbound;
}
public static HashSet<AbstractWorldObject> getObjectsInRangeContains(Vector3fImmutable loc, double r, int mask) { public static HashSet<AbstractWorldObject> getObjectsInRangeContains(Vector3fImmutable loc, double r, int mask) {
HashSet<AbstractWorldObject> outbound = getObjectsInRangePartial(loc, r, mask); HashSet<AbstractWorldObject> outbound = getObjectsInRangePartial(loc, r, mask);
return outbound; return outbound;
+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();
+7 -25
View File
@@ -13,7 +13,6 @@ import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.objects.MobBase; import engine.objects.MobBase;
import engine.objects.MobBaseEffects;
import engine.objects.MobBaseStats; import engine.objects.MobBaseStats;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -41,7 +40,7 @@ public class dbMobBaseHandler extends dbHandlerBase {
return mobBase; return mobBase;
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase` WHERE `ID`=?")) { PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase` WHERE `ID`=? AND `ID` = `loadID`")) {
preparedStatement.setInt(1, id); preparedStatement.setInt(1, id);
@@ -60,8 +59,10 @@ public class dbMobBaseHandler extends dbHandlerBase {
ArrayList<MobBase> mobbaseList = new ArrayList<>(); ArrayList<MobBase> mobbaseList = new ArrayList<>();
// ID <> loadID rows were junk server-made mobbases (15000-17266); skip them on databases that still have them.
try (Connection connection = DbManager.getConnection(); try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase`;")) { PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase` WHERE `ID` = `loadID`;")) {
ResultSet rs = preparedStatement.executeQuery(); ResultSet rs = preparedStatement.executeQuery();
mobbaseList = getObjectsFromRs(rs, 1000); mobbaseList = getObjectsFromRs(rs, 1000);
@@ -69,31 +70,12 @@ public class dbMobBaseHandler extends dbHandlerBase {
} catch (SQLException e) { } catch (SQLException e) {
Logger.error(e); Logger.error(e);
} }
Logger.info("Loaded " + mobbaseList.size() + " mobbases");
return mobbaseList; return mobbaseList;
} }
public ArrayList<MobBaseEffects> GET_RUNEBASE_EFFECTS(int runeID) {
ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
try (Connection connection = DbManager.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `static_npc_mobbase_effects` WHERE `mobbaseUUID` = ?")) {
preparedStatement.setInt(1, runeID);
ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) {
MobBaseEffects mbs = new MobBaseEffects(rs);
effectsList.add(mbs);
}
} catch (SQLException e) {
Logger.error(e);
}
return effectsList;
}
public MobBaseStats LOAD_STATS(int mobBaseUUID) { public MobBaseStats LOAD_STATS(int mobBaseUUID) {
MobBaseStats mobBaseStats = MobBaseStats.GetGenericStats(); MobBaseStats mobBaseStats = MobBaseStats.GetGenericStats();
+1 -1
View File
@@ -57,7 +57,7 @@ public class dbNPCHandler extends dbHandlerBase {
runeSets.put(runeSetID, runeList); runeSets.put(runeSetID, runeList);
} else { } else {
ArrayList<Integer> runeList = runeSets.get(runeSetID); ArrayList<Integer> runeList = runeSets.get(runeSetID);
runeList.add(runeSetID); runeList.add(runeBaseID);
runeSets.put(runeSetID, runeList); runeSets.put(runeSetID, runeList);
} }
} }
@@ -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;
}
}
+38 -170
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) { for (AbstractGameObject promotionClass : DbManager.getMap(mbEnums.GameObjectType.PromotionClass).values()) {
addRuneLookupRow(insertStatement, promotionClass.getObjectUUID(), "PROMOTION", ((PromotionClass) promotionClass).getName());
try (Connection connection = DbManager.getConnection(); rowsWritten++;
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;
}
} }
} catch (SQLException e) { insertStatement.executeBatch();
Logger.error(e); 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 (Exception e) {
Logger.warn("static_rune_lookup not written; DB views keep their old names: " + e.getMessage());
return;
} }
public RuneBase GET_RUNEBASE(final int id) { Logger.info("static_rune_lookup: wrote " + rowsWritten + " rows");
RuneBase runeBase = null;
try (Connection connection = DbManager.getConnection();
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() { private static void addRuneLookupRow(PreparedStatement insertStatement, int runeID, String type, String name) throws SQLException {
insertStatement.setInt(1, runeID);
ArrayList<RuneBase> runeBasesList = new ArrayList<>(); insertStatement.setString(2, type);
insertStatement.setString(3, name);
try (Connection connection = DbManager.getConnection(); insertStatement.addBatch();
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;
}
} }
+19 -5
View File
@@ -22,6 +22,7 @@ import engine.objects.Warehouse;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -134,7 +135,6 @@ public class dbWarehouseHandler extends dbHandlerBase {
while (rs.next()) { while (rs.next()) {
int cityUID = rs.getInt("cityUUID"); int cityUID = rs.getInt("cityUUID");
JSONObject jsonObject = new JSONObject(rs.getString("warehouse"));
City city = City.getCity(cityUID); City city = City.getCity(cityUID);
if (city == null) { if (city == null) {
@@ -142,12 +142,18 @@ public class dbWarehouseHandler extends dbHandlerBase {
continue; continue;
} }
city.warehouse = new Warehouse(jsonObject); try {
city.warehouse = new Warehouse(new JSONObject(rs.getString("warehouse")));
} catch (JSONException e) {
Logger.error("Warehouse for city " + cityUID + " failed to load: " + e.getMessage());
continue;
}
city.warehouse.city = city; city.warehouse.city = city;
// Locate warehouse building // Locate warehouse building
for (Building building : city.parentZone.zoneBuildingSet) { for (Building building : city.parentZone.zoneBuildingSet) {
if (building.getBlueprint().getBuildingGroup().equals(mbEnums.BuildingGroup.WAREHOUSE)) { if (building.getBlueprint().getBuildingGroup().equals(mbEnums.BuildingGroup.WAREHOUSE) && building.getRank() != -1) {
city.warehouse.building = building; city.warehouse.building = building;
break; break;
} }
@@ -205,8 +211,16 @@ public class dbWarehouseHandler extends dbHandlerBase {
ResultSet rs = preparedStatement.executeQuery()) { ResultSet rs = preparedStatement.executeQuery()) {
while (rs.next()) { while (rs.next()) {
JSONObject jsonObject = new JSONObject(rs.getString("workorder")); int workOrderID = rs.getInt("workorderID");
WorkOrder workOrder = new WorkOrder(jsonObject); WorkOrder workOrder;
try {
workOrder = new WorkOrder(new JSONObject(rs.getString("workorder")));
} catch (JSONException e) {
Logger.error("WorkOrder " + workOrderID + " failed to load: " + e.getMessage());
continue;
}
submitList.add(workOrder); submitList.add(workOrder);
} }
} catch (Exception e) { } catch (Exception e) {
+3 -3
View File
@@ -15,8 +15,8 @@ import engine.gameManager.DbManager;
import engine.mbEnums; import engine.mbEnums;
import engine.objects.AbstractGameObject; import engine.objects.AbstractGameObject;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.ItemTemplate;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import engine.parsers.objects.CObjectTemplate;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
/** /**
@@ -38,7 +38,7 @@ public class CreateItemCmd extends AbstractDevCmd {
} }
int templateID = Integer.parseInt(words[0]); int templateID = Integer.parseInt(words[0]);
ItemTemplate template = ItemTemplate.templates.get(templateID); CObjectTemplate template = CObjectTemplate.lookup(templateID);
if (template == null) { if (template == null) {
ChatManager.chatSystemInfo(playerCharacter, "No such template found."); ChatManager.chatSystemInfo(playerCharacter, "No such template found.");
@@ -51,7 +51,7 @@ public class CreateItemCmd extends AbstractDevCmd {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
if (!playerCharacter.charItemManager.hasRoomInventory(template.item_wt)) { if (!playerCharacter.charItemManager.hasRoomInventory(template.getItemWt())) {
ChatManager.chatSystemInfo(playerCharacter, "You are encumbered!."); ChatManager.chatSystemInfo(playerCharacter, "You are encumbered!.");
break; break;
} }
+106
View File
@@ -0,0 +1,106 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd;
import engine.gameManager.ChatManager;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.objects.AbstractGameObject;
import engine.objects.Building;
import engine.objects.City;
import engine.objects.PlayerCharacter;
import engine.objects.Warehouse;
import java.util.HashMap;
public class FillCmd extends AbstractDevCmd {
public FillCmd() {
super("fill");
}
@Override
protected void _doCmd(PlayerCharacter pc, String[] words,
AbstractGameObject target) {
Building building;
City city;
Warehouse warehouse;
int amount = Integer.MAX_VALUE;
HashMap<mbEnums.ResourceType, Integer> oldResources;
if (words.length > 1) {
this.sendUsage(pc);
return;
}
if (words.length == 1 && !words[0].isEmpty()) {
try {
amount = Integer.parseInt(words[0]);
} catch (NumberFormatException e) {
throwbackError(pc, "Amount must be a number, " + words[0] + " is invalid");
return;
}
if (amount < 0) {
throwbackError(pc, "Amount must be 0 or more");
return;
}
}
if (target == null || target.getObjectType() != mbEnums.GameObjectType.Building) {
throwbackError(pc, "Select a warehouse");
return;
}
building = (Building) target;
if (building.getBlueprint() == null || building.getBlueprint().getBuildingGroup() != mbEnums.BuildingGroup.WAREHOUSE) {
throwbackError(pc, "Selected building is not a warehouse");
return;
}
city = building.getCity();
if (city == null || city.warehouse == null || city.warehouse.building == null
|| city.warehouse.building.getObjectUUID() != building.getObjectUUID()) {
throwbackError(pc, "Selected warehouse is not the active warehouse of a city");
return;
}
warehouse = city.warehouse;
oldResources = new HashMap<>(warehouse.resources);
for (mbEnums.ResourceType resourceType : mbEnums.ResourceType.values())
warehouse.resources.put(resourceType, Math.min(amount, resourceType.deposit_limit));
if (!DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse)) {
warehouse.resources.putAll(oldResources);
throwbackError(pc, "Failed to update the warehouse in the database");
return;
}
ChatManager.chatSayInfo(pc, "Filled the " + city.getCityName() + " warehouse"
+ (amount == Integer.MAX_VALUE ? " to each deposit limit" : " with " + amount + " of each resource (capped at the deposit limit)"));
}
@Override
protected String _getHelpString() {
return "Sets every resource in the selected warehouse";
}
@Override
protected String _getUsageString() {
return "' ./fill [amount]' with a warehouse selected (no amount fills each resource to its deposit limit)";
}
}
+6 -5
View File
@@ -18,6 +18,7 @@ import engine.mbEnums.BuildingGroup;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.mbEnums.TargetColor; import engine.mbEnums.TargetColor;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.util.StringUtils; import engine.util.StringUtils;
import java.text.DecimalFormat; import java.text.DecimalFormat;
@@ -507,17 +508,17 @@ public class InfoCmd extends AbstractDevCmd {
case Item: //intentional passthrough case Item: //intentional passthrough
case MobLoot: case MobLoot:
Item item = (Item) target; Item item = (Item) target;
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
output += StringUtils.addWS("Template: " + template.template_id, 20); output += StringUtils.addWS("Template: " + template.getTemplateId(), 20);
output += "Weight: " + template.item_wt; output += "Weight: " + template.getItemWt();
output += newline; output += newline;
DecimalFormat df = new DecimalFormat("###,###,###,###,##0"); DecimalFormat df = new DecimalFormat("###,###,###,###,##0");
output += StringUtils.addWS("Qty: " output += StringUtils.addWS("Qty: "
+ df.format(item.getNumOfItems()), 20); + df.format(item.getNumOfItems()), 20);
output += "Charges: " + (byte) item.chargesRemaining output += "Charges: " + (byte) item.chargesRemaining
+ '/' + item.template.item_initial_charges; + '/' + item.template.getItemInitialCharges();
output += newline; output += newline;
output += "Name: " + template.item_base_name; output += "Name: " + template.getItemBaseName();
output += newline; output += newline;
output += item.getContainerInfo(); output += item.getContainerInfo();
+16 -9
View File
@@ -11,9 +11,11 @@ package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd; import engine.devcmd.AbstractDevCmd;
import engine.gameManager.DbManager; import engine.gameManager.DbManager;
import engine.mbEnums.ItemContainerType;
import engine.mbEnums.ItemType; import engine.mbEnums.ItemType;
import engine.mbEnums.OwnerType; import engine.mbEnums.OwnerType;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
/** /**
@@ -59,18 +61,17 @@ public class MakeItemCmd extends AbstractDevCmd {
} }
int templateID; int templateID;
ItemTemplate template; CObjectTemplate template;
try { try {
templateID = Integer.parseInt(words[0]); templateID = Integer.parseInt(words[0]);
template = ItemTemplate.templates.get(words[0].toLowerCase()); template = CObjectTemplate.lookup(templateID);
if (template == null) { if (template == null) {
throwbackError(pc, "Supplied type " + words[0] throwbackError(pc, "No item template with id " + words[0]);
+ " failed to parse to an Integer");
return; return;
} }
if (template.item_type == ItemType.GOLD) { if (template.getItemType() == ItemType.GOLD) {
this.throwbackInfo(pc, "use /addgold to add gold."); this.throwbackInfo(pc, "use /addgold to add gold.");
return; return;
} }
@@ -132,7 +133,7 @@ public class MakeItemCmd extends AbstractDevCmd {
return; return;
} }
} }
template = ItemTemplate.templates.get(templateID); template = CObjectTemplate.lookup(templateID);
if (template == null) { if (template == null) {
throwbackError(pc, "Unable to find template " + templateID); throwbackError(pc, "Unable to find template " + templateID);
@@ -140,8 +141,8 @@ public class MakeItemCmd extends AbstractDevCmd {
} }
if ((numItems > 1) if ((numItems > 1)
&& (template.item_type.equals(ItemType.RESOURCE) == false) && (template.getItemType().equals(ItemType.RESOURCE) == false)
&& (template.item_type.equals(ItemType.OFFERING)) == false) && (template.getItemType().equals(ItemType.OFFERING)) == false)
numItems = 1; numItems = 1;
CharacterItemManager cim = pc.charItemManager; CharacterItemManager cim = pc.charItemManager;
@@ -155,7 +156,7 @@ public class MakeItemCmd extends AbstractDevCmd {
for (int i = 0; i < quantity; i++) { for (int i = 0; i < quantity; i++) {
int weight = template.item_wt; int weight = template.getItemWt();
if (!cim.hasRoomInventory(weight)) { if (!cim.hasRoomInventory(weight)) {
throwbackError(pc, "Not enough room in inventory for any more of this item. " + i + " produced."); throwbackError(pc, "Not enough room in inventory for any more of this item. " + i + " produced.");
@@ -169,6 +170,7 @@ public class MakeItemCmd extends AbstractDevCmd {
Item item = new Item(templateID); Item item = new Item(templateID);
item.ownerID = pc.getObjectUUID(); item.ownerID = pc.getObjectUUID();
item.ownerType = OwnerType.PlayerCharacter; item.ownerType = OwnerType.PlayerCharacter;
item.containerType = ItemContainerType.INVENTORY;
if (numItems > 1) if (numItems > 1)
item.setNumOfItems(numItems); item.setNumOfItems(numItems);
@@ -180,6 +182,11 @@ public class MakeItemCmd extends AbstractDevCmd {
return; return;
} }
if (item == null) {
throwbackError(pc, "DB error 2: Unable to create item " + templateID + ".");
return;
}
//create prefix //create prefix
if (!prefix.isEmpty()) if (!prefix.isEmpty())
item.addPermanentEnchantmentForDev(prefix, 0); item.addPermanentEnchantmentForDev(prefix, 0);
+3 -2
View File
@@ -11,6 +11,7 @@ package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd; import engine.devcmd.AbstractDevCmd;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import java.util.ArrayList; import java.util.ArrayList;
@@ -52,8 +53,8 @@ public class PrintBankCmd extends AbstractDevCmd {
throwbackInfo(pc, "Bank for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); throwbackInfo(pc, "Bank for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (Item item : list) { for (Item item : list) {
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
throwbackInfo(pc, " " + template.item_base_name + ", count: " + item.getNumOfItems()); throwbackInfo(pc, " " + template.getItemBaseName() + ", count: " + item.getNumOfItems());
} }
Item gold = cim.getGoldBank(); Item gold = cim.getGoldBank();
+3 -2
View File
@@ -13,6 +13,7 @@ import engine.devcmd.AbstractDevCmd;
import engine.gameManager.ChatManager; import engine.gameManager.ChatManager;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.powers.effectmodifiers.AbstractEffectModifier; import engine.powers.effectmodifiers.AbstractEffectModifier;
public class PrintBonusesCmd extends AbstractDevCmd { public class PrintBonusesCmd extends AbstractDevCmd {
@@ -33,8 +34,8 @@ public class PrintBonusesCmd extends AbstractDevCmd {
if (target instanceof Item) { if (target instanceof Item) {
type = "Item"; type = "Item";
tar = (Item) target; tar = (Item) target;
ItemTemplate template = ItemTemplate.templates.get(((Item) tar).templateID); CObjectTemplate template = CObjectTemplate.lookup(((Item) tar).templateID);
name = template.item_base_name; name = template.getItemBaseName();
} else if (target instanceof AbstractCharacter) { } else if (target instanceof AbstractCharacter) {
tar = (AbstractCharacter) target; tar = (AbstractCharacter) target;
name = ((AbstractCharacter) tar).getFirstName(); name = ((AbstractCharacter) tar).getFirstName();
+49 -11
View File
@@ -10,9 +10,15 @@
package engine.devcmd.cmds; package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd; import engine.devcmd.AbstractDevCmd;
import engine.mbEnums.GameObjectType;
import engine.objects.AbstractCharacter; import engine.objects.AbstractCharacter;
import engine.objects.AbstractGameObject; import engine.objects.AbstractGameObject;
import engine.objects.Effect;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import engine.powers.EffectsBase;
import engine.powers.PowersBase;
import java.util.Map;
/** /**
* *
@@ -30,28 +36,60 @@ public class PrintEffectsCmd extends AbstractDevCmd {
AbstractGameObject target) { AbstractGameObject target) {
AbstractCharacter tar; AbstractCharacter tar;
StringBuilder output;
if (target != null && target instanceof AbstractCharacter) {
tar = (AbstractCharacter) target;
String newline = "\r\n "; String newline = "\r\n ";
String output = "Effects For Character: " + tar.getName() + newline; Effect effect;
EffectsBase effectsBase;
PowersBase power;
for (String effect : tar.effects.keySet()) { if (target == null)
output += effect + newline; return;
}
throwbackInfo(pc, output); if (target.getObjectType() != GameObjectType.PlayerCharacter && target.getObjectType() != GameObjectType.Mob
&& target.getObjectType() != GameObjectType.NPC)
return;
tar = (AbstractCharacter) target;
output = new StringBuilder("Effects for " + tar.getName() + " (" + tar.effects.size() + "):" + newline);
// Keys are the effect UUID for mobbase/rune effects, or the stack type (e.g. PetBuff) for power effects
for (Map.Entry<String, Effect> entry : tar.effects.entrySet()) {
effect = entry.getValue();
effectsBase = effect.getEffectsBase();
power = effect.getPower();
output.append(entry.getKey()).append(": ");
if (effectsBase != null)
output.append(effectsBase.getIDString()).append(" '").append(effectsBase.getName()).append("'");
else
output.append("no EffectsBase");
output.append(" rank ").append(effect.getTrains());
if (effect.isStatic())
output.append(", static");
else
output.append(", ").append((int) effect.getDuration()).append("s left");
if (power != null)
output.append(", from ").append(power.getIDString()).append(" '").append(power.getName()).append("'");
output.append(newline);
} }
throwbackInfo(pc, output.toString());
} }
@Override @Override
protected String _getHelpString() { protected String _getHelpString() {
return "Returns the player's current stats"; return "Lists the target's effects: key, effect id and name, rank, time left and source power";
} }
@Override @Override
protected String _getUsageString() { protected String _getUsageString() {
return "' /printstats'"; return "' /printeffects'";
} }
} }
+3 -3
View File
@@ -66,7 +66,7 @@ public class PrintEquipCmd extends AbstractDevCmd {
throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) { for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) {
Item equip = tarMob.charItemManager.equipped.get(slot); Item equip = tarMob.charItemManager.equipped.get(slot);
throwbackInfo(pc, equip.templateID + " : " + equip.template.item_base_name + ", slot: " + slot); throwbackInfo(pc, equip.templateID + " : " + equip.template.getItemBaseName() + ", slot: " + slot);
} }
return; return;
} }
@@ -76,7 +76,7 @@ public class PrintEquipCmd extends AbstractDevCmd {
throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); throwbackInfo(pc, "Equip for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) { for (mbEnums.EquipSlotType slot : tarMob.charItemManager.equipped.keySet()) {
Item equip = tarMob.charItemManager.equipped.get(slot); Item equip = tarMob.charItemManager.equipped.get(slot);
throwbackInfo(pc, equip.templateID + " : " + equip.template.item_base_name + ", slot: " + slot); throwbackInfo(pc, equip.templateID + " : " + equip.template.getItemBaseName() + ", slot: " + slot);
} }
return; return;
} }
@@ -87,7 +87,7 @@ public class PrintEquipCmd extends AbstractDevCmd {
for (mbEnums.EquipSlotType slot : list.keySet()) { for (mbEnums.EquipSlotType slot : list.keySet()) {
Item item = list.get(slot); Item item = list.get(slot);
throwbackInfo(pc, " " + item.template.item_base_name + ", slot: " + slot); throwbackInfo(pc, " " + item.template.getItemBaseName() + ", slot: " + slot);
} }
} }
@@ -12,6 +12,7 @@ package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd; import engine.devcmd.AbstractDevCmd;
import engine.mbEnums.ItemType; import engine.mbEnums.ItemType;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
@@ -75,18 +76,18 @@ public class PrintInventoryCmd extends AbstractDevCmd {
int goldCount = 0; int goldCount = 0;
for (Item item : inventory) { for (Item item : inventory) {
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
if (item.template.item_type.equals(ItemType.GOLD) == false) { if (item.template.getItemType().equals(ItemType.GOLD) == false) {
String chargeInfo = ""; String chargeInfo = "";
int chargeMax = template.item_initial_charges; int chargeMax = template.getItemInitialCharges();
if (chargeMax > 0) { if (chargeMax > 0) {
int charges = item.chargesRemaining; int charges = item.chargesRemaining;
chargeInfo = " charges: " + charges + '/' + chargeMax; chargeInfo = " charges: " + charges + '/' + chargeMax;
} }
throwbackInfo(pc, " " + template.item_base_name + ", count: " + item.getNumOfItems() + chargeInfo); throwbackInfo(pc, " " + template.getItemBaseName() + ", count: " + item.getNumOfItems() + chargeInfo);
} else } else
goldCount += item.getNumOfItems(); goldCount += item.getNumOfItems();
} }
+3 -2
View File
@@ -11,6 +11,7 @@ package engine.devcmd.cmds;
import engine.devcmd.AbstractDevCmd; import engine.devcmd.AbstractDevCmd;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import java.util.ArrayList; import java.util.ArrayList;
@@ -50,8 +51,8 @@ public class PrintVaultCmd extends AbstractDevCmd {
ArrayList<Item> list = cim.getVault(); ArrayList<Item> list = cim.getVault();
throwbackInfo(pc, "Vault for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')'); throwbackInfo(pc, "Vault for " + type + ' ' + name + " (" + tar.getObjectUUID() + ')');
for (Item item : list) { for (Item item : list) {
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
throwbackInfo(pc, " " + template.item_base_name + ", count: " + item.getNumOfItems()); throwbackInfo(pc, " " + template.getItemBaseName() + ", count: " + item.getNumOfItems());
} }
Item gold = cim.getGoldVault(); Item gold = cim.getGoldVault();
if (gold != null) if (gold != null)
+4 -3
View File
@@ -5,6 +5,7 @@ import engine.gameManager.LootManager;
import engine.gameManager.ZoneManager; import engine.gameManager.ZoneManager;
import engine.loot.BootySetEntry; import engine.loot.BootySetEntry;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@@ -34,10 +35,10 @@ public class SimulateBootyCmd extends AbstractDevCmd {
if (mob.bootySet != 0) { if (mob.bootySet != 0) {
for (BootySetEntry entry : LootManager._bootySetMap.get(mob.bootySet)) { for (BootySetEntry entry : LootManager._bootySetMap.get(mob.bootySet)) {
ItemTemplate template = ItemTemplate.templates.get(entry.templateID); CObjectTemplate template = CObjectTemplate.lookup(entry.templateID);
if (template != null) if (template != null)
output += "[" + entry.bootyType + "] " + template.item_base_name + " [Chance] " + entry.dropChance + newline; output += "[" + entry.bootyType + "] " + template.getItemBaseName() + " [Chance] " + entry.dropChance + newline;
} }
} }
@@ -57,7 +58,7 @@ public class SimulateBootyCmd extends AbstractDevCmd {
try { try {
mob.loadInventory(); mob.loadInventory();
for (Item lootItem : mob.charItemManager.getInventory()) { for (Item lootItem : mob.charItemManager.getInventory()) {
switch (lootItem.template.item_type) { switch (lootItem.template.getItemType()) {
case EMPLOYMENTCONTRACT: //CONTRACT case EMPLOYMENTCONTRACT: //CONTRACT
Contracts.add(lootItem); Contracts.add(lootItem);
break; break;
+14 -10
View File
@@ -22,6 +22,7 @@ import engine.mbEnums.BuildingGroup;
import engine.mbEnums.GameObjectType; import engine.mbEnums.GameObjectType;
import engine.net.client.msg.ErrorPopupMsg; import engine.net.client.msg.ErrorPopupMsg;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -199,31 +200,31 @@ public enum BuildingManager {
return false; return false;
} }
ItemTemplate template = ItemTemplate.templates.get(1705032); // Elan Stone CObjectTemplate template = CObjectTemplate.lookup(1705032); // Elan Stone
if (!player.charItemManager.hasRoomInventory(template.item_wt)) if (!player.charItemManager.hasRoomInventory(template.getItemWt()))
return false; return false;
if (!ItemManager.MakeItemForPlayer(template.template_id, player, amount)) if (!ItemManager.MakeItemForPlayer(template.getTemplateId(), player, amount))
return false; return false;
shrine.setFavors(0); shrine.setFavors(0);
break; break;
case WAREHOUSE: case WAREHOUSE:
City city = building.getCity(); Warehouse warehouse = Warehouse.destroyedWarehouses.get(building.getObjectUUID());
if (city == null)
return true;
Warehouse warehouse = city.warehouse;
if (warehouse == null) if (warehouse == null)
return false; return false;
for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) { for (mbEnums.ResourceType resourceType : EnumSet.allOf(mbEnums.ResourceType.class)) {
if (!player.charItemManager.hasRoomInventory(resourceType.template.item_wt)) { CObjectTemplate resourceTemplate = CObjectTemplate.lookup(resourceType.templateID);
if (resourceTemplate == null)
continue;
if (!player.charItemManager.hasRoomInventory(resourceTemplate.getItemWt())) {
ChatManager.chatSystemInfo(player, "You can not carry any more of that item."); ChatManager.chatSystemInfo(player, "You can not carry any more of that item.");
return false; return false;
} }
@@ -241,6 +242,9 @@ public enum BuildingManager {
} }
} }
if (Warehouse.isEmpty(warehouse))
Warehouse.destroyedWarehouses.remove(building.getObjectUUID());
break; break;
} }
+4 -3
View File
@@ -22,6 +22,7 @@ import engine.net.client.ClientConnection;
import engine.net.client.handlers.ClientAdminCommandMsgHandler; import engine.net.client.handlers.ClientAdminCommandMsgHandler;
import engine.net.client.msg.chat.*; import engine.net.client.msg.chat.*;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import engine.server.world.WorldServer; import engine.server.world.WorldServer;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -535,12 +536,12 @@ public enum ChatManager {
if (item != null) //Steal if (item != null) //Steal
if (success) { if (success) {
String name = ""; String name = "";
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) if (item.template.getItemType().equals(mbEnums.ItemType.GOLD))
name = amount + " gold "; name = amount + " gold ";
else { else {
String vowels = "aeiou"; String vowels = "aeiou";
ItemTemplate template = ItemTemplate.templates.get(item.templateID); CObjectTemplate template = CObjectTemplate.lookup(item.templateID);
String iName = template.item_base_name; String iName = template.getItemBaseName();
if (iName.length() > 0) if (iName.length() > 0)
if (vowels.indexOf(iName.substring(0, 1).toLowerCase()) >= 0) if (vowels.indexOf(iName.substring(0, 1).toLowerCase()) >= 0)
name = "an " + iName + ' '; name = "an " + iName + ' ';
+33 -30
View File
@@ -18,6 +18,8 @@ import engine.net.client.msg.PowerProjectileMsg;
import engine.net.client.msg.TargetedActionMsg; import engine.net.client.msg.TargetedActionMsg;
import engine.net.client.msg.UpdateStateMsg; import engine.net.client.msg.UpdateStateMsg;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import engine.parsers.objects.RuneTemplate;
import engine.powers.DamageShield; import engine.powers.DamageShield;
import engine.powers.effectmodifiers.AbstractEffectModifier; import engine.powers.effectmodifiers.AbstractEffectModifier;
import engine.powers.effectmodifiers.WeaponProcEffectModifier; import engine.powers.effectmodifiers.WeaponProcEffectModifier;
@@ -92,25 +94,25 @@ public enum CombatManager {
return; return;
} }
if (mainWeapon == null && offWeapon != null && !offWeapon.template.item_skill_used.equals("Block")) { if (mainWeapon == null && offWeapon != null && !offWeapon.template.getItemSkillUsed().equals("Block")) {
//swing left hand only //swing left hand only
processAttack(attacker, target, mbEnums.EquipSlotType.LHELD); processAttack(attacker, target, mbEnums.EquipSlotType.LHELD);
return; return;
} }
if (mainWeapon == null && offWeapon != null && offWeapon.template.item_skill_used.equals("Block")) { if (mainWeapon == null && offWeapon != null && offWeapon.template.getItemSkillUsed().equals("Block")) {
//no weapon equipped with a shield, punch with one hand //no weapon equipped with a shield, punch with one hand
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD); processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
return; return;
} }
if (mainWeapon != null && offWeapon != null && offWeapon.template.item_skill_used.equals("Block")) { if (mainWeapon != null && offWeapon != null && offWeapon.template.getItemSkillUsed().equals("Block")) {
//one weapon equipped with a shield, swing with one hand //one weapon equipped with a shield, swing with one hand
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD); processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
return; return;
} }
if (mainWeapon != null && offWeapon != null && !offWeapon.template.item_skill_used.equals("Block")) { if (mainWeapon != null && offWeapon != null && !offWeapon.template.getItemSkillUsed().equals("Block")) {
//two weapons equipped, swing both hands //two weapons equipped, swing both hands
processAttack(attacker, target, mbEnums.EquipSlotType.RHELD); processAttack(attacker, target, mbEnums.EquipSlotType.RHELD);
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter))
@@ -146,7 +148,7 @@ public enum CombatManager {
if (bonus != null) if (bonus != null)
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None); rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None);
attackRange += weapon.template.item_weapon_max_range * rangeMod; attackRange += engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_max_range * rangeMod;
} else if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) { } else if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) {
//unarmed mob, add the natural attack range from its rune (0 for most melee mobs) //unarmed mob, add the natural attack range from its rune (0 for most melee mobs)
@@ -211,7 +213,7 @@ public enum CombatManager {
if (weapon != null) { if (weapon != null) {
float wepSpeed = (int) (weapon.template.item_weapon_wepspeed); float wepSpeed = (int) (engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_wepspeed);
if (weapon.getBonusPercent(mbEnums.ModType.WeaponSpeed, mbEnums.SourceType.None) != 0f) //add weapon speed bonus if (weapon.getBonusPercent(mbEnums.ModType.WeaponSpeed, mbEnums.SourceType.None) != 0f) //add weapon speed bonus
wepSpeed *= (1 + weapon.getBonus(mbEnums.ModType.WeaponSpeed, mbEnums.SourceType.None)); wepSpeed *= (1 + weapon.getBonus(mbEnums.ModType.WeaponSpeed, mbEnums.SourceType.None));
@@ -270,13 +272,13 @@ public enum CombatManager {
if (weapon != null && !attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) { if (weapon != null && !attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) {
//check if Out of Stamina //check if Out of Stamina
if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) { if (attacker.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) {
if (attacker.getStamina() < (weapon.template.item_wt / 3f)) { if (attacker.getStamina() < (weapon.template.getItemWt() / 3f)) {
//set auto attack job //set auto attack job
setAutoAttackJob(attacker, slot, delay); setAutoAttackJob(attacker, slot, delay);
return; return;
} }
} }
float stam = weapon.template.item_wt / 3f; float stam = weapon.template.getItemWt() / 3f;
stam = (stam < 1) ? 1 : stam; stam = (stam < 1) ? 1 : stam;
attacker.modifyStamina(-(stam), attacker, true); attacker.modifyStamina(-(stam), attacker, true);
} else } else
@@ -416,7 +418,7 @@ public enum CombatManager {
if (((Mob) attacker).isSiege()) if (((Mob) attacker).isSiege())
damageType = mbEnums.DamageType.SIEGE; damageType = mbEnums.DamageType.SIEGE;
} else { } else {
damageType = (mbEnums.DamageType) attacker.charItemManager.getEquipped().get(slot).template.item_weapon_damage.keySet().toArray()[0]; damageType = engine.parsers.objects.ItemTemplate.getWeapon(attacker.charItemManager.getEquipped().get(slot).templateID).weapon_damage.get(0).damage_type;
} }
//get resists //get resists
@@ -577,13 +579,14 @@ public enum CombatManager {
target.setCombatTarget(attacker); target.setCombatTarget(attacker);
} }
public static int getSwingAnimation(ItemTemplate wb, DeferredPowerJob dpj, mbEnums.EquipSlotType slot) { public static int getSwingAnimation(CObjectTemplate wb, DeferredPowerJob dpj, mbEnums.EquipSlotType slot) {
//No weapon, return default animation //No weapon, return default animation
if (wb == null) if (wb == null)
return 75; return 75;
int token; int token;
engine.parsers.objects.ItemTemplate.Weapon weapon = engine.parsers.objects.ItemTemplate.getWeapon(wb.getTemplateId());
if (dpj != null) { if (dpj != null) {
@@ -594,7 +597,7 @@ public enum CombatManager {
} }
//Item has no equipment slots and should not try to return an animation, return default instead //Item has no equipment slots and should not try to return an animation, return default instead
if (wb.item_eq_slots_or == null || wb.item_eq_slots_or.isEmpty()) { if (wb.getItemEqSlotsOr().isEmpty()) {
return 75; return 75;
} }
@@ -603,18 +606,18 @@ public enum CombatManager {
int random; int random;
//Item can only be equipped in one slot, return animation for that slot //Item can only be equipped in one slot, return animation for that slot
if (wb.item_eq_slots_or.size() == 1) { if (wb.getItemEqSlotsOr().size() == 1) {
if (wb.item_eq_slots_or.iterator().next().equals(mbEnums.EquipSlotType.RHELD)) { if (wb.getItemEqSlotsOr().iterator().next().equals(mbEnums.EquipSlotType.RHELD)) {
anim = wb.weapon_attack_anim_right.get(0)[0]; anim = weapon.weapon_attack_anim_right.get(0)[0];
if (dpj != null) { if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_right.size()); random = ThreadLocalRandom.current().nextInt(weapon.weapon_attack_anim_right.size());
anim = wb.weapon_attack_anim_right.get(random)[0]; anim = weapon.weapon_attack_anim_right.get(random)[0];
} }
} else { } else {
anim = wb.weapon_attack_anim_left.get(0)[0]; anim = weapon.weapon_attack_anim_left.get(0)[0];
if (dpj != null) { if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_left.size()); random = ThreadLocalRandom.current().nextInt(weapon.weapon_attack_anim_left.size());
anim = wb.weapon_attack_anim_left.get(random)[0]; anim = weapon.weapon_attack_anim_left.get(random)[0];
} }
} }
return anim; return anim;
@@ -622,16 +625,16 @@ public enum CombatManager {
//Item can be equipped in either hand, and should have animation sets for each hand //Item can be equipped in either hand, and should have animation sets for each hand
if (slot.equals(mbEnums.EquipSlotType.RHELD)) { if (slot.equals(mbEnums.EquipSlotType.RHELD)) {
anim = wb.weapon_attack_anim_right.get(0)[0]; anim = weapon.weapon_attack_anim_right.get(0)[0];
if (dpj != null) { if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_right.size()); random = ThreadLocalRandom.current().nextInt(weapon.weapon_attack_anim_right.size());
anim = wb.weapon_attack_anim_right.get(random)[0]; anim = weapon.weapon_attack_anim_right.get(random)[0];
} }
} else { } else {
anim = wb.weapon_attack_anim_left.get(0)[0]; anim = weapon.weapon_attack_anim_left.get(0)[0];
if (dpj != null) { if (dpj != null) {
random = ThreadLocalRandom.current().nextInt(wb.weapon_attack_anim_left.size()); random = ThreadLocalRandom.current().nextInt(weapon.weapon_attack_anim_left.size());
anim = wb.weapon_attack_anim_left.get(random)[0]; anim = weapon.weapon_attack_anim_left.get(random)[0];
} }
} }
return anim; return anim;
@@ -713,7 +716,7 @@ public enum CombatManager {
// Mobs use their mobbase rune, players use their race rune. // Mobs use their mobbase rune, players use their race rune.
int runeID; int runeID;
ItemTemplate rune; RuneTemplate rune;
int hand = slot.equals(mbEnums.EquipSlotType.LHELD) ? 2 : 1; int hand = slot.equals(mbEnums.EquipSlotType.LHELD) ? 2 : 1;
switch (attacker.getObjectType()) { switch (attacker.getObjectType()) {
@@ -727,18 +730,18 @@ public enum CombatManager {
return null; return null;
} }
rune = ItemTemplate.templates.get(runeID); rune = RuneTemplate._runes.get(runeID);
if (rune == null || rune.rune_natural_attacks.isEmpty()) if (rune == null || rune.rune_naturalattacks.isEmpty())
return null; return null;
for (float[] attack : rune.rune_natural_attacks) for (float[] attack : rune.rune_naturalattacks)
if (attack.length > 3 && (int) attack[3] == hand) if (attack.length > 3 && (int) attack[3] == hand)
return attack; return attack;
// No entry for this hand, fall back to the first natural attack // No entry for this hand, fall back to the first natural attack
return rune.rune_natural_attacks.get(0); return rune.rune_naturalattacks.get(0);
} }
public static int calculatePetDamage(AbstractCharacter agent) { public static int calculatePetDamage(AbstractCharacter agent) {
+1 -8
View File
@@ -75,6 +75,7 @@ public enum ConfigManager {
// Mobile AI modifiers // Mobile AI modifiers
MB_AI_CAST_FREQUENCY, MB_AI_CAST_FREQUENCY,
MB_AI_AGGRO_RANGE, MB_AI_AGGRO_RANGE,
MB_MINION_SPAWN_TIME,
//drop rates //drop rates
MB_NORMAL_EXP_RATE, MB_NORMAL_EXP_RATE,
@@ -165,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;
} }
+70 -28
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;
} }
@@ -434,28 +436,63 @@ public enum NPCManager {
} }
} }
if (mob.isNecroPet())
applyNecroPetStats(mob);
}
// No client data grants NECROPETSTATS, yet NEC-018 and DRU-066 require it on the target; the server adds it to necro pets
private static void applyNecroPetStats(Mob mob) {
EffectsBase necroPetStats = PowersManager.getEffectByIDString("NECROPETSTATS");
String effectKey;
Effect currentEffect;
int rank = -1;
if (necroPetStats == null) {
Logger.error("NECROPETSTATS effect not loaded");
return;
}
// GUESS: rank follows the pet's own level ladder (PET_NECRO / PET_NECRO_HARDHIT, from reqLvl 9), as the old DB rows did
for (MobBaseEffects mbe : mob.mobBase.effectsList)
if (mbe.getReqLvl() > 0 && mbe.getReqLvl() <= (int) mob.level)
rank = Math.max(rank, mbe.getRank());
if (rank < 0)
return;
effectKey = Integer.toString(necroPetStats.getUUID());
currentEffect = mob.effects.get(effectKey);
if (currentEffect != null) {
if (currentEffect.getTrains() >= rank)
return;
currentEffect.cancelJob();
}
mob.addEffectNoTimer(effectKey, necroPetStats, rank, true);
} }
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));
} }
} }
} }
public static void applyMobbaseSkill(Mob mob) {
SkillsBase baseSkill = DbManager.SkillsBaseQueries.GET_BASE_BY_TOKEN(mob.mobBase.getMobBaseStats().getBaseSkill());
if (baseSkill != null)
mob.getSkills().put(baseSkill.getName(), new CharacterSkill(baseSkill, mob, mob.mobBase.getMobBaseStats().getBaseSkillAmount()));
}
public static void applyRuneSkills(Mob mob, int runeID) { public static void applyRuneSkills(Mob mob, int runeID) {
//load mob skill adjustments from mobbase rune //load mob skill adjustments from mobbase rune
if (PowersManager._allRuneSkillAdjusts.containsKey(runeID)) if (PowersManager._allRuneSkillAdjusts.containsKey(runeID))
@@ -466,7 +503,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()));
@@ -480,6 +517,11 @@ public enum NPCManager {
npc.runes.add(shopkeeper); npc.runes.add(shopkeeper);
if (NPCManager._runeSetMap.containsKey(npc.runeSetID)) { if (NPCManager._runeSetMap.containsKey(npc.runeSetID)) {
for (int runeID : _runeSetMap.get(npc.runeSetID)) { for (int runeID : _runeSetMap.get(npc.runeSetID)) {
// Shopkeeper is already applied above and most rune sets repeat it
if (runeID == 252620)
continue;
RuneBase rb = RuneBase.getRuneBase(runeID); RuneBase rb = RuneBase.getRuneBase(runeID);
if (rb != null) { if (rb != null) {
CharacterRune toApply = new CharacterRune(rb, npc.getObjectUUID()); CharacterRune toApply = new CharacterRune(rb, npc.getObjectUUID());
@@ -536,14 +578,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;
+69 -14
View File
@@ -28,15 +28,18 @@ 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.wpak.PowerTemplate;
import engine.powers.*; import engine.powers.*;
import engine.powers.poweractions.AbstractPowerAction; import engine.powers.poweractions.AbstractPowerAction;
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.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@@ -57,6 +60,8 @@ public enum PowersManager {
public static HashMap<Integer, ArrayList<RunePowerEntry>> _allRunePowers; public static HashMap<Integer, ArrayList<RunePowerEntry>> _allRunePowers;
public static HashMap<Integer, ArrayList<RuneSkillAdjustEntry>> _allRuneSkillAdjusts; public static HashMap<Integer, ArrayList<RuneSkillAdjustEntry>> _allRuneSkillAdjusts;
public static HashMap<String, HashMap<ResourceType, Integer>> _effect_costMaps = new HashMap<>(); public static HashMap<String, HashMap<ResourceType, Integer>> _effect_costMaps = new HashMap<>();
// Succor, Phaedra's Fury and Fleet of Foot keep their DB target type until the Phase 4 power task decides them
private static final Set<String> DB_TARGET_TYPE_POWERS = new HashSet<>(Arrays.asList("HLR-025", "HNT-023", "RNG-033"));
private static JobScheduler js; private static JobScheduler js;
public static void initPowersManager(boolean fullPowersLoad) { public static void initPowersManager(boolean fullPowersLoad) {
@@ -90,6 +95,31 @@ public enum PowersManager {
return PowersManager.effectsBaseByToken.get(token); return PowersManager.effectsBaseByToken.get(token);
} }
// Powers.cfg target type wins over static_power_powerbase; needs PowerTemplate._powers, so the world server calls it after the wpak load
public static void applyClientTargetTypes() {
PowerTemplate powerTemplate;
int applied = 0;
for (PowersBase powersBase : PowersManager.powersBaseByIDString.values()) {
if (DB_TARGET_TYPE_POWERS.contains(powersBase.getIDString()))
continue;
powerTemplate = PowerTemplate._powers.get(powersBase.getIDString());
if (powerTemplate == null || powerTemplate.target_type == null) {
Logger.warn("Power " + powersBase.getIDString() + " has no Powers.cfg target type; keeping the DB value");
continue;
}
powersBase.setTargetType(powerTemplate.target_type.name());
applied++;
}
Logger.info("Applied Powers.cfg target types to " + applied + " powers");
}
// This pre-loads only PowersBase for login // This pre-loads only PowersBase for login
public static void InitializeLoginPowers() { public static void InitializeLoginPowers() {
@@ -1255,6 +1285,27 @@ public enum PowersManager {
return false; return false;
} }
// MONSTERTYPERESTRICTS: mobs use their race rune's monster type, players their RaceType's
private static boolean isRestrictedMonsterType(AbstractWorldObject target, PowersBase pb) {
MonsterType monsterType = null;
PlayerCharacter player;
if (target == null || pb.monsterTypeRestrictions.isEmpty())
return false;
if (target.getObjectType() == GameObjectType.Mob && ((Mob) target).getMobBase() != null)
monsterType = ((Mob) target).getMobBase().monsterType;
else if (target.getObjectType() == GameObjectType.PlayerCharacter) {
player = (PlayerCharacter) target;
if (player.race != null && player.race.getRaceType() != null)
monsterType = player.race.getRaceType().getMonsterType();
}
return monsterType != null && pb.monsterTypeRestrictions.contains(monsterType);
}
public static void sendRecyclePower(int token, ClientConnection origin) { public static void sendRecyclePower(int token, ClientConnection origin) {
RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token); RecyclePowerMsg recyclePowerMsg = new RecyclePowerMsg(token);
@@ -1580,9 +1631,6 @@ public enum PowersManager {
//Pet only //Pet only
allTargets = WorldGrid.getObjectsInRangePartial( allTargets = WorldGrid.getObjectsInRangePartial(
targetLoc, pb.getRadius(), MBServerStatics.MASK_PET); targetLoc, pb.getRadius(), MBServerStatics.MASK_PET);
else if (pb.targetNecroPet())
allTargets = WorldGrid.getObjectsInRangePartialNecroPets(
targetLoc, pb.getRadius());
else else
allTargets = WorldGrid.getObjectsInRangePartial( allTargets = WorldGrid.getObjectsInRangePartial(
targetLoc, pb.getRadius(), 0); targetLoc, pb.getRadius(), 0);
@@ -1694,6 +1742,11 @@ public enum PowersManager {
continue; continue;
} }
if (isRestrictedMonsterType(awo, pb)) {
awolist.remove();
continue;
}
if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {
// Remove players who are in safe mode // Remove players who are in safe mode
@@ -1779,9 +1832,6 @@ public enum PowersManager {
//Pet only //Pet only
allTargets = WorldGrid.getObjectsInRangePartial( allTargets = WorldGrid.getObjectsInRangePartial(
targetLoc, pb.getRadius(), MBServerStatics.MASK_PET); targetLoc, pb.getRadius(), MBServerStatics.MASK_PET);
else if (pb.targetNecroPet())
allTargets = WorldGrid.getObjectsInRangePartialNecroPets(
targetLoc, pb.getRadius());
else else
allTargets = WorldGrid.getObjectsInRangePartial( allTargets = WorldGrid.getObjectsInRangePartial(
targetLoc, pb.getRadius(), 0); targetLoc, pb.getRadius(), 0);
@@ -1846,6 +1896,11 @@ public enum PowersManager {
continue; continue;
} }
if (isRestrictedMonsterType(awo, pb)) {
awolist.remove();
continue;
}
if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) { if (awo != null && awo.getObjectType().equals(GameObjectType.PlayerCharacter)) {
// Remove players who are in safe mode // Remove players who are in safe mode
PlayerCharacter pcc = (PlayerCharacter) awo; PlayerCharacter pcc = (PlayerCharacter) awo;
@@ -2238,14 +2293,14 @@ public enum PowersManager {
String outmsg = "Invalid Target"; String outmsg = "Invalid Target";
ChatManager.chatSystemInfo(pc, outmsg); ChatManager.chatSystemInfo(pc, outmsg);
return false; // can't target player, stop here return false; // can't target player, stop here
} // target is mob } // target is pet; pets also carry MASK_MOB, so test them first. A pet is still a Mobile (client 0x49E440)
else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PET) != 0)
return pb.targetPet() || pb.targetMob();
// target is mob
else if ((target.getObjectTypeMask() & MBServerStatics.MASK_MOB) != 0) else if ((target.getObjectTypeMask() & MBServerStatics.MASK_MOB) != 0)
return pb.targetMob(); return pb.targetMob();
// target is pet
else if ((target.getObjectTypeMask() & MBServerStatics.MASK_PET) != 0)
return pb.targetPet();
// target is Building // target is Building
else if ((target.getObjectTypeMask() & MBServerStatics.MASK_BUILDING) != 0) else if ((target.getObjectTypeMask() & MBServerStatics.MASK_BUILDING) != 0)
return pb.targetBuilding(); return pb.targetBuilding();
@@ -2255,12 +2310,12 @@ public enum PowersManager {
if (pb.targetItem()) if (pb.targetItem())
return true; return true;
// TODO add these checks later // TODO add these checks later
else if (pb.targetArmor() && item.template.item_type.equals(ItemType.ARMOR)) else if (pb.targetArmor() && item.template.getItemType().equals(ItemType.ARMOR))
return true; return true;
else if (pb.targetJewelry() && item.template.item_type.equals(ItemType.JEWELRY)) else if (pb.targetJewelry() && item.template.getItemType().equals(ItemType.JEWELRY))
return true; return true;
else else
return pb.targetWeapon() && item.template.item_type.equals(ItemType.WEAPON); return pb.targetWeapon() && item.template.getItemType().equals(ItemType.WEAPON);
} // How did we get here? all valid targets have been covered } // How did we get here? all valid targets have been covered
else else
return false; return false;
+4 -4
View File
@@ -12,9 +12,9 @@ import engine.gameManager.DbManager;
import engine.gameManager.ForgeManager; import engine.gameManager.ForgeManager;
import engine.mbEnums; import engine.mbEnums;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.ItemTemplate;
import engine.objects.NPC; import engine.objects.NPC;
import engine.objects.Warehouse; import engine.objects.Warehouse;
import engine.parsers.objects.CObjectTemplate;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@@ -127,16 +127,16 @@ public class WorkOrder implements Delayed {
int validation_result = 0; int validation_result = 0;
ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID); CObjectTemplate template = CObjectTemplate.lookup(workOrder.templateID);
if (workOrder.vendor.getBuilding() == null) if (workOrder.vendor.getBuilding() == null)
return 58; //58: The formula is beyond the means of this facility return 58; //58: The formula is beyond the means of this facility
if (!workOrder.vendor.charItemManager.hasRoomInventory(template.item_wt)) if (!workOrder.vendor.charItemManager.hasRoomInventory(template.getItemWt()))
return 30; //30: That person cannot carry that item return 30; //30: That person cannot carry that item
if ((workOrder.prefixToken != 0 || workOrder.suffixToken != 0) && if ((workOrder.prefixToken != 0 || workOrder.suffixToken != 0) &&
!workOrder.vendor.getItemModTable().contains((template.modTable))) !workOrder.vendor.getItemModTable().contains(ForgeManager.getVendorModTable(workOrder.templateID)))
return 59; //59: This hireling does not have this formula return 59; //59: This hireling does not have this formula
if (!Warehouse.calcCostOverrun(workOrder).isEmpty()) if (!Warehouse.calcCostOverrun(workOrder).isEmpty())
-30
View File
@@ -583,36 +583,6 @@ final public class FastMath {
return ret.normalize(); return ret.normalize();
} }
/**
* Turns a String into an Integer hash that the client will recognize.
*
* @param s The String to hash
* @return the Integer hash
*/
public static int hash(String s) {
int out = 0, pad = 0;
for (char c : s.toCharArray()) {
if (c == ' ' || c == '\\') {
pad += 5;
continue;
}
if (c > 0x40 && c < 0x60)
out ^= _rotl((byte) 0x60, pad);
out ^= _rotl((byte) (c & 0xDF), pad);
pad += 5;
}
return out;
}
/**
* Utility function for making hashes
*/
private static int _rotl(int value, int shift) {
if ((shift &= 31) == 0)
return value;
return (value << shift) | (value >> (32 - shift));
}
/** /**
* Gets number of seconds until next hours * Gets number of seconds until next hours
*/ */
+31 -24
View File
@@ -15,7 +15,6 @@ import engine.math.Vector2f;
import engine.math.Vector3f; import engine.math.Vector3f;
import engine.math.Vector3fImmutable; import engine.math.Vector3fImmutable;
import engine.objects.AbstractCharacter; import engine.objects.AbstractCharacter;
import engine.objects.ItemTemplate;
import engine.objects.Shrine; import engine.objects.Shrine;
import engine.objects.Zone; import engine.objects.Zone;
import engine.powers.EffectsBase; import engine.powers.EffectsBase;
@@ -824,6 +823,7 @@ public class mbEnums {
ACID, ACID,
DISEASE, DISEASE,
UNKNOWN, UNKNOWN,
ANTISIEGE,
// these added for immunities // these added for immunities
ATTACK, ATTACK,
POWERS, POWERS,
@@ -842,34 +842,26 @@ public class mbEnums {
public static DamageType getDamageType(String modName) { public static DamageType getDamageType(String modName) {
String name;
if (modName.isEmpty()) if (modName.isEmpty())
return DamageType.NONE; return DamageType.NONE;
if(modName.toLowerCase().equals("blind")) // Client and DB strings vary in case and punctuation ("Fire", "Anti-Siege")
modName = "BLINDNESS";
if(modName.toLowerCase().equals("powerblock")) name = modName.replaceAll("[\\s,-]", "").toUpperCase();
modName = "POWERINHIBITOR";
//validity check for not looking up damage type that doesn't exist if (name.equals("BLIND"))
boolean valid = false; name = "BLINDNESS";
for(DamageType type : DamageType.values()){
if(type.name().equals(modName)) if (name.equals("POWERBLOCK"))
valid = true; name = "POWERINHIBITOR";
}
for (DamageType type : DamageType.values())
if (type.name().equals(name))
return type;
if(!valid)
return DamageType.NONE; return DamageType.NONE;
DamageType damageType;
try {
damageType = DamageType.valueOf(modName.replace(",", "").toUpperCase());
} catch (Exception e) {
Logger.error(e);
return DamageType.NONE;
}
return damageType;
} }
} }
@@ -1509,6 +1501,15 @@ public class mbEnums {
} }
} }
// rune_type values from the client cache data (COBJECTS/RUNE)
public enum RuneType {
CLASS,
DISCIPLINE,
RACE,
TALENT
}
public enum ShrineType { public enum ShrineType {
Aelfborn(1701900, -75506007, true), Aelfborn(1701900, -75506007, true),
@@ -2761,7 +2762,6 @@ public class mbEnums {
public static HashMap<Integer, ResourceType> templateHashLookup = new HashMap<>(); public static HashMap<Integer, ResourceType> templateHashLookup = new HashMap<>();
public static HashMap<Integer, ResourceType> resourceHashLookup = new HashMap<>(); public static HashMap<Integer, ResourceType> resourceHashLookup = new HashMap<>();
public int templateID; public int templateID;
public ItemTemplate template;
public int resourceHash; public int resourceHash;
public int templateHash; public int templateHash;
public int deposit_limit; public int deposit_limit;
@@ -2769,7 +2769,6 @@ public class mbEnums {
ResourceType(int templateID, int resourceHash, int templateHash, int deposit_limit, int mine_production) { ResourceType(int templateID, int resourceHash, int templateHash, int deposit_limit, int mine_production) {
this.templateID = templateID; this.templateID = templateID;
this.template = ItemTemplate.templates.get(this.templateID);
this.resourceHash = resourceHash; this.resourceHash = resourceHash;
this.templateHash = templateHash; this.templateHash = templateHash;
this.deposit_limit = deposit_limit; this.deposit_limit = deposit_limit;
@@ -2908,6 +2907,14 @@ public class mbEnums {
EMPTY_NEWITEM; EMPTY_NEWITEM;
} }
// Cache JSON names. UNKNOWN is raw 0, which the client treats as no slot (sb.exe 0x4AC810).
public enum SheathSlot {
UNKNOWN,
BACK,
WAIST,
NONE
}
public enum AttributeType { public enum AttributeType {
None, None,
Strength, Strength,
@@ -46,7 +46,7 @@ public class ActivateNPCMsgHandler extends AbstractClientMsgHandler {
// Filter hirelings by slot type // Filter hirelings by slot type
for (Item hirelings : player.getInventory()) { for (Item hirelings : player.getInventory()) {
if (hirelings.template.item_type.equals(ItemType.EMPLOYMENTCONTRACT)) { if (hirelings.template.getItemType().equals(ItemType.EMPLOYMENTCONTRACT)) {
contract = DbManager.ContractQueries.GET_CONTRACT(hirelings.templateID); contract = DbManager.ContractQueries.GET_CONTRACT(hirelings.templateID);
@@ -78,7 +78,7 @@ public class AddItemToTradeWindowMsgHandler extends AbstractClientMsgHandler {
if (!item.validForInventory(source.getClientConnection(), source, source.charItemManager)) if (!item.validForInventory(source.getClientConnection(), source, source.charItemManager))
return false; return false;
if (!tradingWith.hasRoomTrade(item.template.item_wt)) { if (!tradingWith.hasRoomTrade(item.template.getItemWt())) {
dispatch = Dispatch.borrow(source, msg); dispatch = Dispatch.borrow(source, msg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY); DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.PRIMARY);
return false; return false;
@@ -97,9 +97,13 @@ public class DestroyBuildingHandler extends AbstractClientMsgHandler {
if (city == null) if (city == null)
return true; return true;
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse); Warehouse.destroyedWarehouses.remove(building.getObjectUUID());
// Rubble no longer owns city.warehouse, which may be null or a newly placed warehouse
if (city.warehouse != null && building.getRank() != -1) {
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
city.warehouse = null; city.warehouse = null;
}
} }
@@ -24,6 +24,7 @@ import engine.net.client.msg.guild.GuildInfoMsg;
import engine.objects.Guild; import engine.objects.Guild;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import engine.parsers.objects.DeedTemplate;
import engine.util.StringUtils; import engine.util.StringUtils;
public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler { public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
@@ -42,6 +43,7 @@ public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
Guild playerGuild; Guild playerGuild;
Item charter; Item charter;
DeedTemplate charterDeed;
Dispatch dispatch; Dispatch dispatch;
msg = (GuildCreationFinalizeMsg) baseMsg; msg = (GuildCreationFinalizeMsg) baseMsg;
@@ -65,7 +67,9 @@ public class GuildCreationFinalizeHandler extends AbstractClientMsgHandler {
// Item must be a valid charterType (type 10 in db) // Item must be a valid charterType (type 10 in db)
if (charter == null || (charter.template.item_type.equals(ItemType.CHARTER) == false)) { charterDeed = DeedTemplate._deeds.get(charter.templateID);
if (charterDeed == null || (charterDeed.item_type.equals(ItemType.CHARTER) == false)) {
ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND); ErrorPopupMsg.sendErrorPopup(player, GuildManager.NO_CHARTER_FOUND);
return true; return true;
} }
@@ -238,10 +238,10 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (targetItem == null) if (targetItem == null)
return; return;
if (targetItem.template.item_type.equals(ItemType.GOLD)) if (targetItem.template.getItemType().equals(ItemType.GOLD))
return; return;
if (!vendor.charItemManager.hasRoomInventory(targetItem.template.item_wt)) { if (!vendor.charItemManager.hasRoomInventory(targetItem.template.getItemWt())) {
ErrorPopupMsg.sendErrorPopup(player, 21); ErrorPopupMsg.sendErrorPopup(player, 21);
return; return;
} }
@@ -319,7 +319,7 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (targetItem == null) if (targetItem == null)
continue; continue;
if (targetItem.template.item_type.equals(ItemType.GOLD)) if (targetItem.template.getItemType().equals(ItemType.GOLD))
return; return;
if (!vendor.charItemManager.doesCharOwnThisItem(targetItem.getObjectUUID())) if (!vendor.charItemManager.doesCharOwnThisItem(targetItem.getObjectUUID()))
@@ -328,14 +328,14 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (!vendor.charItemManager.inventoryContains(targetItem)) if (!vendor.charItemManager.inventoryContains(targetItem))
continue; continue;
itemValue = targetItem.template.item_value; itemValue = targetItem.template.getItemValue();
if (vendor.getBuilding().getStrongboxValue() + itemValue > vendor.getBuilding().getMaxGold()) { if (vendor.getBuilding().getStrongboxValue() + itemValue > vendor.getBuilding().getMaxGold()) {
ErrorPopupMsg.sendErrorPopup(player, 201); ErrorPopupMsg.sendErrorPopup(player, 201);
break; break;
} }
switch (targetItem.template.item_type) { switch (targetItem.template.getItemType()) {
case EMPLOYMENTCONTRACT: case EMPLOYMENTCONTRACT:
case CHARTER: case CHARTER:
case DEED: case DEED:
@@ -465,13 +465,13 @@ public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
if (targetItem == null) if (targetItem == null)
return; return;
if (targetItem.template.item_type.equals(ItemType.GOLD)) if (targetItem.template.getItemType().equals(ItemType.GOLD))
return; return;
if (!vendor.charItemManager.inventoryContains(targetItem)) if (!vendor.charItemManager.inventoryContains(targetItem))
return; return;
if (!player.charItemManager.hasRoomInventory(targetItem.template.item_wt)) if (!player.charItemManager.hasRoomInventory(targetItem.template.getItemWt()))
return; return;
player.charItemManager.buyFromNPC(targetItem, vendor); player.charItemManager.buyFromNPC(targetItem, vendor);
@@ -161,7 +161,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
if (itemRet == null) if (itemRet == null)
return true; return true;
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) { if (item.template.getItemType().equals(mbEnums.ItemType.GOLD)) {
// this is done to prevent the temporary goldItem item // this is done to prevent the temporary goldItem item
// (from the mob) from appearing in player's inventory. // (from the mob) from appearing in player's inventory.
// It also updates the goldItem quantity display // It also updates the goldItem quantity display
@@ -187,7 +187,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
//TODO send group loot message if player is grouped and visible //TODO send group loot message if player is grouped and visible
Group group = GroupManager.getGroup(player); Group group = GroupManager.getGroup(player);
if (group != null && group.getSplitGold() && (item.template.item_type.equals(mbEnums.ItemType.GOLD) == false)) { if (group != null && group.getSplitGold() && (item.template.getItemType().equals(mbEnums.ItemType.GOLD) == false)) {
String name = item.getName(); String name = item.getName();
String text = player.getFirstName() + " has looted " + name + '.'; String text = player.getFirstName() + " has looted " + name + '.';
ChatManager.chatGroupInfoCanSee(player, text); ChatManager.chatGroupInfoCanSee(player, text);
@@ -202,7 +202,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
if (itemRet == null) if (itemRet == null)
return true; return true;
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) { if (item.template.getItemType().equals(mbEnums.ItemType.GOLD)) {
// this is done to prevent the temporary goldItem item // this is done to prevent the temporary goldItem item
// (from the mob) from appearing in player's inventory. // (from the mob) from appearing in player's inventory.
// It also updates the goldItem quantity display // It also updates the goldItem quantity display
@@ -241,7 +241,7 @@ public class LootMsgHandler extends AbstractClientMsgHandler {
//TODO send group loot message if player is grouped and visible //TODO send group loot message if player is grouped and visible
Group group = GroupManager.getGroup(player); Group group = GroupManager.getGroup(player);
if (group != null && group.getSplitGold() && (item.template.item_type.equals(mbEnums.ItemType.GOLD) == false)) { if (group != null && group.getSplitGold() && (item.template.getItemType().equals(mbEnums.ItemType.GOLD) == false)) {
String name = item.getName(); String name = item.getName();
String text = player.getFirstName() + " has looted " + name + '.'; String text = player.getFirstName() + " has looted " + name + '.';
ChatManager.chatGroupInfoCanSee(player, text); ChatManager.chatGroupInfoCanSee(player, text);
@@ -141,17 +141,9 @@ public class ManageCityAssetMsgHandler extends AbstractClientMsgHandler {
return true; return true;
} }
City city = building.getCity(); Warehouse warehouse = Warehouse.destroyedWarehouses.get(building.getObjectUUID());
if (city == null) if (warehouse == null || Warehouse.isEmpty(warehouse)) {
return true;
Warehouse warehouse = city.warehouse;
if (warehouse == null)
return true;
if (Warehouse.isEmpty(warehouse)) {
ErrorPopupMsg.sendErrorPopup(player, 167); // no more resources. ErrorPopupMsg.sendErrorPopup(player, 167); // no more resources.
return true; return true;
} }
@@ -8,6 +8,7 @@ import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.*; import engine.net.client.msg.*;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.DeedTemplate;
import engine.powers.PowersBase; import engine.powers.PowersBase;
import java.util.ArrayList; import java.util.ArrayList;
@@ -78,7 +79,7 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
int uuid = item.templateID; int uuid = item.templateID;
switch (item.template.item_type) { switch (item.template.getItemType()) {
case DEED: //buildings case DEED: //buildings
//Call add building screen here, ib.getUseID() get's building ID //Call add building screen here, ib.getUseID() get's building ID
@@ -99,7 +100,7 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
pam.setX(loc.getX() + 64); //offset grid from tol pam.setX(loc.getX() + 64); //offset grid from tol
pam.setY(loc.getY()); pam.setY(loc.getY());
pam.setZ(loc.getZ() + 64); //offset grid from tol pam.setZ(loc.getZ() + 64); //offset grid from tol
pam.addPlacementInfo(item.template.deed_structure_id); pam.addPlacementInfo(DeedTemplate._deeds.get(item.templateID).deed_structure_id);
dispatch = Dispatch.borrow(player, pam); dispatch = Dispatch.borrow(player, pam);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY); DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
@@ -187,14 +188,14 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
if (uuid > 680069 && uuid < 680074) //Handle Charter, Deed, Petition, Warrant here if (uuid > 680069 && uuid < 680074) //Handle Charter, Deed, Petition, Warrant here
break; break;
if (item.template.item_bane_rank > 0) { if (item.template.getItemBaneRank() > 0) {
// Only one banestone at a time // Only one banestone at a time
lock.writeLock().lock(); lock.writeLock().lock();
try { try {
if (Bane.summonBanestone(player, origin, item.template.item_bane_rank) == true) if (Bane.summonBanestone(player, origin, item.template.getItemBaneRank()) == true)
itemMan.consume(item); itemMan.consume(item);
} finally { } finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
@@ -225,7 +226,7 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
// Bypass for waterbuckets // Bypass for waterbuckets
if (item.template.item_type.equals(mbEnums.ItemType.BUCKET)) { if (item.template.getItemType().equals(mbEnums.ItemType.BUCKET)) {
// test for valid target type // test for valid target type
if (target.getObjectType() == mbEnums.GameObjectType.PlayerCharacter) if (target.getObjectType() == mbEnums.GameObjectType.PlayerCharacter)
@@ -250,10 +251,9 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
} }
if (waterbucketBypass == false) { if (waterbucketBypass == false) {
String powerString = item.template.item_power_grant.keySet().iterator().next(); engine.parsers.objects.PowerGrant powerGrant = item.template.getItemPowerGrant().get(0);
PowersBase powerAction = PowersManager.powersBaseByIDString.get(powerString); PowersBase powerAction = PowersManager.powersBaseByIDString.get(powerGrant.power_type);
int powerValue = item.template.item_power_grant.get(powerString); PowersManager.applyPower(player, target, Vector3fImmutable.ZERO, powerAction.getToken(), powerGrant.power_value, true);
PowersManager.applyPower(player, target, Vector3fImmutable.ZERO, powerAction.getToken(), powerValue, true);
} }
itemMan.consume(item); itemMan.consume(item);
} else //just remove the item at this point } else //just remove the item at this point
@@ -266,7 +266,8 @@ public class ObjectActionMsgHandler extends AbstractClientMsgHandler {
case RUNE: case RUNE:
if(ApplyRuneMsg.applyRune(uuid, origin, player)) { if(ApplyRuneMsg.applyRune(uuid, origin, player)) {
itemMan.consume(item); itemMan.consume(item);
} } else
ErrorPopupMsg.sendErrorPopup(player, 161);
break; break;
default: //shouldn't be here, consume item default: //shouldn't be here, consume item
dispatch = Dispatch.borrow(player, msg); dispatch = Dispatch.borrow(player, msg);
@@ -18,6 +18,7 @@ import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.PlaceAssetMsg; import engine.net.client.msg.PlaceAssetMsg;
import engine.net.client.msg.PlaceAssetMsg.PlacementInfo; import engine.net.client.msg.PlaceAssetMsg.PlacementInfo;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.DeedTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -135,6 +136,8 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
private static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) { private static boolean validateBuildingPlacement(Zone serverZone, PlaceAssetMsg msg, ClientConnection origin, PlayerCharacter player, PlacementInfo placementInfo) {
Bane bane;
if (serverZone.guild_zone == false) { if (serverZone.guild_zone == false) {
PlaceAssetMsg.sendPlaceAssetError(origin, 52, player.getName()); PlaceAssetMsg.sendPlaceAssetError(origin, 52, player.getName());
return false; return false;
@@ -205,6 +208,15 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
return false; return false;
} }
// Siege equipment has its own validation and never reaches here
bane = city.getBane();
if (bane != null) {
PlaceAssetMsg.sendPlaceAssetError(origin, 74, ""); //"Only buildings of war may be placed during a bane"
return false;
}
if (placementCollisionCheck(serverZone, origin, placementInfo)) { if (placementCollisionCheck(serverZone, origin, placementInfo)) {
PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets PlaceAssetMsg.sendPlaceAssetError(origin, 3, ""); // Conflict between proposed assets
return false; return false;
@@ -345,8 +357,8 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
Item contract = null; Item contract = null;
for (Item inventoryItem : playerCharacter.getInventory()) { for (Item inventoryItem : playerCharacter.getInventory()) {
if (inventoryItem.template.item_type.equals(ItemType.DEED) && if (inventoryItem.template.getItemType().equals(ItemType.DEED) &&
inventoryItem.template.deed_structure_id == buildingList.getBlueprintUUID()) { DeedTemplate._deeds.get(inventoryItem.templateID).deed_structure_id == buildingList.getBlueprintUUID()) {
contract = inventoryItem; contract = inventoryItem;
break; break;
} }
@@ -1341,6 +1353,7 @@ public class PlaceAssetMsgHandler extends AbstractClientMsgHandler {
} }
Warehouse warehouse = new Warehouse(warehouseBuilding); Warehouse warehouse = new Warehouse(warehouseBuilding);
Warehouse.destroyedWarehouses.values().removeIf(destroyed -> destroyed.city == city);
city.warehouse = warehouse; city.warehouse = warehouse;
DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse); DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse);
@@ -85,7 +85,7 @@ public class RepairMsgHandler extends AbstractClientMsgHandler {
//make sure item is damaged and not destroyed //make sure item is damaged and not destroyed
short dur = (short) toRepair.combat_health_current; short dur = (short) toRepair.combat_health_current;
short max = (short) toRepair.template.combat_health_full; short max = (short) toRepair.template.getCombatHealthFull();
//account for durability modifications //account for durability modifications
@@ -68,7 +68,7 @@ public class TransferItemFromBankMsgHandler extends AbstractClientMsgHandler {
return true; return true;
; ;
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) { if (item.template.getItemType().equals(mbEnums.ItemType.GOLD)) {
if (!itemManager.moveGoldToInventory(item, msg.getNumItems())) if (!itemManager.moveGoldToInventory(item, msg.getNumItems()))
return true; return true;
@@ -85,7 +85,7 @@ public class TransferItemFromBankMsgHandler extends AbstractClientMsgHandler {
// Not gold, process update here // Not gold, process update here
if (!itemManager.hasRoomInventory(item.template.item_wt)) if (!itemManager.hasRoomInventory(item.template.getItemWt()))
return true; return true;
if (itemManager.moveItemToInventory(item) == false) if (itemManager.moveItemToInventory(item) == false)
@@ -65,7 +65,7 @@ public class TransferItemToBankMsgHandler extends AbstractClientMsgHandler {
return true; return true;
if (item.containerType == mbEnums.ItemContainerType.INVENTORY && itemManager.isBankOpen()) if (item.containerType == mbEnums.ItemContainerType.INVENTORY && itemManager.isBankOpen())
if (item.template.item_type.equals(mbEnums.ItemType.GOLD)) { if (item.template.getItemType().equals(mbEnums.ItemType.GOLD)) {
if (!itemManager.moveGoldToBank(item, msg.getNumItems())) if (!itemManager.moveGoldToBank(item, msg.getNumItems()))
return true; return true;
UpdateGoldMsg goldMes = new UpdateGoldMsg(player); UpdateGoldMsg goldMes = new UpdateGoldMsg(player);
@@ -76,7 +76,7 @@ public class TransferItemToBankMsgHandler extends AbstractClientMsgHandler {
} else { } else {
if (!itemManager.hasRoomBank(item.template.item_wt)) if (!itemManager.hasRoomBank(item.template.getItemWt()))
return true; return true;
if (!itemManager.moveItemToBank(item)) if (!itemManager.moveItemToBank(item))
@@ -86,7 +86,7 @@ public class VendorBuyMsgHandler extends AbstractClientMsgHandler {
if (me.objectUUID == vendorBuyMsg.getItemID()) { if (me.objectUUID == vendorBuyMsg.getItemID()) {
//test room available for item //test room available for item
if (!itemMan.hasRoomInventory(me.template.item_wt)) if (!itemMan.hasRoomInventory(me.template.getItemWt()))
return true; return true;
int cost = me.getValue(); int cost = me.getValue();
@@ -148,10 +148,10 @@ public class VendorBuyMsgHandler extends AbstractClientMsgHandler {
//test room available for item //test room available for item
if (!itemMan.hasRoomInventory(vendorItem.template.item_wt)) if (!itemMan.hasRoomInventory(vendorItem.template.getItemWt()))
return true; return true;
int cost = vendorItem.template.item_value; int cost = vendorItem.template.getItemValue();
if (vendorItem.flags.contains(mbEnums.ItemFlags.Identified) || vendorItem.isCustomValue()) if (vendorItem.flags.contains(mbEnums.ItemFlags.Identified) || vendorItem.isCustomValue())
cost = vendorItem.magicValue; cost = vendorItem.magicValue;
@@ -94,7 +94,7 @@ public class VendorSellMsgHandler extends AbstractClientMsgHandler {
return true; return true;
if (npc.getParentZone() != null && npc.getParentZone().playerCityUUID != 0) if (npc.getParentZone() != null && npc.getParentZone().playerCityUUID != 0)
if (!npc.charItemManager.hasRoomInventory(sell.template.item_wt)) { if (!npc.charItemManager.hasRoomInventory(sell.template.getItemWt())) {
ErrorPopupMsg.sendErrorPopup(player, 21); ErrorPopupMsg.sendErrorPopup(player, 21);
return true; return true;
} }
@@ -104,7 +104,7 @@ public class VendorSellMsgHandler extends AbstractClientMsgHandler {
//get goldItem cost to sell //get goldItem cost to sell
cost = (int) (sell.template.item_value * (sell.combat_health_current / sell.template.combat_health_full)); cost = (int) (sell.template.getItemValue() * (sell.combat_health_current / sell.template.getCombatHealthFull()));
float bargain = player.getBargain(); float bargain = player.getBargain();
float profit = npc.getBuyPercent(player) + bargain; float profit = npc.getBuyPercent(player) + bargain;
+73 -72
View File
@@ -22,10 +22,17 @@ import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class ApplyRuneMsg extends ClientNetMsg { public class ApplyRuneMsg extends ClientNetMsg {
// Client ArcCharacter_AddRune (0x4695D0)
private static final int MAX_TALENTS = 8;
private int targetType; private int targetType;
private int targetID; private int targetID;
private int removeRuneBase; private int removeRuneBase;
@@ -78,67 +85,28 @@ public class ApplyRuneMsg extends ClientNetMsg {
return false; return false;
} }
//Check race is met
ConcurrentHashMap<Integer, Boolean> races = rb.getRace();
if (races.size() > 0) {
int raceID = playerCharacter.getRaceID();
boolean valid = false;
for (int validID : races.keySet()) {
if (validID == raceID) {
valid = true;
break;
}
}
if (!valid) {
return false;
}
}
//Check base class is met
ConcurrentHashMap<Integer, Boolean> baseClasses = rb.getBaseClass();
if (baseClasses.size() > 0) {
int baseClassID = playerCharacter.getBaseClassID();
boolean valid = false;
for (int validID : baseClasses.keySet()) {
if (validID == baseClassID) {
valid = true;
break;
}
}
if (!valid) {
return false;
}
}
//Check promotion class is met
ConcurrentHashMap<Integer, Boolean> promotionClasses = rb.getPromotionClass();
if (promotionClasses.size() > 0) {
int promotionClassID = playerCharacter.getPromotionClassID();
boolean valid = false;
for (int validID : promotionClasses.keySet()) {
if (validID == promotionClassID) {
valid = true;
break;
}
}
if (!valid) {
return false;
}
}
//Check disciplines are met
ArrayList<CharacterRune> runes = playerCharacter.getRunes(); ArrayList<CharacterRune> runes = playerCharacter.getRunes();
ConcurrentHashMap<Integer, Boolean> disciplines = rb.getDiscipline(); HashSet<Integer> classIDs = new HashSet<>();
if (disciplines.size() > 0) { HashSet<Integer> heldDiscIDs = new HashSet<>();
for (CharacterRune cr : runes) {
int runeBaseID = cr.getRuneBaseID(); // Client ArcCharacter_MeetsItemReqs (0x475F40)
for (Integer prohID : disciplines.keySet()) { if (!meetsListReq(rb.getRace().keySet(), rb.isRaceRestrict(), Collections.singleton(playerCharacter.getRaceID())))
if (runeBaseID == prohID) { return false;
return false; //Prohibited rune
} // A listed base class covers its promotions: base or promotion listed is enough
} classIDs.addAll(rb.getBaseClass().keySet());
} classIDs.addAll(rb.getPromotionClass().keySet());
}
if (!meetsListReq(classIDs, rb.isClassRestrict(), Arrays.asList(playerCharacter.getBaseClassID(), playerCharacter.getPromotionClassID())))
return false;
// Only the new rune's list is checked, not the held discs' lists
for (CharacterRune cr : runes)
if (isDiscipline(cr.getRuneBaseID()))
heldDiscIDs.add(cr.getRuneBaseID());
if (!meetsListReq(rb.getDiscipline().keySet(), rb.isDiscRestrict(), heldDiscIDs))
return false;
int discCount = 0; int discCount = 0;
for (CharacterRune cr : runes) { for (CharacterRune cr : runes) {
@@ -158,6 +126,32 @@ public class ApplyRuneMsg extends ClientNetMsg {
return false; return false;
} }
// Client 0x4768D0 refuses a talent unless its rank beats the held one with the same sub type (rank 0 included);
// AddRune (0x4695D0) then replaces the held talent
CharacterRune replacedRune = null;
if (rb.getRuneType() == mbEnums.RuneType.TALENT) {
int talentCount = 0;
for (CharacterRune cr : runes) {
RuneBase heldRuneBase = cr.getRuneBase();
if (heldRuneBase == null || heldRuneBase.getRuneType() != mbEnums.RuneType.TALENT)
continue;
talentCount++;
if (rb.getSubTypeToken() != 0 && heldRuneBase.getSubTypeToken() == rb.getSubTypeToken())
replacedRune = cr;
}
if (replacedRune != null) {
if (rb.getRank() <= replacedRune.getRuneBase().getRank())
return false;
} else if (talentCount >= MAX_TALENTS)
return false;
}
int strTotal = 0; int strTotal = 0;
int dexTotal = 0; int dexTotal = 0;
int conTotal = 0; int conTotal = 0;
@@ -227,11 +221,6 @@ public class ApplyRuneMsg extends ClientNetMsg {
} }
} }
//Check if max number runes already reached
if (runes.size() > 12) {
return false;
}
//if discipline, check number applied //if discipline, check number applied
if (isDiscipline(runeID)) { if (isDiscipline(runeID)) {
if (playerCharacter.getLevel() < 70) { if (playerCharacter.getLevel() < 70) {
@@ -259,13 +248,10 @@ public class ApplyRuneMsg extends ClientNetMsg {
return false; return false;
} }
//remove any overridden runes from player //remove the replaced talent from player
ArrayList<Integer> overwrite = rb.getOverwrite();
CharacterRune toRemove = null; CharacterRune toRemove = null;
if (overwrite.size() > 0) { if (replacedRune != null) {
for (int overwriteID : overwrite) { toRemove = playerCharacter.removeRune(replacedRune.getRuneBaseID());
toRemove = playerCharacter.removeRune(overwriteID);
}
} }
//add rune to player //add rune to player
@@ -331,6 +317,21 @@ public class ApplyRuneMsg extends ClientNetMsg {
return runeID > 3000 && runeID < 3050; return runeID > 3000 && runeID < 3050;
} }
// Client ArcItemRestrict_Check (0x4BAC60). An empty list has no requirement; no rune has an empty whitelist.
private static boolean meetsListReq(Set<Integer> listedIDs, boolean restrict, Collection<Integer> heldIDs) {
boolean listed = false;
if (listedIDs.isEmpty())
return true;
for (int heldID : heldIDs)
if (listedIDs.contains(heldID))
listed = true;
return listed != restrict;
}
/** /**
* Serializes the subclass specific items to the supplied NetMsgWriter. * Serializes the subclass specific items to the supplied NetMsgWriter.
*/ */
+9 -7
View File
@@ -19,6 +19,7 @@ import engine.net.ByteBufferReader;
import engine.net.ByteBufferWriter; import engine.net.ByteBufferWriter;
import engine.net.Protocol; import engine.net.Protocol;
import engine.objects.*; import engine.objects.*;
import engine.parsers.objects.CObjectTemplate;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Period; import org.joda.time.Period;
import org.joda.time.Seconds; import org.joda.time.Seconds;
@@ -420,17 +421,18 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(rollableSet.size()); writer.putInt(rollableSet.size());
for (int templateID : rollableSet) { for (int templateID : rollableSet) {
ItemTemplate template = ItemTemplate.templates.get(templateID); CObjectTemplate template = CObjectTemplate.lookup(templateID);
int modTable = ForgeManager.getVendorModTable(templateID);
writer.put((byte) 1); writer.put((byte) 1);
writer.putInt(0); writer.putInt(0);
writer.putInt(templateID); //itemID writer.putInt(templateID); //itemID
writer.putInt(template.item_value); writer.putInt(template.getItemValue());
writer.putInt(600); writer.putInt(600);
writer.put((byte) 1); writer.put((byte) 1);
writer.put((byte) template.modTable); writer.put((byte) modTable);
writer.put((byte) template.modTable); writer.put((byte) modTable);
writer.put((byte) template.modTable); writer.put((byte) modTable);
writer.put((byte) template.modTable);//EffectItemType writer.put((byte) modTable);//EffectItemType
} }
ArrayList<Item> cooking = NPCManager.getAllCookingForVendor(npc); ArrayList<Item> cooking = NPCManager.getAllCookingForVendor(npc);
@@ -444,7 +446,7 @@ public class ManageNPCMsg extends ClientNetMsg {
writer.putInt(0); writer.putInt(0);
writer.putInt(item.templateID); writer.putInt(item.templateID);
writer.putInt(item.template.item_value); writer.putInt(item.template.getItemValue());
long timeLife = item.getDateToUpgrade() - System.currentTimeMillis(); long timeLife = item.getDateToUpgrade() - System.currentTimeMillis();
@@ -54,7 +54,7 @@ public class UpdateInventoryMsg extends ClientNetMsg {
for (Item item : list) { for (Item item : list) {
if (item.template.item_type.equals(ItemType.GOLD)) { if (item.template.getItemType().equals(ItemType.GOLD)) {
if (item.getNumOfItems() == 0) if (item.getNumOfItems() == 0)
continue; continue;
} }
+6 -1
View File
@@ -16,6 +16,7 @@ import engine.net.ByteBufferWriter;
import engine.net.Protocol; import engine.net.Protocol;
import engine.objects.Item; import engine.objects.Item;
import engine.objects.PlayerCharacter; import engine.objects.PlayerCharacter;
import engine.parsers.objects.DeedTemplate;
public class UseCharterMsg extends ClientNetMsg { public class UseCharterMsg extends ClientNetMsg {
@@ -61,9 +62,13 @@ public class UseCharterMsg extends ClientNetMsg {
public void configure() { public void configure() {
DeedTemplate charterDeed;
if (close) { if (close) {
for (Item i : player.getInventory()) { for (Item i : player.getInventory()) {
if (i.template.item_type.equals(ItemType.CHARTER)) { charterDeed = DeedTemplate._deeds.get(i.templateID);
if (charterDeed != null && charterDeed.item_type.equals(ItemType.CHARTER)) {
charterUUID = i.getObjectUUID(); charterUUID = i.getObjectUUID();
break; break;
} }
@@ -156,7 +156,7 @@ public class VendorBuyWindowMsg extends ClientNetMsg {
for (Item item : inventory) { for (Item item : inventory) {
if (item.getOwnerID() != ownerID) if (item.getOwnerID() != ownerID)
continue; continue;
if (item.template.item_type.equals(ItemType.GOLD)) { if (item.template.getItemType().equals(ItemType.GOLD)) {
if (item.getNumOfItems() == 0) if (item.getNumOfItems() == 0)
continue; continue;
} }
+39 -38
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,16 +884,13 @@ 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)) {
if (ibo != null && otherHand.template.equals(ItemType.WEAPON)) {
min *= 0.7f; min *= 0.7f;
max *= 0.7f; max *= 0.7f;
} }
}
// calculate damage // calculate damage
float minDamage; float minDamage;
@@ -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;
} }
+1
View File
@@ -433,6 +433,7 @@ public class Building extends AbstractWorldObject {
// Special handling of warehouses // Special handling of warehouses
if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) { if (blueprint.getBuildingGroup() == BuildingGroup.WAREHOUSE) {
Warehouse.destroyedWarehouses.put(this.getObjectUUID(), city.warehouse);
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse); DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
city.warehouse = null; city.warehouse = null;
} }
+34 -33
View File
@@ -17,6 +17,7 @@ import engine.mbEnums.ItemType;
import engine.net.Dispatch; import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.*; import engine.net.client.msg.*;
import engine.parsers.objects.CObjectTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger; import org.pmw.tinylog.Logger;
@@ -130,19 +131,19 @@ public class CharacterItemManager {
this.equipped.put(i.equipSlot, i); this.equipped.put(i.equipSlot, i);
break; break;
case BANK: case BANK:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldBank = i; this.goldBank = i;
else if (this.bank.contains(i) == false) else if (this.bank.contains(i) == false)
this.bank.add(i); this.bank.add(i);
break; break;
case INVENTORY: case INVENTORY:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldInventory = i; this.goldInventory = i;
else if (this.inventory.contains(i) == false) else if (this.inventory.contains(i) == false)
this.inventory.add(i); this.inventory.add(i);
break; break;
case VAULT: case VAULT:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldVault = i; this.goldVault = i;
else if (this.vault.contains(i) == false) else if (this.vault.contains(i) == false)
this.vault.add(i); this.vault.add(i);
@@ -175,13 +176,13 @@ public class CharacterItemManager {
this.equipped.put(i.equipSlot, i); this.equipped.put(i.equipSlot, i);
break; break;
case BANK: case BANK:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldBank = i; this.goldBank = i;
else if (this.bank.contains(i) == false) else if (this.bank.contains(i) == false)
this.bank.add(i); this.bank.add(i);
break; break;
case INVENTORY: case INVENTORY:
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
this.goldInventory = i; this.goldInventory = i;
else if (this.inventory.contains(i) == false) else if (this.inventory.contains(i) == false)
this.inventory.add(i); this.inventory.add(i);
@@ -511,7 +512,7 @@ public class CharacterItemManager {
if (i == null) if (i == null)
return false; return false;
if (i.template.item_type.equals(ItemType.GOLD)) { if (i.template.getItemType().equals(ItemType.GOLD)) {
if (this.getGoldInventory() != null) { if (this.getGoldInventory() != null) {
if (i.getObjectUUID() == this.getGoldInventory().getObjectUUID()) if (i.getObjectUUID() == this.getGoldInventory().getObjectUUID())
this.goldInventory = null; this.goldInventory = null;
@@ -541,7 +542,7 @@ public class CharacterItemManager {
} }
private synchronized boolean junk(Item i, boolean updateInventory) { private synchronized boolean junk(Item i, boolean updateInventory) {
if (i.template.item_type.equals(ItemType.GOLD)) { if (i.template.getItemType().equals(ItemType.GOLD)) {
if (this.getGoldInventory().getObjectUUID() == i.getObjectUUID()) if (this.getGoldInventory().getObjectUUID() == i.getObjectUUID())
if (DbManager.ItemQueries.UPDATE_GOLD(i, 0)) { if (DbManager.ItemQueries.UPDATE_GOLD(i, 0)) {
this.getGoldInventory().setNumOfItems(0); this.getGoldInventory().setNumOfItems(0);
@@ -596,7 +597,7 @@ public class CharacterItemManager {
if (equippedContains(item)) { if (equippedContains(item)) {
fromEquip = true; fromEquip = true;
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
this.absCharacter.cancelOnUnEquip(); this.absCharacter.cancelOnUnEquip();
} }
@@ -814,7 +815,7 @@ public class CharacterItemManager {
// This removes ingame item from inventory for loot. // This removes ingame item from inventory for loot.
private synchronized boolean removeItemFromInventory(Item i) { private synchronized boolean removeItemFromInventory(Item i) {
if (i.template.item_type.equals(ItemType.GOLD)) { if (i.template.getItemType().equals(ItemType.GOLD)) {
if (i.getObjectUUID() != this.getGoldInventory().getObjectUUID()) if (i.getObjectUUID() != this.getGoldInventory().getObjectUUID())
return false; return false;
if (!DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) { if (!DbManager.ItemQueries.UPDATE_GOLD(this.goldInventory, 0)) {
@@ -837,7 +838,7 @@ public class CharacterItemManager {
// This adds item to inventory for loot. Validity checks already handled // This adds item to inventory for loot. Validity checks already handled
public synchronized boolean addItemToInventory(Item i) { public synchronized boolean addItemToInventory(Item i) {
if (i.template.item_type.equals(ItemType.GOLD)) if (i.template.getItemType().equals(ItemType.GOLD))
if (this.absCharacter.getObjectType() == GameObjectType.Mob) { if (this.absCharacter.getObjectType() == GameObjectType.Mob) {
if (this.goldInventory == null) if (this.goldInventory == null)
loadGoldItems(); loadGoldItems();
@@ -856,7 +857,7 @@ public class CharacterItemManager {
this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal()); this.itemIDtoType.put(i.getObjectUUID(), i.getObjectType().ordinal());
if (i.template != null) if (i.template != null)
this.inventoryWeight += i.template.item_wt; this.inventoryWeight += i.template.getItemWt();
return true; return true;
} }
@@ -1041,7 +1042,7 @@ public class CharacterItemManager {
if (npc.getContractID() >= 1900 && npc.getContractID() <= 1906) if (npc.getContractID() >= 1900 && npc.getContractID() <= 1906)
eatItem = true; eatItem = true;
if (itemToSell.template.item_type.equals(ItemType.POTION)) if (itemToSell.template.getItemType().equals(ItemType.POTION))
eatItem = true; eatItem = true;
if (eatItem) { if (eatItem) {
@@ -1085,14 +1086,14 @@ public class CharacterItemManager {
synchronized (this) { synchronized (this) {
synchronized (itemMan) { synchronized (itemMan) {
ItemTemplate template = purchasedItem.template; CObjectTemplate template = purchasedItem.template;
if (template == null) if (template == null)
return false; return false;
//test inventory is not full //test inventory is not full
if (!hasRoomInventory(template.item_wt)) if (!hasRoomInventory(template.getItemWt()))
return false; return false;
if (!itemMan.inventory.contains(purchasedItem)) if (!itemMan.inventory.contains(purchasedItem))
@@ -1105,8 +1106,8 @@ public class CharacterItemManager {
// Reset value // Reset value
purchasedItem.value = (int) (purchasedItem.template.item_value * (purchasedItem.combat_health_current / purchasedItem.template.combat_health_full)); purchasedItem.value = (int) (purchasedItem.template.getItemValue() * (purchasedItem.combat_health_current / purchasedItem.template.getCombatHealthFull()));
purchasedItem.magicValue = purchasedItem.template.item_value + purchasedItem.calcMagicValue(); purchasedItem.magicValue = purchasedItem.template.getItemValue() + purchasedItem.calcMagicValue();
// db transfer successfully, remove from this character // db transfer successfully, remove from this character
// skip this check if this is a mobLoot item (which is not in any inventory) // skip this check if this is a mobLoot item (which is not in any inventory)
@@ -1184,9 +1185,9 @@ public class CharacterItemManager {
synchronized (lockFirst) { synchronized (lockFirst) {
synchronized (lockSecond) { synchronized (lockSecond) {
// make sure current player has item in inventory // make sure current player has item in inventory
if (lootItem.template.item_type.equals(ItemType.GOLD) && lootItem.getObjectUUID() != this.getGoldInventory().getObjectUUID() && !(this.absCharacter.getObjectType().equals(GameObjectType.Mob))) if (lootItem.template.getItemType().equals(ItemType.GOLD) && lootItem.getObjectUUID() != this.getGoldInventory().getObjectUUID() && !(this.absCharacter.getObjectType().equals(GameObjectType.Mob)))
return null; return null;
else if (!this.inventory.contains(lootItem) && !this.equipped.containsValue(lootItem) && !lootItem.template.item_type.equals(ItemType.GOLD)) else if (!this.inventory.contains(lootItem) && !this.equipped.containsValue(lootItem) && !lootItem.template.getItemType().equals(ItemType.GOLD))
return null; return null;
// get weight of item // get weight of item
@@ -1194,13 +1195,13 @@ public class CharacterItemManager {
if (lootItem.template == null) if (lootItem.template == null)
return null; return null;
int weight = lootItem.template.item_wt; int weight = lootItem.template.getItemWt();
// make sure lootingPlayer has room for item // make sure lootingPlayer has room for item
if (!lootItem.template.item_type.equals(ItemType.GOLD) && !looterItems.hasRoomInventory(weight)) if (!lootItem.template.getItemType().equals(ItemType.GOLD) && !looterItems.hasRoomInventory(weight))
return null; return null;
if (lootItem.template.item_type.equals(ItemType.GOLD)) if (lootItem.template.getItemType().equals(ItemType.GOLD))
if (amount != -1) { //from steal if (amount != -1) { //from steal
int total = lootItem.getNumOfItems(); int total = lootItem.getNumOfItems();
amount = (amount > total) ? total : amount; amount = (amount > total) ? total : amount;
@@ -1263,19 +1264,19 @@ public class CharacterItemManager {
* Delegates * Delegates
*/ */
public synchronized boolean bankContains(Item item) { public synchronized boolean bankContains(Item item) {
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
return (this.getGoldBank() != null && this.goldBank.getObjectUUID() == item.getObjectUUID()); return (this.getGoldBank() != null && this.goldBank.getObjectUUID() == item.getObjectUUID());
return bank.contains(item); return bank.contains(item);
} }
public synchronized boolean inventoryContains(Item item) { public synchronized boolean inventoryContains(Item item) {
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
return (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == item.getObjectUUID()); return (this.getGoldInventory() != null && this.goldInventory.getObjectUUID() == item.getObjectUUID());
return inventory.contains(item); return inventory.contains(item);
} }
public synchronized boolean vaultContains(Item item) { public synchronized boolean vaultContains(Item item) {
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
return (this.getGoldVault() != null && this.goldVault.getObjectUUID() == item.getObjectUUID()); return (this.getGoldVault() != null && this.goldVault.getObjectUUID() == item.getObjectUUID());
return this.account.getVault().contains(item); return this.account.getVault().contains(item);
} }
@@ -1518,13 +1519,13 @@ public class CharacterItemManager {
item.zeroItem(); item.zeroItem();
item.containerType = mbEnums.ItemContainerType.INVENTORY; item.containerType = mbEnums.ItemContainerType.INVENTORY;
if (item.template.item_type.equals(ItemType.GOLD)) if (item.template.getItemType().equals(ItemType.GOLD))
//only add gold item once //only add gold item once
if (!corpse.hasGold()) if (!corpse.hasGold())
corpse.setHasGold(true); corpse.setHasGold(true);
newInventory.add(item); newInventory.add(item);
} else //item } else //item
if (item.template.item_type.equals(ItemType.GOLD)) { if (item.template.getItemType().equals(ItemType.GOLD)) {
int amt = item.getNumOfItems(); int amt = item.getNumOfItems();
item.setNumOfItems(0); item.setNumOfItems(0);
MobLoot ml = new MobLoot(this.absCharacter, amt); MobLoot ml = new MobLoot(this.absCharacter, amt);
@@ -1676,7 +1677,7 @@ public class CharacterItemManager {
if (item == null) if (item == null)
continue; continue;
weight += item.template.item_wt; weight += item.template.getItemWt();
} }
return weight; return weight;
} }
@@ -1696,7 +1697,7 @@ public class CharacterItemManager {
this.bankWeight = 0; this.bankWeight = 0;
for (Item item : this.bank) { for (Item item : this.bank) {
if (item.template != null) if (item.template != null)
this.bankWeight += item.template.item_wt; this.bankWeight += item.template.getItemWt();
} }
} }
@@ -1707,7 +1708,7 @@ public class CharacterItemManager {
while (it.hasNext()) { while (it.hasNext()) {
Item item = it.next(); Item item = it.next();
if (item.template != null) if (item.template != null)
this.equipWeight += item.template.item_wt; this.equipWeight += item.template.getItemWt();
} }
} }
@@ -1715,7 +1716,7 @@ public class CharacterItemManager {
this.inventoryWeight = 0; this.inventoryWeight = 0;
for (Item item : this.inventory) { for (Item item : this.inventory) {
if (item.template != null) if (item.template != null)
this.inventoryWeight += item.template.item_wt; this.inventoryWeight += item.template.getItemWt();
} }
} }
@@ -1723,7 +1724,7 @@ public class CharacterItemManager {
this.vaultWeight = 0; this.vaultWeight = 0;
for (Item item : this.vault) { for (Item item : this.vault) {
if (item.template != null) if (item.template != null)
this.vaultWeight += item.template.item_wt; this.vaultWeight += item.template.getItemWt();
} }
} }
@@ -1755,7 +1756,7 @@ public class CharacterItemManager {
return; return;
if (!item.moveItemToInventory(player)) if (!item.moveItemToInventory(player))
Logger.error("templateL " + item.template.template_id); Logger.error("templateL " + item.template.getTemplateId());
// remove it from other lists: // remove it from other lists:
this.remItemFromLists(item); this.remItemFromLists(item);
@@ -1932,7 +1933,7 @@ public class CharacterItemManager {
return; return;
//don't damage noob gear, hair or beards. //don't damage noob gear, hair or beards.
if (item.template.combat_health_full == 0) if (item.template.getCombatHealthFull() == 0)
return; return;
if (!item.isCanDestroy()) if (!item.isCanDestroy())
+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;
} }
-643
View File
@@ -1,643 +0,0 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
public class ItemTemplate {
// MB Dev Notes:
// This class parses JSON data generated by mbEditor Pro 2.1.
// Cache definitions are loaded for ItemType.ITEM, ItemType,DEED
// and ItemType.RUNE.
//
// Exported JSON data is stored in db table static_item_templates
// and can be easily mass updated using tooling available on the Wiki.
//
// Always read never write!
// Use copies of fields to avoid side effects.
// Global template lookup
public static HashMap<Integer, ItemTemplate> templates = new HashMap<>();
public int template_id;
// Template Properties
public String obj_name;
public boolean obj_pickable;
public Vector3fImmutable obj_scale;
public Vector3fImmutable obj_forward_vector;
public Vector3fImmutable obj_default_alignment;
public int obj_render_object;
public int obj_icon;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public float combat_health_current;
public float combat_health_full;
public final HashMap<String, Float> combat_attack_resist = new HashMap<>();
public mbEnums.ItemType item_type;
public int item_eq_slots_value;
public boolean item_eq_slots_type;
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public boolean item_takeable;
public int item_value;
public int item_wt;
public float item_passive_defense_mod;
public String item_base_name;
public String item_dsc;
public int item_render_object_female;
public float item_health_full;
public String item_skill_used = "";
public String item_skill_mastery_used = "";
public int item_parry_anim_id;
public float item_bulk_factor;
public final HashMap<mbEnums.ShrineType, Integer> item_offering_info = new HashMap<>();
public int item_defense_rating;
public float item_weapon_wepspeed;
public float item_weapon_max_range;
public int item_weapon_projectile_id;
public float item_weapon_projectile_speed;
public int item_weapon_combat_idle_anim;
public final HashMap<mbEnums.DamageType, int[]> item_weapon_damage = new HashMap<>();
public final ArrayList<int[]> weapon_attack_anim_right = new ArrayList<>();
public final ArrayList<int[]> weapon_attack_anim_left = new ArrayList<>();
public mbEnums.AttributeType item_primary_attr = mbEnums.AttributeType.None;
public mbEnums.AttributeType item_secondary_attr = mbEnums.AttributeType.None;
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
public int item_initial_charges;
public final HashMap<String, Integer> item_skill_required = new HashMap<>();
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public final EnumSet<mbEnums.MonsterType> item_race_res = EnumSet.noneOf(mbEnums.MonsterType.class);
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public final EnumSet<mbEnums.ClassType> item_class_res = EnumSet.noneOf(mbEnums.ClassType.class);
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumSet<mbEnums.DisciplineType> item_disc_res = EnumSet.noneOf(mbEnums.DisciplineType.class);
public int item_level_req;
public mbEnums.SexType item_sex_req;
public final HashMap<String, int[]> item_user_power_action = new HashMap<>();
public final HashMap<String, Integer> item_power_grant = new HashMap<>();
public final HashMap<String, int[]> item_power_action = new HashMap<>();
public final HashMap<mbEnums.ResourceType, Integer> item_resource_cost = new HashMap<>();
public int modTable;
public int deed_type;
public int deed_furniture_id;
public int deed_target_id;
public int deed_employment;
public int deed_start_rank;
public int deed_name_lookup;
public boolean deed_indoors;
public boolean deed_is_fortress;
public float deed_namelookup_val;
public boolean deed_custom_city;
public int deed_structure_id;
public final HashMap<mbEnums.AttributeType, Integer> rune_attr_adj = new HashMap<>();
public final HashMap<mbEnums.AttributeType, Integer> rune_max_attr_adj = new HashMap<>();
public final HashMap<String, Integer> rune_skill_grant = new HashMap<>();
public final HashMap<String, Integer> skill_granted_skills = new HashMap<>();
public final HashMap<String, Integer> power_granted_skills = new HashMap<>();
public String rune_type;
public String rune_sub_type;
public boolean rune_is_standard_character_creation = false;
public int rune_creation_cost;
public int rune_rank;
public int rune_pracs_per_level;
public float rune_exp_req_to_level;
public mbEnums.SexType rune_sex;
public int rune_class_icon;
public int rune_health;
public int rune_mana;
public int rune_stamina;
public float rune_min_damage;
public float rune_max_damage;
public int rune_attack;
public int rune_defense;
public int rune_level;
public final HashMap<mbEnums.MovementType, Float> rune_speed = new HashMap<>();
public int rune_group_type;
public boolean rune_group_is_faction = false;
public boolean rune_group_is_guild = false;
public String rune_dsc;
public String rune_fx_txt;
public final EnumSet<mbEnums.MobBehaviourType> rune_group_tactics = EnumSet.noneOf(mbEnums.MobBehaviourType.class);
public final EnumSet<mbEnums.MobBehaviourType> rune_group_role_set = EnumSet.noneOf(mbEnums.MobBehaviourType.class);
public boolean rune_renderable = false;
public int rune_natural_power_attack;
// One float[] per rune_naturalattacks entry. Same "attack info" struct as a weapon (verified in IDA, see
// MBDATA/docs/client-map.md): [0] attack speed (WEPSPEED), [1] range (MAXRANGE), [2] unknown int,
// [3] equip slot (1 = RHELD, 2 = LHELD), [4] projectile template id, [5] projectile speed, [6] swing anim,
// [7] damage type, [8] min damage, [9] max damage
public final ArrayList<float[]> rune_natural_attacks = new ArrayList<>();
public final HashMap<String, String> rune_sparse_data = new HashMap<>();
public final HashMap<String, int[]> rune_skill_adj = new HashMap<>();
public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
public final EnumSet<mbEnums.MonsterType> rune_not_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
public final ArrayList<Integer> rune_groupee_monster_types = new ArrayList<>();
public final ArrayList<Integer> rune_helper_monster_types = new ArrayList<>();
public int item_bane_rank;
public ItemTemplate(JSONObject jsonObject) {
try {
// Reading a String
obj_name = jsonObject.getString("obj_name");
// Reading a boolean
obj_pickable = jsonObject.getBoolean("obj_pickable");
// Reading floats from an array (note always check for empty arrays)
JSONArray scaleData = jsonObject.getJSONArray("obj_scale");
if (scaleData.isEmpty() == false)
obj_scale = new Vector3fImmutable(scaleData.getFloat(0), scaleData.getFloat(1),
scaleData.getFloat(2));
JSONArray forwardVector = jsonObject.getJSONArray("obj_forward_vector");
if (forwardVector.isEmpty() == false)
obj_forward_vector = new Vector3fImmutable(forwardVector.getFloat(0), forwardVector.getFloat(1),
forwardVector.getFloat(2));
JSONArray defaultAlignment = jsonObject.getJSONArray("obj_default_alignment");
if (defaultAlignment.isEmpty() == false)
obj_default_alignment = new Vector3fImmutable(defaultAlignment.getFloat(0), defaultAlignment.getFloat(1),
defaultAlignment.getFloat(2));
// Reading an integer value
obj_render_object = jsonObject.getInt("obj_render_object");
obj_icon = jsonObject.getInt("obj_icon");
JSONObject obj_sparse_json = jsonObject.getJSONObject("obj_sparse_data");
for (String sparseType : obj_sparse_json.keySet()) {
Object sparseValue = obj_sparse_json.get(sparseType);
obj_sparse_data.put(sparseType, sparseValue.toString());
}
// Banes are defined by their sparse data field
if (obj_sparse_data.get("ACTIONRESPONSE") != null)
if (obj_sparse_data.get("ACTIONRESPONSE").equals("4250517122"))
item_bane_rank = jsonObject.getInt("item_bane_rank");
// Reading float values
combat_health_current = jsonObject.getFloat("combat_health_current");
combat_health_full = jsonObject.getFloat("combat_health_full");
// Reading a hashmap of floats
JSONObject resist_json = jsonObject.getJSONObject("combat_attack_resist");
for (String resistType : resist_json.keySet()) {
float resistValue = resist_json.getFloat(resistType);
combat_attack_resist.put(resistType, resistValue);
}
// Parsing an enum
item_type = mbEnums.ItemType.valueOf(jsonObject.getString("item_type"));
item_eq_slots_value = jsonObject.getInt("item_eq_slots_value");
item_eq_slots_type = jsonObject.getBoolean("item_eq_slots_type");
// Parsing an enumset
JSONArray eq_slots_or = jsonObject.getJSONArray("item_eq_slots_or");
if (eq_slots_or.isEmpty() == false)
for (Object o : eq_slots_or)
item_eq_slots_or.add(mbEnums.EquipSlotType.valueOf((String) o));
JSONArray eq_slots_and = jsonObject.getJSONArray("item_eq_slots_and");
if (eq_slots_and.isEmpty() == false)
for (Object o : eq_slots_and)
item_eq_slots_and.add(mbEnums.EquipSlotType.valueOf((String) o));
item_takeable = jsonObject.getBoolean("item_takeable");
item_value = (jsonObject.getInt("item_value"));
item_wt = jsonObject.getInt("item_wt");
item_passive_defense_mod = jsonObject.getFloat("item_passive_defense_mod");
item_base_name = jsonObject.getString("item_base_name");
item_dsc = jsonObject.getString("item_dsc");
item_render_object_female = jsonObject.getInt("item_render_object_female");
item_health_full = jsonObject.getFloat("item_health_full");
Object skill_used = jsonObject.get("item_skill_used");
if (skill_used instanceof String) {
if (skill_used.equals("Cloth") == false)
item_skill_used = (String) skill_used;
}
Object mastery_used = jsonObject.get("item_skill_mastery_used");
if (mastery_used instanceof String)
item_skill_mastery_used = (String) mastery_used;
item_parry_anim_id = jsonObject.getInt("item_parry_anim_id");
// Reading offering data
JSONArray offering_data = jsonObject.getJSONArray("item_offering_info");
for (Object entry : offering_data) {
JSONObject offering_entry = (JSONObject) entry;
String offering_type = offering_entry.getString("offering_type").replaceAll("-", "");
int offering_value = offering_entry.getInt("offering_value");
item_offering_info.put(mbEnums.ShrineType.valueOf(offering_type), offering_value);
}
// Fields only present for ARMOR
if (item_type.equals(mbEnums.ItemType.ARMOR)) {
item_bulk_factor = jsonObject.getFloat("item_bulk_factor");
item_defense_rating = jsonObject.getInt("item_defense_rating");
}
// Fields only present for WEAPON
if (item_type.equals(mbEnums.ItemType.WEAPON)) {
JSONObject item_weapon = jsonObject.getJSONObject("item_weapon");
item_weapon_wepspeed = item_weapon.getFloat("weapon_wepspeed");
item_weapon_max_range = item_weapon.getFloat("weapon_max_range");
item_weapon_projectile_id = item_weapon.getInt("weapon_projectile_id");
item_weapon_projectile_speed = item_weapon.getFloat("weapon_projectile_speed");
item_weapon_combat_idle_anim = item_weapon.getInt("weapon_combat_idle_anim");
JSONArray weapon_damage = item_weapon.getJSONArray("weapon_damage");
if (weapon_damage.isEmpty() == false)
for (Object o : weapon_damage) {
JSONObject damage_entry = (JSONObject) o;
mbEnums.DamageType damageType = mbEnums.DamageType.getDamageType(damage_entry.getString("damage_type"));
int min = damage_entry.getInt("damage_min");
int max = damage_entry.getInt("damage_max");
int[] minMax = {min, max};
item_weapon_damage.put(damageType, minMax);
}
JSONArray attack_anim_right = item_weapon.getJSONArray("weapon_attack_anim_right");
if (attack_anim_right.isEmpty() == false)
for (Object o : attack_anim_right) {
JSONArray animationEntry = (JSONArray) o;
int animation = animationEntry.getInt(0);
int duration = animationEntry.getInt(1);
weapon_attack_anim_right.add(new int[]{animation, duration});
}
JSONArray attack_anim_left = item_weapon.getJSONArray("weapon_attack_anim_left");
if (attack_anim_left.isEmpty() == false)
for (Object o : attack_anim_left) {
JSONArray animationEntry = (JSONArray) o;
int animation = animationEntry.getInt(0);
int duration = animationEntry.getInt(1);
weapon_attack_anim_left.add(new int[]{animation, duration});
}
item_primary_attr = mbEnums.AttributeType.valueOf(jsonObject.getString("item_primary_attr"));
item_secondary_attr = mbEnums.AttributeType.valueOf(jsonObject.getString("item_secondary_attr"));
}
JSONArray itemflags = jsonObject.getJSONArray("item_flags");
if (itemflags.isEmpty() == false)
for (Object o : itemflags) {
String flag = (String) o;
if (flag.equals("None") == false)
item_flags.add(mbEnums.ItemFlags.valueOf((String) o));
}
JSONArray itemUseflags = jsonObject.getJSONArray("item_use_flags");
if (itemUseflags.isEmpty() == false)
for (Object o : itemUseflags)
item_use_flags.add(mbEnums.ItemUseFlags.valueOf((String) o));
item_initial_charges = jsonObject.getInt("item_initial_charges");
JSONArray skill_required = jsonObject.getJSONArray("item_skill_req");
if (skill_required.isEmpty() == false)
for (Object o : skill_required) {
JSONObject skill_req = (JSONObject) o;
String skill_type = skill_req.getString("skill_type");
int skill_level = skill_req.getInt("skill_level");
if (skill_type.equals("Cloth") == false)
item_skill_required.put(skill_type, skill_level);
}
JSONObject race_required = jsonObject.getJSONObject("item_race_req");
boolean restrict = race_required.getBoolean("restrict");
JSONArray races = race_required.getJSONArray("races");
if (races.isEmpty() == false)
for (Object o : races) {
String race = (String) o;
race = race.replaceAll("\\s", "");
race = race.replaceAll(",", "");
race = race.replaceAll("-", "");
if (restrict)
item_race_res.add(mbEnums.MonsterType.valueOf(race));
else
item_race_req.add(mbEnums.MonsterType.valueOf(race));
}
JSONObject class_required = jsonObject.getJSONObject("item_class_req");
restrict = class_required.getBoolean("restrict");
JSONArray classes = class_required.getJSONArray("classes");
if (classes.isEmpty() == false)
for (Object o : classes) {
String classEntry = (String) o;
classEntry = classEntry.replaceAll("\\s", "");
classEntry = classEntry.replaceAll(",", "");
if (restrict)
item_class_res.add(mbEnums.ClassType.valueOf(classEntry));
else
item_class_req.add(mbEnums.ClassType.valueOf(classEntry));
}
JSONObject disc_required = jsonObject.getJSONObject("item_disc_req");
restrict = disc_required.getBoolean("restrict");
JSONArray discs = disc_required.getJSONArray("discs");
if (discs.isEmpty() == false)
for (Object o : discs) {
String disc = (String) o;
disc = disc.replaceAll("\\s", "");
disc = disc.replaceAll(",", "");
if (restrict)
item_disc_res.add(mbEnums.DisciplineType.valueOf(disc));
else
item_disc_req.add(mbEnums.DisciplineType.valueOf(disc));
}
item_level_req = jsonObject.getInt("item_level_req");
item_sex_req = mbEnums.SexType.valueOf(jsonObject.getString("item_sex_req"));
JSONArray userPowerActions = jsonObject.getJSONArray("item_user_power_action");
if (userPowerActions.isEmpty() == false)
for (Object o : userPowerActions) {
JSONObject powerActionEntry = (JSONObject) o;
String power = powerActionEntry.getString("power");
JSONArray args = powerActionEntry.getJSONArray("arguments");
int[] arguments = {args.getInt(0), args.getInt(1)};
item_user_power_action.put(power, arguments);
}
JSONArray powerGrantsArray = jsonObject.getJSONArray("item_power_grant");
if (powerGrantsArray.isEmpty() == false) {
for (Object grantArrayEntry : powerGrantsArray) {
JSONObject powerGrantEntry = (JSONObject) grantArrayEntry;
String power_type = powerGrantEntry.getString("power_type");
int power_value = powerGrantEntry.getInt("power_value");
item_power_grant.put(power_type, power_value);
JSONArray skill_granted_array = powerGrantEntry.getJSONArray("power_granted_skills");
for (Object skillGrantEntry : skill_granted_array) {
JSONArray skill_entry = (JSONArray) skillGrantEntry;
String skill_type = skill_entry.getString(0);
int skill_level = skill_entry.getInt(1);
power_granted_skills.put(skill_type, skill_level);
}
}
}
JSONArray item_power_actions = jsonObject.getJSONArray("item_power_action");
if (item_power_actions.isEmpty() == false)
for (Object o : item_power_actions) {
JSONObject powerActionEntry = (JSONObject) o;
String power = powerActionEntry.getString("power_type");
JSONArray power_actions = powerActionEntry.getJSONArray("power_actions");
JSONObject argument_entry = power_actions.getJSONObject(0);
JSONArray power_args = argument_entry.getJSONArray("power_arguments");
int[] power_arguments = {power_args.getInt(0), power_args.getInt(1)};
item_power_action.put(power, power_arguments);
}
JSONArray resource_costs = jsonObject.getJSONArray("item_resource_costs");
if (resource_costs.isEmpty() == false)
for (Object o : resource_costs) {
JSONObject resource_entry = (JSONObject) o;
mbEnums.ResourceType resource_type = mbEnums.ResourceType.valueOf(resource_entry.getString("resource_type").toUpperCase());
int resource_value = resource_entry.getInt("resource_value");
item_resource_cost.put(resource_type, resource_value);
}
// Deed related fields
if (item_type.equals(mbEnums.ItemType.DEED)) {
deed_type = jsonObject.getInt("deed_type");
deed_furniture_id = jsonObject.getInt("deed_furniture_id");
deed_target_id = jsonObject.getInt("deed_target_id");
deed_employment = jsonObject.getInt("deed_employment");
deed_start_rank = jsonObject.getInt("deed_start_rank");
deed_name_lookup = jsonObject.getInt("deed_name_lookup");
deed_indoors = jsonObject.getBoolean("deed_indoors");
deed_is_fortress = jsonObject.getBoolean("deed_is_fortress");
deed_namelookup_val = jsonObject.getFloat("deed_namelookup_val");
deed_custom_city = jsonObject.getBoolean("deed_custom_city");
deed_structure_id = jsonObject.getInt("deed_structure_id");
}
if (item_type.equals(mbEnums.ItemType.RUNE)) {
rune_type = jsonObject.getString("rune_type");
Object subType = jsonObject.get("rune_sub_type");
if (subType instanceof String)
rune_sub_type = (String) subType;
rune_is_standard_character_creation = jsonObject.getBoolean("rune_is_standard_character_creation");
rune_creation_cost = jsonObject.getInt("rune_creation_cost");
rune_rank = jsonObject.getInt("rune_rank");
rune_pracs_per_level = jsonObject.getInt("rune_pracs_per_level");
rune_exp_req_to_level = jsonObject.getFloat("rune_exp_req_to_level");
rune_sex = mbEnums.SexType.valueOf(jsonObject.getString("rune_sex"));
rune_class_icon = jsonObject.getInt("rune_class_icon");
rune_health = jsonObject.getInt("rune_health");
rune_mana = jsonObject.getInt("rune_mana");
rune_stamina = jsonObject.getInt("rune_stamina");
rune_min_damage = jsonObject.getFloat("rune_min_damage");
rune_max_damage = jsonObject.getFloat("rune_max_damage");
rune_attack = jsonObject.getInt("rune_attack");
rune_defense = jsonObject.getInt("rune_defense");
rune_level = jsonObject.getInt("rune_level");
JSONObject rune_speed_json = jsonObject.getJSONObject("rune_speed");
for (String key : rune_speed_json.keySet()) {
mbEnums.MovementType movementType = mbEnums.MovementType.valueOf(key);
float speed = rune_speed_json.getFloat(key);
rune_speed.put(movementType, speed);
}
JSONObject rune_group = jsonObject.getJSONObject("rune_group");
rune_group_type = rune_group.getInt("group_type");
rune_group_is_faction = rune_group.getBoolean("group_is_faction");
rune_group_is_guild = rune_group.getBoolean("group_is_guild");
rune_dsc = jsonObject.getString("rune_dsc");
rune_fx_txt = jsonObject.getString("rune_fx_txt");
long group_tactics_bitvector = jsonObject.getLong("rune_group_tactics");
rune_group_tactics.addAll(mbEnums.fromLong(group_tactics_bitvector, mbEnums.MobBehaviourType.class));
long group_role_bitvector = jsonObject.getLong("rune_group_role_set");
rune_group_role_set.addAll(mbEnums.fromLong(group_role_bitvector, mbEnums.MobBehaviourType.class));
JSONArray enemy_types_json = jsonObject.getJSONArray("rune_enemy_monster_types");
for (Object o : enemy_types_json) {
String enemy = (String) o;
enemy = enemy.replaceAll("-", "");
mbEnums.MonsterType monsterType = mbEnums.MonsterType.valueOf(enemy);
rune_enemy_monster_types.add(monsterType);
}
JSONArray not_enemy_types_json = jsonObject.getJSONArray("rune_not_enemy_monster_types");
for (Object o : not_enemy_types_json) {
String notenemy = (String) o;
notenemy = notenemy.replaceAll("-", "");
mbEnums.MonsterType monsterType = mbEnums.MonsterType.valueOf(notenemy);
rune_not_enemy_monster_types.add(monsterType);
}
JSONArray groupee_types_json = jsonObject.getJSONArray("rune_groupee_monster_types");
for (Object o : groupee_types_json) {
int groupeeId = (int) o;
rune_groupee_monster_types.add(groupeeId);
}
JSONArray helper_types_json = jsonObject.getJSONArray("rune_helper_monster_types");
for (Object o : helper_types_json) {
int helperId = (int) o;
rune_helper_monster_types.add(helperId);
}
rune_renderable = jsonObject.getBoolean("rune_renderable");
rune_natural_power_attack = jsonObject.getInt("rune_natural_power_attack");
if (jsonObject.has("rune_naturalattacks")) {
JSONArray natural_attacks_json = jsonObject.getJSONArray("rune_naturalattacks");
for (Object attackEntry : natural_attacks_json) {
JSONArray attackArray = (JSONArray) attackEntry;
float[] attack = new float[attackArray.length()];
for (int i = 0; i < attackArray.length(); i++)
attack[i] = attackArray.getFloat(i);
rune_natural_attacks.add(attack);
}
}
JSONObject rune_sparse_json = jsonObject.getJSONObject("rune_sparse_data");
for (String sparseType : rune_sparse_json.keySet()) {
Object sparseValue = rune_sparse_json.get(sparseType);
rune_sparse_data.put(sparseType, sparseValue.toString());
}
JSONArray skill_adj_json = (JSONArray) jsonObject.get("rune_skill_adj");
for (Object skillEntry : skill_adj_json) {
JSONObject skill = (JSONObject) skillEntry;
String typeString = skill.getString("skill_type");
JSONArray adjArray = skill.getJSONArray("skill_adjusts");
for (Object adjustList : adjArray) {
JSONArray adjustEntry = (JSONArray) adjustList;
int[] adjust = {adjustEntry.getInt(0), adjustEntry.getInt(1)};
rune_skill_adj.put(typeString, adjust);
}
}
JSONArray attr_adj_json = jsonObject.getJSONArray("rune_attr_adj");
for (Object attributeEntry : attr_adj_json) {
JSONObject attribute = (JSONObject) attributeEntry;
String typeString = attribute.getString("attr_type");
mbEnums.AttributeType attributeType = mbEnums.AttributeType.valueOf(typeString);
int attributeValue = attribute.getInt("attr_value");
rune_attr_adj.put(attributeType, attributeValue);
}
JSONArray max_attr_adj_json = jsonObject.getJSONArray("rune_max_attr_adj");
for (Object attributeEntry : max_attr_adj_json) {
JSONObject attribute = (JSONObject) attributeEntry;
String typeString = attribute.getString("attr_type");
mbEnums.AttributeType attributeType = mbEnums.AttributeType.valueOf(typeString);
int attributeValue = attribute.getInt("attr_value");
rune_max_attr_adj.put(attributeType, attributeValue);
}
JSONArray skill_grant_json = jsonObject.getJSONArray("rune_skill_grant");
if (skill_grant_json.isEmpty() == false)
for (Object runeSkillEntry : skill_grant_json) {
JSONObject skill_granted = (JSONObject) runeSkillEntry;
String typeString = skill_granted.getString("skill_type");
int skill_level = skill_granted.getInt("skill_value");
rune_skill_grant.put(typeString, skill_level);
JSONArray skill_granted_array = skill_granted.getJSONArray("skill_granted_skills");
for (Object skillGrantEntry : skill_granted_array) {
JSONArray skill_entry = (JSONArray) skillGrantEntry;
typeString = skill_entry.getString(0);
skill_level = skill_entry.getInt(1);
skill_granted_skills.put(typeString, skill_level);
}
}
}
} catch (Exception e) {
Logger.error(obj_name + ":" + e);
}
}
}
+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;
} }
+28 -7
View File
@@ -181,7 +181,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneTwo.ordinal(), 252621); //guard rune serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneTwo.ordinal(), 252621); //guard rune
} else if (mob.contract != null || mob.isPlayerGuard()) { } else if (mob.contract != null || mob.isPlayerGuard()) {
writer.putInt(3); writer.putInt(3);
serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), MobBase.GetClassType(mob.getMobBaseID())); //warrior class serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), 2518); //warrior class
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune
} else } else
writer.putInt(1); writer.putInt(1);
@@ -436,6 +436,30 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
return mobile; return mobile;
} }
// MB_MINION_SPAWN_TIME (seconds) shortens guard/siege minion spawns for testing; 0 or blank keeps the normal timers.
private static int minionSpawnDelay(int defaultDelay) {
String overrideValue;
int overrideDelay;
overrideValue = ConfigManager.MB_MINION_SPAWN_TIME.getValue();
if (overrideValue == null || overrideValue.trim().isEmpty())
return defaultDelay;
try {
overrideDelay = Integer.parseInt(overrideValue.trim());
} catch (NumberFormatException e) {
Logger.warn("MB_MINION_SPAWN_TIME is not a number: " + overrideValue);
return defaultDelay;
}
if (overrideDelay <= 0)
return defaultDelay;
return overrideDelay;
}
public static synchronized Mob createGuardMinion(Mob guardCaptain, short level, String minionName) { public static synchronized Mob createGuardMinion(Mob guardCaptain, short level, String minionName) {
Mob minionMobile; Mob minionMobile;
@@ -455,7 +479,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
minionMobile.deathTime = System.currentTimeMillis(); minionMobile.deathTime = System.currentTimeMillis();
minionMobile.guardCaptain = guardCaptain; minionMobile.guardCaptain = guardCaptain;
minionMobile.spawnDelay = (int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60; minionMobile.spawnDelay = minionSpawnDelay((int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60);
minionMobile.behaviourType = mbEnums.MobBehaviourType.GuardMinion; minionMobile.behaviourType = mbEnums.MobBehaviourType.GuardMinion;
minionMobile.agentType = AIAgentType.GUARDMINION; minionMobile.agentType = AIAgentType.GUARDMINION;
minionMobile.guardedCity = guardCaptain.guardedCity; minionMobile.guardedCity = guardCaptain.guardedCity;
@@ -514,7 +538,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
siegeMinion.behaviourType = MobBehaviourType.SiegeEngine; siegeMinion.behaviourType = MobBehaviourType.SiegeEngine;
siegeMinion.agentType = AIAgentType.SIEGEENGINE; siegeMinion.agentType = AIAgentType.SIEGEENGINE;
siegeMinion.bindLoc = Vector3fImmutable.ZERO; siegeMinion.bindLoc = Vector3fImmutable.ZERO;
siegeMinion.spawnDelay = (60 * 15); siegeMinion.spawnDelay = minionSpawnDelay(60 * 15);
siegeMinion.runAfterLoad(); siegeMinion.runAfterLoad();
@@ -553,9 +577,7 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
petMinion.despawned = false; petMinion.despawned = false;
petMinion.runAfterLoad(); petMinion.runAfterLoad();
// Summoned pets carry MASK_PET. Necro pets keep their own NecroPet handling. // Necro pets count as pets too: DRU-066 targets Pet and requires NECROPETSTATS
if (!petMinion.isNecroPet())
petMinion.setObjectTypeMask(petMinion.getObjectTypeMask() | MBServerStatics.MASK_PET); petMinion.setObjectTypeMask(petMinion.getObjectTypeMask() | MBServerStatics.MASK_PET);
DbManager.addToCache(petMinion); DbManager.addToCache(petMinion);
@@ -1266,7 +1288,6 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
NPCManager.applyMobbaseEffects(this); NPCManager.applyMobbaseEffects(this);
NPCManager.applyEquipmentResists(this); NPCManager.applyEquipmentResists(this);
NPCManager.applyMobbaseSkill(this);
NPCManager.applyRuneSkills(this, this.getMobBaseID()); NPCManager.applyRuneSkills(this, this.getMobBaseID());
this.recalculateStats(); this.recalculateStats();
this.setHealth(this.healthMax); this.setHealth(this.healthMax);
+42 -39
View File
@@ -14,7 +14,10 @@ import engine.gameManager.ItemManager;
import engine.gameManager.LootManager; import engine.gameManager.LootManager;
import engine.loot.BootySetEntry; import engine.loot.BootySetEntry;
import engine.mbEnums; import engine.mbEnums;
import engine.parsers.cache.CacheParserUtil;
import engine.parsers.objects.RuneTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -33,6 +36,7 @@ public class MobBase extends AbstractGameObject {
public mbEnums.MobBehaviourType fsm; public mbEnums.MobBehaviourType fsm;
public EnumSet<mbEnums.MonsterType> notEnemy; public EnumSet<mbEnums.MonsterType> notEnemy;
public EnumSet<mbEnums.MonsterType> enemy; public EnumSet<mbEnums.MonsterType> enemy;
public mbEnums.MonsterType monsterType;
private float healthMax; private float healthMax;
private int attackRating; private int attackRating;
private int defenseRating; private int defenseRating;
@@ -58,28 +62,45 @@ public class MobBase extends AbstractGameObject {
public MobBase(ResultSet rs) throws SQLException { public MobBase(ResultSet rs) throws SQLException {
super(rs, rs.getInt("ID")); super(rs, rs.getInt("ID"));
this.loadID = rs.getInt("loadID"); RuneTemplate rune = null;
String fsmName;
this.firstName = rs.getString("name"); this.loadID = rs.getInt("loadID");
this.level = rs.getByte("level"); rune = RuneTemplate._runes.get(this.loadID);
if (rune != null) {
this.firstName = rune.obj_name;
this.level = (byte) rune.rune_level;
this.healthMax = rune.rune_health;
this.damageMin = rune.rune_min_damage;
this.damageMax = rune.rune_max_damage;
this.attackRating = rune.rune_attack;
this.defenseRating = rune.rune_defense;
fsmName = rune.obj_sparse_data.getOrDefault("ROOTFSMID", mbEnums.MobBehaviourType.None.name());
this.fsm = mbEnums.MobBehaviourType.valueOf(fsmName);
this.notEnemy = EnumSet.copyOf(rune.rune_not_enemy_monster_types);
this.enemy = EnumSet.copyOf(rune.rune_enemy_monster_types);
// The race rune's sub type is its monster type (client g_MonsterTypeEnum, rune +0xC)
this.monsterType = CacheParserUtil.readEnum(mbEnums.MonsterType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type");
this.walk = rune.rune_speed.getOrDefault(mbEnums.MovementType.WALK, 0f);
this.walkCombat = rune.rune_speed.getOrDefault(mbEnums.MovementType.COMBATWALK, 0f);
this.run = rune.rune_speed.getOrDefault(mbEnums.MovementType.RUN, 0f);
this.runCombat = rune.rune_speed.getOrDefault(mbEnums.MovementType.COMBATRUN, 0f);
} else {
Logger.error("Mobbase " + this.getObjectUUID() + " has no race rune " + this.loadID);
this.firstName = "";
this.level = 0;
this.fsm = mbEnums.MobBehaviourType.None;
this.notEnemy = EnumSet.noneOf(mbEnums.MonsterType.class);
this.enemy = EnumSet.noneOf(mbEnums.MonsterType.class);
}
this.goldMod = rs.getInt("goldMod"); this.goldMod = rs.getInt("goldMod");
this.spawnTime = rs.getInt("spawnTime"); this.spawnTime = rs.getInt("spawnTime");
this.healthMax = rs.getInt("health");
this.damageMin = rs.getFloat("minDmg");
this.damageMax = rs.getFloat("maxDmg");
this.attackRating = rs.getInt("atr");
this.defenseRating = rs.getInt("defense");
this.attackRange = rs.getFloat("attackRange"); this.attackRange = rs.getFloat("attackRange");
this.bootySet = rs.getInt("bootySet"); this.bootySet = rs.getInt("bootySet");
this.fsm = mbEnums.MobBehaviourType.valueOf(rs.getString("fsm"));
this.flags = mbEnums.fromLong(rs.getLong("flags"), mbEnums.MobFlagType.class); this.flags = mbEnums.fromLong(rs.getLong("flags"), mbEnums.MobFlagType.class);
this.notEnemy = mbEnums.fromLong(rs.getLong("notEnemy"), mbEnums.MonsterType.class);
this.enemy = mbEnums.fromLong(rs.getLong("enemy"), mbEnums.MonsterType.class);
this.seeInvis = rs.getInt("seeInvis"); this.seeInvis = rs.getInt("seeInvis");
this.scale = rs.getFloat("scale"); this.scale = rs.getFloat("scale");
@@ -104,10 +125,8 @@ public class MobBase extends AbstractGameObject {
if (this.flags.contains(mbEnums.MobFlagType.RAT)) if (this.flags.contains(mbEnums.MobFlagType.RAT))
this.mask += MBServerStatics.MASK_RAT; this.mask += MBServerStatics.MASK_RAT;
this.mobBaseStats = DbManager.MobBaseQueries.LOAD_STATS(this.loadID); this.mobBaseStats = (rune != null) ? new MobBaseStats(rune) : MobBaseStats.GetGenericStats();
DbManager.MobBaseQueries.LOAD_ALL_MOBBASE_SPEEDS(this); this.effectsList = MobBaseEffects.getEffectsForRune(this.loadID);
//load effects for mobbase
this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.loadID);
} }
@@ -127,9 +146,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);
} }
@@ -141,26 +164,6 @@ public class MobBase extends AbstractGameObject {
return DbManager.MobBaseQueries.GET_MOBBASE(id); return DbManager.MobBaseQueries.GET_MOBBASE(id);
} }
public static int GetClassType(int mobbaseID) {
switch (mobbaseID) {
case 17235:
case 17233:
case 17256:
case 17259:
case 17260:
case 17261:
return 2518;
case 17258:
case 17257:
case 17237:
case 17234:
return 2521;
default:
return 2518;
}
}
public void updateSpeeds(float walk, float walkCombat, float run, float runCombat) { public void updateSpeeds(float walk, float walkCombat, float run, float runCombat) {
this.walk = walk; this.walk = walk;
this.walkCombat = walkCombat; this.walkCombat = walkCombat;
+50 -19
View File
@@ -9,38 +9,69 @@
package engine.objects; package engine.objects;
import java.sql.ResultSet; import engine.parsers.objects.RuneTemplate;
import java.sql.SQLException; import engine.parsers.objects.UserPowerAction;
import engine.parsers.objects.wpak.PowerActionTemplate;
import engine.server.MBServerStatics;
import engine.util.Hasher;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
public class MobBaseEffects { public class MobBaseEffects {
private int mobBaseID; private static final ConcurrentHashMap<Integer, ArrayList<MobBaseEffects>> effectsByRune = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private int token; private int token;
private int rank; private int rank;
private int reqLvl; private int reqLvl;
public MobBaseEffects(int token, int rank, int reqLvl) {
/** this.token = token;
* ResultSet Constructor this.rank = rank;
*/ this.reqLvl = reqLvl;
public MobBaseEffects(ResultSet rs) throws SQLException {
this.token = rs.getInt("token");
this.rank = rs.getInt("rank");
this.reqLvl = rs.getInt("reqLvl");
} }
public static ArrayList<MobBaseEffects> getEffectsForRune(int runeID) {
/** return effectsByRune.computeIfAbsent(runeID, MobBaseEffects::loadEffectsForRune);
* @return the mobBaseID
*/
public int getMobBaseID() {
return mobBaseID;
} }
// item_user_power_action entries are {action id, [rank, reqLvl]}; the action names the effect to apply
private static ArrayList<MobBaseEffects> loadEffectsForRune(int runeID) {
public void setMobBaseID(int mobBaseID) { ArrayList<MobBaseEffects> runeEffects = new ArrayList<>();
this.mobBaseID = mobBaseID; 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;
} }
+11 -15
View File
@@ -9,6 +9,9 @@
package engine.objects; package engine.objects;
import engine.mbEnums;
import engine.parsers.objects.RuneTemplate;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -21,9 +24,6 @@ public class MobBaseStats {
private final int baseSpi; private final int baseSpi;
private final int baseDex; private final int baseDex;
private final int mobbaseSkill;
private final int mobbaseSkillAmount;
/** /**
* ResultSet Constructor * ResultSet Constructor
@@ -34,8 +34,14 @@ public class MobBaseStats {
this.baseCon = rs.getInt("Constitution"); this.baseCon = rs.getInt("Constitution");
this.baseSpi = rs.getInt("Spirit"); this.baseSpi = rs.getInt("Spirit");
this.baseDex = rs.getInt("Dexterity"); this.baseDex = rs.getInt("Dexterity");
this.mobbaseSkill = rs.getInt("baseSkills"); }
this.mobbaseSkillAmount = rs.getInt("skillAmount");
public MobBaseStats(RuneTemplate rune) {
this.baseStr = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Strength, 0);
this.baseInt = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Intelligence, 0);
this.baseCon = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Constitution, 0);
this.baseSpi = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Spirit, 0);
this.baseDex = rune.rune_attr_adj.getOrDefault(mbEnums.AttributeType.Dexterity, 0);
} }
/** /**
@@ -48,8 +54,6 @@ public class MobBaseStats {
this.baseCon = 0; this.baseCon = 0;
this.baseSpi = 0; this.baseSpi = 0;
this.baseDex = 0; this.baseDex = 0;
this.mobbaseSkill = 0;
this.mobbaseSkillAmount = 0;
} }
public static MobBaseStats GetGenericStats() { public static MobBaseStats GetGenericStats() {
@@ -79,12 +83,4 @@ public class MobBaseStats {
return baseDex; return baseDex;
} }
public int getBaseSkill() {
return mobbaseSkill;
}
public int getBaseSkillAmount() {
return mobbaseSkillAmount;
}
} }
+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;
}
while (rs.next()) { token = Hasher.SBStringHash(powerAction.getKey());
powerGrants.computeIfAbsent(token, PowerGrantedTrains::new)
int token = rs.getInt("powerToken"); .addRuneGrant(rune.template_id, (short) powerAction.getValue().get(0)[0]);
grantCount++;
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) { PowerGrantedTrains.grantedPowers = powerGrants;
Logger.error("SQL Error number: " + e.getErrorCode(), e); 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;
+195 -73
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) {
raceRuneIDs = CObjectIndex.getRaceRuneIds(raceType);
if (raceRuneIDs.isEmpty())
Logger.warn("RuneBase " + rune.template_id + ": race " + raceType + " has no race rune");
for (int raceRuneID : raceRuneIDs)
this.race.put(raceRuneID, true);
} }
/** for (mbEnums.ClassType classType : rune.item_class_req) {
* Normal Constructor classRuneID = CObjectIndex.getClassRuneId(classType);
*/
public RuneBase(String name, String description, int type, byte subtype, ArrayList<RuneBaseAttribute> attrs, int newUUID) {
super(newUUID);
this.name = name;
this.description = description;
this.type = type;
this.subtype = subtype;
if (classRuneID == null) {
Logger.warn("RuneBase " + rune.template_id + ": class " + classType + " has no class rune");
continue;
} }
/** classRune = RuneTemplate._runes.get(classRuneID);
* ResultSet Constructor
*/
public RuneBase(ResultSet rs) throws SQLException {
super(rs);
this.name = rs.getString("name"); if (classRune.rune_is_standard_character_creation)
this.description = rs.getString("description"); this.baseClass.put(classRuneID, true);
this.type = rs.getInt("type"); else
this.subtype = rs.getByte("subtype"); this.promotionClass.put(classRuneID, true);
}
DbManager.RuneBaseQueries.GET_RUNE_REQS(this); for (mbEnums.DisciplineType discType : rune.item_disc_req) {
this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()); 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)
+10 -6
View File
@@ -17,6 +17,7 @@ import engine.mbEnums;
import engine.net.Dispatch; import engine.net.Dispatch;
import engine.net.client.ClientConnection; import engine.net.client.ClientConnection;
import engine.net.client.msg.*; import engine.net.client.msg.*;
import engine.parsers.objects.CObjectTemplate;
import engine.server.MBServerStatics; import engine.server.MBServerStatics;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.json.JSONArray; import org.json.JSONArray;
@@ -31,6 +32,9 @@ import java.util.concurrent.ConcurrentHashMap;
public class Warehouse { public class Warehouse {
// Destroyed warehouses keyed by rubble building UUID; memory only so the rubble can be looted
public static final ConcurrentHashMap<Integer, Warehouse> destroyedWarehouses = new ConcurrentHashMap<>();
public EnumSet<mbEnums.ResourceType> locked = EnumSet.noneOf(mbEnums.ResourceType.class); public EnumSet<mbEnums.ResourceType> locked = EnumSet.noneOf(mbEnums.ResourceType.class);
public Building building; public Building building;
public City city; public City city;
@@ -301,10 +305,10 @@ public class Warehouse {
return false; return false;
} }
if (resource.template.item_type.equals(mbEnums.ItemType.GOLD)) if (resource.template.getItemType().equals(mbEnums.ItemType.GOLD))
resourceType = mbEnums.ResourceType.GOLD; resourceType = mbEnums.ResourceType.GOLD;
else else
resourceType = mbEnums.ResourceType.valueOf(ItemTemplate.templates.get(resource.templateID).item_base_name.toUpperCase()); resourceType = mbEnums.ResourceType.valueOf(CObjectTemplate.lookup(resource.templateID).getItemBaseName().toUpperCase());
if (transaction) if (transaction)
AddTransactionToWarehouse(warehouse, playerCharacter.getObjectType(), playerCharacter.getObjectUUID(), mbEnums.TransactionType.DEPOSIT, resourceType, amount); AddTransactionToWarehouse(warehouse, playerCharacter.getObjectType(), playerCharacter.getObjectUUID(), mbEnums.TransactionType.DEPOSIT, resourceType, amount);
@@ -415,7 +419,7 @@ public class Warehouse {
if (playerCharacter == null) if (playerCharacter == null)
return false; return false;
ItemTemplate template = ItemTemplate.templates.get(resourceType.templateID); CObjectTemplate template = CObjectTemplate.lookup(resourceType.templateID);
if (warehouse.resources.get(resourceType) == null) if (warehouse.resources.get(resourceType) == null)
return false; return false;
@@ -429,7 +433,7 @@ public class Warehouse {
return false; return false;
if (addToInventory) if (addToInventory)
if (!itemMan.hasRoomInventory(template.item_wt)) { if (!itemMan.hasRoomInventory(template.getItemWt())) {
ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item."); ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item.");
return false; return false;
} }
@@ -498,7 +502,7 @@ public class Warehouse {
if (playerCharacter == null) if (playerCharacter == null)
return false; return false;
ItemTemplate template = ItemTemplate.templates.get(resourceType); CObjectTemplate template = CObjectTemplate.lookup(resourceType.templateID);
if (template == null) if (template == null)
return false; return false;
@@ -514,7 +518,7 @@ public class Warehouse {
if (itemMan == null) if (itemMan == null)
return false; return false;
if (!itemMan.hasRoomInventory(template.item_wt)) { if (!itemMan.hasRoomInventory(template.getItemWt())) {
ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item."); ChatManager.chatSystemInfo(playerCharacter, "You can not carry any more of that item.");
return false; return false;
} }
+235
View File
@@ -0,0 +1,235 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.mbEnums;
import engine.parsers.objects.CObjectTemplate;
import engine.parsers.objects.DeedTemplate;
import engine.parsers.objects.ItemTemplate;
import engine.parsers.objects.PowerGrant;
import engine.parsers.objects.RuneTemplate;
import engine.parsers.objects.UserPowerAction;
import engine.util.Hasher;
import org.pmw.tinylog.Logger;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
// Lookups across the ITEM, DEED and RUNE cache templates.
// The JSON references runes, skills and powers by name; the server and DB use ids and tokens.
// Build once after all three parsers have loaded. Read only afterwards.
public class CObjectIndex {
private static final EnumMap<mbEnums.MonsterType, ArrayList<Integer>> raceRuneIds = new EnumMap<>(mbEnums.MonsterType.class);
private static final EnumMap<mbEnums.ClassType, Integer> classRuneIds = new EnumMap<>(mbEnums.ClassType.class);
private static final EnumMap<mbEnums.DisciplineType, Integer> disciplineRuneIds = new EnumMap<>(mbEnums.DisciplineType.class);
private static final HashMap<Integer, ArrayList<RuneTemplate>> runesBySubTypeToken = new HashMap<>();
private static final HashMap<String, Integer> skillTokens = new HashMap<>();
private static final HashMap<Integer, String> skillNames = new HashMap<>();
private static final HashMap<String, Integer> powerTokens = new HashMap<>();
private static final HashMap<Integer, String> powerNames = new HashMap<>();
// Same order as the client's id space checks (JSON-Refactor-Plan D1).
public static CObjectTemplate lookup(int templateId) {
CObjectTemplate template;
template = ItemTemplate._items.get(templateId);
if (template == null)
template = DeedTemplate._deeds.get(templateId);
if (template == null)
template = RuneTemplate._runes.get(templateId);
return template;
}
// Male and female runes share a race, so most races return two ids.
public static ArrayList<Integer> getRaceRuneIds(mbEnums.MonsterType race) {
return raceRuneIds.getOrDefault(race, new ArrayList<>());
}
public static Integer getClassRuneId(mbEnums.ClassType classType) {
return classRuneIds.get(classType);
}
public static Integer getDisciplineRuneId(mbEnums.DisciplineType discipline) {
return disciplineRuneIds.get(discipline);
}
// DB race and class tokens are SBStringHash(rune_sub_type). Mob and talent runes share sub types.
public static ArrayList<RuneTemplate> getRunesBySubTypeToken(int token) {
return runesBySubTypeToken.getOrDefault(token, new ArrayList<>());
}
public static Integer getSkillToken(String skillName) {
return skillTokens.get(skillName);
}
public static String getSkillName(int token) {
return skillNames.get(token);
}
public static Integer getPowerToken(String powerName) {
return powerTokens.get(powerName);
}
public static String getPowerName(int token) {
return powerNames.get(token);
}
public static void build() {
int overlapCount = 0;
for (Integer templateId : RuneTemplate._runes.keySet())
if (ItemTemplate._items.containsKey(templateId) || DeedTemplate._deeds.containsKey(templateId))
overlapCount++;
for (Integer templateId : DeedTemplate._deeds.keySet())
if (ItemTemplate._items.containsKey(templateId))
overlapCount++;
if (overlapCount > 0)
Logger.warn(overlapCount + " template ids exist in more than one of ITEM/DEED/RUNE");
for (RuneTemplate rune : RuneTemplate._runes.values()) {
indexRuneName(rune);
indexSubTypeToken(rune);
indexRuneNames(rune);
}
for (ItemTemplate item : ItemTemplate._items.values()) {
addSkill(item.item_skill_used);
addSkill(item.item_skill_mastery_used);
indexItemFields(item.item_skill_req, item.item_user_power_action, item.item_power_grant, item.item_power_action);
}
for (DeedTemplate deed : DeedTemplate._deeds.values()) {
addSkill(deed.item_skill_used);
addSkill(deed.item_skill_mastery_used);
indexItemFields(deed.item_skill_req, deed.item_user_power_action, deed.item_power_grant, deed.item_power_action);
}
Logger.info("Indexed " + raceRuneIds.size() + " races, " + classRuneIds.size() + " classes, "
+ disciplineRuneIds.size() + " disciplines, " + runesBySubTypeToken.size() + " rune sub type tokens, "
+ skillTokens.size() + " skills, " + powerTokens.size() + " powers");
}
private static void indexRuneName(RuneTemplate rune) {
Integer previousId;
if (rune.rune_type == null || rune.rune_sub_type.isEmpty())
return;
try {
switch (rune.rune_type) {
case RACE:
// Mob runes reuse race sub types; only creation races map to player race ids
if (rune.rune_is_standard_character_creation)
raceRuneIds.computeIfAbsent(CacheParserUtil.readEnum(mbEnums.MonsterType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"),
k -> new ArrayList<>()).add(rune.template_id);
break;
case CLASS:
previousId = classRuneIds.put(CacheParserUtil.readEnum(mbEnums.ClassType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"), rune.template_id);
if (previousId != null)
Logger.warn(rune.template_id + ": class " + rune.rune_sub_type + " already indexed by " + previousId);
break;
case DISCIPLINE:
previousId = disciplineRuneIds.put(CacheParserUtil.readEnum(mbEnums.DisciplineType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"), rune.template_id);
if (previousId != null)
Logger.warn(rune.template_id + ": discipline " + rune.rune_sub_type + " already indexed by " + previousId);
break;
default:
break;
}
} catch (IllegalArgumentException e) {
// The Pet class rune has no ClassType
Logger.warn(e.getMessage());
}
}
private static void indexSubTypeToken(RuneTemplate rune) {
int token;
if (!rune.rune_sub_type.isEmpty())
token = Hasher.SBStringHash(rune.rune_sub_type);
else if (rune.rune_sub_type_hash != 0)
token = rune.rune_sub_type_hash;
else
return;
runesBySubTypeToken.computeIfAbsent(token, k -> new ArrayList<>()).add(rune);
}
private static void indexRuneNames(RuneTemplate rune) {
for (RuneTemplate.SkillGrant grant : rune.rune_skill_grant) {
addSkill(grant.skill_type);
grant.skill_granted_skills.keySet().forEach(CObjectIndex::addSkill);
grant.skill_granted_powers.keySet().forEach(CObjectIndex::addPower);
}
rune.rune_skill_adj.keySet().forEach(CObjectIndex::addSkill);
addSkill(rune.item_skill_used);
addSkill(rune.item_skill_mastery_used);
indexItemFields(rune.item_skill_req, rune.item_user_power_action, rune.item_power_grant, rune.item_power_action);
}
private static void indexItemFields(Map<String, Integer> skillReq, ArrayList<UserPowerAction> userPowerActions,
ArrayList<PowerGrant> powerGrants, Map<String, ArrayList<int[]>> powerActions) {
skillReq.keySet().forEach(CObjectIndex::addSkill);
for (UserPowerAction action : userPowerActions)
addPower(action.power);
for (PowerGrant grant : powerGrants) {
addPower(grant.power_type);
grant.power_granted_skills.keySet().forEach(CObjectIndex::addSkill);
grant.power_granted_powers.keySet().forEach(CObjectIndex::addPower);
}
powerActions.keySet().forEach(CObjectIndex::addPower);
}
private static void addSkill(String skillName) {
addName(skillName, skillTokens, skillNames, "skill");
}
private static void addPower(String powerName) {
addName(powerName, powerTokens, powerNames, "power");
}
private static void addName(String name, HashMap<String, Integer> tokens, HashMap<Integer, String> names, String kind) {
int token;
String previousName;
// readHashLabel keeps unresolved hashes as decimal strings; those are not names
if (name == null || name.isEmpty() || name.matches("-?\\d+") || tokens.containsKey(name))
return;
token = Hasher.SBStringHash(name);
tokens.put(name, token);
previousName = names.putIfAbsent(token, name);
if (previousName != null)
Logger.warn(kind + " token " + token + " shared by '" + previousName + "' and '" + name + "'");
}
}
+353
View File
@@ -0,0 +1,353 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.math.Vector3fImmutable;
import engine.parsers.objects.PowerGrant;
import engine.parsers.objects.UserPowerAction;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Shared JSON helpers for the COBJECTS cache parsers.
// Only generic reading lives here; each parser owns its own field mapping.
public class CacheParserUtil {
// Files are named <template id>.json
public static File[] listJsonFiles(String folderPath) {
File folder = new File(folderPath);
File[] jsonFiles = folder.listFiles((dir, name) -> name.endsWith(".json") && hasTemplateId(name));
if (jsonFiles == null) {
Logger.error("Cache folder not found: " + folder.getAbsolutePath());
return new File[0];
}
return jsonFiles;
}
// Template ids are 32-bit. The client cache has RUNE/10788957847552, which is
// 2512 << 32 (the Huntress id in the high word); files like it are skipped.
private static boolean hasTemplateId(String fileName) {
String idText = fileName.substring(0, fileName.length() - ".json".length());
try {
Integer.parseInt(idText);
return true;
} catch (NumberFormatException e) {
Logger.warn("Skipping " + fileName + ": name is not a 32-bit template id");
return false;
}
}
public static int templateIdFromFile(File jsonFile) {
String fileName = jsonFile.getName();
return Integer.parseInt(fileName.substring(0, fileName.length() - ".json".length()));
}
public static JSONObject readJsonFile(File jsonFile) throws IOException {
byte[] fileData = Files.readAllBytes(jsonFile.toPath());
return new JSONObject(new String(fileData, StandardCharsets.UTF_8));
}
// [x, y, z] -> vector. Returns null for an empty array.
public static Vector3fImmutable readVector3(JSONArray vectorJson) {
if (vectorJson.length() < 3)
return null;
return new Vector3fImmutable(vectorJson.getFloat(0), vectorJson.getFloat(1), vectorJson.getFloat(2));
}
// Client strings carry spaces, hyphens and commas that enum constants
// can't ("Half-Giant", "Battle Magus").
public static String enumName(String value) {
String name = value;
name = name.replaceAll("\\s", "");
name = name.replaceAll("-", "");
name = name.replaceAll(",", "");
return name;
}
// Exact match on the cleaned name first, then upper case (ResourceType).
// Unknown values throw so the template fails to load.
public static <E extends Enum<E>> E readEnum(Class<E> enumClass, String value, int templateId, String fieldName) {
String name = enumName(value);
try {
return Enum.valueOf(enumClass, name);
} catch (IllegalArgumentException e) {
// fall through to upper case
}
try {
return Enum.valueOf(enumClass, name.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(templateId + ": " + fieldName + " has no " + enumClass.getSimpleName() + " for '" + value + "'");
}
}
public static <E extends Enum<E>> EnumSet<E> readEnumSet(Class<E> enumClass, JSONArray valuesJson, int templateId, String fieldName) {
EnumSet<E> enumSet = EnumSet.noneOf(enumClass);
for (int i = 0; i < valuesJson.length(); i++)
enumSet.add(readEnum(enumClass, valuesJson.getString(i), templateId, fieldName));
return enumSet;
}
// [{keyName: "Strength", valueName: 40}, ...] -> enum map
public static <E extends Enum<E>> EnumMap<E, Integer> readEnumValueMap(Class<E> enumClass, JSONArray entriesJson,
String keyName, String valueName,
int templateId, String fieldName) {
EnumMap<E, Integer> enumMap = new EnumMap<>(enumClass);
JSONObject entry;
for (int i = 0; i < entriesJson.length(); i++) {
entry = entriesJson.getJSONObject(i);
enumMap.put(readEnum(enumClass, entry.getString(keyName), templateId, fieldName), entry.getInt(valueName));
}
return enumMap;
}
// [{keyName: "Sword", valueName: 20}, ...] -> map in file order
public static LinkedHashMap<String, Integer> readStringValueMap(JSONArray entriesJson, String keyName, String valueName) {
LinkedHashMap<String, Integer> valueMap = new LinkedHashMap<>();
JSONObject entry;
for (int i = 0; i < entriesJson.length(); i++) {
entry = entriesJson.getJSONObject(i);
valueMap.put(entry.getString(keyName), (int) entry.getLong(valueName));
}
return valueMap;
}
// {"SLASHING": 0.0, ...} -> enum map
public static <E extends Enum<E>> EnumMap<E, Float> readEnumFloatMap(Class<E> enumClass, JSONObject mapJson,
int templateId, String fieldName) {
EnumMap<E, Float> enumMap = new EnumMap<>(enumClass);
for (String key : mapJson.keySet())
enumMap.put(readEnum(enumClass, key, templateId, fieldName), mapJson.getFloat(key));
return enumMap;
}
// Hash and flag fields are still unsigned in the JSON; read as long and wrapped to int.
public static int[] readIntArray(JSONArray valuesJson) {
int[] values = new int[valuesJson.length()];
for (int i = 0; i < valuesJson.length(); i++)
values[i] = (int) valuesJson.getLong(i);
return values;
}
public static float[] readFloatArray(JSONArray valuesJson) {
float[] values = new float[valuesJson.length()];
for (int i = 0; i < valuesJson.length(); i++)
values[i] = valuesJson.getFloat(i);
return values;
}
public static ArrayList<Integer> readIntList(JSONArray valuesJson) {
ArrayList<Integer> values = new ArrayList<>();
for (int i = 0; i < valuesJson.length(); i++)
values.add((int) valuesJson.getLong(i));
return values;
}
// [[a, b], [a, b], ...] -> list of int[]
public static ArrayList<int[]> readIntArrays(JSONArray arraysJson) {
ArrayList<int[]> arrays = new ArrayList<>();
for (int i = 0; i < arraysJson.length(); i++)
arrays.add(readIntArray(arraysJson.getJSONArray(i)));
return arrays;
}
// Sparse data values are mixed bool/int/string; stored as strings.
public static HashMap<String, String> readSparseData(JSONObject sparseJson) {
HashMap<String, String> sparseData = new HashMap<>();
for (String key : sparseJson.keySet())
sparseData.put(key, sparseJson.get(key).toString());
return sparseData;
}
// Some fields are a name when set and the number 0 when not
// (obj_sound_table, item_skill_used). The number becomes "".
public static String readLabel(JSONObject json, String key) {
Object value = json.get(key);
if (value instanceof String)
return (String) value;
return "";
}
// Like readLabel, but a non-zero number is a hash the exporter could not
// name; it is kept as its decimal string rather than dropped.
public static String readHashLabel(JSONObject json, String key, int templateId) {
Object value = json.get(key);
long hash;
if (value instanceof String)
return (String) value;
hash = json.getLong(key);
if (hash == 0)
return "";
Logger.warn(templateId + ": " + key + " has unresolved hash " + hash);
return Long.toString(hash);
}
public static ArrayList<UserPowerAction> readUserPowerActions(JSONArray actionsJson) {
ArrayList<UserPowerAction> userPowerActions = new ArrayList<>();
JSONObject entry;
for (int i = 0; i < actionsJson.length(); i++) {
entry = actionsJson.getJSONObject(i);
userPowerActions.add(new UserPowerAction(entry.getString("power"), readIntArray(entry.getJSONArray("arguments"))));
}
return userPowerActions;
}
// [[key, value], ...] -> map in file order. Keys are names when the exporter
// knows the hash, otherwise the raw hash, which is kept as its decimal string.
public static LinkedHashMap<String, Integer> readGrantPairs(JSONArray pairsJson, int templateId, String fieldName) {
LinkedHashMap<String, Integer> grantPairs = new LinkedHashMap<>();
JSONArray pair;
Object key;
String keyName;
for (int i = 0; i < pairsJson.length(); i++) {
pair = pairsJson.getJSONArray(i);
key = pair.get(0);
if (key instanceof String) {
keyName = (String) key;
} else {
keyName = Long.toString(pair.getLong(0));
Logger.warn(templateId + ": " + fieldName + " has unresolved hash " + keyName);
}
grantPairs.put(keyName, pair.getInt(1));
}
return grantPairs;
}
public static ArrayList<PowerGrant> readPowerGrants(JSONArray grantsJson, int templateId) {
ArrayList<PowerGrant> powerGrants = new ArrayList<>();
JSONObject entry;
PowerGrant powerGrant;
for (int i = 0; i < grantsJson.length(); i++) {
entry = grantsJson.getJSONObject(i);
powerGrant = new PowerGrant();
powerGrant.power_type = entry.getString("power_type");
powerGrant.power_value = entry.getInt("power_value");
powerGrant.power_granted_attrs.putAll(readGrantPairs(entry.getJSONArray("power_granted_attrs"), templateId, "power_granted_attrs"));
powerGrant.power_granted_skills.putAll(readGrantPairs(entry.getJSONArray("power_granted_skills"), templateId, "power_granted_skills"));
powerGrant.power_granted_powers.putAll(readGrantPairs(entry.getJSONArray("power_granted_powers"), templateId, "power_granted_powers"));
powerGrant.power_monster_types = entry.getJSONArray("power_monster_types");
powerGrants.add(powerGrant);
}
return powerGrants;
}
// [{power_type: "X", power_actions: [{power_arguments: [a, b]}, ...]}, ...]
// -> power_type -> list of argument arrays (one per power_actions entry)
public static LinkedHashMap<String, ArrayList<int[]>> readPowerActions(JSONArray actionsJson) {
LinkedHashMap<String, ArrayList<int[]>> powerActions = new LinkedHashMap<>();
JSONObject entry;
JSONArray argumentsJson;
ArrayList<int[]> arguments;
for (int i = 0; i < actionsJson.length(); i++) {
entry = actionsJson.getJSONObject(i);
argumentsJson = entry.getJSONArray("power_actions");
arguments = new ArrayList<>();
for (int j = 0; j < argumentsJson.length(); j++)
arguments.add(readIntArray(argumentsJson.getJSONObject(j).getJSONArray("power_arguments")));
powerActions.put(entry.getString("power_type"), arguments);
}
return powerActions;
}
}
+186
View File
@@ -0,0 +1,186 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.DeedTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads DEED templates from the client cache JSON (COBJECTS/DEED/<id>.json).
public class DeedParser {
private static final String deedPath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/DEED/";
public static HashMap<Integer, DeedTemplate> parseDeedFolder() {
HashMap<Integer, DeedTemplate> deedTemplates = new HashMap<>();
File[] deedFiles = CacheParserUtil.listJsonFiles(deedPath);
int templateId;
JSONObject deedJson;
int failedCount = 0;
for (File deedFile : deedFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(deedFile);
deedJson = CacheParserUtil.readJsonFile(deedFile);
deedTemplates.put(templateId, parseDeedEntry(templateId, deedJson));
} catch (Exception e) {
Logger.error(deedFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + deedTemplates.size() + " of " + deedFiles.length + " deed templates");
// Partial deed data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return deedTemplates;
}
private static DeedTemplate parseDeedEntry(int templateId, JSONObject deedJson) {
DeedTemplate deed = new DeedTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
String flag;
deed.template_id = templateId;
// obj_
deed.obj_name = deedJson.getString("obj_name");
deed.obj_pickable = deedJson.getBoolean("obj_pickable");
deed.obj_gravity = deedJson.getFloat("obj_gravity");
deed.obj_cull_distance = deedJson.getFloat("obj_cull_distance");
deed.obj_scale = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_scale"));
deed.obj_render_object = deedJson.getInt("obj_render_object");
deed.obj_double_fusion = deedJson.getBoolean("obj_double_fusion");
deed.obj_forward_vector = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_forward_vector"));
deed.obj_tracking_name = deedJson.getString("obj_tracking_name");
deed.obj_max_tracking_distance = deedJson.getFloat("obj_max_tracking_distance");
deed.obj_icon = deedJson.getInt("obj_icon");
deed.obj_gravity_f = deedJson.getFloat("obj_gravity_f");
deed.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(deedJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
deed.obj_arc_hardpoint_list = deedJson.getJSONArray("obj_arc_hardpoint_list");
deed.obj_sparse_data.putAll(CacheParserUtil.readSparseData(deedJson.getJSONObject("obj_sparse_data")));
deed.obj_render_object_low_detail = deedJson.getInt("obj_render_object_low_detail");
deed.obj_default_alignment = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_default_alignment"));
deed.obj_sound_table = CacheParserUtil.readLabel(deedJson, "obj_sound_table");
// combat_
deed.combat_health_current = deedJson.getFloat("combat_health_current");
deed.combat_health_full = deedJson.getFloat("combat_health_full");
deed.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, deedJson.getJSONObject("combat_attack_resist"),
templateId, "combat_attack_resist"));
deed.combat_powers = deedJson.getJSONArray("combat_powers");
// item_
deed.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, deedJson.getString("item_type"), templateId, "item_type");
deed.item_eq_slots_value = deedJson.getInt("item_eq_slots_value");
deed.item_eq_slots_type = deedJson.getBoolean("item_eq_slots_type");
// The exporter writes ["NONE"] for an empty slot mask
deed.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, deedJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
deed.item_eq_slots_or.remove(mbEnums.EquipSlotType.NONE);
deed.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, deedJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
deed.item_eq_slots_and.remove(mbEnums.EquipSlotType.NONE);
deed.item_takeable = deedJson.getBoolean("item_takeable");
deed.item_value = deedJson.getInt("item_value");
deed.item_wt = deedJson.getInt("item_wt");
deed.item_passive_defense_mod = deedJson.getFloat("item_passive_defense_mod");
deed.item_base_name = deedJson.getString("item_base_name");
deed.item_dsc = deedJson.getString("item_dsc");
deed.item_render_object_female = deedJson.getInt("item_render_object_female");
deed.item_health_full = deedJson.getFloat("item_health_full");
deed.item_skill_used = CacheParserUtil.readLabel(deedJson, "item_skill_used");
deed.item_skill_mastery_used = CacheParserUtil.readLabel(deedJson, "item_skill_mastery_used");
deed.item_parry_anim_id = deedJson.getInt("item_parry_anim_id");
deed.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, deedJson.getJSONArray("item_offering_info"),
"offering_type", "offering_value", templateId, "item_offering_info"));
// "None" is the client's empty marker, not a flag
itemFlags = deedJson.getJSONArray("item_flags");
for (int i = 0; i < itemFlags.length(); i++) {
flag = itemFlags.getString(i);
if (flag.equals("None"))
continue;
deed.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
}
deed.item_use_flags.addAll(CacheParserUtil.readEnumSet(mbEnums.ItemUseFlags.class, deedJson.getJSONArray("item_use_flags"), templateId, "item_use_flags"));
deed.item_post_item_id = deedJson.getInt("item_post_item_id");
deed.item_initial_charges = deedJson.getInt("item_initial_charges");
deed.item_book_arcana = deedJson.getInt("item_book_arcana");
deed.item_skill_req.putAll(CacheParserUtil.readStringValueMap(deedJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
raceReq = deedJson.getJSONObject("item_race_req");
deed.item_race_req_restrict = raceReq.getBoolean("restrict");
deed.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
classReq = deedJson.getJSONObject("item_class_req");
deed.item_class_req_restrict = classReq.getBoolean("restrict");
deed.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
discReq = deedJson.getJSONObject("item_disc_req");
deed.item_disc_req_restrict = discReq.getBoolean("restrict");
deed.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
deed.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, deedJson.getJSONArray("item_attr_req"),
"attr_type", "attr_value", templateId, "item_attr_req"));
deed.item_level_req = deedJson.getInt("item_level_req");
deed.item_rank_req = deedJson.getInt("item_rank_req");
deed.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, deedJson.getString("item_sex_req"), templateId, "item_sex_req");
deed.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(deedJson.getJSONArray("item_user_power_action")));
deed.item_power_grant.addAll(CacheParserUtil.readPowerGrants(deedJson.getJSONArray("item_power_grant"), templateId));
deed.item_power_action.putAll(CacheParserUtil.readPowerActions(deedJson.getJSONArray("item_power_action")));
deed.item_sheathable = deedJson.getBoolean("item_sheathable");
deed.item_has_stub = deedJson.getBoolean("item_has_stub");
deed.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, deedJson.getJSONArray("item_offering_adjustments"),
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
deed.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, deedJson.getJSONArray("item_resource_costs"),
"resource_type", "resource_value", templateId, "item_resource_costs"));
deed.item_bane_rank = deedJson.getInt("item_bane_rank");
deed.item_ignore_saved_actions = deedJson.getBoolean("item_ignore_saved_actions");
// deed_
deed.deed_type = deedJson.getInt("deed_type");
deed.deed_furniture_id = deedJson.getInt("deed_furniture_id");
deed.deed_target_id = deedJson.getInt("deed_target_id");
deed.deed_employment = deedJson.getInt("deed_employment");
deed.deed_start_rank = deedJson.getInt("deed_start_rank");
deed.deed_name_lookup = deedJson.getInt("deed_name_lookup");
deed.deed_indoors = deedJson.getBoolean("deed_indoors");
deed.deed_is_fortress = deedJson.getBoolean("deed_is_fortress");
deed.deed_namelookup_val = deedJson.getFloat("deed_namelookup_val");
deed.deed_custom_city = deedJson.getBoolean("deed_custom_city");
deed.deed_structure_id = deedJson.getInt("deed_structure_id");
return deed;
}
}
+248
View File
@@ -0,0 +1,248 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.ItemTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads ITEM templates from the client cache JSON (COBJECTS/ITEM/<id>.json).
public class ItemParser {
private static final String itemPath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/ITEM/";
public static HashMap<Integer, ItemTemplate> parseItemFolder() {
HashMap<Integer, ItemTemplate> itemTemplates = new HashMap<>();
File[] itemFiles = CacheParserUtil.listJsonFiles(itemPath);
int templateId;
JSONObject itemJson;
int failedCount = 0;
for (File itemFile : itemFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(itemFile);
itemJson = CacheParserUtil.readJsonFile(itemFile);
itemTemplates.put(templateId, parseItemEntry(templateId, itemJson));
} catch (Exception e) {
Logger.error(itemFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + itemTemplates.size() + " of " + itemFiles.length + " item templates");
// Partial item data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return itemTemplates;
}
private static ItemTemplate parseItemEntry(int templateId, JSONObject itemJson) {
ItemTemplate item = new ItemTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
JSONArray useFlags;
String flag;
Object stubJson;
JSONArray stubHolder;
item.template_id = templateId;
// obj_
item.obj_name = itemJson.getString("obj_name");
item.obj_pickable = itemJson.getBoolean("obj_pickable");
item.obj_gravity = itemJson.getFloat("obj_gravity");
item.obj_cull_distance = itemJson.getFloat("obj_cull_distance");
item.obj_scale = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_scale"));
item.obj_render_object = itemJson.getInt("obj_render_object");
item.obj_double_fusion = itemJson.getBoolean("obj_double_fusion");
item.obj_forward_vector = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_forward_vector"));
item.obj_tracking_name = itemJson.getString("obj_tracking_name");
item.obj_max_tracking_distance = itemJson.getFloat("obj_max_tracking_distance");
item.obj_icon = itemJson.getInt("obj_icon");
item.obj_gravity_f = itemJson.getFloat("obj_gravity_f");
item.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(itemJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
item.obj_arc_hardpoint_list = itemJson.getJSONArray("obj_arc_hardpoint_list");
item.obj_sparse_data.putAll(CacheParserUtil.readSparseData(itemJson.getJSONObject("obj_sparse_data")));
item.obj_render_object_low_detail = itemJson.getInt("obj_render_object_low_detail");
item.obj_default_alignment = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_default_alignment"));
item.obj_sound_table = CacheParserUtil.readHashLabel(itemJson, "obj_sound_table", templateId);
// combat_
item.combat_health_current = itemJson.getFloat("combat_health_current");
item.combat_health_full = itemJson.getFloat("combat_health_full");
item.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, itemJson.getJSONObject("combat_attack_resist"),
templateId, "combat_attack_resist"));
item.combat_powers = itemJson.getJSONArray("combat_powers");
// item_
item.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, itemJson.getString("item_type"), templateId, "item_type");
item.item_eq_slots_value = itemJson.getInt("item_eq_slots_value");
item.item_eq_slots_type = itemJson.getBoolean("item_eq_slots_type");
// The exporter writes ["NONE"] for an empty slot mask
item.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, itemJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
item.item_eq_slots_or.remove(mbEnums.EquipSlotType.NONE);
item.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, itemJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
item.item_eq_slots_and.remove(mbEnums.EquipSlotType.NONE);
item.item_takeable = itemJson.getBoolean("item_takeable");
item.item_value = itemJson.getInt("item_value");
item.item_wt = itemJson.getInt("item_wt");
item.item_passive_defense_mod = itemJson.getFloat("item_passive_defense_mod");
item.item_base_name = itemJson.getString("item_base_name");
item.item_dsc = itemJson.getString("item_dsc");
item.item_render_object_female = itemJson.getInt("item_render_object_female");
item.item_health_full = itemJson.getFloat("item_health_full");
item.item_skill_used = CacheParserUtil.readHashLabel(itemJson, "item_skill_used", templateId);
item.item_skill_mastery_used = CacheParserUtil.readHashLabel(itemJson, "item_skill_mastery_used", templateId);
item.item_parry_anim_id = itemJson.getInt("item_parry_anim_id");
item.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, itemJson.getJSONArray("item_offering_info"),
"offering_type", "offering_value", templateId, "item_offering_info"));
// The client reads these blocks by item type, not by key presence
if (item.item_type == mbEnums.ItemType.WEAPON) {
item.item_weapon = parseWeapon(templateId, itemJson.getJSONObject("item_weapon"));
item.item_primary_attr = CacheParserUtil.readEnum(mbEnums.AttributeType.class, itemJson.getString("item_primary_attr"), templateId, "item_primary_attr");
item.item_secondary_attr = CacheParserUtil.readEnum(mbEnums.AttributeType.class, itemJson.getString("item_secondary_attr"), templateId, "item_secondary_attr");
}
if (item.item_type == mbEnums.ItemType.ARMOR) {
item.item_bulk_factor = itemJson.getFloat("item_bulk_factor");
item.item_defense_rating = itemJson.getInt("item_defense_rating");
}
// "None" is bit 0 in the client's flag tables, not a real flag
itemFlags = itemJson.getJSONArray("item_flags");
for (int i = 0; i < itemFlags.length(); i++) {
flag = itemFlags.getString(i);
if (flag.equals("None"))
continue;
item.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
}
useFlags = itemJson.getJSONArray("item_use_flags");
for (int i = 0; i < useFlags.length(); i++) {
flag = useFlags.getString(i);
if (flag.equals("NONE"))
continue;
item.item_use_flags.add(CacheParserUtil.readEnum(mbEnums.ItemUseFlags.class, flag, templateId, "item_use_flags"));
}
item.item_post_item_id = itemJson.getInt("item_post_item_id");
item.item_initial_charges = itemJson.getInt("item_initial_charges");
item.item_book_arcana = itemJson.getInt("item_book_arcana");
item.item_skill_req.putAll(CacheParserUtil.readStringValueMap(itemJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
raceReq = itemJson.getJSONObject("item_race_req");
item.item_race_req_restrict = raceReq.getBoolean("restrict");
item.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
classReq = itemJson.getJSONObject("item_class_req");
item.item_class_req_restrict = classReq.getBoolean("restrict");
item.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
discReq = itemJson.getJSONObject("item_disc_req");
item.item_disc_req_restrict = discReq.getBoolean("restrict");
item.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
item.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, itemJson.getJSONArray("item_attr_req"),
"attr_type", "attr_value", templateId, "item_attr_req"));
item.item_level_req = itemJson.getInt("item_level_req");
item.item_rank_req = itemJson.getInt("item_rank_req");
item.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, itemJson.getString("item_sex_req"), templateId, "item_sex_req");
item.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(itemJson.getJSONArray("item_user_power_action")));
item.item_power_grant.addAll(CacheParserUtil.readPowerGrants(itemJson.getJSONArray("item_power_grant"), templateId));
item.item_power_action.putAll(CacheParserUtil.readPowerActions(itemJson.getJSONArray("item_power_action")));
item.item_sheathable = itemJson.getBoolean("item_sheathable");
if (item.item_sheathable) {
item.item_sheath_slot = CacheParserUtil.readEnum(mbEnums.SheathSlot.class, itemJson.getString("item_sheath_slot"), templateId, "item_sheath_slot");
item.item_sheath_offset = CacheParserUtil.readVector3(itemJson.getJSONArray("item_sheath_offset"));
item.item_sheath_rotation = CacheParserUtil.readVector3(itemJson.getJSONArray("item_sheath_rotation"));
}
stubJson = itemJson.get("item_has_stub");
if (stubJson instanceof JSONArray) {
stubHolder = (JSONArray) stubJson;
item.item_has_stub = true;
item.item_stub_flag = stubHolder.getBoolean(0);
item.item_stub_id = stubHolder.getInt(1);
} else {
item.item_has_stub = itemJson.getBoolean("item_has_stub");
}
item.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, itemJson.getJSONArray("item_offering_adjustments"),
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
item.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, itemJson.getJSONArray("item_resource_costs"),
"resource_type", "resource_value", templateId, "item_resource_costs"));
item.item_bane_rank = itemJson.getInt("item_bane_rank");
item.item_ignore_saved_actions = itemJson.getBoolean("item_ignore_saved_actions");
return item;
}
private static ItemTemplate.Weapon parseWeapon(int templateId, JSONObject weaponJson) {
ItemTemplate.Weapon weapon = new ItemTemplate.Weapon();
JSONArray damageJson;
JSONObject damageEntry;
ItemTemplate.Damage damage;
weapon.weapon_wepspeed = weaponJson.getFloat("weapon_wepspeed");
weapon.weapon_max_range = weaponJson.getFloat("weapon_max_range");
weapon.weapon_projectile_id = weaponJson.getInt("weapon_projectile_id");
weapon.weapon_projectile_speed = weaponJson.getFloat("weapon_projectile_speed");
weapon.weapon_combat_idle_anim = weaponJson.getInt("weapon_combat_idle_anim");
damageJson = weaponJson.getJSONArray("weapon_damage");
for (int i = 0; i < damageJson.length(); i++) {
damageEntry = damageJson.getJSONObject(i);
damage = new ItemTemplate.Damage();
damage.damage_type = CacheParserUtil.readEnum(mbEnums.DamageType.class, damageEntry.getString("damage_type"), templateId, "weapon_damage");
damage.damage_min = damageEntry.getInt("damage_min");
damage.damage_max = damageEntry.getInt("damage_max");
weapon.weapon_damage.add(damage);
}
weapon.weapon_attack_anim_right.addAll(CacheParserUtil.readIntArrays(weaponJson.getJSONArray("weapon_attack_anim_right")));
weapon.weapon_attack_anim_left.addAll(CacheParserUtil.readIntArrays(weaponJson.getJSONArray("weapon_attack_anim_left")));
return weapon;
}
}
+84
View File
@@ -0,0 +1,84 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import org.pmw.tinylog.Logger;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.function.Consumer;
// Dev-only check that compares DB-built objects with the cache JSON before a loader is switched.
// Enabled by the optional env var MB_PARITY_CHECK; not in the ConfigManager enum, which requires every entry.
public class ParityCheck {
private static final String FLAG_NAME = "MB_PARITY_CHECK";
private static final LinkedHashMap<String, Consumer<ParityCheck>> comparators = new LinkedHashMap<>();
private final String table;
private int comparedCount = 0;
private int differenceCount = 0;
private int missingCount = 0;
private ParityCheck(String table) {
this.table = table;
}
public static boolean isEnabled() {
return ConfigManager.configMap.getOrDefault(FLAG_NAME, "false").equalsIgnoreCase("true");
}
public static void register(String table, Consumer<ParityCheck> comparator) {
comparators.put(table, comparator);
}
public static void run() {
ParityCheck check;
for (String table : comparators.keySet()) {
check = new ParityCheck(table);
try {
comparators.get(table).accept(check);
} catch (Exception e) {
Logger.error("Parity: " + table + ": comparator failed: " + e);
}
Logger.info("Parity: " + table + ": " + check.comparedCount + " compared, "
+ check.differenceCount + " differences, " + check.missingCount + " missing in json");
}
}
// Returns false when the row has no JSON counterpart, so the comparator can skip its fields.
public boolean compareRow(int id, Object jsonObject) {
comparedCount++;
if (jsonObject == null) {
missingCount++;
Logger.warn("Parity: " + table + "/" + id + ": missing in json");
return false;
}
return true;
}
public void compareField(int id, String field, Object dbValue, Object jsonValue) {
if (Objects.equals(dbValue, jsonValue))
return;
differenceCount++;
Logger.warn("Parity: " + table + "/" + id + "/" + field + ": db=" + dbValue + " json=" + jsonValue);
}
}
+338
View File
@@ -0,0 +1,338 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.mbEnums;
import engine.parsers.objects.RuneTemplate;
import org.json.JSONArray;
import org.json.JSONObject;
import org.pmw.tinylog.Logger;
import java.io.File;
import java.util.HashMap;
// Loads RUNE templates from the client cache JSON (COBJECTS/RUNE/<id>.json).
public class RuneParser {
private static final String runePath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/RUNE/";
public static HashMap<Integer, RuneTemplate> parseRuneFolder() {
HashMap<Integer, RuneTemplate> runeTemplates = new HashMap<>();
File[] runeFiles = CacheParserUtil.listJsonFiles(runePath);
int templateId;
JSONObject runeJson;
int failedCount = 0;
for (File runeFile : runeFiles) {
try {
templateId = CacheParserUtil.templateIdFromFile(runeFile);
runeJson = CacheParserUtil.readJsonFile(runeFile);
runeTemplates.put(templateId, parseRuneEntry(templateId, runeJson));
} catch (Exception e) {
Logger.error(runeFile.getName() + ": " + e);
failedCount++;
}
}
Logger.info("Loaded " + runeTemplates.size() + " of " + runeFiles.length + " rune templates");
// Partial rune data is never usable; null tells the caller to abort
if (failedCount > 0)
return null;
return runeTemplates;
}
private static RuneTemplate parseRuneEntry(int templateId, JSONObject runeJson) {
RuneTemplate rune = new RuneTemplate();
JSONObject raceReq;
JSONObject classReq;
JSONObject discReq;
JSONArray itemFlags;
String flag;
Object subType;
JSONObject speedJson;
JSONObject groupJson;
JSONArray skillGrantsJson;
JSONObject skillGrantJson;
RuneTemplate.SkillGrant skillGrant;
JSONArray skillAdjJson;
JSONObject skillAdjEntry;
JSONArray naturalAttacksJson;
JSONArray inventoryJson;
JSONObject inventoryEntryJson;
RuneTemplate.InventoryEntry inventoryEntry;
JSONArray bodyPartsJson;
JSONObject bodyPartJson;
RuneTemplate.BodyPart bodyPart;
rune.template_id = templateId;
// obj_
rune.obj_name = runeJson.getString("obj_name");
rune.obj_pickable = runeJson.getBoolean("obj_pickable");
rune.obj_gravity = runeJson.getFloat("obj_gravity");
rune.obj_cull_distance = runeJson.getFloat("obj_cull_distance");
rune.obj_scale = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_scale"));
rune.obj_render_object = runeJson.getInt("obj_render_object");
rune.obj_double_fusion = runeJson.getBoolean("obj_double_fusion");
rune.obj_forward_vector = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_forward_vector"));
rune.obj_tracking_name = runeJson.getString("obj_tracking_name");
rune.obj_max_tracking_distance = runeJson.getFloat("obj_max_tracking_distance");
rune.obj_icon = runeJson.getInt("obj_icon");
rune.obj_gravity_f = runeJson.getFloat("obj_gravity_f");
rune.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
rune.obj_arc_hardpoint_list = runeJson.getJSONArray("obj_arc_hardpoint_list");
rune.obj_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("obj_sparse_data")));
rune.obj_render_object_low_detail = runeJson.getInt("obj_render_object_low_detail");
rune.obj_default_alignment = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_default_alignment"));
rune.obj_sound_table = CacheParserUtil.readLabel(runeJson, "obj_sound_table");
// combat_
rune.combat_health_current = runeJson.getFloat("combat_health_current");
rune.combat_health_full = runeJson.getFloat("combat_health_full");
rune.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, runeJson.getJSONObject("combat_attack_resist"),
templateId, "combat_attack_resist"));
rune.combat_powers = runeJson.getJSONArray("combat_powers");
// item_
rune.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, runeJson.getString("item_type"), templateId, "item_type");
rune.item_eq_slots_value = runeJson.getInt("item_eq_slots_value");
rune.item_eq_slots_type = runeJson.getBoolean("item_eq_slots_type");
// The exporter writes ["NONE"] for an empty slot mask
rune.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
rune.item_eq_slots_or.remove(mbEnums.EquipSlotType.NONE);
rune.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
rune.item_eq_slots_and.remove(mbEnums.EquipSlotType.NONE);
rune.item_takeable = runeJson.getBoolean("item_takeable");
rune.item_value = runeJson.getInt("item_value");
rune.item_wt = runeJson.getInt("item_wt");
rune.item_passive_defense_mod = runeJson.getFloat("item_passive_defense_mod");
rune.item_base_name = runeJson.getString("item_base_name");
rune.item_dsc = runeJson.getString("item_dsc");
rune.item_render_object_female = runeJson.getInt("item_render_object_female");
rune.item_health_full = runeJson.getFloat("item_health_full");
rune.item_skill_used = CacheParserUtil.readLabel(runeJson, "item_skill_used");
rune.item_skill_mastery_used = CacheParserUtil.readLabel(runeJson, "item_skill_mastery_used");
rune.item_parry_anim_id = runeJson.getInt("item_parry_anim_id");
rune.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_info"),
"offering_type", "offering_value", templateId, "item_offering_info"));
// "None" is the client's empty marker, not a flag
itemFlags = runeJson.getJSONArray("item_flags");
for (int i = 0; i < itemFlags.length(); i++) {
flag = itemFlags.getString(i);
if (flag.equals("None"))
continue;
rune.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
}
rune.item_use_flags.addAll(CacheParserUtil.readEnumSet(mbEnums.ItemUseFlags.class, runeJson.getJSONArray("item_use_flags"), templateId, "item_use_flags"));
rune.item_post_item_id = runeJson.getInt("item_post_item_id");
rune.item_initial_charges = runeJson.getInt("item_initial_charges");
rune.item_book_arcana = runeJson.getInt("item_book_arcana");
rune.item_skill_req.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
raceReq = runeJson.getJSONObject("item_race_req");
rune.item_race_req_restrict = raceReq.getBoolean("restrict");
rune.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
classReq = runeJson.getJSONObject("item_class_req");
rune.item_class_req_restrict = classReq.getBoolean("restrict");
rune.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
discReq = runeJson.getJSONObject("item_disc_req");
rune.item_disc_req_restrict = discReq.getBoolean("restrict");
rune.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
rune.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("item_attr_req"),
"attr_type", "attr_value", templateId, "item_attr_req"));
rune.item_level_req = runeJson.getInt("item_level_req");
rune.item_rank_req = runeJson.getInt("item_rank_req");
rune.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("item_sex_req"), templateId, "item_sex_req");
rune.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(runeJson.getJSONArray("item_user_power_action")));
rune.item_power_grant.addAll(CacheParserUtil.readPowerGrants(runeJson.getJSONArray("item_power_grant"), templateId));
rune.item_power_action.putAll(CacheParserUtil.readPowerActions(runeJson.getJSONArray("item_power_action")));
rune.item_sheathable = runeJson.getBoolean("item_sheathable");
rune.item_has_stub = runeJson.getBoolean("item_has_stub");
rune.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_adjustments"),
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
rune.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, runeJson.getJSONArray("item_resource_costs"),
"resource_type", "resource_value", templateId, "item_resource_costs"));
rune.item_bane_rank = runeJson.getInt("item_bane_rank");
rune.item_ignore_saved_actions = runeJson.getBoolean("item_ignore_saved_actions");
// rune_
rune.rune_type = CacheParserUtil.readEnum(mbEnums.RuneType.class, runeJson.getString("rune_type"), templateId, "rune_type");
subType = runeJson.get("rune_sub_type");
if (subType instanceof String) {
rune.rune_sub_type = (String) subType;
} else {
rune.rune_sub_type = "";
rune.rune_sub_type_hash = (int) runeJson.getLong("rune_sub_type");
}
rune.rune_is_standard_character_creation = runeJson.getBoolean("rune_is_standard_character_creation");
rune.rune_creation_cost = runeJson.getInt("rune_creation_cost");
rune.rune_rank = runeJson.getInt("rune_rank");
rune.rune_pracs_per_level = runeJson.getInt("rune_pracs_per_level");
rune.rune_exp_req_to_level = runeJson.getFloat("rune_exp_req_to_level");
rune.rune_sex = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("rune_sex"), templateId, "rune_sex");
rune.rune_class_icon = runeJson.getInt("rune_class_icon");
rune.rune_health = runeJson.getInt("rune_health");
rune.rune_mana = runeJson.getInt("rune_mana");
rune.rune_stamina = runeJson.getInt("rune_stamina");
rune.rune_min_damage = runeJson.getFloat("rune_min_damage");
rune.rune_max_damage = runeJson.getFloat("rune_max_damage");
rune.rune_attack = runeJson.getInt("rune_attack");
rune.rune_defense = runeJson.getInt("rune_defense");
rune.rune_level = runeJson.getInt("rune_level");
speedJson = runeJson.getJSONObject("rune_speed");
for (String speedKey : speedJson.keySet())
rune.rune_speed.put(CacheParserUtil.readEnum(mbEnums.MovementType.class, speedKey, templateId, "rune_speed"), speedJson.getFloat(speedKey));
groupJson = runeJson.getJSONObject("rune_group");
rune.rune_group_type = groupJson.getInt("group_type");
rune.rune_group_is_faction = groupJson.getBoolean("group_is_faction");
rune.rune_group_is_guild = groupJson.getBoolean("group_is_guild");
rune.rune_dsc = runeJson.getString("rune_dsc");
rune.rune_fx_txt = runeJson.getString("rune_fx_txt");
rune.rune_group_tactics = runeJson.getLong("rune_group_tactics");
rune.rune_group_role_set = runeJson.getLong("rune_group_role_set");
rune.rune_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_enemy_monster_types"),
templateId, "rune_enemy_monster_types"));
rune.rune_groupee_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_groupee_monster_types")));
rune.rune_helper_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_helper_monster_types")));
rune.rune_not_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_not_enemy_monster_types"),
templateId, "rune_not_enemy_monster_types"));
rune.rune_enemy_gender = runeJson.getJSONArray("rune_enemy_gender");
skillGrantsJson = runeJson.getJSONArray("rune_skill_grant");
for (int i = 0; i < skillGrantsJson.length(); i++) {
skillGrantJson = skillGrantsJson.getJSONObject(i);
skillGrant = new RuneTemplate.SkillGrant();
skillGrant.skill_type = skillGrantJson.getString("skill_type");
skillGrant.skill_value = skillGrantJson.getInt("skill_value");
skillGrant.skill_granted_attrs.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_attrs"),
templateId, "skill_granted_attrs"));
skillGrant.skill_granted_skills.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_skills"),
templateId, "skill_granted_skills"));
skillGrant.skill_granted_powers.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_powers"),
templateId, "skill_granted_powers"));
skillGrant.skill_monster_types = skillGrantJson.getJSONArray("skill_monster_types");
rune.rune_skill_grant.add(skillGrant);
}
skillAdjJson = runeJson.getJSONArray("rune_skill_adj");
for (int i = 0; i < skillAdjJson.length(); i++) {
skillAdjEntry = skillAdjJson.getJSONObject(i);
rune.rune_skill_adj.put(skillAdjEntry.getString("skill_type"),
CacheParserUtil.readIntArrays(skillAdjEntry.getJSONArray("skill_adjusts")));
}
rune.rune_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_attr_adj"),
"attr_type", "attr_value", templateId, "rune_attr_adj"));
rune.rune_max_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_max_attr_adj"),
"attr_type", "attr_value", templateId, "rune_max_attr_adj"));
naturalAttacksJson = runeJson.getJSONArray("rune_naturalattacks");
for (int i = 0; i < naturalAttacksJson.length(); i++)
rune.rune_naturalattacks.add(CacheParserUtil.readFloatArray(naturalAttacksJson.getJSONArray(i)));
rune.rune_enchantment_type = runeJson.getJSONArray("rune_enchantment_type");
inventoryJson = runeJson.getJSONArray("rune_inventory_contents");
for (int i = 0; i < inventoryJson.length(); i++) {
inventoryEntryJson = inventoryJson.getJSONObject(i);
inventoryEntry = new RuneTemplate.InventoryEntry();
inventoryEntry.type = inventoryEntryJson.getString("type");
if (inventoryEntryJson.has("gold"))
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("gold"));
else if (inventoryEntryJson.has("table"))
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("table"));
else
Logger.error(templateId + ": rune_inventory_contents entry of type " + inventoryEntry.type + " has no values");
rune.rune_inventory_contents.add(inventoryEntry);
}
rune.rune_renderable = runeJson.getBoolean("rune_renderable");
// Render fields are absent on some runes
if (runeJson.has("rune_scale_factor"))
rune.rune_scale_factor = CacheParserUtil.readVector3(runeJson.getJSONArray("rune_scale_factor"));
if (runeJson.has("rune_skeleton"))
rune.rune_skeleton = runeJson.getInt("rune_skeleton");
if (runeJson.has("rune_slope_hugger"))
rune.rune_slope_hugger = runeJson.getBoolean("rune_slope_hugger");
if (runeJson.has("rune_can_fly"))
rune.rune_can_fly = runeJson.getBoolean("rune_can_fly");
if (runeJson.has("rune_death_effect"))
rune.rune_death_effect = runeJson.getInt("rune_death_effect");
if (runeJson.has("rune_tombstone_id"))
rune.rune_tombstone_id = runeJson.getInt("rune_tombstone_id");
if (runeJson.has("rune_body_parts")) {
bodyPartsJson = runeJson.getJSONArray("rune_body_parts");
for (int i = 0; i < bodyPartsJson.length(); i++) {
bodyPartJson = bodyPartsJson.getJSONObject(i);
bodyPart = new RuneTemplate.BodyPart();
bodyPart.body_part_render = bodyPartJson.getInt("body_part_render");
bodyPart.body_part_position = CacheParserUtil.readVector3(bodyPartJson.getJSONArray("body_part_position"));
rune.rune_body_parts.add(bodyPart);
}
}
if (runeJson.has("rune_hair"))
rune.rune_hair.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_hair")));
if (runeJson.has("rune_beard"))
rune.rune_beard.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_beard")));
rune.rune_natural_power_attack = runeJson.getInt("rune_natural_power_attack");
rune.rune_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("rune_sparse_data")));
return rune;
}
}
@@ -0,0 +1,99 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import engine.parsers.cache.CObjectIndex;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Common type for ITEM, DEED and RUNE cache templates, which share one id space.
// Getters cover only the obj_/combat_/item_ fields all three define (JSON-Refactor-Plan D1).
// Returned collections are the template's own; never modify them.
public interface CObjectTemplate {
static CObjectTemplate lookup(int templateId) {
return CObjectIndex.lookup(templateId);
}
int getTemplateId();
String getObjName();
boolean getObjPickable();
float getObjGravity();
float getObjCullDistance();
Vector3fImmutable getObjScale();
int getObjRenderObject();
boolean getObjDoubleFusion();
Vector3fImmutable getObjForwardVector();
String getObjTrackingName();
float getObjMaxTrackingDistance();
int getObjIcon();
float getObjGravityF();
LinkedHashMap<String, Integer> getObjSoundEvents();
JSONArray getObjArcHardpointList();
HashMap<String, String> getObjSparseData();
int getObjRenderObjectLowDetail();
Vector3fImmutable getObjDefaultAlignment();
String getObjSoundTable();
float getCombatHealthCurrent();
float getCombatHealthFull();
EnumMap<mbEnums.DamageType, Float> getCombatAttackResist();
JSONArray getCombatPowers();
mbEnums.ItemType getItemType();
int getItemEqSlotsValue();
boolean getItemEqSlotsType();
EnumSet<mbEnums.EquipSlotType> getItemEqSlotsOr();
EnumSet<mbEnums.EquipSlotType> getItemEqSlotsAnd();
boolean getItemTakeable();
int getItemValue();
int getItemWt();
float getItemPassiveDefenseMod();
String getItemBaseName();
String getItemDsc();
int getItemRenderObjectFemale();
float getItemHealthFull();
String getItemSkillUsed();
String getItemSkillMasteryUsed();
int getItemParryAnimId();
EnumMap<mbEnums.ShrineType, Integer> getItemOfferingInfo();
EnumSet<mbEnums.ItemFlags> getItemFlags();
EnumSet<mbEnums.ItemUseFlags> getItemUseFlags();
int getItemPostItemId();
int getItemInitialCharges();
int getItemBookArcana();
LinkedHashMap<String, Integer> getItemSkillReq();
boolean getItemRaceReqRestrict();
EnumSet<mbEnums.MonsterType> getItemRaceReq();
boolean getItemClassReqRestrict();
EnumSet<mbEnums.ClassType> getItemClassReq();
boolean getItemDiscReqRestrict();
EnumSet<mbEnums.DisciplineType> getItemDiscReq();
EnumMap<mbEnums.AttributeType, Integer> getItemAttrReq();
int getItemLevelReq();
int getItemRankReq();
mbEnums.SexType getItemSexReq();
ArrayList<UserPowerAction> getItemUserPowerAction();
ArrayList<PowerGrant> getItemPowerGrant();
LinkedHashMap<String, ArrayList<int[]>> getItemPowerAction();
boolean getItemSheathable();
boolean getItemHasStub();
EnumMap<mbEnums.ShrineType, Integer> getItemOfferingAdjustments();
EnumMap<mbEnums.ResourceType, Integer> getItemResourceCosts();
int getItemBaneRank();
boolean getItemIgnoreSavedActions();
}
@@ -0,0 +1,447 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Deed template as defined by the client's cache data
// (MBDATA/data/COBJECTS/DEED). Field names match the JSON keys.
//
// Values are kept as the client defines them: no req/res splitting,
// no defaults substituted. Strings with a matching enum in mbEnums
// are stored as that enum; everything else stays a String.
//
// Read only once loaded.
public class DeedTemplate implements CObjectTemplate {
public static HashMap<Integer, DeedTemplate> _deeds = new HashMap<>();
public int template_id;
// obj_
public String obj_name;
public boolean obj_pickable;
public float obj_gravity;
public float obj_cull_distance;
public Vector3fImmutable obj_scale;
public int obj_render_object;
public boolean obj_double_fusion;
public Vector3fImmutable obj_forward_vector;
public String obj_tracking_name;
public float obj_max_tracking_distance;
public int obj_icon;
public float obj_gravity_f;
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
public JSONArray obj_arc_hardpoint_list;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public int obj_render_object_low_detail;
public Vector3fImmutable obj_default_alignment;
public String obj_sound_table;
// combat_
public float combat_health_current;
public float combat_health_full;
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
public JSONArray combat_powers;
// item_
public mbEnums.ItemType item_type;
public int item_eq_slots_value;
public boolean item_eq_slots_type;
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public boolean item_takeable;
public int item_value;
public int item_wt;
public float item_passive_defense_mod;
public String item_base_name;
public String item_dsc;
public int item_render_object_female;
public float item_health_full;
public String item_skill_used;
public String item_skill_mastery_used;
public int item_parry_anim_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
public int item_post_item_id;
public int item_initial_charges;
public int item_book_arcana;
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
public boolean item_race_req_restrict;
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public boolean item_class_req_restrict;
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public boolean item_disc_req_restrict;
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
public int item_level_req;
public int item_rank_req;
public mbEnums.SexType item_sex_req;
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
public boolean item_sheathable;
public boolean item_has_stub;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
public int item_bane_rank;
public boolean item_ignore_saved_actions;
// deed_
public int deed_type;
public int deed_furniture_id;
public int deed_target_id;
public int deed_employment;
public int deed_start_rank;
public int deed_name_lookup;
public boolean deed_indoors;
public boolean deed_is_fortress;
public float deed_namelookup_val;
public boolean deed_custom_city;
public int deed_structure_id;
@Override
public int getTemplateId() {
return template_id;
}
@Override
public String getObjName() {
return obj_name;
}
@Override
public boolean getObjPickable() {
return obj_pickable;
}
@Override
public float getObjGravity() {
return obj_gravity;
}
@Override
public float getObjCullDistance() {
return obj_cull_distance;
}
@Override
public Vector3fImmutable getObjScale() {
return obj_scale;
}
@Override
public int getObjRenderObject() {
return obj_render_object;
}
@Override
public boolean getObjDoubleFusion() {
return obj_double_fusion;
}
@Override
public Vector3fImmutable getObjForwardVector() {
return obj_forward_vector;
}
@Override
public String getObjTrackingName() {
return obj_tracking_name;
}
@Override
public float getObjMaxTrackingDistance() {
return obj_max_tracking_distance;
}
@Override
public int getObjIcon() {
return obj_icon;
}
@Override
public float getObjGravityF() {
return obj_gravity_f;
}
@Override
public LinkedHashMap<String, Integer> getObjSoundEvents() {
return obj_sound_events;
}
@Override
public JSONArray getObjArcHardpointList() {
return obj_arc_hardpoint_list;
}
@Override
public HashMap<String, String> getObjSparseData() {
return obj_sparse_data;
}
@Override
public int getObjRenderObjectLowDetail() {
return obj_render_object_low_detail;
}
@Override
public Vector3fImmutable getObjDefaultAlignment() {
return obj_default_alignment;
}
@Override
public String getObjSoundTable() {
return obj_sound_table;
}
@Override
public float getCombatHealthCurrent() {
return combat_health_current;
}
@Override
public float getCombatHealthFull() {
return combat_health_full;
}
@Override
public EnumMap<mbEnums.DamageType, Float> getCombatAttackResist() {
return combat_attack_resist;
}
@Override
public JSONArray getCombatPowers() {
return combat_powers;
}
@Override
public mbEnums.ItemType getItemType() {
return item_type;
}
@Override
public int getItemEqSlotsValue() {
return item_eq_slots_value;
}
@Override
public boolean getItemEqSlotsType() {
return item_eq_slots_type;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsOr() {
return item_eq_slots_or;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsAnd() {
return item_eq_slots_and;
}
@Override
public boolean getItemTakeable() {
return item_takeable;
}
@Override
public int getItemValue() {
return item_value;
}
@Override
public int getItemWt() {
return item_wt;
}
@Override
public float getItemPassiveDefenseMod() {
return item_passive_defense_mod;
}
@Override
public String getItemBaseName() {
return item_base_name;
}
@Override
public String getItemDsc() {
return item_dsc;
}
@Override
public int getItemRenderObjectFemale() {
return item_render_object_female;
}
@Override
public float getItemHealthFull() {
return item_health_full;
}
@Override
public String getItemSkillUsed() {
return item_skill_used;
}
@Override
public String getItemSkillMasteryUsed() {
return item_skill_mastery_used;
}
@Override
public int getItemParryAnimId() {
return item_parry_anim_id;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingInfo() {
return item_offering_info;
}
@Override
public EnumSet<mbEnums.ItemFlags> getItemFlags() {
return item_flags;
}
@Override
public EnumSet<mbEnums.ItemUseFlags> getItemUseFlags() {
return item_use_flags;
}
@Override
public int getItemPostItemId() {
return item_post_item_id;
}
@Override
public int getItemInitialCharges() {
return item_initial_charges;
}
@Override
public int getItemBookArcana() {
return item_book_arcana;
}
@Override
public LinkedHashMap<String, Integer> getItemSkillReq() {
return item_skill_req;
}
@Override
public boolean getItemRaceReqRestrict() {
return item_race_req_restrict;
}
@Override
public EnumSet<mbEnums.MonsterType> getItemRaceReq() {
return item_race_req;
}
@Override
public boolean getItemClassReqRestrict() {
return item_class_req_restrict;
}
@Override
public EnumSet<mbEnums.ClassType> getItemClassReq() {
return item_class_req;
}
@Override
public boolean getItemDiscReqRestrict() {
return item_disc_req_restrict;
}
@Override
public EnumSet<mbEnums.DisciplineType> getItemDiscReq() {
return item_disc_req;
}
@Override
public EnumMap<mbEnums.AttributeType, Integer> getItemAttrReq() {
return item_attr_req;
}
@Override
public int getItemLevelReq() {
return item_level_req;
}
@Override
public int getItemRankReq() {
return item_rank_req;
}
@Override
public mbEnums.SexType getItemSexReq() {
return item_sex_req;
}
@Override
public ArrayList<UserPowerAction> getItemUserPowerAction() {
return item_user_power_action;
}
@Override
public ArrayList<PowerGrant> getItemPowerGrant() {
return item_power_grant;
}
@Override
public LinkedHashMap<String, ArrayList<int[]>> getItemPowerAction() {
return item_power_action;
}
@Override
public boolean getItemSheathable() {
return item_sheathable;
}
@Override
public boolean getItemHasStub() {
return item_has_stub;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingAdjustments() {
return item_offering_adjustments;
}
@Override
public EnumMap<mbEnums.ResourceType, Integer> getItemResourceCosts() {
return item_resource_costs;
}
@Override
public int getItemBaneRank() {
return item_bane_rank;
}
@Override
public boolean getItemIgnoreSavedActions() {
return item_ignore_saved_actions;
}
}
@@ -0,0 +1,501 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import engine.math.Vector3fImmutable;
import engine.mbEnums;
import org.json.JSONArray;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
// Item template as defined by the client's cache data
// (MBDATA/data/COBJECTS/ITEM). Field names match the JSON keys.
// Field layout follows ArcItemTemplate_Load (sb.exe 0x4AABD0).
//
// Values are kept as the client defines them: no req/res splitting,
// no defaults substituted. Strings with a matching enum in mbEnums
// are stored as that enum; everything else stays a String.
// Read only once loaded.
public class ItemTemplate implements CObjectTemplate {
public static HashMap<Integer, ItemTemplate> _items = new HashMap<>();
// Legacy templates zero-filled the weapon block on non-weapons; held shields and the like rely on it.
private static final Weapon NO_WEAPON = new Weapon();
public int template_id;
// obj_
public String obj_name;
public boolean obj_pickable;
public float obj_gravity;
public float obj_cull_distance;
public Vector3fImmutable obj_scale;
public int obj_render_object;
public boolean obj_double_fusion;
public Vector3fImmutable obj_forward_vector;
public String obj_tracking_name;
public float obj_max_tracking_distance;
public int obj_icon;
public float obj_gravity_f;
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
public JSONArray obj_arc_hardpoint_list;
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
public int obj_render_object_low_detail;
public Vector3fImmutable obj_default_alignment;
public String obj_sound_table;
// combat_
public float combat_health_current;
public float combat_health_full;
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
public JSONArray combat_powers;
// item_
public mbEnums.ItemType item_type;
// Index into the client's equip slot names; the leading slot of EQSLOTAND.
public int item_eq_slots_value;
// true: any one of item_eq_slots_or; false: all of item_eq_slots_and (sb.exe 0x4AC430).
public boolean item_eq_slots_type;
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
public boolean item_takeable;
public int item_value;
public int item_wt;
public float item_passive_defense_mod;
public String item_base_name;
public String item_dsc;
public int item_render_object_female;
public float item_health_full;
public String item_skill_used;
public String item_skill_mastery_used;
public int item_parry_anim_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
// WEAPON only; null otherwise.
public Weapon item_weapon;
public mbEnums.AttributeType item_primary_attr;
public mbEnums.AttributeType item_secondary_attr;
// ARMOR only.
public float item_bulk_factor;
public int item_defense_rating;
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
public int item_post_item_id;
public int item_initial_charges;
// Read as an unsigned value, possibly a hash; 0 in every ITEM file.
public int item_book_arcana;
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
// For race, class and disc: restrict true means the list is excluded
// (RACERESTRICT), false means it is required (RACEREQ).
public boolean item_race_req_restrict;
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
public boolean item_class_req_restrict;
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
public boolean item_disc_req_restrict;
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
public int item_level_req;
public int item_rank_req;
public mbEnums.SexType item_sex_req;
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
// Sheath fields are only present when item_sheathable is true.
public boolean item_sheathable;
public mbEnums.SheathSlot item_sheath_slot;
public Vector3fImmutable item_sheath_offset;
public Vector3fImmutable item_sheath_rotation;
// The JSON holds false, or [flag, id] when the stub is present (ArcItemStubHolder2, sb.exe 0x4A8D40).
// Meaning of the flag is not known.
public boolean item_has_stub;
public boolean item_stub_flag;
public int item_stub_id;
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
public int item_bane_rank;
public boolean item_ignore_saved_actions;
@Override
public int getTemplateId() {
return template_id;
}
@Override
public String getObjName() {
return obj_name;
}
@Override
public boolean getObjPickable() {
return obj_pickable;
}
@Override
public float getObjGravity() {
return obj_gravity;
}
@Override
public float getObjCullDistance() {
return obj_cull_distance;
}
@Override
public Vector3fImmutable getObjScale() {
return obj_scale;
}
@Override
public int getObjRenderObject() {
return obj_render_object;
}
@Override
public boolean getObjDoubleFusion() {
return obj_double_fusion;
}
@Override
public Vector3fImmutable getObjForwardVector() {
return obj_forward_vector;
}
@Override
public String getObjTrackingName() {
return obj_tracking_name;
}
@Override
public float getObjMaxTrackingDistance() {
return obj_max_tracking_distance;
}
@Override
public int getObjIcon() {
return obj_icon;
}
@Override
public float getObjGravityF() {
return obj_gravity_f;
}
@Override
public LinkedHashMap<String, Integer> getObjSoundEvents() {
return obj_sound_events;
}
@Override
public JSONArray getObjArcHardpointList() {
return obj_arc_hardpoint_list;
}
@Override
public HashMap<String, String> getObjSparseData() {
return obj_sparse_data;
}
@Override
public int getObjRenderObjectLowDetail() {
return obj_render_object_low_detail;
}
@Override
public Vector3fImmutable getObjDefaultAlignment() {
return obj_default_alignment;
}
@Override
public String getObjSoundTable() {
return obj_sound_table;
}
@Override
public float getCombatHealthCurrent() {
return combat_health_current;
}
@Override
public float getCombatHealthFull() {
return combat_health_full;
}
@Override
public EnumMap<mbEnums.DamageType, Float> getCombatAttackResist() {
return combat_attack_resist;
}
@Override
public JSONArray getCombatPowers() {
return combat_powers;
}
@Override
public mbEnums.ItemType getItemType() {
return item_type;
}
@Override
public int getItemEqSlotsValue() {
return item_eq_slots_value;
}
@Override
public boolean getItemEqSlotsType() {
return item_eq_slots_type;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsOr() {
return item_eq_slots_or;
}
@Override
public EnumSet<mbEnums.EquipSlotType> getItemEqSlotsAnd() {
return item_eq_slots_and;
}
@Override
public boolean getItemTakeable() {
return item_takeable;
}
@Override
public int getItemValue() {
return item_value;
}
@Override
public int getItemWt() {
return item_wt;
}
@Override
public float getItemPassiveDefenseMod() {
return item_passive_defense_mod;
}
@Override
public String getItemBaseName() {
return item_base_name;
}
@Override
public String getItemDsc() {
return item_dsc;
}
@Override
public int getItemRenderObjectFemale() {
return item_render_object_female;
}
@Override
public float getItemHealthFull() {
return item_health_full;
}
@Override
public String getItemSkillUsed() {
return item_skill_used;
}
@Override
public String getItemSkillMasteryUsed() {
return item_skill_mastery_used;
}
@Override
public int getItemParryAnimId() {
return item_parry_anim_id;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingInfo() {
return item_offering_info;
}
@Override
public EnumSet<mbEnums.ItemFlags> getItemFlags() {
return item_flags;
}
@Override
public EnumSet<mbEnums.ItemUseFlags> getItemUseFlags() {
return item_use_flags;
}
@Override
public int getItemPostItemId() {
return item_post_item_id;
}
@Override
public int getItemInitialCharges() {
return item_initial_charges;
}
@Override
public int getItemBookArcana() {
return item_book_arcana;
}
@Override
public LinkedHashMap<String, Integer> getItemSkillReq() {
return item_skill_req;
}
@Override
public boolean getItemRaceReqRestrict() {
return item_race_req_restrict;
}
@Override
public EnumSet<mbEnums.MonsterType> getItemRaceReq() {
return item_race_req;
}
@Override
public boolean getItemClassReqRestrict() {
return item_class_req_restrict;
}
@Override
public EnumSet<mbEnums.ClassType> getItemClassReq() {
return item_class_req;
}
@Override
public boolean getItemDiscReqRestrict() {
return item_disc_req_restrict;
}
@Override
public EnumSet<mbEnums.DisciplineType> getItemDiscReq() {
return item_disc_req;
}
@Override
public EnumMap<mbEnums.AttributeType, Integer> getItemAttrReq() {
return item_attr_req;
}
@Override
public int getItemLevelReq() {
return item_level_req;
}
@Override
public int getItemRankReq() {
return item_rank_req;
}
@Override
public mbEnums.SexType getItemSexReq() {
return item_sex_req;
}
@Override
public ArrayList<UserPowerAction> getItemUserPowerAction() {
return item_user_power_action;
}
@Override
public ArrayList<PowerGrant> getItemPowerGrant() {
return item_power_grant;
}
@Override
public LinkedHashMap<String, ArrayList<int[]>> getItemPowerAction() {
return item_power_action;
}
@Override
public boolean getItemSheathable() {
return item_sheathable;
}
@Override
public boolean getItemHasStub() {
return item_has_stub;
}
@Override
public EnumMap<mbEnums.ShrineType, Integer> getItemOfferingAdjustments() {
return item_offering_adjustments;
}
@Override
public EnumMap<mbEnums.ResourceType, Integer> getItemResourceCosts() {
return item_resource_costs;
}
@Override
public int getItemBaneRank() {
return item_bane_rank;
}
@Override
public boolean getItemIgnoreSavedActions() {
return item_ignore_saved_actions;
}
public static Weapon getWeapon(int templateID) {
ItemTemplate template = _items.get(templateID);
if (template == null || template.item_weapon == null)
return NO_WEAPON;
return template.item_weapon;
}
// item_weapon (ArcAttackInfo_LoadWeapon, sb.exe 0x61FA60).
public static class Weapon {
public float weapon_wepspeed;
public float weapon_max_range;
public int weapon_projectile_id;
public float weapon_projectile_speed;
public int weapon_combat_idle_anim;
public final ArrayList<Damage> weapon_damage = new ArrayList<>();
// [animation, duration] pairs in file order; names from the old static_item_templates export, not the client.
public final ArrayList<int[]> weapon_attack_anim_right = new ArrayList<>();
public final ArrayList<int[]> weapon_attack_anim_left = new ArrayList<>();
}
// One entry of weapon_damage.
public static class Damage {
public mbEnums.DamageType damage_type;
public int damage_min;
public int damage_max;
}
}
@@ -0,0 +1,27 @@
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.parsers.objects;
import org.json.JSONArray;
import java.util.LinkedHashMap;
// One entry of item_power_grant.
// The granted lists are [key, value] pairs, unsigned key and signed value (sb.exe 0x6D7640).
// power_monster_types is empty in every DEED and RUNE file, so it is kept as raw JSON.
public class PowerGrant {
public String power_type;
public int power_value;
public final LinkedHashMap<String, Integer> power_granted_attrs = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> power_granted_skills = new LinkedHashMap<>();
public final LinkedHashMap<String, Integer> power_granted_powers = new LinkedHashMap<>();
public JSONArray power_monster_types;
}
Loaded 100 of 145 files, more files were not shown because too many files have changed in this diff. Show more