azeroth-shell 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -2
- package/bin/azsh.mjs +19 -0
- package/docs/aifid/eventai-texts.json +4414 -0
- package/docs/dungeons/atlas/atlas-rows-dungeons.json +8989 -0
- package/docs/dungeons/survey/pack-id-migration.json +2418 -0
- package/package.json +26 -4
- package/src/autopilot/bossSkip.ts +26 -0
- package/src/autopilot/combatRotation.ts +824 -0
- package/src/autopilot/config.ts +533 -0
- package/src/autopilot/consumables.ts +58 -0
- package/src/autopilot/encounterStand.ts +121 -0
- package/src/autopilot/objectGatePlan.ts +53 -0
- package/src/autopilot/pathEngage.ts +229 -0
- package/src/autopilot/plan.ts +1643 -0
- package/src/autopilot/questPolicy.ts +274 -0
- package/src/autopilot/routeStep.ts +69 -0
- package/src/autopilot/sessionReport.ts +88 -0
- package/src/autopilot/topOffPower.ts +26 -0
- package/src/autopilot/view.ts +476 -0
- package/src/cli/bootCheck.mjs +34 -0
- package/src/cli/bootData.mjs +57 -0
- package/src/cli/combatLogFilter.ts +153 -0
- package/src/cli/combatLogFormat.ts +613 -0
- package/src/cli/fileRepository.mjs +105 -0
- package/src/cli/gzLoader.mjs +31 -0
- package/src/cli/liveGauntletEscort.ts +978 -0
- package/src/cli/liveStratUndead.ts +305 -0
- package/src/cli/macroHostCombat.ts +545 -0
- package/src/cli/macroHostWorld.ts +330 -0
- package/src/cli/prefs.mjs +31 -0
- package/src/cli/register.mjs +4 -0
- package/src/cli/repl.mjs +693 -0
- package/src/cli/rotationText.mjs +226 -0
- package/src/cli/squad.mjs +105 -0
- package/src/cli/term.mjs +126 -0
- package/src/cli/text.mjs +122 -0
- package/src/cli/tui.mjs +626 -0
- package/src/cli/tuiArena.mjs +109 -0
- package/src/cli/tuiAutopilotSettings.mjs +93 -0
- package/src/cli/tuiBags.mjs +180 -0
- package/src/cli/tuiBars.mjs +133 -0
- package/src/cli/tuiBg.mjs +243 -0
- package/src/cli/tuiBulk.mjs +162 -0
- package/src/cli/tuiCheck.mjs +1882 -0
- package/src/cli/tuiCity.mjs +396 -0
- package/src/cli/tuiCompanionKit.mjs +300 -0
- package/src/cli/tuiCompanions.mjs +471 -0
- package/src/cli/tuiDev.mjs +62 -0
- package/src/cli/tuiDungeon.mjs +606 -0
- package/src/cli/tuiEnchantTarget.mjs +120 -0
- package/src/cli/tuiFight.mjs +577 -0
- package/src/cli/tuiFightPanes.mjs +228 -0
- package/src/cli/tuiFloor.mjs +506 -0
- package/src/cli/tuiGear.mjs +158 -0
- package/src/cli/tuiHonor.mjs +74 -0
- package/src/cli/tuiLoot.mjs +97 -0
- package/src/cli/tuiMacros.mjs +144 -0
- package/src/cli/tuiOutfitter.mjs +115 -0
- package/src/cli/tuiPet.mjs +116 -0
- package/src/cli/tuiProfessions.mjs +800 -0
- package/src/cli/tuiRaid.mjs +127 -0
- package/src/cli/tuiRep.mjs +65 -0
- package/src/cli/tuiSaves.mjs +69 -0
- package/src/cli/tuiSearch.mjs +363 -0
- package/src/cli/tuiSettings.mjs +151 -0
- package/src/cli/tuiSimLab.mjs +80 -0
- package/src/cli/tuiTalents.mjs +60 -0
- package/src/cli/tuiViews.mjs +1178 -0
- package/src/cli/tuiWiki.mjs +181 -0
- package/src/cli/tuiWorld.mjs +361 -0
- package/src/cli/wikiIndex.ts +259 -0
- package/src/cli/wikiSources.ts +355 -0
- package/src/data/alteracValley.ts +176 -0
- package/src/data/appearanceOptions.ts +534 -0
- package/src/data/applyLootChanceSanity.ts +21 -0
- package/src/data/createFlavor.ts +96 -0
- package/src/data/customItems.ts +33 -0
- package/src/data/dataStore.ts +1904 -0
- package/src/data/dungeonGameObjects.ts +84 -0
- package/src/data/dungeonPlateCover.ts +66 -0
- package/src/data/dungeonSpawns.ts +294 -0
- package/src/data/dungeons/alterac-valley.ts +135 -0
- package/src/data/dungeons/apply-dungeon-rooms.ts +29 -0
- package/src/data/dungeons/apply-encounter-outdoors.ts +72 -0
- package/src/data/dungeons/apply-instance-geometry.ts +65 -0
- package/src/data/dungeons/apply-spawn-placement.ts +171 -0
- package/src/data/dungeons/blackfathom-deeps.ts +539 -0
- package/src/data/dungeons/blackrock-depths.ts +1410 -0
- package/src/data/dungeons/blackwing-lair.ts +129 -0
- package/src/data/dungeons/boss-eventai.ts +8 -0
- package/src/data/dungeons/deadmines.ts +410 -0
- package/src/data/dungeons/dire-maul-east.ts +980 -0
- package/src/data/dungeons/dire-maul-north.ts +661 -0
- package/src/data/dungeons/dire-maul-west.ts +785 -0
- package/src/data/dungeons/dungeon-rooms.json +5479 -0
- package/src/data/dungeons/encounter-outdoors.json +359 -0
- package/src/data/dungeons/eventai/blackfathom-deeps.ts +308 -0
- package/src/data/dungeons/eventai/blackrock-depths.ts +1257 -0
- package/src/data/dungeons/eventai/deadmines.ts +264 -0
- package/src/data/dungeons/eventai/dire-maul-east.ts +320 -0
- package/src/data/dungeons/eventai/gnomeregan.ts +213 -0
- package/src/data/dungeons/eventai/helpers.ts +79 -0
- package/src/data/dungeons/eventai/index.ts +133 -0
- package/src/data/dungeons/eventai/lbrs.ts +372 -0
- package/src/data/dungeons/eventai/maraudon.ts +323 -0
- package/src/data/dungeons/eventai/ragefire-chasm.ts +108 -0
- package/src/data/dungeons/eventai/razorfen-downs.ts +207 -0
- package/src/data/dungeons/eventai/razorfen-kraul.ts +326 -0
- package/src/data/dungeons/eventai/scholomance.ts +680 -0
- package/src/data/dungeons/eventai/shadowfang-keep.ts +230 -0
- package/src/data/dungeons/eventai/sm-cathedral.ts +75 -0
- package/src/data/dungeons/eventai/sm-graveyard.ts +150 -0
- package/src/data/dungeons/eventai/sm-library.ts +55 -0
- package/src/data/dungeons/eventai/stratholme-undead.ts +127 -0
- package/src/data/dungeons/eventai/sunken-temple.ts +582 -0
- package/src/data/dungeons/eventai/the-stockade.ts +228 -0
- package/src/data/dungeons/eventai/ubrs.ts +225 -0
- package/src/data/dungeons/eventai/uldaman.ts +355 -0
- package/src/data/dungeons/eventai/wailing-caverns.ts +396 -0
- package/src/data/dungeons/eventai/zul-farrak.ts +591 -0
- package/src/data/dungeons/gnomeregan.ts +909 -0
- package/src/data/dungeons/index.ts +126 -0
- package/src/data/dungeons/instance-geometry.json +5233 -0
- package/src/data/dungeons/key-sources.json +169 -0
- package/src/data/dungeons/lbrs.ts +996 -0
- package/src/data/dungeons/maraudon.ts +786 -0
- package/src/data/dungeons/molten-core.ts +194 -0
- package/src/data/dungeons/naxxramas.ts +173 -0
- package/src/data/dungeons/onyxias-lair.ts +71 -0
- package/src/data/dungeons/pack-from-clusters.ts +1256 -0
- package/src/data/dungeons/ragefire-chasm.ts +238 -0
- package/src/data/dungeons/razorfen-downs.ts +698 -0
- package/src/data/dungeons/razorfen-kraul.ts +766 -0
- package/src/data/dungeons/ruins-of-ahnqiraj.ts +121 -0
- package/src/data/dungeons/scholomance.ts +1076 -0
- package/src/data/dungeons/shadowfang-keep.ts +740 -0
- package/src/data/dungeons/sm-armory.ts +266 -0
- package/src/data/dungeons/sm-cathedral.ts +430 -0
- package/src/data/dungeons/sm-graveyard.ts +482 -0
- package/src/data/dungeons/sm-library.ts +337 -0
- package/src/data/dungeons/stratholme-live.ts +808 -0
- package/src/data/dungeons/stratholme-undead.ts +1164 -0
- package/src/data/dungeons/sunken-temple.ts +931 -0
- package/src/data/dungeons/temple-of-ahnqiraj.ts +119 -0
- package/src/data/dungeons/the-stockade.ts +282 -0
- package/src/data/dungeons/ubrs.ts +1286 -0
- package/src/data/dungeons/uldaman.ts +685 -0
- package/src/data/dungeons/wailing-caverns.ts +356 -0
- package/src/data/dungeons/zul-farrak.ts +686 -0
- package/src/data/dungeons/zul-gurub.ts +142 -0
- package/src/data/dungeons.ts +42 -0
- package/src/data/fixtures.ts +446 -0
- package/src/data/gameobject-data.json.gz +0 -0
- package/src/data/item-data.json.gz +0 -0
- package/src/data/loot-data.json.gz +0 -0
- package/src/data/maps/atlas-approach-maps.json +1425 -0
- package/src/data/maps/atlas-landmarks.json +1711 -0
- package/src/data/maps/dungeon-viewports.json +392 -0
- package/src/data/materializeModDamagePercentTaken.ts +70 -0
- package/src/data/materializeModHealthRegen.ts +89 -0
- package/src/data/materializeSpellReputation.ts +26 -0
- package/src/data/pageTexts.ts +19 -0
- package/src/data/profession-data.json.gz +0 -0
- package/src/data/raid-plans/blackfathom-deeps.json +86 -0
- package/src/data/raid-plans/blackrock-depths.json +419 -0
- package/src/data/raid-plans/blackwing-lair.json +328 -0
- package/src/data/raid-plans/deadmines.json +55 -0
- package/src/data/raid-plans/dire-maul-east.json +94 -0
- package/src/data/raid-plans/dire-maul-north.json +70 -0
- package/src/data/raid-plans/dire-maul-west.json +75 -0
- package/src/data/raid-plans/gnomeregan.json +131 -0
- package/src/data/raid-plans/lbrs.json +176 -0
- package/src/data/raid-plans/maraudon.json +102 -0
- package/src/data/raid-plans/molten-core.json +169 -0
- package/src/data/raid-plans/naxxramas.json +354 -0
- package/src/data/raid-plans/onyxias-lair.json +18 -0
- package/src/data/raid-plans/ragefire-chasm.json +30 -0
- package/src/data/raid-plans/razorfen-downs.json +75 -0
- package/src/data/raid-plans/razorfen-kraul.json +53 -0
- package/src/data/raid-plans/ruins-of-ahnqiraj.json +110 -0
- package/src/data/raid-plans/scholomance.json +170 -0
- package/src/data/raid-plans/shadowfang-keep.json +87 -0
- package/src/data/raid-plans/sm-armory.json +53 -0
- package/src/data/raid-plans/sm-cathedral.json +72 -0
- package/src/data/raid-plans/sm-graveyard.json +63 -0
- package/src/data/raid-plans/sm-library.json +23 -0
- package/src/data/raid-plans/stratholme-live.json +129 -0
- package/src/data/raid-plans/stratholme-undead.json +73 -0
- package/src/data/raid-plans/sunken-temple.json +159 -0
- package/src/data/raid-plans/temple-of-ahnqiraj.json +163 -0
- package/src/data/raid-plans/the-stockade.json +42 -0
- package/src/data/raid-plans/ubrs.json +53 -0
- package/src/data/raid-plans/uldaman.json +131 -0
- package/src/data/raid-plans/wailing-caverns.json +90 -0
- package/src/data/raid-plans/zul-farrak.json +156 -0
- package/src/data/raid-plans/zul-gurub.json +307 -0
- package/src/data/raidInstanceCreatures.mjs +473 -0
- package/src/data/rotations/index.ts +152 -0
- package/src/data/rotations/layout.mjs +72 -0
- package/src/data/rotations/pve/druid/balance.json +754 -0
- package/src/data/rotations/pve/druid/feral-tank.json +2448 -0
- package/src/data/rotations/pve/druid/feral.json +2660 -0
- package/src/data/rotations/pve/druid/restoration.json +424 -0
- package/src/data/rotations/pve/hunter/beast-mastery.json +1288 -0
- package/src/data/rotations/pve/hunter/marksmanship.json +1260 -0
- package/src/data/rotations/pve/hunter/survival.json +1373 -0
- package/src/data/rotations/pve/mage/arcane-leveling.json +1798 -0
- package/src/data/rotations/pve/mage/fire-leveling.json +2003 -0
- package/src/data/rotations/pve/mage/frost-leveling.json +1912 -0
- package/src/data/rotations/pve/paladin/holy.json +755 -0
- package/src/data/rotations/pve/paladin/protection.json +962 -0
- package/src/data/rotations/pve/paladin/retribution.json +924 -0
- package/src/data/rotations/pve/priest/discipline.json +772 -0
- package/src/data/rotations/pve/priest/holy.json +707 -0
- package/src/data/rotations/pve/priest/shadow.json +787 -0
- package/src/data/rotations/pve/rogue/assassination.json +2477 -0
- package/src/data/rotations/pve/rogue/combat.json +2483 -0
- package/src/data/rotations/pve/rogue/subtlety.json +2523 -0
- package/src/data/rotations/pve/shaman/elemental.json +1157 -0
- package/src/data/rotations/pve/shaman/enhancement.json +1085 -0
- package/src/data/rotations/pve/shaman/restoration.json +592 -0
- package/src/data/rotations/pve/warlock/affliction.json +1185 -0
- package/src/data/rotations/pve/warlock/demonology.json +1165 -0
- package/src/data/rotations/pve/warlock/destruction.json +1177 -0
- package/src/data/rotations/pve/warrior/arms.json +3020 -0
- package/src/data/rotations/pve/warrior/fury.json +2996 -0
- package/src/data/rotations/pve/warrior/protection.json +2509 -0
- package/src/data/rotations/pvp/druid/balance.json +593 -0
- package/src/data/rotations/pvp/druid/feral.json +3276 -0
- package/src/data/rotations/pvp/druid/restoration.json +662 -0
- package/src/data/rotations/pvp/hunter/beast-mastery.json +665 -0
- package/src/data/rotations/pvp/hunter/marksmanship.json +2406 -0
- package/src/data/rotations/pvp/hunter/survival.json +635 -0
- package/src/data/rotations/pvp/mage/arcane.json +574 -0
- package/src/data/rotations/pvp/mage/fire.json +570 -0
- package/src/data/rotations/pvp/mage/frost.json +4048 -0
- package/src/data/rotations/pvp/paladin/holy.json +1498 -0
- package/src/data/rotations/pvp/paladin/protection.json +567 -0
- package/src/data/rotations/pvp/paladin/retribution.json +1110 -0
- package/src/data/rotations/pvp/priest/discipline.json +1848 -0
- package/src/data/rotations/pvp/priest/holy.json +6340 -0
- package/src/data/rotations/pvp/priest/shadow.json +2329 -0
- package/src/data/rotations/pvp/rogue/assassination.json +1322 -0
- package/src/data/rotations/pvp/rogue/combat.json +5258 -0
- package/src/data/rotations/pvp/rogue/subtlety.json +586 -0
- package/src/data/rotations/pvp/shaman/elemental.json +1404 -0
- package/src/data/rotations/pvp/shaman/enhancement.json +1277 -0
- package/src/data/rotations/pvp/shaman/restoration.json +2490 -0
- package/src/data/rotations/pvp/warlock/affliction.json +1017 -0
- package/src/data/rotations/pvp/warlock/demonology.json +1503 -0
- package/src/data/rotations/pvp/warlock/destruction.json +638 -0
- package/src/data/rotations/pvp/warrior/arms.json +5665 -0
- package/src/data/rotations/pvp/warrior/fury.json +850 -0
- package/src/data/rotations/pvp/warrior/protection.json +777 -0
- package/src/data/schema/bundle.ts +511 -0
- package/src/data/schema/constants.ts +49 -0
- package/src/data/schema/creature.ts +454 -0
- package/src/data/schema/encounter.ts +155 -0
- package/src/data/schema/faction.ts +48 -0
- package/src/data/schema/item.ts +297 -0
- package/src/data/schema/itemEnchant.ts +105 -0
- package/src/data/schema/npc.ts +44 -0
- package/src/data/schema/primitives.ts +295 -0
- package/src/data/schema/profession.ts +226 -0
- package/src/data/schema/quest.ts +154 -0
- package/src/data/schema/spell.ts +1149 -0
- package/src/data/schema/talent.ts +74 -0
- package/src/data/schema/world.ts +124 -0
- package/src/data/schema.ts +19 -0
- package/src/data/skinNodeGapFill.ts +68 -0
- package/src/data/spell-data.json.gz +0 -0
- package/src/data/spell-script-targets.json +1 -0
- package/src/data/tables/ahItemValues.json.gz +0 -0
- package/src/data/tables/alteracValley.json.gz +0 -0
- package/src/data/tables/areatriggerTeleports.json.gz +0 -0
- package/src/data/tables/botNames.json.gz +0 -0
- package/src/data/tables/botPopulation.json.gz +0 -0
- package/src/data/tables/botSpecs.json.gz +0 -0
- package/src/data/tables/classLevelStats.json.gz +0 -0
- package/src/data/tables/combatConditions.json.gz +0 -0
- package/src/data/tables/combatSkillMeta.json.gz +0 -0
- package/src/data/tables/conditions.json.gz +0 -0
- package/src/data/tables/createInfo.json.gz +0 -0
- package/src/data/tables/creatureAggroGroups.json.gz +0 -0
- package/src/data/tables/creatureClassLevelStats.json.gz +0 -0
- package/src/data/tables/creaturePatrolPacks.json.gz +0 -0
- package/src/data/tables/creatureWeapons.json.gz +0 -0
- package/src/data/tables/creatures.json.gz +0 -0
- package/src/data/tables/dbScripts.json.gz +0 -0
- package/src/data/tables/dungeonAuthoredRoutes.json.gz +0 -0
- package/src/data/tables/dungeonCensus.json.gz +0 -0
- package/src/data/tables/dungeonGameObjects.json.gz +0 -0
- package/src/data/tables/dungeonPacks.json.gz +0 -0
- package/src/data/tables/dungeonPlateCover.json.gz +0 -0
- package/src/data/tables/dungeonSpawns.json.gz +0 -0
- package/src/data/tables/dungeonSurvey.json.gz +0 -0
- package/src/data/tables/durabilityCosts.json.gz +0 -0
- package/src/data/tables/durabilityQuality.json.gz +0 -0
- package/src/data/tables/enchantApplicators.json.gz +0 -0
- package/src/data/tables/entranceYards.json.gz +0 -0
- package/src/data/tables/explorationBasexp.json.gz +0 -0
- package/src/data/tables/factionTemplates.json.gz +0 -0
- package/src/data/tables/factions.json.gz +0 -0
- package/src/data/tables/gameEvents.json.gz +0 -0
- package/src/data/tables/gameObjects.json.gz +0 -0
- package/src/data/tables/gossipMenus.json.gz +0 -0
- package/src/data/tables/graveyards.json.gz +0 -0
- package/src/data/tables/instanceMaps.json.gz +0 -0
- package/src/data/tables/itemEnchantmentTemplates.json.gz +0 -0
- package/src/data/tables/itemEnchants.json.gz +0 -0
- package/src/data/tables/itemRandomProperties.json.gz +0 -0
- package/src/data/tables/itemSets.json.gz +0 -0
- package/src/data/tables/items.json.gz +0 -0
- package/src/data/tables/levelStats.json.gz +0 -0
- package/src/data/tables/locks.json.gz +0 -0
- package/src/data/tables/map-pin-overrides.json.gz +0 -0
- package/src/data/tables/nonCombatTrash.json.gz +0 -0
- package/src/data/tables/npcTexts.json.gz +0 -0
- package/src/data/tables/pageTexts.json.gz +0 -0
- package/src/data/tables/petAbilityTP.json.gz +0 -0
- package/src/data/tables/petGenericSpells.json.gz +0 -0
- package/src/data/tables/petLevelStats.json.gz +0 -0
- package/src/data/tables/playerbot-weightscales.json.gz +0 -0
- package/src/data/tables/pools.json.gz +0 -0
- package/src/data/tables/quests.json.gz +0 -0
- package/src/data/tables/races.json.gz +0 -0
- package/src/data/tables/reputationRanks.json.gz +0 -0
- package/src/data/tables/reputationRates.json.gz +0 -0
- package/src/data/tables/reputationSpillover.json.gz +0 -0
- package/src/data/tables/ridingSpells.json.gz +0 -0
- package/src/data/tables/scriptTexts.json.gz +0 -0
- package/src/data/tables/soundClipVolume.json.gz +0 -0
- package/src/data/tables/spellChain.json.gz +0 -0
- package/src/data/tables/spellTeleports.json.gz +0 -0
- package/src/data/tables/spells.json.gz +0 -0
- package/src/data/tables/talents.json.gz +0 -0
- package/src/data/tables/tavernTriggers.json.gz +0 -0
- package/src/data/tables/taxiNodes.json.gz +0 -0
- package/src/data/tables/taxiRoutes.json.gz +0 -0
- package/src/data/tables/trainers.json.gz +0 -0
- package/src/data/tables/transports.json.gz +0 -0
- package/src/data/tables/travelAnchors.json.gz +0 -0
- package/src/data/tables/travelEdges.json.gz +0 -0
- package/src/data/tables/unitConditions.json.gz +0 -0
- package/src/data/tables/vendors.json.gz +0 -0
- package/src/data/tables/weaponEnchantProcs.json.gz +0 -0
- package/src/data/tables/xp.json.gz +0 -0
- package/src/data/tables/zoneWeather.json.gz +0 -0
- package/src/data/tables/zones.json.gz +0 -0
- package/src/data/world-data.ts +56 -0
- package/src/data/zone-music-sets.json +2751 -0
- package/src/dev/etl/lib/loot-chance-sanity.mjs +180 -0
- package/src/domain/audioQuality.mjs +145 -0
- package/src/domain/bg/ab.ts +433 -0
- package/src/domain/bg/arenaComps.ts +644 -0
- package/src/domain/bg/arenaLab.ts +695 -0
- package/src/domain/bg/arenaPace.ts +31 -0
- package/src/domain/bg/arenaRating.ts +110 -0
- package/src/domain/bg/av.ts +758 -0
- package/src/domain/bg/avEnemyPush.ts +219 -0
- package/src/domain/bg/avField.ts +283 -0
- package/src/domain/bg/avInstanceNodes.ts +197 -0
- package/src/domain/bg/avInstanceScore.ts +123 -0
- package/src/domain/bg/avQuestItems.ts +55 -0
- package/src/domain/bg/avQuests.ts +210 -0
- package/src/domain/bg/avSession.ts +196 -0
- package/src/domain/bg/bgConsumes.ts +269 -0
- package/src/domain/bg/bgPrebuff.ts +153 -0
- package/src/domain/bg/bgRest.ts +91 -0
- package/src/domain/bg/bgRoster.ts +305 -0
- package/src/domain/bg/bgTier.ts +61 -0
- package/src/domain/bg/bgWorld.ts +862 -0
- package/src/domain/bg/match.ts +215 -0
- package/src/domain/bg/strategies.ts +552 -0
- package/src/domain/bg/types.ts +291 -0
- package/src/domain/bg/wsg.ts +264 -0
- package/src/domain/bots/actionProviderSpec.ts +38 -0
- package/src/domain/bots/autoConsumables.ts +285 -0
- package/src/domain/bots/betweenPullRest.ts +239 -0
- package/src/domain/bots/botGear.ts +248 -0
- package/src/domain/bots/botInventory.ts +486 -0
- package/src/domain/bots/botPet.ts +535 -0
- package/src/domain/bots/botPool.ts +291 -0
- package/src/domain/bots/botProfessions.ts +334 -0
- package/src/domain/bots/companionActionBar.ts +209 -0
- package/src/domain/bots/companionHireCost.ts +236 -0
- package/src/domain/bots/companionMissions.ts +582 -0
- package/src/domain/bots/companionPrep.ts +254 -0
- package/src/domain/bots/companionQuests.ts +270 -0
- package/src/domain/bots/companionRep.ts +75 -0
- package/src/domain/bots/companionRotation.ts +708 -0
- package/src/domain/bots/companionXp.ts +66 -0
- package/src/domain/bots/consumableShare.ts +811 -0
- package/src/domain/bots/dpsMate.ts +1072 -0
- package/src/domain/bots/groupCompReport.ts +132 -0
- package/src/domain/bots/healthstoneSupply.ts +198 -0
- package/src/domain/bots/outfitter.ts +1056 -0
- package/src/domain/bots/paladinPrePull.ts +231 -0
- package/src/domain/bots/partyRole.ts +40 -0
- package/src/domain/bots/playerbotEquipCache.ts +412 -0
- package/src/domain/bots/playerbotItemEligibility.ts +60 -0
- package/src/domain/bots/playerbotStatWeight.ts +853 -0
- package/src/domain/bots/playerbotUnavailableItems.ts +147 -0
- package/src/domain/bots/prePullBuffs.ts +219 -0
- package/src/domain/bots/prePullStealth.ts +82 -0
- package/src/domain/bots/runSupply.ts +251 -0
- package/src/domain/bots/runSupplyPrefs.ts +28 -0
- package/src/domain/bots/sendTradeGoods.ts +394 -0
- package/src/domain/bots/soulShardCap.ts +35 -0
- package/src/domain/bots/squad.ts +842 -0
- package/src/domain/bots/squadRun.ts +81 -0
- package/src/domain/character/actionbar.ts +346 -0
- package/src/domain/character/appearance.ts +165 -0
- package/src/domain/character/buildCombatant.ts +1726 -0
- package/src/domain/character/character.ts +356 -0
- package/src/domain/character/characterName.ts +43 -0
- package/src/domain/character/characterRatings.ts +424 -0
- package/src/domain/character/characterSpells.ts +282 -0
- package/src/domain/character/characterStats.ts +1559 -0
- package/src/domain/character/characterTalents.ts +398 -0
- package/src/domain/character/combatSkills.ts +994 -0
- package/src/domain/character/displayForm.ts +72 -0
- package/src/domain/character/formTalentAuras.ts +171 -0
- package/src/domain/character/forms.ts +499 -0
- package/src/domain/character/races.ts +40 -0
- package/src/domain/character/resurrectionSickness.ts +49 -0
- package/src/domain/character/spellbook.ts +169 -0
- package/src/domain/character/stanceBar.ts +354 -0
- package/src/domain/character/stancePrereq.ts +70 -0
- package/src/domain/character/talentBuilds.ts +63 -0
- package/src/domain/character/talentImport.ts +130 -0
- package/src/domain/character/talentLink.ts +123 -0
- package/src/domain/character/talents.ts +201 -0
- package/src/domain/core/conditionSnapshot.ts +92 -0
- package/src/domain/core/hover.ts +14 -0
- package/src/domain/core/ids.ts +58 -0
- package/src/domain/core/internalNameTags.ts +5 -0
- package/src/domain/core/runtimeDataPatches.ts +357 -0
- package/src/domain/creature/aiTargeting.ts +588 -0
- package/src/domain/creature/combatCondition.ts +157 -0
- package/src/domain/creature/creatureAggro.ts +804 -0
- package/src/domain/creature/creatureAi.ts +206 -0
- package/src/domain/creature/creatureAiConstants.ts +661 -0
- package/src/domain/creature/creatureCastList.ts +1007 -0
- package/src/domain/creature/creatureEventAi.ts +1409 -0
- package/src/domain/creature/creatureGroups.ts +144 -0
- package/src/domain/creature/creatureLinking.ts +333 -0
- package/src/domain/creature/creatureScaling.ts +320 -0
- package/src/domain/creature/creatureTrigger.ts +53 -0
- package/src/domain/creature/creatureWander.ts +74 -0
- package/src/domain/creature/eventAiTexts.ts +145 -0
- package/src/domain/creature/faction.ts +287 -0
- package/src/domain/creature/forcedReaction.ts +48 -0
- package/src/domain/creature/outdoorHuntable.ts +74 -0
- package/src/domain/creature/patrolPacks.ts +210 -0
- package/src/domain/creature/reputation.ts +967 -0
- package/src/domain/creature/unitFlags.ts +74 -0
- package/src/domain/creature/worldBosses.ts +393 -0
- package/src/domain/dungeonMusic.mjs +48 -0
- package/src/domain/economy/auction.ts +282 -0
- package/src/domain/economy/auctionBot.ts +1060 -0
- package/src/domain/economy/honorPrice.ts +100 -0
- package/src/domain/economy/mail.ts +958 -0
- package/src/domain/economy/money.ts +42 -0
- package/src/domain/economy/syntheticClassTrainers.ts +62 -0
- package/src/domain/economy/trading.ts +296 -0
- package/src/domain/economy/vendorSellAll.ts +252 -0
- package/src/domain/economy/vendorStock.ts +146 -0
- package/src/domain/items/ammo.ts +817 -0
- package/src/domain/items/bank.ts +162 -0
- package/src/domain/items/consumableFamily.ts +205 -0
- package/src/domain/items/craftUpgrades.ts +83 -0
- package/src/domain/items/createItem.ts +93 -0
- package/src/domain/items/durability.ts +581 -0
- package/src/domain/items/equipment.ts +441 -0
- package/src/domain/items/equipmentHelper.ts +767 -0
- package/src/domain/items/inventory.ts +561 -0
- package/src/domain/items/itemBonding.ts +93 -0
- package/src/domain/items/itemContainer.ts +245 -0
- package/src/domain/items/itemDisplayName.ts +33 -0
- package/src/domain/items/itemInstance.ts +181 -0
- package/src/domain/items/itemRandomEnchantOutcomes.ts +315 -0
- package/src/domain/items/itemRandomProps.ts +77 -0
- package/src/domain/items/itemRestrictions.ts +329 -0
- package/src/domain/items/itemTeaches.ts +82 -0
- package/src/domain/items/itemUseCooldown.ts +52 -0
- package/src/domain/items/itemUseTarget.ts +90 -0
- package/src/domain/items/junkCategories.ts +174 -0
- package/src/domain/items/junkFilter.ts +368 -0
- package/src/domain/items/keyring.ts +131 -0
- package/src/domain/items/loot.ts +221 -0
- package/src/domain/items/mounts.ts +190 -0
- package/src/domain/items/onUsePayload.ts +102 -0
- package/src/domain/items/pendingLoot.ts +107 -0
- package/src/domain/items/relic.ts +105 -0
- package/src/domain/items/rocketBoots.ts +36 -0
- package/src/domain/items/sortContainer.ts +139 -0
- package/src/domain/items/spareContainers.ts +71 -0
- package/src/domain/items/spellReagentIndex.ts +131 -0
- package/src/domain/items/staleConsumables.ts +66 -0
- package/src/domain/items/weaponSpender.ts +110 -0
- package/src/domain/listDungeonIds.mjs +28 -0
- package/src/domain/macro/compose.ts +239 -0
- package/src/domain/macro/conditions.ts +260 -0
- package/src/domain/macro/creatureTypes.ts +40 -0
- package/src/domain/macro/describe.ts +169 -0
- package/src/domain/macro/icon.ts +60 -0
- package/src/domain/macro/index.ts +20 -0
- package/src/domain/macro/normalize.ts +112 -0
- package/src/domain/macro/parse.ts +316 -0
- package/src/domain/macro/plan.ts +288 -0
- package/src/domain/macro/rotation.ts +590 -0
- package/src/domain/macro/rotationEnv.ts +173 -0
- package/src/domain/macro/slots.ts +101 -0
- package/src/domain/macro/snapshot.ts +114 -0
- package/src/domain/macro/templates.ts +446 -0
- package/src/domain/macro/types.ts +70 -0
- package/src/domain/macro/units.ts +93 -0
- package/src/domain/macro/weaponTypes.ts +83 -0
- package/src/domain/nav/cityTeleports.ts +10 -0
- package/src/domain/nav/mounting.ts +113 -0
- package/src/domain/nav/nav.ts +43 -0
- package/src/domain/nav/navCollision.ts +606 -0
- package/src/domain/nav/navOutdoors.ts +91 -0
- package/src/domain/nav/navRegistry.ts +179 -0
- package/src/domain/nav/navRoomGraph.ts +302 -0
- package/src/domain/nav/navTypes.ts +43 -0
- package/src/domain/nav/navmesh.ts +1715 -0
- package/src/domain/nav/speed.ts +200 -0
- package/src/domain/nav/storey.ts +10 -0
- package/src/domain/nav/travel.ts +725 -0
- package/src/domain/nav/travelTypes.ts +75 -0
- package/src/domain/pet/pet.ts +1150 -0
- package/src/domain/pet/petActionBar.ts +180 -0
- package/src/domain/pet/petSpellbook.ts +39 -0
- package/src/domain/pet/petStable.ts +81 -0
- package/src/domain/professions/autoDisenchant.ts +236 -0
- package/src/domain/professions/characterEnchants.ts +176 -0
- package/src/domain/professions/craftCooldown.ts +52 -0
- package/src/domain/professions/enchantApply.ts +62 -0
- package/src/domain/professions/enchantTargets.ts +114 -0
- package/src/domain/professions/enchanting.ts +236 -0
- package/src/domain/professions/gatherFilter.ts +149 -0
- package/src/domain/professions/gearEnchant.ts +137 -0
- package/src/domain/professions/heldTools.ts +29 -0
- package/src/domain/professions/learnAllRecipes.ts +146 -0
- package/src/domain/professions/miningHarvest.ts +69 -0
- package/src/domain/professions/partyDisenchant.ts +294 -0
- package/src/domain/professions/partyWork.ts +83 -0
- package/src/domain/professions/pickpocket.ts +172 -0
- package/src/domain/professions/poisons.ts +240 -0
- package/src/domain/professions/professions.ts +677 -0
- package/src/domain/professions/rareNodes.ts +45 -0
- package/src/domain/professions/skinLoot.ts +27 -0
- package/src/domain/professions/vendorReagents.ts +152 -0
- package/src/domain/professions/weaponImbueHand.ts +171 -0
- package/src/domain/questFarmFilter.mjs +42 -0
- package/src/domain/quests/doQuest.ts +1204 -0
- package/src/domain/quests/gossip.ts +308 -0
- package/src/domain/quests/gossipTalk.ts +298 -0
- package/src/domain/quests/interpreter/canComplete.ts +150 -0
- package/src/domain/quests/interpreter/claim.ts +137 -0
- package/src/domain/quests/interpreter/dbscript/handlers.ts +1371 -0
- package/src/domain/quests/interpreter/dbscript/handlersBatch.ts +1251 -0
- package/src/domain/quests/interpreter/dbscript/hooks.ts +331 -0
- package/src/domain/quests/interpreter/dbscript/index.ts +212 -0
- package/src/domain/quests/interpreter/dbscript/opcodes.ts +257 -0
- package/src/domain/quests/interpreter/dbscript/registry.ts +1194 -0
- package/src/domain/quests/interpreter/dbscript/registryAreatriggerScripts.ts +539 -0
- package/src/domain/quests/interpreter/dbscript/registryAshenvale.ts +553 -0
- package/src/domain/quests/interpreter/dbscript/registryAzshara.ts +272 -0
- package/src/domain/quests/interpreter/dbscript/registryDarkshore.ts +637 -0
- package/src/domain/quests/interpreter/dbscript/registryDesolace.ts +501 -0
- package/src/domain/quests/interpreter/dbscript/registryDustwallowMarsh.ts +735 -0
- package/src/domain/quests/interpreter/dbscript/registryFelwood.ts +493 -0
- package/src/domain/quests/interpreter/dbscript/registryFeralas.ts +485 -0
- package/src/domain/quests/interpreter/dbscript/registryGoScripts.ts +203 -0
- package/src/domain/quests/interpreter/dbscript/registryMoonglade.ts +288 -0
- package/src/domain/quests/interpreter/dbscript/registryNpcProfessions.ts +230 -0
- package/src/domain/quests/interpreter/dbscript/registryNpcsSpecial.ts +545 -0
- package/src/domain/quests/interpreter/dbscript/registryRazorfenDowns.ts +293 -0
- package/src/domain/quests/interpreter/dbscript/registryRazorfenKraul.ts +221 -0
- package/src/domain/quests/interpreter/dbscript/registryRedridge.ts +112 -0
- package/src/domain/quests/interpreter/dbscript/registrySearingGorge.ts +125 -0
- package/src/domain/quests/interpreter/dbscript/registrySiblings.ts +51 -0
- package/src/domain/quests/interpreter/dbscript/registrySilverpineForest.ts +172 -0
- package/src/domain/quests/interpreter/dbscript/registryStonetalonMountains.ts +180 -0
- package/src/domain/quests/interpreter/dbscript/registryStormwindCity.ts +369 -0
- package/src/domain/quests/interpreter/dbscript/registryStranglethornVale.ts +115 -0
- package/src/domain/quests/interpreter/dbscript/registryStratholme.ts +146 -0
- package/src/domain/quests/interpreter/dbscript/registrySunkenTemple.ts +149 -0
- package/src/domain/quests/interpreter/dbscript/registrySwampOfSorrows.ts +163 -0
- package/src/domain/quests/interpreter/dbscript/registryTanaris.ts +318 -0
- package/src/domain/quests/interpreter/dbscript/registryTeldrassil.ts +120 -0
- package/src/domain/quests/interpreter/dbscript/registryTheBarrens.ts +603 -0
- package/src/domain/quests/interpreter/dbscript/registryThousandNeedles.ts +499 -0
- package/src/domain/quests/interpreter/dbscript/registryTirisfalGlades.ts +222 -0
- package/src/domain/quests/interpreter/dbscript/registryUngoroCrater.ts +291 -0
- package/src/domain/quests/interpreter/dbscript/registryWarEffort.ts +773 -0
- package/src/domain/quests/interpreter/dbscript/registryWesternPlaguelands.ts +206 -0
- package/src/domain/quests/interpreter/dbscript/registryWestfall.ts +190 -0
- package/src/domain/quests/interpreter/dbscript/registryWetlands.ts +186 -0
- package/src/domain/quests/interpreter/dbscript/registryWinterspring.ts +235 -0
- package/src/domain/quests/interpreter/dbscript/types.ts +817 -0
- package/src/domain/quests/interpreter/dbscript/vm.ts +171 -0
- package/src/domain/quests/interpreter/diff.ts +349 -0
- package/src/domain/quests/interpreter/fixtures/oracle-questlog-qi2.json +2417 -0
- package/src/domain/quests/interpreter/flags.ts +101 -0
- package/src/domain/quests/interpreter/index.ts +113 -0
- package/src/domain/quests/interpreter/live.ts +178 -0
- package/src/domain/quests/interpreter/types.ts +81 -0
- package/src/domain/quests/interpreter/updaters.ts +412 -0
- package/src/domain/quests/questCorpseUse.ts +155 -0
- package/src/domain/quests/questDialog.ts +43 -0
- package/src/domain/quests/questFight.ts +34 -0
- package/src/domain/quests/questObjectiveLabel.ts +39 -0
- package/src/domain/quests/questRewardChoice.ts +85 -0
- package/src/domain/quests/questScriptCredit.ts +26 -0
- package/src/domain/quests/questScriptItemUse.ts +74 -0
- package/src/domain/quests/questTameRods.ts +79 -0
- package/src/domain/quests/questlog.ts +1545 -0
- package/src/domain/quests/talkDeliver.ts +170 -0
- package/src/domain/raid/globalActionBar.ts +195 -0
- package/src/domain/raid/instanceRoster.ts +194 -0
- package/src/domain/raid/partySummon.ts +198 -0
- package/src/domain/raid/partyVault.ts +320 -0
- package/src/domain/raid/raidBossAbilities.ts +268 -0
- package/src/domain/raid/raidComp.ts +335 -0
- package/src/domain/raid/raidCustomRows.ts +453 -0
- package/src/domain/raid/raidEncounterGear.ts +298 -0
- package/src/domain/raid/raidEncounterTactics.ts +412 -0
- package/src/domain/raid/raidPlan.ts +509 -0
- package/src/domain/raid/raidPlanEdits.ts +98 -0
- package/src/domain/raid/raidPlanPacks.ts +480 -0
- package/src/domain/raid/raidRecovery.ts +183 -0
- package/src/domain/raid/raidRosterTemplate.ts +138 -0
- package/src/domain/rotation/auraSide.ts +68 -0
- package/src/domain/rotation/build.ts +3031 -0
- package/src/domain/rotation/classes/druid.ts +58 -0
- package/src/domain/rotation/classes/hooks.ts +59 -0
- package/src/domain/rotation/classes/hunter.ts +156 -0
- package/src/domain/rotation/classes/index.ts +104 -0
- package/src/domain/rotation/classes/mage.ts +99 -0
- package/src/domain/rotation/classes/paladin.ts +296 -0
- package/src/domain/rotation/classes/priest.ts +94 -0
- package/src/domain/rotation/classes/pullOpener.ts +92 -0
- package/src/domain/rotation/classes/rogue.ts +69 -0
- package/src/domain/rotation/classes/shaman.ts +29 -0
- package/src/domain/rotation/classes/supportSpec.ts +94 -0
- package/src/domain/rotation/classes/warlock.ts +81 -0
- package/src/domain/rotation/classes/warrior.ts +402 -0
- package/src/domain/rotation/classify.ts +1381 -0
- package/src/domain/rotation/condMetricPicker.ts +379 -0
- package/src/domain/rotation/consumables.ts +242 -0
- package/src/domain/rotation/evaluate.ts +1115 -0
- package/src/domain/rotation/finisherLadder.ts +145 -0
- package/src/domain/rotation/invent.ts +938 -0
- package/src/domain/rotation/killOrder.ts +30 -0
- package/src/domain/rotation/openerForm.ts +22 -0
- package/src/domain/rotation/paladinBuffs.ts +568 -0
- package/src/domain/rotation/prebuff.ts +283 -0
- package/src/domain/rotation/raidRowTuning.ts +34 -0
- package/src/domain/rotation/rotation.ts +11 -0
- package/src/domain/rotation/rotationProfiles.ts +180 -0
- package/src/domain/rotation/rotationSuggestions.ts +851 -0
- package/src/domain/rotation/ruleAfterChain.ts +49 -0
- package/src/domain/rotation/spec.ts +70 -0
- package/src/domain/rotation/spellPicker.ts +140 -0
- package/src/domain/rotation/stand.ts +143 -0
- package/src/domain/rotation/types.ts +718 -0
- package/src/domain/run/battlegroundRun.ts +111 -0
- package/src/domain/run/canEnterContext.ts +32 -0
- package/src/domain/run/death.ts +561 -0
- package/src/domain/run/deathRecovery.ts +289 -0
- package/src/domain/run/duel.ts +141 -0
- package/src/domain/run/dungeonRun.ts +344 -0
- package/src/domain/run/floorRest.ts +159 -0
- package/src/domain/run/honor.ts +557 -0
- package/src/domain/run/honorWeek.ts +180 -0
- package/src/domain/run/whenMap.ts +977 -0
- package/src/domain/scriptdev/arathiHighlands.ts +445 -0
- package/src/domain/scriptdev/ashenvale.ts +635 -0
- package/src/domain/scriptdev/ashenvaleHelpers.ts +1215 -0
- package/src/domain/scriptdev/azshara.ts +892 -0
- package/src/domain/scriptdev/blackfathomDeeps.ts +212 -0
- package/src/domain/scriptdev/blackrockDepths.ts +1434 -0
- package/src/domain/scriptdev/blackrockSpire.ts +730 -0
- package/src/domain/scriptdev/blackwingLair.ts +521 -0
- package/src/domain/scriptdev/burningSteppes.ts +62 -0
- package/src/domain/scriptdev/classFilter.ts +43 -0
- package/src/domain/scriptdev/darkshore.ts +817 -0
- package/src/domain/scriptdev/darkshoreHelpers.ts +853 -0
- package/src/domain/scriptdev/deadmines.ts +331 -0
- package/src/domain/scriptdev/desolace.ts +1239 -0
- package/src/domain/scriptdev/direMaul.ts +859 -0
- package/src/domain/scriptdev/durotar.ts +532 -0
- package/src/domain/scriptdev/dustwallowMarsh.ts +758 -0
- package/src/domain/scriptdev/dustwallowMarshHelpers.ts +994 -0
- package/src/domain/scriptdev/easternPlaguelands.ts +21 -0
- package/src/domain/scriptdev/felwood.ts +1055 -0
- package/src/domain/scriptdev/feralas.ts +571 -0
- package/src/domain/scriptdev/feralasHelpers.ts +838 -0
- package/src/domain/scriptdev/gnomeregan.ts +466 -0
- package/src/domain/scriptdev/guards.ts +616 -0
- package/src/domain/scriptdev/hinterlands.ts +693 -0
- package/src/domain/scriptdev/host.ts +1134 -0
- package/src/domain/scriptdev/index.ts +123 -0
- package/src/domain/scriptdev/instanceRaids.ts +136 -0
- package/src/domain/scriptdev/itemScripts.ts +37 -0
- package/src/domain/scriptdev/lochModan.ts +283 -0
- package/src/domain/scriptdev/maraudon.ts +232 -0
- package/src/domain/scriptdev/moltenCore.ts +831 -0
- package/src/domain/scriptdev/moonglade.ts +659 -0
- package/src/domain/scriptdev/moongladeHelpers.ts +933 -0
- package/src/domain/scriptdev/naxxramas.ts +577 -0
- package/src/domain/scriptdev/naxxramasConstruct.ts +364 -0
- package/src/domain/scriptdev/naxxramasFrostwyrm.ts +378 -0
- package/src/domain/scriptdev/naxxramasMilitary.ts +276 -0
- package/src/domain/scriptdev/npcProfessions.ts +1025 -0
- package/src/domain/scriptdev/npcsSpecial.ts +1379 -0
- package/src/domain/scriptdev/onyxiasLair.ts +313 -0
- package/src/domain/scriptdev/orgrimmar.ts +147 -0
- package/src/domain/scriptdev/professionSpecConsumer.ts +481 -0
- package/src/domain/scriptdev/razorfenDowns.ts +468 -0
- package/src/domain/scriptdev/razorfenKraul.ts +410 -0
- package/src/domain/scriptdev/redridgeMountains.ts +163 -0
- package/src/domain/scriptdev/registry.ts +945 -0
- package/src/domain/scriptdev/ruinsOfAhnqiraj.ts +356 -0
- package/src/domain/scriptdev/scarletMonastery.ts +713 -0
- package/src/domain/scriptdev/scholomance.ts +682 -0
- package/src/domain/scriptdev/searingGorge.ts +295 -0
- package/src/domain/scriptdev/shadowfangKeep.ts +181 -0
- package/src/domain/scriptdev/silithus.ts +71 -0
- package/src/domain/scriptdev/silverpineForest.ts +662 -0
- package/src/domain/scriptdev/stonetalonMountains.ts +493 -0
- package/src/domain/scriptdev/stormwindCity.ts +1192 -0
- package/src/domain/scriptdev/stranglethornVale.ts +402 -0
- package/src/domain/scriptdev/stratholme.ts +650 -0
- package/src/domain/scriptdev/stratholmeScripts.ts +505 -0
- package/src/domain/scriptdev/sunkenTemple.ts +736 -0
- package/src/domain/scriptdev/swampOfSorrows.ts +22 -0
- package/src/domain/scriptdev/tanaris.ts +391 -0
- package/src/domain/scriptdev/tanarisHelpers.ts +661 -0
- package/src/domain/scriptdev/teldrassil.ts +143 -0
- package/src/domain/scriptdev/templeOfAhnqiraj.ts +1013 -0
- package/src/domain/scriptdev/theBarrens.ts +709 -0
- package/src/domain/scriptdev/theBarrensHelpers.ts +1026 -0
- package/src/domain/scriptdev/thousandNeedles.ts +426 -0
- package/src/domain/scriptdev/thousandNeedlesHelpers.ts +566 -0
- package/src/domain/scriptdev/tirisfalGlades.ts +533 -0
- package/src/domain/scriptdev/types.ts +1026 -0
- package/src/domain/scriptdev/uldaman.ts +437 -0
- package/src/domain/scriptdev/ungoroCrater.ts +549 -0
- package/src/domain/scriptdev/ungoroCraterHelpers.ts +1006 -0
- package/src/domain/scriptdev/wailingCaverns.ts +700 -0
- package/src/domain/scriptdev/warEffort.ts +371 -0
- package/src/domain/scriptdev/westernPlaguelands.ts +952 -0
- package/src/domain/scriptdev/westfall.ts +767 -0
- package/src/domain/scriptdev/wetlands.ts +660 -0
- package/src/domain/scriptdev/winterspring.ts +482 -0
- package/src/domain/scriptdev/winterspringHelpers.ts +801 -0
- package/src/domain/scriptdev/worldBosses.ts +605 -0
- package/src/domain/scriptdev/zulfarrak.ts +478 -0
- package/src/domain/scriptdev/zulgurub.ts +1152 -0
- package/src/domain/sim/dummySim.ts +408 -0
- package/src/domain/sim/targetDummy.ts +29 -0
- package/src/domain/social/guild.ts +148 -0
- package/src/domain/spells/areaAuraMembership.ts +93 -0
- package/src/domain/spells/breakableStunCc.ts +166 -0
- package/src/domain/spells/cancelAura.ts +80 -0
- package/src/domain/spells/diminishing.ts +167 -0
- package/src/domain/spells/dispel.ts +149 -0
- package/src/domain/spells/dispelMechanic.ts +105 -0
- package/src/domain/spells/equipSpells.ts +247 -0
- package/src/domain/spells/learnOnTargets.ts +260 -0
- package/src/domain/spells/mergeSchoolImmunityFromRaw.ts +66 -0
- package/src/domain/spells/resurrectSpell.ts +124 -0
- package/src/domain/spells/spellPushback.ts +39 -0
- package/src/domain/spells/spellReflect.ts +43 -0
- package/src/domain/spells/stealth.ts +183 -0
- package/src/domain/spells/totem.ts +394 -0
- package/src/domain/spells/worldCast.ts +187 -0
- package/src/domain/world/areaTriggerLocks.ts +177 -0
- package/src/domain/world/dungeonMusic.ts +108 -0
- package/src/domain/world/gameEvents.ts +119 -0
- package/src/domain/world/gameObjects.ts +1030 -0
- package/src/domain/world/goEventFire.ts +234 -0
- package/src/domain/world/goMoTransport.ts +74 -0
- package/src/domain/world/goTransport.ts +81 -0
- package/src/domain/world/hazards.ts +459 -0
- package/src/domain/world/openLock.ts +398 -0
- package/src/domain/world/pools.ts +470 -0
- package/src/domain/world/restArea.ts +37 -0
- package/src/domain/world/weather.ts +201 -0
- package/src/domain/zoneAmbience.mjs +87 -0
- package/src/domain/zoneMusic.mjs +62 -0
- package/src/domain/zoneMusicQueue.mjs +7 -0
- package/src/engine/combat/activeSeal.ts +39 -0
- package/src/engine/combat/aiTargetResolve.ts +64 -0
- package/src/engine/combat/aoeCast.ts +289 -0
- package/src/engine/combat/applyConsumeAura.ts +74 -0
- package/src/engine/combat/applyDemonicSacrifice.ts +70 -0
- package/src/engine/combat/applyDispelMechanic.ts +30 -0
- package/src/engine/combat/applyHealEffect.ts +301 -0
- package/src/engine/combat/applySchoolAbsorb.ts +114 -0
- package/src/engine/combat/applySpell.ts +3169 -0
- package/src/engine/combat/applySpellLate.ts +2026 -0
- package/src/engine/combat/applySpellLateBag.ts +144 -0
- package/src/engine/combat/areaAura.ts +283 -0
- package/src/engine/combat/attackTable.ts +508 -0
- package/src/engine/combat/battleRez.ts +87 -0
- package/src/engine/combat/castAction.ts +36 -0
- package/src/engine/combat/castTargetGates.ts +75 -0
- package/src/engine/combat/castableKit.ts +68 -0
- package/src/engine/combat/ccMirrorUpgrade.ts +39 -0
- package/src/engine/combat/chainFan.ts +210 -0
- package/src/engine/combat/channel.ts +423 -0
- package/src/engine/combat/chaseCloseness.ts +95 -0
- package/src/engine/combat/cmangosCastGates.ts +129 -0
- package/src/engine/combat/combatFlag.ts +77 -0
- package/src/engine/combat/combatGeometry.ts +1329 -0
- package/src/engine/combat/combustion.ts +68 -0
- package/src/engine/combat/context.ts +730 -0
- package/src/engine/combat/createSession.ts +1165 -0
- package/src/engine/combat/creatureLeash.ts +38 -0
- package/src/engine/combat/crowdControl.ts +1259 -0
- package/src/engine/combat/dispelDuty.ts +54 -0
- package/src/engine/combat/dispelReflect.ts +609 -0
- package/src/engine/combat/displaceFloor.ts +113 -0
- package/src/engine/combat/duelBounds.ts +47 -0
- package/src/engine/combat/equippedItemGate.ts +297 -0
- package/src/engine/combat/evadeLifecycle.ts +63 -0
- package/src/engine/combat/eventAi.ts +2220 -0
- package/src/engine/combat/facing.ts +74 -0
- package/src/engine/combat/fearFlee.ts +137 -0
- package/src/engine/combat/fighterFactory.ts +1144 -0
- package/src/engine/combat/forceDespawnAction.ts +51 -0
- package/src/engine/combat/formulas.ts +807 -0
- package/src/engine/combat/friendlyFan.ts +55 -0
- package/src/engine/combat/groundPoolTick.ts +210 -0
- package/src/engine/combat/hazards.ts +114 -0
- package/src/engine/combat/healTargets.ts +226 -0
- package/src/engine/combat/heartbeatResist.ts +49 -0
- package/src/engine/combat/hostileItemUse.ts +75 -0
- package/src/engine/combat/interruptAuras.ts +66 -0
- package/src/engine/combat/itemRescue.ts +49 -0
- package/src/engine/combat/itemUse.ts +260 -0
- package/src/engine/combat/kitById.ts +23 -0
- package/src/engine/combat/knockBack.ts +201 -0
- package/src/engine/combat/leap.ts +89 -0
- package/src/engine/combat/mitigation.ts +587 -0
- package/src/engine/combat/modTaunt.ts +43 -0
- package/src/engine/combat/motionClaim.ts +268 -0
- package/src/engine/combat/motionGate.ts +150 -0
- package/src/engine/combat/motionGenerator.ts +27 -0
- package/src/engine/combat/navSight.ts +138 -0
- package/src/engine/combat/perfSink.ts +28 -0
- package/src/engine/combat/periodicDue.ts +55 -0
- package/src/engine/combat/petAuras.ts +86 -0
- package/src/engine/combat/pickAction.ts +3449 -0
- package/src/engine/combat/powerForm.ts +735 -0
- package/src/engine/combat/powerPools.ts +49 -0
- package/src/engine/combat/predicates.ts +538 -0
- package/src/engine/combat/procsDamage.ts +1823 -0
- package/src/engine/combat/reactiveGates.ts +135 -0
- package/src/engine/combat/roles.ts +389 -0
- package/src/engine/combat/rotationSubject.ts +78 -0
- package/src/engine/combat/ruleAfter.ts +106 -0
- package/src/engine/combat/seedDisplayAuras.ts +203 -0
- package/src/engine/combat/seedOocEffects.ts +317 -0
- package/src/engine/combat/seedSchoolImmunities.ts +45 -0
- package/src/engine/combat/session-types.ts +458 -0
- package/src/engine/combat/sessionApi.ts +1785 -0
- package/src/engine/combat/sessionCharge.ts +124 -0
- package/src/engine/combat/sessionChase.ts +869 -0
- package/src/engine/combat/sessionDeaths.ts +469 -0
- package/src/engine/combat/sessionFocus.ts +506 -0
- package/src/engine/combat/sessionFollow.ts +167 -0
- package/src/engine/combat/sessionGeometry.ts +188 -0
- package/src/engine/combat/sessionHome.ts +75 -0
- package/src/engine/combat/sessionInject.ts +719 -0
- package/src/engine/combat/sessionMechanicMove.ts +555 -0
- package/src/engine/combat/sessionMotion.ts +1553 -0
- package/src/engine/combat/sessionMotionKit.ts +247 -0
- package/src/engine/combat/sessionMoveIntent.ts +49 -0
- package/src/engine/combat/sessionMoveOrder.ts +200 -0
- package/src/engine/combat/sessionPathTo.ts +657 -0
- package/src/engine/combat/sessionPools.ts +688 -0
- package/src/engine/combat/sessionSteer.ts +144 -0
- package/src/engine/combat/sessionStepBack.ts +151 -0
- package/src/engine/combat/sessionStepWrap.ts +116 -0
- package/src/engine/combat/sessionView.ts +442 -0
- package/src/engine/combat/simEffects.ts +32 -0
- package/src/engine/combat/simFacade.ts +620 -0
- package/src/engine/combat/simPump.ts +264 -0
- package/src/engine/combat/simSnapshot.ts +165 -0
- package/src/engine/combat/simThreadPool.ts +35 -0
- package/src/engine/combat/skeramImages.ts +57 -0
- package/src/engine/combat/slowUpdate.ts +178 -0
- package/src/engine/combat/snapToFloor.ts +42 -0
- package/src/engine/combat/speedAura.ts +29 -0
- package/src/engine/combat/spellReagents.ts +71 -0
- package/src/engine/combat/stealthDetect.ts +93 -0
- package/src/engine/combat/stepBackMotion.ts +171 -0
- package/src/engine/combat/stepSession.ts +3102 -0
- package/src/engine/combat/summonCast.ts +243 -0
- package/src/engine/combat/thaddiusPolarity.ts +97 -0
- package/src/engine/combat/threat.ts +388 -0
- package/src/engine/combat/threatOps.ts +272 -0
- package/src/engine/combat/tickWorkerBody.ts +211 -0
- package/src/engine/combat/totemAuras.ts +122 -0
- package/src/engine/combat/triggerPayload.ts +42 -0
- package/src/engine/combat/triggeredCompanion.ts +63 -0
- package/src/engine/combat/types.ts +1897 -0
- package/src/engine/combat/useObjectTrap.ts +140 -0
- package/src/engine/combat/walkSurface.ts +54 -0
- package/src/engine/combat/weapon.ts +1248 -0
- package/src/engine/combat/worldFrame.ts +43 -0
- package/src/engine/combat.solo-golden.json +2242 -0
- package/src/engine/combat.ts +84 -0
- package/src/engine/core/detMath.ts +289 -0
- package/src/engine/core/events.ts +357 -0
- package/src/engine/core/rng.ts +36 -0
- package/src/engine/game/actionBars.ts +124 -0
- package/src/engine/game/activateObjectFromSpell.ts +320 -0
- package/src/engine/game/applyActiveObjectFromDbScript.ts +98 -0
- package/src/engine/game/applyDeathAndSpellScripts.ts +268 -0
- package/src/engine/game/applyDoorFromDbScript.ts +388 -0
- package/src/engine/game/applyEscortFromDbScript.ts +290 -0
- package/src/engine/game/applyGoSpellcasterCast.ts +40 -0
- package/src/engine/game/applyHoverFromDbScript.ts +110 -0
- package/src/engine/game/applyOocResurrect.ts +71 -0
- package/src/engine/game/applySpellDurability.ts +115 -0
- package/src/engine/game/applyTailFromDbScript.ts +478 -0
- package/src/engine/game/applyTerminateFromDbScript.ts +95 -0
- package/src/engine/game/auctionMail.ts +269 -0
- package/src/engine/game/bagOverflow.ts +130 -0
- package/src/engine/game/bank.ts +110 -0
- package/src/engine/game/barAutofill.ts +26 -0
- package/src/engine/game/battleground.ts +369 -0
- package/src/engine/game/combatEnemySpec.ts +216 -0
- package/src/engine/game/combatObjectTraps.ts +167 -0
- package/src/engine/game/combatSeed.ts +320 -0
- package/src/engine/game/combatSession.ts +3241 -0
- package/src/engine/game/combatSessionClock.ts +62 -0
- package/src/engine/game/companionQuestActions.ts +242 -0
- package/src/engine/game/companionTraining.ts +40 -0
- package/src/engine/game/consumables.ts +624 -0
- package/src/engine/game/context.ts +53 -0
- package/src/engine/game/createHealthstone.ts +45 -0
- package/src/engine/game/deathFlow.ts +127 -0
- package/src/engine/game/devGrant.ts +117 -0
- package/src/engine/game/dynamicObject.ts +365 -0
- package/src/engine/game/enchantItem.ts +177 -0
- package/src/engine/game/equipmentHelper.ts +806 -0
- package/src/engine/game/factionState.ts +108 -0
- package/src/engine/game/garmentsOoc.ts +69 -0
- package/src/engine/game/globalActionBar.ts +179 -0
- package/src/engine/game/goInteractQuestCredit.ts +51 -0
- package/src/engine/game/goMoTransportUse.ts +61 -0
- package/src/engine/game/goTransportUse.ts +60 -0
- package/src/engine/game/healthstoneUpkeep.ts +59 -0
- package/src/engine/game/killSkin.ts +124 -0
- package/src/engine/game/learnFromItem.ts +118 -0
- package/src/engine/game/loadReconcile.ts +551 -0
- package/src/engine/game/loot.ts +294 -0
- package/src/engine/game/macros.ts +125 -0
- package/src/engine/game/mail.ts +293 -0
- package/src/engine/game/meetingStoneQueue.ts +75 -0
- package/src/engine/game/meetingStoneUse.ts +54 -0
- package/src/engine/game/newGameState.ts +363 -0
- package/src/engine/game/oocAuraInterrupt.ts +92 -0
- package/src/engine/game/oocCast.ts +241 -0
- package/src/engine/game/oocCastEffects.ts +803 -0
- package/src/engine/game/oocCastMethods.ts +1077 -0
- package/src/engine/game/openLockFromSpell.ts +174 -0
- package/src/engine/game/outdoorPvp.ts +187 -0
- package/src/engine/game/outfitter.ts +644 -0
- package/src/engine/game/paladinBoard.ts +393 -0
- package/src/engine/game/partyAuras.ts +121 -0
- package/src/engine/game/persistCombatAuras.ts +91 -0
- package/src/engine/game/pets.ts +459 -0
- package/src/engine/game/prePullHealthstones.ts +154 -0
- package/src/engine/game/professions.ts +2227 -0
- package/src/engine/game/pruneStaleRuntime.ts +39 -0
- package/src/engine/game/questFightCredit.ts +36 -0
- package/src/engine/game/questItemOnCorpse.ts +166 -0
- package/src/engine/game/questItemOnCreature.ts +103 -0
- package/src/engine/game/questLog.ts +879 -0
- package/src/engine/game/questSpellComplete.ts +21 -0
- package/src/engine/game/questTurnIn.ts +598 -0
- package/src/engine/game/raidParty.ts +32 -0
- package/src/engine/game/reagentBuyIn.ts +39 -0
- package/src/engine/game/regenRest.ts +186 -0
- package/src/engine/game/rotation.ts +425 -0
- package/src/engine/game/runFullDungeon.ts +611 -0
- package/src/engine/game/runSupply.ts +80 -0
- package/src/engine/game/sanitizeDriftedItems.ts +135 -0
- package/src/engine/game/scriptDevGoUse.ts +397 -0
- package/src/engine/game/scriptDevInnkeeper.ts +150 -0
- package/src/engine/game/scriptDevProfessionSpec.ts +99 -0
- package/src/engine/game/sendEventFromSpell.ts +87 -0
- package/src/engine/game/sortContainers.ts +85 -0
- package/src/engine/game/spellReputation.ts +69 -0
- package/src/engine/game/squad/squadDungeonSeating.ts +197 -0
- package/src/engine/game/squadDungeon.ts +4222 -0
- package/src/engine/game/squadQuestShare.ts +260 -0
- package/src/engine/game/summonObjectSlot.ts +69 -0
- package/src/engine/game/summonObjectWild.ts +73 -0
- package/src/engine/game/summoningRitualUse.ts +141 -0
- package/src/engine/game/talents.ts +356 -0
- package/src/engine/game/tamingRodUse.ts +66 -0
- package/src/engine/game/tempSpawnCreature.ts +136 -0
- package/src/engine/game/tickDungeonPatrolScripts.ts +205 -0
- package/src/engine/game/tickTimedQuests.ts +28 -0
- package/src/engine/game/trade.ts +586 -0
- package/src/engine/game/travel.ts +1155 -0
- package/src/engine/game/types.ts +1855 -0
- package/src/engine/game/vendorOffers.ts +244 -0
- package/src/engine/game/weaponImbueItem.ts +281 -0
- package/src/engine/game/worldSession.ts +183 -0
- package/src/engine/game/worldState.ts +166 -0
- package/src/engine/game.ts +968 -0
- package/src/engine/spells/GAP.md +67 -0
- package/src/engine/spells/absorbs.ts +376 -0
- package/src/engine/spells/addTargetTrigger.ts +53 -0
- package/src/engine/spells/auraPerSecondCost.ts +50 -0
- package/src/engine/spells/auraStacking.ts +497 -0
- package/src/engine/spells/cmangos-special-cases-combustion.ts +13 -0
- package/src/engine/spells/cmangos-special-cases-s18.ts +445 -0
- package/src/engine/spells/cmangos-special-cases-s19.ts +324 -0
- package/src/engine/spells/cmangos-special-cases-s20.ts +385 -0
- package/src/engine/spells/cmangos-special-cases-s21.ts +116 -0
- package/src/engine/spells/cmangos-special-cases-s22.ts +24 -0
- package/src/engine/spells/cmangos-special-cases-s23.ts +52 -0
- package/src/engine/spells/cmangos-special-cases.ts +1570 -0
- package/src/engine/spells/conditions.ts +718 -0
- package/src/engine/spells/effects.ts +203 -0
- package/src/engine/spells/fixtures/oracle-effects-lock9a.json +1 -0
- package/src/engine/spells/immunity.ts +942 -0
- package/src/engine/spells/intentEffects.ts +351 -0
- package/src/engine/spells/interpreter/GAPS.md +21 -0
- package/src/engine/spells/interpreter/amount.ts +109 -0
- package/src/engine/spells/interpreter/class-scripts-kit.ts +478 -0
- package/src/engine/spells/interpreter/diff.ts +246 -0
- package/src/engine/spells/interpreter/duration.ts +90 -0
- package/src/engine/spells/interpreter/form-attrs.json +1 -0
- package/src/engine/spells/interpreter/form-attrs.ts +95 -0
- package/src/engine/spells/interpreter/handlers/aura-remove.ts +113 -0
- package/src/engine/spells/interpreter/handlers/auras-control.ts +1547 -0
- package/src/engine/spells/interpreter/handlers/auras-core.ts +1583 -0
- package/src/engine/spells/interpreter/handlers/auras-misc.ts +1774 -0
- package/src/engine/spells/interpreter/handlers/effect-disenchant.ts +17 -0
- package/src/engine/spells/interpreter/handlers/effect-skinning.ts +20 -0
- package/src/engine/spells/interpreter/handlers/effects-action-utility.ts +1455 -0
- package/src/engine/spells/interpreter/handlers/effects-core.ts +432 -0
- package/src/engine/spells/interpreter/handlers/effects-remainder.ts +105 -0
- package/src/engine/spells/interpreter/handlers/effects-summon-item.ts +687 -0
- package/src/engine/spells/interpreter/handlers/mod-increase-energy-percent.ts +36 -0
- package/src/engine/spells/interpreter/handlers/mod-increase-energy.ts +36 -0
- package/src/engine/spells/interpreter/handlers/periodic-trigger-zero.ts +327 -0
- package/src/engine/spells/interpreter/handlers/procs.ts +131 -0
- package/src/engine/spells/interpreter/handlers/registry.ts +975 -0
- package/src/engine/spells/interpreter/handlers/shared.ts +667 -0
- package/src/engine/spells/interpreter/handlers.ts +9 -0
- package/src/engine/spells/interpreter/hunter-kit.ts +202 -0
- package/src/engine/spells/interpreter/index.ts +525 -0
- package/src/engine/spells/interpreter/item-triggers.ts +1121 -0
- package/src/engine/spells/interpreter/lift-consume-energy.ts +37 -0
- package/src/engine/spells/interpreter/mage-portals.ts +37 -0
- package/src/engine/spells/interpreter/materialize-charm.ts +88 -0
- package/src/engine/spells/interpreter/materialize-dispel-mechanic.ts +25 -0
- package/src/engine/spells/interpreter/materialize-rating-buffs.ts +81 -0
- package/src/engine/spells/interpreter/materialize-speed.ts +109 -0
- package/src/engine/spells/interpreter/materialize-spell-magnet.ts +22 -0
- package/src/engine/spells/interpreter/opcodes.ts +789 -0
- package/src/engine/spells/interpreter/paladin-kit.ts +95 -0
- package/src/engine/spells/interpreter/paladin-seals.ts +499 -0
- package/src/engine/spells/interpreter/priest-warlock-kit.ts +135 -0
- package/src/engine/spells/interpreter/proc.ts +443 -0
- package/src/engine/spells/interpreter/registry/helpers.ts +16 -0
- package/src/engine/spells/interpreter/registry/index.ts +216 -0
- package/src/engine/spells/interpreter/registry/packs/class-abilities.ts +304 -0
- package/src/engine/spells/interpreter/registry/packs/dire-maul.ts +44 -0
- package/src/engine/spells/interpreter/registry/packs/dummy-script-tail.ts +1065 -0
- package/src/engine/spells/interpreter/registry/packs/ex-wave-m.ts +70 -0
- package/src/engine/spells/interpreter/registry/packs/life-tap.ts +107 -0
- package/src/engine/spells/interpreter/registry/packs/paladin.ts +246 -0
- package/src/engine/spells/interpreter/registry/packs/raid-script-summons.ts +26 -0
- package/src/engine/spells/interpreter/registry/packs/raid-threat-wipe.ts +22 -0
- package/src/engine/spells/interpreter/registry/packs/shaman.ts +214 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-a.ts +269 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-b.ts +370 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-c.ts +172 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-hunter.ts +60 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-warrior.ts +31 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-a.ts +114 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-b.ts +249 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-c.ts +233 -0
- package/src/engine/spells/interpreter/registry/types.ts +48 -0
- package/src/engine/spells/interpreter/talent-scripted.ts +329 -0
- package/src/engine/spells/interpreter/targeting.ts +90 -0
- package/src/engine/spells/interpreter/types-intent.ts +1501 -0
- package/src/engine/spells/interpreter/types.ts +419 -0
- package/src/engine/spells/interpreter/warrior-druid-kit.ts +314 -0
- package/src/engine/spells/interpreter/weapon-imbue-kit.ts +384 -0
- package/src/engine/spells/interrupt.ts +217 -0
- package/src/engine/spells/meleeAttackReset.ts +60 -0
- package/src/engine/spells/oocTriggerSpell.ts +49 -0
- package/src/engine/spells/overrideClassScripts.ts +319 -0
- package/src/engine/spells/parryHaste.ts +20 -0
- package/src/engine/spells/procs.ts +300 -0
- package/src/engine/spells/recovery.ts +203 -0
- package/src/engine/spells/scriptTargetDest.ts +76 -0
- package/src/engine/spells/scriptTargets.ts +85 -0
- package/src/engine/spells/singleTargetAura.ts +82 -0
- package/src/engine/spells/specialCasePayloads.ts +57 -0
- package/src/engine/spells/spellAttributes.ts +249 -0
- package/src/engine/spells/spellBonus.ts +143 -0
- package/src/engine/spells/spellModIntents.ts +419 -0
- package/src/engine/spells/spellMods.ts +283 -0
- package/src/engine/spells/spellRadius.ts +15 -0
- package/src/engine/spells/trapObjects.ts +26 -0
- package/src/json.d.ts +7 -0
- package/src/persistence/memoryRepository.ts +23 -0
- package/src/persistence/saveBackups.ts +162 -0
- package/src/persistence/saveFile.ts +68 -0
- package/src/persistence/saveRepository.ts +11 -0
- package/src/persistence/saveShape.ts +668 -0
- package/src/progression/leveling.ts +89 -0
- package/src/progression/rest.ts +1434 -0
- package/src/progression/training.ts +226 -0
- package/src/progression/xpGain.ts +369 -0
- package/src/quests/quests.ts +1580 -0
- package/src/world/aoePull.ts +198 -0
- package/src/world/areaTableDbc.ts +39 -0
- package/src/world/areaTableMapIdPairs.ts +141 -0
- package/src/world/bakeSlug.ts +43 -0
- package/src/world/continuousDungeon.ts +1250 -0
- package/src/world/dungeonBakeProjection.ts +190 -0
- package/src/world/dungeonMapDensity.ts +342 -0
- package/src/world/dungeonMapProjection.ts +135 -0
- package/src/world/dungeonNav.ts +904 -0
- package/src/world/dungeonNpcSim.ts +147 -0
- package/src/world/dungeonObjectives.ts +234 -0
- package/src/world/dungeonPreset.ts +35 -0
- package/src/world/dungeonRoute.ts +311 -0
- package/src/world/dungeonRouteGeometry.ts +732 -0
- package/src/world/dungeonRouteOrder.ts +1037 -0
- package/src/world/dungeonWingRooms.ts +426 -0
- package/src/world/escort.ts +296 -0
- package/src/world/goPoolRoll.ts +75 -0
- package/src/world/instanceObjects.ts +785 -0
- package/src/world/instances.ts +1572 -0
- package/src/world/meetingStones.ts +56 -0
- package/src/world/navmeshFloors.ts +145 -0
- package/src/world/nearGrid.ts +167 -0
- package/src/world/packBudget.ts +613 -0
- package/src/world/rareRoll.ts +335 -0
- package/src/world/routeAuthoring.ts +132 -0
- package/src/world/routeEdit.ts +209 -0
- package/src/world/world.ts +812 -0
- package/src/world/zoneHazards.ts +47 -0
|
@@ -0,0 +1,1904 @@
|
|
|
1
|
+
// Typed, indexed read access over the committed data bundle. Hollow percent-stat rows (empty
|
|
2
|
+
// `effects[]` + rawEffects aura 80/137) are SI5-materialized on first spell(). Cite: KI-178/179.
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
DataBundle, SpellDef, CreatureDef, ItemDef, LootEntryDef, QuestDef,
|
|
6
|
+
SoundTables, WorldTables,
|
|
7
|
+
TrainerDef, VendorDef, CreateInfoDef, PrimaryStats, ClassLevelRow, ZoneDef, RaceDef,
|
|
8
|
+
ProfessionDef, RecipeDef, GatherNodeDef, ProfTrainerDef, InstanceDef,
|
|
9
|
+
EnchantDef, EnchantRodDef, DisenchantRuleDef, CompactLoot, LootTuple, CompactProfessions,
|
|
10
|
+
TalentTreeDef, TalentDef, GameObjectDef, CompactSpells, CompactGameObjects, CompactItems, ItemSetDef,
|
|
11
|
+
ItemEnchantDef, EnchantApplicatorDef,
|
|
12
|
+
CreatureClassLvlStatsRow, PetLevelStatsRow, FactionDef, FactionTemplateDef, ReputationRankDef,
|
|
13
|
+
ReputationRateDef, ReputationSpilloverDef, BotSpecDef, LockDataDef, RandomPropertyDef,
|
|
14
|
+
DbScriptRow, SpellSoundStages, CreatureSoundStages, NpcVoiceStages, WeaponSoundSet, CreatureWeapon,
|
|
15
|
+
CombatConditionDef, UnitConditionDef, NpcTextDef,
|
|
16
|
+
} from './schema.ts';
|
|
17
|
+
import type { CombatSkillMeta } from '../domain/character/combatSkills.ts';
|
|
18
|
+
import { retagBreakableStunOnSpell, retagDisorientOnSpell } from '../domain/spells/breakableStunCc.ts';
|
|
19
|
+
import { CUSTOM_ITEMS } from './customItems.ts';
|
|
20
|
+
import { applyTamingRodUseSpells } from '../domain/quests/questTameRods.ts';
|
|
21
|
+
import { stampMissingSpeedFromRaw } from '../engine/spells/interpreter/materialize-speed.ts';
|
|
22
|
+
import { stampMissingRatingBuffsFromRaw } from '../engine/spells/interpreter/materialize-rating-buffs.ts';
|
|
23
|
+
import { stampMissingCharmFromRaw } from '../engine/spells/interpreter/materialize-charm.ts';
|
|
24
|
+
import { materializeModHealthRegenInCombat } from './materializeModHealthRegen.ts';
|
|
25
|
+
import { materializeModDamagePercentTaken } from './materializeModDamagePercentTaken.ts';
|
|
26
|
+
import { materializeSpellReputation } from './materializeSpellReputation.ts';
|
|
27
|
+
import { withResurrectEffectsIfEmpty } from '../domain/spells/resurrectSpell.ts';
|
|
28
|
+
import { withPetLearnTeachesIfEmpty } from '../domain/character/spellbook.ts';
|
|
29
|
+
// ManaCostPercentage / NOT_SHAPESHIFT from classic-db form-attrs (SI path — no bake re-edit).
|
|
30
|
+
// Cite: form-attrs.ts; Spell.cpp CalculatePowerCost; KI-120 Purge / percentage-cost kit.
|
|
31
|
+
import { hydrateSpellFormAttrs } from '../engine/spells/interpreter/form-attrs.ts';
|
|
32
|
+
import { hydrateSchoolImmunityOnSpell } from '../domain/spells/mergeSchoolImmunityFromRaw.ts';
|
|
33
|
+
import { normalizeIceBlock } from '../engine/combat/predicates.ts';
|
|
34
|
+
// Lazy companion to the eager materializers below: fills a spell whose bake `effects[]` is EMPTY on
|
|
35
|
+
// first access. The eager passes patch specific auras onto already-populated rows.
|
|
36
|
+
import { fillEmptyEffectsFromRaw } from '../engine/spells/interpreter/item-triggers.ts';
|
|
37
|
+
// KI-562 / KI-673 / KI-762 / KI-768+: outdoor zone.huntable must not list instance/AQ/guard
|
|
38
|
+
// / wrong-zone / level-mismatch pollution. Runtime prune so cold PRs need no bake rewrite.
|
|
39
|
+
import {
|
|
40
|
+
battlegroundCreatureEntrySet,
|
|
41
|
+
buildOutdoorSpawnIndex,
|
|
42
|
+
CLASSIC_BG_MAP_IDS,
|
|
43
|
+
CLASSIC_BG_NAME_BY_MAP,
|
|
44
|
+
instanceCreatureEntrySet,
|
|
45
|
+
isExcludedFromOutdoorHuntable,
|
|
46
|
+
shouldKeepOutdoorHuntableEntry,
|
|
47
|
+
zoneSpawnLevelBand,
|
|
48
|
+
} from './raidInstanceCreatures.mjs';
|
|
49
|
+
import { getFactionReaction } from '../domain/creature/faction.ts';
|
|
50
|
+
import { REP_RANK } from '../domain/creature/reputation.ts';
|
|
51
|
+
import { isOutdoorNonGrindCreature } from '../domain/creature/outdoorHuntable.ts';
|
|
52
|
+
import { applyLootChanceSanity } from './applyLootChanceSanity.ts'; // KI-108
|
|
53
|
+
import { stripInternalNameTags } from '../domain/core/internalNameTags.ts';
|
|
54
|
+
import { createSkinGapFill } from './skinNodeGapFill.ts'; // KI-904/905
|
|
55
|
+
import {
|
|
56
|
+
ensureFlightMasterCreatures,
|
|
57
|
+
patchAbsurdQuestFarms,
|
|
58
|
+
patchBeliaPetTrainer,
|
|
59
|
+
patchBlastedLandsVendors,
|
|
60
|
+
patchBogusQuestMinRepGates,
|
|
61
|
+
patchCannibalizeHeal,
|
|
62
|
+
patchFoodDrinkDescriptions,
|
|
63
|
+
patchGarmentHealQuests,
|
|
64
|
+
patchLegendsOfTheEarthQuest,
|
|
65
|
+
patchWindshearVerminDamage,
|
|
66
|
+
patchZoneClassTrainers, patchZoneInnkeepers, patchZoneQuestsFromStarterZones,
|
|
67
|
+
remapSyntheticClassTrainers,
|
|
68
|
+
normalizeSyntheticTrainerEntry,
|
|
69
|
+
} from '../domain/core/runtimeDataPatches.ts';
|
|
70
|
+
import { lockHasFreeOpenCase } from '../domain/world/openLock.ts';
|
|
71
|
+
import { ensurePoisonsProfessionSlice, patchPoisonQuestReward } from '../domain/professions/poisons.ts';
|
|
72
|
+
|
|
73
|
+
export interface DataStore {
|
|
74
|
+
spell(id: number): SpellDef | undefined;
|
|
75
|
+
creature(entry: number): CreatureDef | undefined;
|
|
76
|
+
/** Every attached creature_template row. Used by AHBot rank-split loot sources. */
|
|
77
|
+
creatures(): CreatureDef[];
|
|
78
|
+
// `creature_template_classlevelstats` by (level, unitClass), returning a row only when ObjectMgr
|
|
79
|
+
// validity holds; otherwise undefined so SelectLevel uses the Min/Max fallback.
|
|
80
|
+
creatureClassLvlStats(level: number, unitClass: number): CreatureClassLvlStatsRow | undefined;
|
|
81
|
+
// `pet_levelstats` by (creatureEntry, level); hunter pets use entry 1. Gap-fills a missing level
|
|
82
|
+
// from the previous one. Cite: ObjectMgr.cpp GetPetLevelInfo ~3045; LoadPetLevelInfo ~3030.
|
|
83
|
+
petLevelStats(creatureEntry: number, level: number): PetLevelStatsRow | undefined;
|
|
84
|
+
// SkillLineAbility.reqtrainpoints for a pet ability spell id (0 if absent / free). Baked `petAbilityTP`
|
|
85
|
+
// keys are stringified spell ids. Cite: Pet.cpp GetTPForSpell ~916.
|
|
86
|
+
petAbilityTP(spellId: number): number;
|
|
87
|
+
/** Shared Pet-Generic skill-line 270 catalog (Growl/Cower/Great Stamina/…). */
|
|
88
|
+
petGenericSpells(): { id: number; tp: number; innate?: boolean }[];
|
|
89
|
+
// SpellItemEnchantment type-1 payload for an enchant id — what an ENCHANT_HELD_ITEM (92) effect actually
|
|
90
|
+
// grants (Windfury 1783 → 8516 @ 20% for 10s). Undefined when the enchant is not a combat-spell enchant.
|
|
91
|
+
weaponEnchantProc(enchantId: number): { spellId: number; chancePct?: number; durationMs: number } | undefined;
|
|
92
|
+
// What a SpellItemEnchantment id GRANTS — kits, arcanums, oils, poisons, scopes, the enchanting
|
|
93
|
+
// catalogue. Superset of weaponEnchantProc, which sees only the type-1 proc slot.
|
|
94
|
+
itemEnchant(enchantId: number): ItemEnchantDef | undefined;
|
|
95
|
+
// The targeting rule for an enchant APPLY spell: which slots it accepts, for how long, and
|
|
96
|
+
// whether it refuses someone else's item. Keyed by apply spell — 16 enchant ids span slots.
|
|
97
|
+
enchantApplicator(spellId: number): EnchantApplicatorDef | undefined;
|
|
98
|
+
item(entry: number): ItemDef | undefined;
|
|
99
|
+
// Every known item (boot keep set + anything merged via attachItems). Do not mutate. Items may be slim at
|
|
100
|
+
// construction; bulk arrives from lazy item-data.json (Wave Z).
|
|
101
|
+
items(): ItemDef[];
|
|
102
|
+
/** Same full attached catalog as items() — prefer items() at new call sites. */
|
|
103
|
+
allItems(): ItemDef[];
|
|
104
|
+
// False while only the boot keep set is present (115 of 17,603 rows), so `item()` answers
|
|
105
|
+
// "missing" for almost everything a real character owns. Anything that DELETES must check it.
|
|
106
|
+
itemCatalogComplete(): boolean;
|
|
107
|
+
// Lookup a loot table by id: creature and other bare tables by numeric `lootId` or its decimal
|
|
108
|
+
// string, GameObject container tables by the string key `go:<n>`.
|
|
109
|
+
lootTable(lootId: number | string): LootEntryDef[];
|
|
110
|
+
// Every attached loot template id (creature / GO / etc), used by AHBot loot-supply sampling.
|
|
111
|
+
// Only what attachLoot or construction seeded — do not invent ids.
|
|
112
|
+
lootTemplateIds(): Array<number | string>;
|
|
113
|
+
// Loot keys whose table lists `itemId`, read off the compact tuples so a reverse-loot shortlist
|
|
114
|
+
// decodes nothing. A key can repeat, so callers that score a creature once dedupe.
|
|
115
|
+
lootKeysForItem(itemId: number): readonly (number | string)[];
|
|
116
|
+
/** Whether one loot table lists `itemId` at all — the same index, as a membership test. */
|
|
117
|
+
lootHasItem(lootId: number | string | undefined, itemId: number): boolean;
|
|
118
|
+
// Moves whenever a lazy attach can change what the zone walk yields. Cache key for anything
|
|
119
|
+
// derived from `zones()` — a plain WeakMap goes stale when creatures or quests land late.
|
|
120
|
+
zoneWorldGen(): number;
|
|
121
|
+
// Lock.dbc row for a GameObjectDef.lockId (P0.3 bake). Undefined when lock missing. Cite:
|
|
122
|
+
// DataBundle.locks; domain/world/openLock.ts canOpenLock / gameObjects.checkGoLock.
|
|
123
|
+
lock(lockId: number): LockDataDef | undefined;
|
|
124
|
+
// Populate the loot tables from the lazily-loaded compact loot-data.json: decodes the tuple
|
|
125
|
+
// encoding and merges into the table map, keeping loot present at construction. Idempotent.
|
|
126
|
+
attachLoot(compact: CompactLoot): void;
|
|
127
|
+
// Called from lootTable / lootTemplateIds while compact loot is still empty. Boot wires this to parse
|
|
128
|
+
// loot-data.json on first demand (not at menu).
|
|
129
|
+
setLootLoader(fn: (() => void) | null): void;
|
|
130
|
+
/** True after attachLoot. First corpse/gather should await ensureLoot(). */
|
|
131
|
+
lootAttached(): boolean;
|
|
132
|
+
/** Fetch + parse loot-data.json if needed. Resolves once tables are attached. */
|
|
133
|
+
ensureLoot(): Promise<void>;
|
|
134
|
+
// Populate the profession mechanics from the lazily-loaded profession-data.json, rebuilding the
|
|
135
|
+
// lookup maps in place. A partial chunk attaches only the fields it carries; re-calling is safe.
|
|
136
|
+
attachProfessions(chunk: CompactProfessions): void;
|
|
137
|
+
// Populate additional spells (creature casts, procs, higher ranks, enchants) from spell-data.json.
|
|
138
|
+
// Idempotent merge; core player spells from construction stay. Bridges on-next-swing as usual.
|
|
139
|
+
attachSpells(chunk: CompactSpells): void;
|
|
140
|
+
// Populate the bulk of GameObject templates from gameobject-data.json. Idempotent merge; GOs
|
|
141
|
+
// present at construction (the quest-derived minimal set) are preserved.
|
|
142
|
+
attachGameObjects(chunk: CompactGameObjects): void;
|
|
143
|
+
// Populate bulk ItemDef rows from item-data.json. Idempotent merge by entry, re-applying
|
|
144
|
+
// taming-rod useSpell stamps on newly attached items.
|
|
145
|
+
attachItems(chunk: CompactItems): void;
|
|
146
|
+
// Populate the bulk creature templates from the lazy tables/creatures.json chunk. Idempotent
|
|
147
|
+
// merge by entry, preserving fixtures and slim boot sets. Awaited at the menu→world seam.
|
|
148
|
+
attachCreatures(list: CreatureDef[]): void;
|
|
149
|
+
/** Populate the bulk quest templates from lazily-loaded tables/quests.json — same
|
|
150
|
+
* seam and idempotence rules as attachCreatures. */
|
|
151
|
+
attachQuests(list: QuestDef[]): void;
|
|
152
|
+
/** Fill the sound tables from the lazy src/data/sound-data.ts chunk (4.8 MB). Maps are
|
|
153
|
+
* filled in place, so every accessor closure already handed out keeps working. */
|
|
154
|
+
attachSounds(chunk: SoundTables): void;
|
|
155
|
+
/** Merge the call-time tables from the lazy src/data/world-data.ts chunk (3.9 MB) into the
|
|
156
|
+
* bundle this store reads. Only tables with no construction-derived structure belong here —
|
|
157
|
+
* see WorldTables. */
|
|
158
|
+
attachWorldTables(extra: WorldTables): void;
|
|
159
|
+
quest(entry: number): QuestDef | undefined;
|
|
160
|
+
/** All baked quest templates (boot + lazy attachQuests). */
|
|
161
|
+
quests(): QuestDef[];
|
|
162
|
+
trainer(entry: number): TrainerDef | undefined;
|
|
163
|
+
/** Every trainer in the catalog (same shape as vendors()). Reverse "who teaches spell X" reads this. */
|
|
164
|
+
trainers(): TrainerDef[];
|
|
165
|
+
vendor(entry: number): VendorDef | undefined;
|
|
166
|
+
/** Every attached npc_vendor. AHBot vendor-price clamp walks items[]. */
|
|
167
|
+
vendors(): VendorDef[];
|
|
168
|
+
createInfo(race: number, cls: number): CreateInfoDef | undefined;
|
|
169
|
+
levelStats(race: number, cls: number, level: number): PrimaryStats | undefined;
|
|
170
|
+
classLevel(cls: number, level: number): { baseHp: number; baseMana: number } | undefined;
|
|
171
|
+
xpToNext(level: number): number;
|
|
172
|
+
zone(id: string): ZoneDef | undefined;
|
|
173
|
+
zones(): ZoneDef[];
|
|
174
|
+
race(id: number): RaceDef | undefined;
|
|
175
|
+
races(): RaceDef[];
|
|
176
|
+
allSpells(): SpellDef[];
|
|
177
|
+
// Professions (Phase 4)
|
|
178
|
+
profession(id: string): ProfessionDef | undefined;
|
|
179
|
+
professions(): ProfessionDef[];
|
|
180
|
+
recipe(id: number): RecipeDef | undefined;
|
|
181
|
+
recipes(): RecipeDef[];
|
|
182
|
+
gatherNode(id: number): GatherNodeDef | undefined;
|
|
183
|
+
/** Same list `gatherNode` reads — recipes() for nodes. */
|
|
184
|
+
gatherNodes(): GatherNodeDef[];
|
|
185
|
+
profTrainer(entry: number): ProfTrainerDef | undefined;
|
|
186
|
+
/** The skinning gather-node a creature's corpse offers, if it is skinnable. */
|
|
187
|
+
skinNode(creatureEntry: number): number | undefined;
|
|
188
|
+
// Enchanting
|
|
189
|
+
enchant(id: number): EnchantDef | undefined;
|
|
190
|
+
enchants(): EnchantDef[];
|
|
191
|
+
/** The tier of an enchanting rod item (undefined if the item isn't a rod). */
|
|
192
|
+
enchantRodTier(entry: number): number | undefined;
|
|
193
|
+
/** Every rod row, so a gate can name the rod a tier wants instead of printing the number. */
|
|
194
|
+
enchantRods(): EnchantRodDef[];
|
|
195
|
+
disenchantRules(): DisenchantRuleDef[];
|
|
196
|
+
// Instances / dungeons (Phase 5)
|
|
197
|
+
instance(id: string): InstanceDef | undefined;
|
|
198
|
+
instances(): InstanceDef[];
|
|
199
|
+
// True when this creature only exists inside a 5-man or raid — the same block set that prunes
|
|
200
|
+
// zone.huntable. An outdoor planner must refuse these: there is no world pull.
|
|
201
|
+
isInstanceCreature(entry: number): boolean;
|
|
202
|
+
/** Instance hosting `entry`, for the honest-skip line: the wing that fights it when known. */
|
|
203
|
+
instanceForCreature(entry: number): InstanceDef | undefined;
|
|
204
|
+
// True when this creature only ever spawns inside a battleground (maps 30/489/529). Separate from
|
|
205
|
+
// isInstanceCreature: the census had no BG maps until 2026-08-21, so 174 entries were invisible.
|
|
206
|
+
isBattlegroundCreature(entry: number): boolean;
|
|
207
|
+
/** Battleground name hosting `entry` ("Alterac Valley"), for the honest-skip line. */
|
|
208
|
+
battlegroundForCreature(entry: number): string | undefined;
|
|
209
|
+
// Talents (Phase A+)
|
|
210
|
+
talentTrees(classId: number): TalentTreeDef[];
|
|
211
|
+
talent(id: number): TalentDef | undefined;
|
|
212
|
+
/** Class a trainer teaches (zone.trainers[].cls), 0 for a profession trainer. */
|
|
213
|
+
trainerClass(entry: number): number;
|
|
214
|
+
// GameObjects (chests/containers/quest nodes) — Lane F / Item 4
|
|
215
|
+
gameObject(entry: number): GameObjectDef | undefined;
|
|
216
|
+
gameObjects(): GameObjectDef[];
|
|
217
|
+
// Item sets for set-bonus spells (E1)
|
|
218
|
+
itemSet(id: number): ItemSetDef | undefined;
|
|
219
|
+
itemSets(): ItemSetDef[];
|
|
220
|
+
/** Playerbot companion name pool (seed-bot-names.mjs). Empty arrays when the bake predates it. */
|
|
221
|
+
botNames(): { male: readonly string[]; female: readonly string[] };
|
|
222
|
+
/** Premade playerbot talent specs for a class (seed-bot-specs.mjs). Empty when the bake predates it. */
|
|
223
|
+
botSpecs(cls: number): readonly BotSpecDef[];
|
|
224
|
+
/** Random-bot population weights "cls.race" -> weight (seed-bot-specs.mjs). Empty pre-bake. */
|
|
225
|
+
botPopulation(): Record<string, number>;
|
|
226
|
+
// R8b sound manifests (compact baked; values are clip names under /audio/sfx/ or absent)
|
|
227
|
+
spellSound(id: number): string | undefined;
|
|
228
|
+
/** ImpactKit landing clip (spellImpactSounds). Undefined when bake predates Wave G or spell has no impact. */
|
|
229
|
+
spellImpact(id: number): string | undefined;
|
|
230
|
+
creatureSound(entry: number): {
|
|
231
|
+
aggro?: string; death?: string; attack?: string; greeting?: string; farewell?: string;
|
|
232
|
+
} | undefined;
|
|
233
|
+
// The spell's full SpellVisual stage set — precast / cast / impact / travel, each a variation
|
|
234
|
+
// list. Undefined when the spell has no SpellVisual sound; callers fall back to school tables.
|
|
235
|
+
spellStages(id: number): SpellSoundStages | undefined;
|
|
236
|
+
// The creature's CreatureSoundData reaction set: exertion, injury, aggro, alert, death, stun and
|
|
237
|
+
// loop, each a variation list. Undefined when the display chain has no sound row.
|
|
238
|
+
creatureSoundSet(entry: number): CreatureSoundStages | undefined;
|
|
239
|
+
// The PLAYER's reaction set, off the same chain: ChrRaces display id -> CreatureSoundData.
|
|
240
|
+
// Undefined only for a race/gender the DBC has no display for.
|
|
241
|
+
raceSoundSet(race: number, gender: 0 | 1 | undefined): CreatureSoundStages | undefined;
|
|
242
|
+
// The NPC's gossip voice set from NPCSounds — greeting, farewell, pissed — each a variation list.
|
|
243
|
+
// Undefined when the player cannot open a window (NpcFlags 0) or the display has no NPCSoundID.
|
|
244
|
+
npcVoice(entry: number): NpcVoiceStages | undefined;
|
|
245
|
+
// The recorded line for a script_texts / broadcast_text id — what the boss actually says out loud.
|
|
246
|
+
// Undefined when the row has no sound or the clip is not shipped.
|
|
247
|
+
textVoice(textId: number): readonly string[] | undefined;
|
|
248
|
+
/** Client VolumeFloat for a clip key (SoundEntries): 1 unless the client attenuates it. */
|
|
249
|
+
clipVolume(key: string): number;
|
|
250
|
+
// The blow set for a weapon subclass (WeaponImpactSounds row), metal or not. Undefined for subclasses
|
|
251
|
+
// the DBC has no row for (bow, gun, crossbow, wand, thrown) and for a bake that predates the table.
|
|
252
|
+
weaponSounds(subclass: number, metal: boolean): WeaponSoundSet | undefined;
|
|
253
|
+
/** What a creature swings (creature_equip_template). Undefined = unarmed. */
|
|
254
|
+
creatureWeapon(entry: number): CreatureWeapon | undefined;
|
|
255
|
+
// Combat skill DBC meta (SkillLine / SkillTiers / spell grants). Undefined when the bake predates
|
|
256
|
+
// combat-skill enrichment — callers use hardcoded fallbacks.
|
|
257
|
+
combatSkillMeta(): CombatSkillMeta | undefined;
|
|
258
|
+
// Reputation factions / ranks (Wave C / E1 bake). Empty when pre-reputation bundle.
|
|
259
|
+
factions(): FactionDef[];
|
|
260
|
+
faction(id: number): FactionDef | undefined;
|
|
261
|
+
/** FactionTemplate.dbc reaction masks by id (= creature_template.Faction). Undefined when
|
|
262
|
+
* the id is unknown or the bundle predates the faction bake — callers fail open. */
|
|
263
|
+
factionTemplate(id: number): FactionTemplateDef | undefined;
|
|
264
|
+
reputationRanks(): ReputationRankDef[];
|
|
265
|
+
/** Per-faction kill/quest/spell rate multipliers (reputation_reward_rate). */
|
|
266
|
+
reputationRates(): ReputationRateDef[];
|
|
267
|
+
/** Spillover templates (reputation_spillover_template). */
|
|
268
|
+
reputationSpillover(): ReputationSpilloverDef[];
|
|
269
|
+
// Wave D/E/F optional bakes
|
|
270
|
+
explorationBasexp(): Array<{ level: number; basexp: number }>;
|
|
271
|
+
graveyards(): NonNullable<DataBundle['graveyards']>;
|
|
272
|
+
gameEvents(): NonNullable<DataBundle['gameEvents']>;
|
|
273
|
+
// dbscripts_on_event rows for a fire-on-use eventId (goober data2 / chest data6); empty when the
|
|
274
|
+
// id is unknown. Never a calendar gate. Cite: GameObject.cpp StartEvents_Event.
|
|
275
|
+
eventScripts(eventId: number): DbScriptRow[];
|
|
276
|
+
// dbscripts_on_go_template_use rows for a GO entry (command 10 = summon creature at x/y/z — the Dark
|
|
277
|
+
// Keeper portraits). Empty when the entry has no use script.
|
|
278
|
+
goUseScripts(goEntry: number): DbScriptRow[];
|
|
279
|
+
// dbscripts_on_go_use rows for a GO SPAWN GUID, not an entry (BRD Shadowforge Lock 2300093).
|
|
280
|
+
// Cite: GameObject.cpp:1424-1426 ScriptsStart(SCRIPT_TYPE_GAMEOBJECT, GetDbGuid()).
|
|
281
|
+
goGuidUseScripts(goGuid: number): DbScriptRow[];
|
|
282
|
+
// dbscripts_on_gossip rows for a script id — `gossip_menu.script_id` fires on open with source
|
|
283
|
+
// PLAYER, `gossip_menu_option.action_script_id` on the pick with source CREATURE.
|
|
284
|
+
gossipScripts(scriptId: number): DbScriptRow[];
|
|
285
|
+
// dbscripts_on_relay rows for a relay id — START_RELAY_SCRIPT ScriptsStart target. Cite:
|
|
286
|
+
// ScriptMgr.cpp:2938–2948.
|
|
287
|
+
relayScripts(relayId: number): DbScriptRow[];
|
|
288
|
+
// dbscripts_on_creature_movement rows keyed by creature_movement.ScriptId. Cite:
|
|
289
|
+
// WaypointMovementGenerator.cpp:183–186 ScriptsStart(SCRIPT_TYPE_CREATURE_MOVEMENT, …).
|
|
290
|
+
creatureMovementScripts(scriptId: number): DbScriptRow[];
|
|
291
|
+
// dbscripts_on_creature_death rows keyed by creature entry. Cite: Unit.cpp:1382
|
|
292
|
+
// ScriptsStart(SCRIPT_TYPE_CREATURE_DEATH, victim->GetEntry(), …).
|
|
293
|
+
creatureDeathScripts(entry: number): DbScriptRow[];
|
|
294
|
+
// dbscripts_on_spell rows keyed by spell id (still returned when the slim spell table lacks the id).
|
|
295
|
+
// Cite: SpellEffects.cpp:1529–1531 / :4189–4191 ScriptsStart(SCRIPT_TYPE_SPELL, m_spellInfo->Id, …).
|
|
296
|
+
spellScripts(spellId: number): DbScriptRow[];
|
|
297
|
+
pools(): NonNullable<DataBundle['pools']>;
|
|
298
|
+
zoneWeather(): NonNullable<DataBundle['zoneWeather']>;
|
|
299
|
+
conditionsList(): NonNullable<DataBundle['conditions']>;
|
|
300
|
+
/** combat_condition rows by id — the creature_spell_list gate. Cite: UnitAI.cpp:1218-1224. */
|
|
301
|
+
combatCondition(id: number): CombatConditionDef | undefined;
|
|
302
|
+
/** unit_condition rows by id, the predicate a combat condition points at. */
|
|
303
|
+
unitCondition(id: number): UnitConditionDef | undefined;
|
|
304
|
+
gossipMenus(): NonNullable<DataBundle['gossipMenus']>;
|
|
305
|
+
// npc_text row by id — the body a gossip menu shows above its options (`gossipMenus[].textId` is the join
|
|
306
|
+
// key). Undefined when the dump has no text for the id.
|
|
307
|
+
npcText(id: number): NpcTextDef | undefined;
|
|
308
|
+
// Hub→dungeon entrance yardages (T0.5 enrich-travel-edges). Empty when pre-travel bake. Cite:
|
|
309
|
+
// DataBundle.entranceYards; consumed by P-now / DF corpse-run helpers.
|
|
310
|
+
entranceYards(): NonNullable<DataBundle['entranceYards']>;
|
|
311
|
+
itemEnchantmentTemplate(entry: number): Array<{ ench: number; chance: number }> | undefined;
|
|
312
|
+
// ItemRandomProperties.dbc row by id; undefined when the id is missing or the bundle predates the
|
|
313
|
+
// table. Not a profession EnchantDef — PROP slots only.
|
|
314
|
+
itemRandomProperty(id: number): RandomPropertyDef | undefined;
|
|
315
|
+
// areatrigger_tavern rest markers (P0.6), zone-attributed. Empty when pre-areatrigger bake. Cite:
|
|
316
|
+
// ObjectMgr.cpp:6287-6328; CITY rest still beats TAVERN (MiscHandler.cpp:710-717).
|
|
317
|
+
tavernTriggers(): NonNullable<DataBundle['tavernTriggers']>;
|
|
318
|
+
// areatrigger_teleport dungeon/portal lock rows (P0.6). Empty when pre-areatrigger bake. Cite:
|
|
319
|
+
// ObjectMgr.cpp:6521-6626; consumed by world/instances canEnter (H3).
|
|
320
|
+
areatriggerTeleports(): NonNullable<DataBundle['areatriggerTeleports']>;
|
|
321
|
+
// Riding skill-GRANT rows (P0.7 enrich-mounts). Empty when pre-mount bake. Cite: DataBundle.ridingSpells;
|
|
322
|
+
// mounts-riding.md (33388@75, 33391@150).
|
|
323
|
+
ridingSpells(): NonNullable<DataBundle['ridingSpells']>;
|
|
324
|
+
// Rank-chain node for a spell, or undefined when it is not part of a ladder. `prev`/`req` are the
|
|
325
|
+
// prerequisites a trainer checks. Cite: SpellMgr::GetSpellChainNode; Player.cpp:3974-3982.
|
|
326
|
+
spellChain(spellId: number): NonNullable<DataBundle['spellChain']>[number] | undefined;
|
|
327
|
+
// FLEET-ROADMAP-3 T0.5 zone-graph travel matrix. Empty when pre-travel bake. Cite:
|
|
328
|
+
// DataBundle.travelEdges; enrich-travel-edges.mjs.
|
|
329
|
+
travelEdges(): NonNullable<DataBundle['travelEdges']>;
|
|
330
|
+
// T0.4 hub anchors per zone. Empty object when pre-travel bake. Cite: DataBundle.travelAnchors;
|
|
331
|
+
// enrich-travel-edges.mjs.
|
|
332
|
+
travelAnchors(): NonNullable<DataBundle['travelAnchors']>;
|
|
333
|
+
// T0.6 taxi nodes (TaxiNodes.dbc). Empty when pre-taxi bake. Cite: DataBundle.taxiNodes; enrich-taxi.mjs.
|
|
334
|
+
taxiNodes(): NonNullable<DataBundle['taxiNodes']>;
|
|
335
|
+
// T0.6 taxi routes (TaxiPath.dbc + spline seconds). Empty when pre-taxi bake. Cite:
|
|
336
|
+
// DataBundle.taxiRoutes; enrich-taxi.mjs.
|
|
337
|
+
taxiRoutes(): NonNullable<DataBundle['taxiRoutes']>;
|
|
338
|
+
// T0.7 boats/zeppelins/ferries. Empty when pre-transport bake. Cite: DataBundle.transports;
|
|
339
|
+
// enrich-transports.mjs.
|
|
340
|
+
transports(): NonNullable<DataBundle['transports']>;
|
|
341
|
+
// K4 class-travel destinations (spell_target_position + reagent gates). Empty when pre-class-travel bake.
|
|
342
|
+
// Cite: DataBundle.spellTeleports; enrich-class-travel.mjs.
|
|
343
|
+
spellTeleports(): NonNullable<DataBundle['spellTeleports']>;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Fewer attached items than this means only the boot keep set is here (itemCatalogComplete). */
|
|
347
|
+
const BULK_ITEM_FLOOR = 1000;
|
|
348
|
+
|
|
349
|
+
const triKey = (a: number, b: number, c: number): string => `${a}:${b}:${c}`;
|
|
350
|
+
const duoKey = (a: number, b: number): string => `${a}:${b}`;
|
|
351
|
+
|
|
352
|
+
// Warrior Charge ranks carry SPELL_ATTR_NOT_IN_COMBAT_ONLY_PEACEFUL (0x10000000), which bake rows
|
|
353
|
+
// leave unset, so stamp it here. Intercept and Feral Charge omit it. Cite: Spell.cpp:4598–4599.
|
|
354
|
+
const WARRIOR_CHARGE_NOT_IN_COMBAT_IDS = new Set([100, 6178, 11578]);
|
|
355
|
+
|
|
356
|
+
/** Apply runtime spell flags that the current bake still omits. Idempotent. */
|
|
357
|
+
function withRuntimeSpellFlags(s: SpellDef): SpellDef {
|
|
358
|
+
let out = s;
|
|
359
|
+
if (WARRIOR_CHARGE_NOT_IN_COMBAT_IDS.has(out.id) && !out.notInCombatOnly) {
|
|
360
|
+
out = { ...out, notInCombatOnly: true };
|
|
361
|
+
}
|
|
362
|
+
// KI-745: Multi-Shot / Aimed Shot bake role=shoot but are real mana+GCD casts, not Auto Shot
|
|
363
|
+
// toggles. Pure toggles stay cost=0 cast=0 gcd=0 (Auto Shot 75, Shoot 5019, Throw 2764).
|
|
364
|
+
if (
|
|
365
|
+
out.role === 'shoot'
|
|
366
|
+
&& ((out.cost ?? 0) > 0 || (out.castTimeMs ?? 0) > 0 || (out.gcdMs ?? 0) > 0)
|
|
367
|
+
) {
|
|
368
|
+
out = { ...out, role: (out.castTimeMs ?? 0) > 0 ? 'nuke' : 'instant_nuke' };
|
|
369
|
+
}
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Reverse split-loot.mjs's compact encoding back into a LootEntryDef, restoring the count pair
|
|
374
|
+
// omitted when it was the near-universal 1..1. An optional 6th element is conditionId.
|
|
375
|
+
function decodeLoot(t: LootTuple | LootEntryDef): LootEntryDef {
|
|
376
|
+
// Object-form rows (quest soft-lock tooling / e.item scanners) pass through.
|
|
377
|
+
if (t && typeof t === 'object' && !Array.isArray(t) && 'item' in t) {
|
|
378
|
+
const o = t as LootEntryDef & { referenceLootId?: number };
|
|
379
|
+
const ent: LootEntryDef = {
|
|
380
|
+
item: o.item | 0,
|
|
381
|
+
chance: Number(o.chance ?? 0),
|
|
382
|
+
groupId: (o.groupId ?? 0) | 0,
|
|
383
|
+
minCount: (o.minCount ?? 1) | 0 || 1,
|
|
384
|
+
maxCount: (o.maxCount ?? 1) | 0 || 1,
|
|
385
|
+
};
|
|
386
|
+
if (o.conditionId) ent.conditionId = o.conditionId | 0;
|
|
387
|
+
if (o.questRequired) ent.questRequired = true;
|
|
388
|
+
if (o.referenceLootId) (ent as { referenceLootId?: number }).referenceLootId = o.referenceLootId | 0;
|
|
389
|
+
return ent;
|
|
390
|
+
}
|
|
391
|
+
const row = t as LootTuple;
|
|
392
|
+
const ent: LootEntryDef = {
|
|
393
|
+
item: row[0],
|
|
394
|
+
chance: row[1],
|
|
395
|
+
groupId: row[2],
|
|
396
|
+
minCount: row.length >= 5 ? (row[3] as number) : 1,
|
|
397
|
+
maxCount: row.length >= 5 ? (row[4] as number) : 1,
|
|
398
|
+
};
|
|
399
|
+
if (row.length >= 6 && ((row[5] as number) | 0) !== 0) ent.conditionId = (row[5] as number) | 0;
|
|
400
|
+
return ent;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Loot map keys normalize as: digit strings → number, `go:`/`item:`/`pickpocket:`… → the full
|
|
404
|
+
// string, anything else as-is. EMPTY_LOOT_KEYS below is the shared frozen result, never mutated.
|
|
405
|
+
const EMPTY_LOOT_KEYS: readonly (number | string)[] = Object.freeze([]);
|
|
406
|
+
|
|
407
|
+
export function normalizeLootKey(id: string | number): number | string {
|
|
408
|
+
if (typeof id === 'number') return id;
|
|
409
|
+
if (/^(go|item|pickpocket|skinning|fishing|mail|spell|prospecting):\d+$/.test(id)) return id;
|
|
410
|
+
if (/^\d+$/.test(id)) return Number(id);
|
|
411
|
+
return id;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// FO1 discoverability bridge: GO container tables live under `go:<lootId>`, but zone scans call
|
|
415
|
+
// `lootTable(go.lootId)` bare, so dual-write. On collision only quest-gated GO rows merge.
|
|
416
|
+
export function dualWriteGoLootDiscoverability(
|
|
417
|
+
loot: Map<number | string, LootEntryDef[]>,
|
|
418
|
+
bareTable?: (id: number) => LootEntryDef[],
|
|
419
|
+
): void {
|
|
420
|
+
for (const [key, entries] of [...loot.entries()]) {
|
|
421
|
+
if (typeof key !== 'string' || !/^go:\d+$/.test(key)) continue;
|
|
422
|
+
const n = Number(key.slice(3));
|
|
423
|
+
if (!(n > 0) || !entries.length) continue;
|
|
424
|
+
// Creature tables decode lazily out of compact loot, so `loot` alone cannot see the collision —
|
|
425
|
+
// pass ensureLoot. Reading only `loot` replaced 132 creature tables with the GO's 1-3 rows.
|
|
426
|
+
const bare = loot.get(n) ?? bareTable?.(n);
|
|
427
|
+
if (!bare || bare.length === 0) {
|
|
428
|
+
loot.set(n, entries.map((e) => ({ ...e })));
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
const have = new Set(bare.map((e) => e.item));
|
|
432
|
+
for (const e of entries) {
|
|
433
|
+
const questGated = e.questRequired === true || e.chance < 0;
|
|
434
|
+
if (!questGated || have.has(e.item)) continue;
|
|
435
|
+
bare.push({ ...e });
|
|
436
|
+
have.add(e.item);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Bridge CMaNGOS SrcItemId grants that have no gameobject_loot_template row so zone loot scans can
|
|
442
|
+
// find the item. Surfaces already-baked srcItem onto the starter GO; invents no item ids.
|
|
443
|
+
export function bridgeGoStarterSrcItemLoot(
|
|
444
|
+
loot: Map<number | string, LootEntryDef[]>,
|
|
445
|
+
gameObjects: Map<number, GameObjectDef>,
|
|
446
|
+
quests: Iterable<QuestDef>,
|
|
447
|
+
zones: Map<string, ZoneDef>,
|
|
448
|
+
get: (key: number | string) => LootEntryDef[] = (key) => loot.get(key) ?? [],
|
|
449
|
+
): void {
|
|
450
|
+
for (const q of quests) {
|
|
451
|
+
const src = q.srcItem;
|
|
452
|
+
if (!src?.item) continue;
|
|
453
|
+
const goEntries =
|
|
454
|
+
(q.goStarterEntries?.length ? q.goStarterEntries : null)
|
|
455
|
+
?? (q.goStarter && q.goStarter > 0 ? [q.goStarter] : []);
|
|
456
|
+
if (!goEntries.length) continue;
|
|
457
|
+
|
|
458
|
+
const count = src.count > 0 ? src.count : 1;
|
|
459
|
+
for (const ge of goEntries) {
|
|
460
|
+
const go = gameObjects.get(ge);
|
|
461
|
+
if (!go) continue;
|
|
462
|
+
// Prefer existing chest/fishing loot id; else use GO entry (free in classic for type-2).
|
|
463
|
+
let lid = ((go.lootId ?? 0) | 0) > 0 ? ((go.lootId ?? 0) | 0) : ge;
|
|
464
|
+
if (!(((go.lootId ?? 0) | 0) > 0)) go.lootId = lid;
|
|
465
|
+
|
|
466
|
+
const ensure = (key: number | string) => {
|
|
467
|
+
let rows = get(key);
|
|
468
|
+
if (!loot.has(key)) {
|
|
469
|
+
rows = rows.length ? rows : [];
|
|
470
|
+
loot.set(key, rows);
|
|
471
|
+
}
|
|
472
|
+
if (rows.some((r) => r.item === src.item)) return;
|
|
473
|
+
rows.push({
|
|
474
|
+
item: src.item,
|
|
475
|
+
chance: -100, // quest-gated (FQ3 sign) — only while objective needs the item
|
|
476
|
+
groupId: 0,
|
|
477
|
+
minCount: count,
|
|
478
|
+
maxCount: count,
|
|
479
|
+
});
|
|
480
|
+
};
|
|
481
|
+
ensure(`go:${lid}`);
|
|
482
|
+
ensure(lid);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Place goStarter on starter zone object lists (FO2 discoverability).
|
|
486
|
+
for (const zid of q.starterZones ?? []) {
|
|
487
|
+
const z = zones.get(zid);
|
|
488
|
+
if (!z) continue;
|
|
489
|
+
if (!z.objects) z.objects = [];
|
|
490
|
+
for (const ge of goEntries) {
|
|
491
|
+
if (!z.objects.includes(ge)) z.objects.push(ge);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// Place GOs that yield quest itemTargets via quest-gated gameobject_loot onto zone.objects when the
|
|
498
|
+
// bake left them orphaned. Name-token co-location plus a death-summon fallback; no invented zones.
|
|
499
|
+
const QUEST_LOOT_GO_NAME_STOP = new Set([
|
|
500
|
+
'eggs', 'egg', 'chest', 'crate', 'box', 'food', 'water', 'armor', 'iron', 'gold',
|
|
501
|
+
'ore', 'vein', 'deposit', 'covered', 'large', 'small', 'locked', 'rusty', 'pitted',
|
|
502
|
+
'filled', 'strongbox', 'clusters', 'formation', 'rubble', 'artifact', 'the', 'and',
|
|
503
|
+
'of', 'with', 'from', 'ooze', 'skin', 'shadow', 'heart', 'mountain', 'scar', 'thread',
|
|
504
|
+
'enchanted', 'tablet', 'fifth', 'sixth', 'data', 'safe', 'dirt', 'mound', 'banner',
|
|
505
|
+
]);
|
|
506
|
+
const QUEST_LOOT_GO_MAX_TOKEN_ZONES = 2;
|
|
507
|
+
/** CreatureEventAI event_type = JustDied. Cite: CreatureEventAI.h EVENT_T_DEATH. */
|
|
508
|
+
const EVENT_T_DEATH = 6;
|
|
509
|
+
/** CreatureEventAI action_type = cast spell. Cite: CreatureEventAI.h ACTION_T_CAST. */
|
|
510
|
+
const ACTION_T_CAST = 11;
|
|
511
|
+
/** SPELL_EFFECT_SUMMON_OBJECT_WILD. Cite: SharedDefines.h / SpellEffects.cpp EffectSummonObjectWild. */
|
|
512
|
+
const SPELL_EFFECT_SUMMON_OBJECT_WILD = 76;
|
|
513
|
+
|
|
514
|
+
function lootRowsForGo(
|
|
515
|
+
get: (key: number | string) => LootEntryDef[],
|
|
516
|
+
lootId: number,
|
|
517
|
+
): LootEntryDef[] {
|
|
518
|
+
const a = get(`go:${lootId}`);
|
|
519
|
+
const b = get(lootId);
|
|
520
|
+
if (!a.length) return b;
|
|
521
|
+
if (!b.length) return a;
|
|
522
|
+
// Prefer namespaced table; merge unique items from bare for safety.
|
|
523
|
+
const seen = new Set(a.map((r) => r.item));
|
|
524
|
+
const out = a.slice();
|
|
525
|
+
for (const r of b) {
|
|
526
|
+
if (!seen.has(r.item)) out.push(r);
|
|
527
|
+
}
|
|
528
|
+
return out;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function goNameTokens(name: string | undefined): string[] {
|
|
532
|
+
return String(name || '')
|
|
533
|
+
.toLowerCase()
|
|
534
|
+
.split(/[^a-z0-9]+/)
|
|
535
|
+
.filter((t) => t.length >= 4 && !QUEST_LOOT_GO_NAME_STOP.has(t));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** GO entry summoned by SPELL_EFFECT_SUMMON_OBJECT_WILD (effects[] or rawEffects). */
|
|
539
|
+
function summonObjectWildGoEntry(spell: SpellDef | undefined): number {
|
|
540
|
+
if (!spell) return 0;
|
|
541
|
+
for (const e of spell.effects ?? []) {
|
|
542
|
+
if (e.kind === 'summon_object_wild' && (e.goEntry | 0) > 0) return e.goEntry | 0;
|
|
543
|
+
}
|
|
544
|
+
for (const r of spell.rawEffects ?? []) {
|
|
545
|
+
if ((r.effect | 0) === SPELL_EFFECT_SUMMON_OBJECT_WILD && (r.miscValue | 0) > 0) {
|
|
546
|
+
return r.miscValue | 0;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Map goEntry → creature entries that death-cast a SUMMON_OBJECT_WILD spell for it. Sourced:
|
|
553
|
+
// creature_ai_scripts event_type=6 action type=11 + spell EffectMiscValue.
|
|
554
|
+
function deathSummonCreaturesByGo(
|
|
555
|
+
creatures: Map<number, CreatureDef>,
|
|
556
|
+
spellOf: ((id: number) => SpellDef | undefined) | undefined,
|
|
557
|
+
): Map<number, Set<number>> {
|
|
558
|
+
const out = new Map<number, Set<number>>();
|
|
559
|
+
if (!spellOf) return out;
|
|
560
|
+
for (const cr of creatures.values()) {
|
|
561
|
+
const ce = cr.entry | 0;
|
|
562
|
+
if (!(ce > 0)) continue;
|
|
563
|
+
for (const scr of cr.ai?.scripts ?? []) {
|
|
564
|
+
if ((scr.eventType | 0) !== EVENT_T_DEATH) continue;
|
|
565
|
+
for (const act of scr.actions ?? []) {
|
|
566
|
+
if ((act.type | 0) !== ACTION_T_CAST) continue;
|
|
567
|
+
const go = summonObjectWildGoEntry(spellOf(act.param1 | 0));
|
|
568
|
+
if (!(go > 0)) continue;
|
|
569
|
+
let set = out.get(go);
|
|
570
|
+
if (!set) {
|
|
571
|
+
set = new Set();
|
|
572
|
+
out.set(go, set);
|
|
573
|
+
}
|
|
574
|
+
set.add(ce);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return out;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export function bridgeQuestItemLootGoZones(
|
|
582
|
+
loot: Map<number | string, LootEntryDef[]>,
|
|
583
|
+
gameObjects: Map<number, GameObjectDef>,
|
|
584
|
+
creatures: Map<number, CreatureDef>,
|
|
585
|
+
quests: Iterable<QuestDef>,
|
|
586
|
+
zones: Map<string, ZoneDef>,
|
|
587
|
+
gatherNodeNames?: Set<string>,
|
|
588
|
+
/** Optional spell lookup — enables death-summon co-location (KI-739 Rusty Chest). */
|
|
589
|
+
spellOf?: (id: number) => SpellDef | undefined,
|
|
590
|
+
/** Lock.dbc — free-open quest plants (KI-775) may dual-list on zone.objects. */
|
|
591
|
+
locks?: Map<number, LockDataDef>,
|
|
592
|
+
getLoot: (key: number | string) => LootEntryDef[] = (key) => loot.get(key) ?? [],
|
|
593
|
+
): void {
|
|
594
|
+
const itemTargets = new Set<number>();
|
|
595
|
+
for (const q of quests) {
|
|
596
|
+
for (const it of q.itemTargets ?? []) {
|
|
597
|
+
if (it?.item) itemTargets.add(it.item | 0);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (!itemTargets.size) return;
|
|
601
|
+
|
|
602
|
+
const freeOpenGo = (go: GameObjectDef): boolean => {
|
|
603
|
+
if (!locks || go.lockId == null) return false;
|
|
604
|
+
const lock = locks.get(go.lockId | 0);
|
|
605
|
+
if (!lock?.cases?.length) return false;
|
|
606
|
+
// LockDataDef cases match LockDef shape for free-open detection.
|
|
607
|
+
return lockHasFreeOpenCase(lock as Parameters<typeof lockHasFreeOpenCase>[0]);
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// token → outdoor zones that host a huntable whose name contains the token
|
|
611
|
+
const tokenZones = new Map<string, Set<string>>();
|
|
612
|
+
// creature entry → zones listing it as huntable (death-summon home)
|
|
613
|
+
const huntableZones = new Map<number, Set<string>>();
|
|
614
|
+
for (const z of zones.values()) {
|
|
615
|
+
for (const ce of z.huntable ?? []) {
|
|
616
|
+
let hz = huntableZones.get(ce);
|
|
617
|
+
if (!hz) {
|
|
618
|
+
hz = new Set();
|
|
619
|
+
huntableZones.set(ce, hz);
|
|
620
|
+
}
|
|
621
|
+
hz.add(z.id);
|
|
622
|
+
const cr = creatures.get(ce);
|
|
623
|
+
if (!cr?.name) continue;
|
|
624
|
+
for (const tok of goNameTokens(cr.name)) {
|
|
625
|
+
if (!tokenZones.has(tok)) tokenZones.set(tok, new Set());
|
|
626
|
+
tokenZones.get(tok)!.add(z.id);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const deathSummonByGo = deathSummonCreaturesByGo(creatures, spellOf);
|
|
632
|
+
|
|
633
|
+
const listed = new Set<number>();
|
|
634
|
+
for (const z of zones.values()) {
|
|
635
|
+
for (const e of z.objects ?? []) listed.add(e);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
for (const go of gameObjects.values()) {
|
|
639
|
+
const entry = go.entry | 0;
|
|
640
|
+
if (!(entry > 0) || listed.has(entry)) continue;
|
|
641
|
+
const lid = ((go.lootId ?? 0) | 0);
|
|
642
|
+
if (!(lid > 0)) continue;
|
|
643
|
+
// Profession herbs stay nodes-only; free-open quest plants (Gloom Weed) may dual-list.
|
|
644
|
+
if (gatherNodeNames?.has(String(go.name || '').toLowerCase()) && !freeOpenGo(go)) continue;
|
|
645
|
+
|
|
646
|
+
const rows = lootRowsForGo(getLoot, lid);
|
|
647
|
+
// Quest-gated (chance < 0) OR free-open GO that yields any itemTarget (Serpentbloom +100).
|
|
648
|
+
const isQuestLoot = rows.some((r) => {
|
|
649
|
+
if (!itemTargets.has(r.item | 0)) return false;
|
|
650
|
+
if (r.questRequired === true || r.chance < 0) return true;
|
|
651
|
+
return freeOpenGo(go);
|
|
652
|
+
});
|
|
653
|
+
if (!isQuestLoot) continue;
|
|
654
|
+
|
|
655
|
+
// Prefer the most exclusive name token (fewest co-located zones, ≥1).
|
|
656
|
+
let best: string[] | null = null;
|
|
657
|
+
let bestSize = Infinity;
|
|
658
|
+
for (const tok of goNameTokens(go.name)) {
|
|
659
|
+
const zs = tokenZones.get(tok);
|
|
660
|
+
if (!zs || zs.size === 0) continue;
|
|
661
|
+
if (zs.size > QUEST_LOOT_GO_MAX_TOKEN_ZONES) continue;
|
|
662
|
+
if (zs.size < bestSize) {
|
|
663
|
+
bestSize = zs.size;
|
|
664
|
+
best = [...zs];
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// KI-739: death-summoned quest chests have only stop-word names ("Rusty Chest").
|
|
669
|
+
// Place on zones hosting the EventAI death-caster (Rotting Slime → ashenvale).
|
|
670
|
+
if (!best?.length) {
|
|
671
|
+
const summoners = deathSummonByGo.get(entry);
|
|
672
|
+
if (summoners?.size) {
|
|
673
|
+
const zs = new Set<string>();
|
|
674
|
+
for (const ce of summoners) {
|
|
675
|
+
for (const zid of huntableZones.get(ce) ?? []) zs.add(zid);
|
|
676
|
+
}
|
|
677
|
+
if (zs.size) best = [...zs].sort();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (!best?.length) continue;
|
|
681
|
+
|
|
682
|
+
for (const zid of best) {
|
|
683
|
+
const z = zones.get(zid);
|
|
684
|
+
if (!z) continue;
|
|
685
|
+
if (!z.objects) z.objects = [];
|
|
686
|
+
if (!z.objects.includes(entry)) z.objects.push(entry);
|
|
687
|
+
}
|
|
688
|
+
listed.add(entry);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// KI-775: free-open quest plants already on zone.nodes (DB spawn path via extract-professions) promote to
|
|
692
|
+
// zone.objects. Name co-location cannot place "Gloom Weed" / "Doom Weed"; nodes list is the sourced home.
|
|
693
|
+
for (const z of zones.values()) {
|
|
694
|
+
for (const nodeId of z.nodes ?? []) {
|
|
695
|
+
const entry = nodeId | 0;
|
|
696
|
+
if (!(entry > 0)) continue;
|
|
697
|
+
const go = gameObjects.get(entry);
|
|
698
|
+
if (!go) continue;
|
|
699
|
+
if (!freeOpenGo(go)) continue;
|
|
700
|
+
const lid = ((go.lootId ?? 0) | 0);
|
|
701
|
+
if (!(lid > 0)) continue;
|
|
702
|
+
const rows = lootRowsForGo(getLoot, lid);
|
|
703
|
+
const yieldsTarget = rows.some((r) => itemTargets.has(r.item | 0));
|
|
704
|
+
if (!yieldsTarget) continue;
|
|
705
|
+
if (!z.objects) z.objects = [];
|
|
706
|
+
if (!z.objects.includes(entry)) z.objects.push(entry);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
export function createDataStore(b: DataBundle): DataStore {
|
|
711
|
+
// Spells: core player spells from the boot bundle, with attachSpells merging the rest. The runtime
|
|
712
|
+
// materializers below run in a fixed order and must be added to BOTH this path and attachSpells.
|
|
713
|
+
let allSpellDefs = b.spells
|
|
714
|
+
.map(withRuntimeSpellFlags)
|
|
715
|
+
.map(withResurrectEffectsIfEmpty)
|
|
716
|
+
.map(withPetLearnTeachesIfEmpty);
|
|
717
|
+
for (const s of allSpellDefs) stampMissingSpeedFromRaw(s);
|
|
718
|
+
for (const s of allSpellDefs) stampMissingRatingBuffsFromRaw(s);
|
|
719
|
+
for (const s of allSpellDefs) stampMissingCharmFromRaw(s);
|
|
720
|
+
for (const s of allSpellDefs) materializeModHealthRegenInCombat(s);
|
|
721
|
+
for (const s of allSpellDefs) materializeModDamagePercentTaken(s);
|
|
722
|
+
for (const s of allSpellDefs) materializeSpellReputation(s);
|
|
723
|
+
for (const s of allSpellDefs) hydrateSpellFormAttrs(s);
|
|
724
|
+
// Cannibalize seed flat 20 → OBS_MOD 7% max (KI-806). Cite: runtimeDataPatches.
|
|
725
|
+
for (const s of allSpellDefs) patchCannibalizeHeal(s);
|
|
726
|
+
// Sleep / Shackle Undead / Hibernate were baked as hard stun (MOD_STUN generic); retag in-memory
|
|
727
|
+
// so ds.spell() matches SI resolveCombatSpell without a re-bake. Cite: KI-151 / KI-189.
|
|
728
|
+
for (const s of allSpellDefs) retagBreakableStunOnSpell(s);
|
|
729
|
+
for (const s of allSpellDefs) retagDisorientOnSpell(s);
|
|
730
|
+
for (const s of allSpellDefs) hydrateSchoolImmunityOnSpell(s);
|
|
731
|
+
// Ice Block: silence+pacify lockout, not stun debuff (KI-709). After school_immunity hydrate.
|
|
732
|
+
allSpellDefs = allSpellDefs.map((s) => normalizeIceBlock(s));
|
|
733
|
+
const spells = new Map(allSpellDefs.map((s) => [s.id, s]));
|
|
734
|
+
/** id → index in allSpellDefs. Replaces a 13.5k findIndex on the spell() hot path. */
|
|
735
|
+
const spellIndexById = new Map<number, number>();
|
|
736
|
+
const reindexSpells = (from = 0): void => {
|
|
737
|
+
for (let i = from; i < allSpellDefs.length; i++) spellIndexById.set(allSpellDefs[i]!.id, i);
|
|
738
|
+
};
|
|
739
|
+
reindexSpells();
|
|
740
|
+
// KI-520: Seal of the Crusader ranks were mis-baked as plain MOD_ATTACK_POWER buffs. Strip the
|
|
741
|
+
// AP-only shell; combat still resolves seal + Judgement of the Crusader from rawEffects.
|
|
742
|
+
const SEAL_OF_THE_CRUSADER_IDS = [21082, 20162, 20305, 20306, 20307, 20308] as const;
|
|
743
|
+
for (const id of SEAL_OF_THE_CRUSADER_IDS) {
|
|
744
|
+
const sp = spells.get(id);
|
|
745
|
+
if (!sp) continue;
|
|
746
|
+
const hasApOnly = (sp.effects ?? []).some(
|
|
747
|
+
(e) => e.kind === 'buff' && e.mod === 'attack_power' && (e.amount ?? 0) > 0,
|
|
748
|
+
);
|
|
749
|
+
const hasSeal = (sp.effects ?? []).some((e) => e.kind === 'seal');
|
|
750
|
+
if (hasApOnly && !hasSeal) {
|
|
751
|
+
const patched = { ...sp, effects: [] as typeof sp.effects, role: 'seal' as const };
|
|
752
|
+
spells.set(id, patched);
|
|
753
|
+
const idx = allSpellDefs.findIndex((s) => s.id === id);
|
|
754
|
+
if (idx >= 0) allSpellDefs = allSpellDefs.map((s, i) => (i === idx ? patched : s));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
const creatures = new Map(b.creatures.map((c) => {
|
|
758
|
+
const name = stripInternalNameTags(c.name);
|
|
759
|
+
return [c.entry, name === c.name ? c : { ...c, name }];
|
|
760
|
+
}));
|
|
761
|
+
// Items: createInfo starters (and inline fixtures) from the boot bundle; attachItems merges the
|
|
762
|
+
// rest. A `let` list so items()/allItems() reflect the attached catalog, not a stale array.
|
|
763
|
+
let allItemDefs: ItemDef[] = (b.items ?? []).slice();
|
|
764
|
+
const items = new Map(allItemDefs.map((i) => [i.entry, i]));
|
|
765
|
+
// The boot keep set is 115 rows; the lazy chunk is 17,603. A bundle that already carries a
|
|
766
|
+
// real catalog (probes merge it inline) counts as complete without waiting for attachItems.
|
|
767
|
+
let bulkItemsPresent = allItemDefs.length >= BULK_ITEM_FLOOR;
|
|
768
|
+
// Custom items are not in the bake, so they are merged for every bundle — boot, lazy, fixtures.
|
|
769
|
+
// After bulkItemsPresent, so they can never make a boot-only bundle read as a full catalog.
|
|
770
|
+
for (const it of CUSTOM_ITEMS) {
|
|
771
|
+
if (items.has(it.entry)) continue;
|
|
772
|
+
items.set(it.entry, it);
|
|
773
|
+
allItemDefs.push(it);
|
|
774
|
+
}
|
|
775
|
+
// FQ2 / KI-141: class-12 Taming Rods are injected without useSpell; stamp sourced spellid_1
|
|
776
|
+
// so rods are usable (item_template → channel tame spell). Cite: domain/quests/questTameRods.ts.
|
|
777
|
+
applyTamingRodUseSpells(items.values());
|
|
778
|
+
// Loot is usually attached later from the lazy compact bundle. Fixtures and the dungeon merge
|
|
779
|
+
// still pass it inline, so seed from `b.loot` when present. Keys: numbers, or `go:<n>` strings.
|
|
780
|
+
const loot = new Map<number | string, LootEntryDef[]>(
|
|
781
|
+
(b.loot ?? []).map((l) => [normalizeLootKey(l.lootId), l.entries]),
|
|
782
|
+
);
|
|
783
|
+
// Compact tuples from loot-data.json. Decode on first lootTable() — 1.36M object rows
|
|
784
|
+
// at attach was the boot spike (505 ms parse + 138 ms 39 MB clone + 90 ms decode).
|
|
785
|
+
const compactLoot = new Map<number | string, CompactLoot[string]>();
|
|
786
|
+
let attachedCompact: CompactLoot | null = null;
|
|
787
|
+
let lootSanitized = false;
|
|
788
|
+
let lootLoader: (() => void) | null = null;
|
|
789
|
+
let lootWaiters: Array<() => void> = [];
|
|
790
|
+
const sanitizeAttachedLoot = (): void => {
|
|
791
|
+
if (lootSanitized || !attachedCompact || quests.size === 0) return;
|
|
792
|
+
applyLootChanceSanity(attachedCompact, quests.values());
|
|
793
|
+
lootSanitized = true;
|
|
794
|
+
invalidateItemLootIndex();
|
|
795
|
+
// Drop creature tables decoded before patches landed (go: keys are not patched).
|
|
796
|
+
for (const k of [...loot.keys()]) {
|
|
797
|
+
if (compactLoot.has(k) && (typeof k === 'number' || /^\d+$/.test(String(k)))) loot.delete(k);
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
const decodeLootTable = (rows: CompactLoot[string]): LootEntryDef[] => {
|
|
801
|
+
const len = rows.length;
|
|
802
|
+
const out = new Array(len);
|
|
803
|
+
for (let i = 0; i < len; i++) {
|
|
804
|
+
out[i] = decodeLoot(rows[i]);
|
|
805
|
+
}
|
|
806
|
+
return out;
|
|
807
|
+
};
|
|
808
|
+
// itemId → numeric loot keys listing it. Built from the compact tuples (t[0] is the item),
|
|
809
|
+
// so a shortlist costs no decode. Invalidated wherever the loot map can gain rows.
|
|
810
|
+
let itemLootKeys: Map<number, (number | string)[]> | null = null;
|
|
811
|
+
const itemLootSets = new Map<number, Set<number | string>>();
|
|
812
|
+
const invalidateItemLootIndex = (): void => {
|
|
813
|
+
itemLootKeys = null;
|
|
814
|
+
itemLootSets.clear();
|
|
815
|
+
};
|
|
816
|
+
const buildItemLootKeys = (): Map<number, (number | string)[]> => {
|
|
817
|
+
const idx = new Map<number, (number | string)[]>();
|
|
818
|
+
const push = (item: number, key: number | string): void => {
|
|
819
|
+
let list = idx.get(item);
|
|
820
|
+
if (list === undefined) {
|
|
821
|
+
list = [];
|
|
822
|
+
idx.set(item, list);
|
|
823
|
+
}
|
|
824
|
+
// Rows of one table are contiguous, so this collapses a table's repeat items.
|
|
825
|
+
if (list[list.length - 1] !== key) list.push(key);
|
|
826
|
+
};
|
|
827
|
+
if (attachedCompact) {
|
|
828
|
+
for (const id in attachedCompact) {
|
|
829
|
+
const key = normalizeLootKey(id);
|
|
830
|
+
for (const t of attachedCompact[id]!) push(t[0] | 0, key);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
// Decoded tables too, INCLUDING keys the compact map also has: the two bridges push rows onto
|
|
834
|
+
// an already-decoded table. That can repeat a key, so callers scoring a creature once dedupe.
|
|
835
|
+
for (const [key, rows] of loot) {
|
|
836
|
+
for (const d of rows) push(d.item | 0, key);
|
|
837
|
+
}
|
|
838
|
+
return idx;
|
|
839
|
+
};
|
|
840
|
+
const lootKeysForItemImpl = (itemId: number): readonly (number | string)[] => {
|
|
841
|
+
if (!compactLoot.size) lootLoader?.();
|
|
842
|
+
if (itemLootKeys === null) itemLootKeys = buildItemLootKeys();
|
|
843
|
+
return itemLootKeys.get(itemId | 0) ?? EMPTY_LOOT_KEYS;
|
|
844
|
+
};
|
|
845
|
+
const ensureLoot = (key: number | string): LootEntryDef[] => {
|
|
846
|
+
if (!compactLoot.size) lootLoader?.();
|
|
847
|
+
sanitizeAttachedLoot();
|
|
848
|
+
let rows = loot.get(key);
|
|
849
|
+
if (rows) return rows;
|
|
850
|
+
const raw = compactLoot.get(key);
|
|
851
|
+
if (raw == null) return [];
|
|
852
|
+
rows = decodeLootTable(raw);
|
|
853
|
+
loot.set(key, rows);
|
|
854
|
+
return rows;
|
|
855
|
+
};
|
|
856
|
+
// Construction-time loot (fixtures / inline) also gets GO discoverability dual-write.
|
|
857
|
+
dualWriteGoLootDiscoverability(loot);
|
|
858
|
+
const quests = new Map(b.quests.map((q) => {
|
|
859
|
+
const patched = patchPoisonQuestReward(q);
|
|
860
|
+
return [q.entry, patched === q ? q : { ...q, ...patched }];
|
|
861
|
+
}));
|
|
862
|
+
const trainers = remapSyntheticClassTrainers(b.trainers, b.zones); // KI-918
|
|
863
|
+
// Runtime trainer patches for core class spells missing from the bake's npc_trainer extract, at
|
|
864
|
+
// cost 0 or peer cost — never invented prices. Judgement 20271 is the KI-244 case.
|
|
865
|
+
const JUDGEMENT_SPELL_ID = 20271;
|
|
866
|
+
const JUDGEMENT_REQ_LEVEL = 4;
|
|
867
|
+
const JUDGEMENT_TRAIN_COST = 100; // sourced: peer L4 rows on paladin trainer
|
|
868
|
+
// Warrior quest-only teaches: 0 npc_trainer rows in 1.12, so the synthetic class trainer still
|
|
869
|
+
// needs the offers (KI-819). Cost 0 = quest rewSpell; reqLevel is the quest MinLevel.
|
|
870
|
+
const WARRIOR_QUEST_TRAINER_GRANTS: ReadonlyArray<{ spell: number; reqLevel: number }> = [
|
|
871
|
+
{ spell: 71, reqLevel: 10 }, // Defensive Stance
|
|
872
|
+
{ spell: 355, reqLevel: 10 }, // Taunt
|
|
873
|
+
{ spell: 2458, reqLevel: 30 }, // Berserker Stance
|
|
874
|
+
];
|
|
875
|
+
// TrainerDef carries no class — the mapping is baked on `zone.trainers[].cls`. Reading `t.cls`
|
|
876
|
+
// here was always undefined, so the KI-244 Judgement patch never applied to any trainer.
|
|
877
|
+
const trainerClass = new Map<number, number>();
|
|
878
|
+
for (const z of b.zones ?? []) {
|
|
879
|
+
for (const tr of z.trainers ?? []) trainerClass.set(tr.entry | 0, tr.cls | 0);
|
|
880
|
+
}
|
|
881
|
+
const withCoreTrainerSpells = (t: TrainerDef): TrainerDef => {
|
|
882
|
+
// CLASS.WARRIOR=1, PALADIN=2, ROGUE=4 (schema.ts); keep dataStore leaf-clean (no CLASS import).
|
|
883
|
+
const cls = trainerClass.get(t.entry | 0);
|
|
884
|
+
const extras: TrainerDef['spells'] = [];
|
|
885
|
+
if (cls === 1) {
|
|
886
|
+
for (const g of WARRIOR_QUEST_TRAINER_GRANTS) {
|
|
887
|
+
if (!t.spells.some((s) => s.spell === g.spell) && spells.has(g.spell)) {
|
|
888
|
+
extras.push({
|
|
889
|
+
spell: g.spell,
|
|
890
|
+
cost: 0,
|
|
891
|
+
reqLevel: g.reqLevel,
|
|
892
|
+
reqSkill: 0,
|
|
893
|
+
reqSkillValue: 0,
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (cls === 2 && !t.spells.some((s) => s.spell === JUDGEMENT_SPELL_ID) && spells.has(JUDGEMENT_SPELL_ID)) {
|
|
899
|
+
extras.push({
|
|
900
|
+
spell: JUDGEMENT_SPELL_ID,
|
|
901
|
+
cost: JUDGEMENT_TRAIN_COST,
|
|
902
|
+
reqLevel: JUDGEMENT_REQ_LEVEL,
|
|
903
|
+
reqSkill: 0,
|
|
904
|
+
reqSkillValue: 0,
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
// KI-871: trainer offer reqLevel must not undercut Spell.dbc cast/learn gate
|
|
908
|
+
// (synthetic class trainers: Intimidating Shout 20 vs 22, Cure Disease, Banish).
|
|
909
|
+
let spellsOut = extras.length ? [...t.spells, ...extras] : t.spells;
|
|
910
|
+
let clamped = false;
|
|
911
|
+
spellsOut = spellsOut.map((s) => {
|
|
912
|
+
const sp = spells.get(s.spell);
|
|
913
|
+
if (!sp) return s;
|
|
914
|
+
const spellReq = Math.max(sp.reqLevel | 0, (sp.spellLevel ?? 0) | 0);
|
|
915
|
+
if (spellReq <= (s.reqLevel | 0)) return s;
|
|
916
|
+
clamped = true;
|
|
917
|
+
return { ...s, reqLevel: spellReq };
|
|
918
|
+
});
|
|
919
|
+
if (!extras.length && !clamped) return t;
|
|
920
|
+
return { ...t, spells: spellsOut };
|
|
921
|
+
};
|
|
922
|
+
// `?? []` because the offline-baked bundle can lag the schema; a JSON without a
|
|
923
|
+
// `vendors` key must not crash the store (boot casts the JSON, so tsc won't catch it).
|
|
924
|
+
const vendors = new Map((b.vendors ?? []).map((v) => [v.entry, v]));
|
|
925
|
+
const createInfo = new Map(b.createInfo.map((c) => [duoKey(c.race, c.cls), c]));
|
|
926
|
+
const levelStats = new Map(b.levelStats.map((r) => [triKey(r.race, r.cls, r.level), r.stats]));
|
|
927
|
+
const classLevel = new Map<string, ClassLevelRow>(
|
|
928
|
+
b.classLevelStats.map((r) => [duoKey(r.cls, r.level), r]),
|
|
929
|
+
);
|
|
930
|
+
// Creature class×level stats (optional; System 8 SelectLevel). Key: level:unitClass.
|
|
931
|
+
const creatureCls = new Map<string, CreatureClassLvlStatsRow>(
|
|
932
|
+
(b.creatureClassLevelStats ?? []).map((r) => [duoKey(r.level, r.unitClass), r]),
|
|
933
|
+
);
|
|
934
|
+
// Pet levelstats (optional; Wave 3A). Key: creatureEntry:level. Hunter = entry 1.
|
|
935
|
+
const petLvl = new Map<string, PetLevelStatsRow>(
|
|
936
|
+
(b.petLevelStats ?? []).map((r) => [duoKey(r.creatureEntry, r.level), r]),
|
|
937
|
+
);
|
|
938
|
+
// Per-entry sorted levels for gap-fill (ObjectMgr copies previous when a level is missing).
|
|
939
|
+
const petLvlByEntry = new Map<number, number[]>();
|
|
940
|
+
for (const r of b.petLevelStats ?? []) {
|
|
941
|
+
let arr = petLvlByEntry.get(r.creatureEntry);
|
|
942
|
+
if (!arr) { arr = []; petLvlByEntry.set(r.creatureEntry, arr); }
|
|
943
|
+
arr.push(r.level);
|
|
944
|
+
}
|
|
945
|
+
for (const arr of petLvlByEntry.values()) arr.sort((a, b) => a - b);
|
|
946
|
+
// Pet training points (optional; Wave B). String keys in the bake → number lookup.
|
|
947
|
+
const petAbilityTpMap = b.petAbilityTP ?? {};
|
|
948
|
+
// Weapon-enchant combat-spell payloads (optional; absent on a bake predating the table).
|
|
949
|
+
const weaponEnchantProcMap = b.weaponEnchantProcs ?? {};
|
|
950
|
+
// Item-enchant spine (optional; absent on a bake predating the tables).
|
|
951
|
+
const itemEnchantMap = b.itemEnchants ?? {};
|
|
952
|
+
const enchantApplicatorMap = b.enchantApplicators ?? {};
|
|
953
|
+
const petGenericSpellList = b.petGenericSpells ?? [];
|
|
954
|
+
const xp = new Map(b.xp.map((r) => [r.level, r.toNext]));
|
|
955
|
+
// Races (guard with `?? []`: a bundle baked before Phase 3 lacks them).
|
|
956
|
+
const racesList = b.races ?? [];
|
|
957
|
+
const races = new Map(racesList.map((r) => [r.id, r]));
|
|
958
|
+
// Faction templates — used to prune never-attackable rows from zone.huntable.
|
|
959
|
+
const factionTemplatesEarly = new Map((b.factionTemplates ?? []).map((t) => [t.id, t]));
|
|
960
|
+
// Outdoor spawn index + instance block (5-man + raid + summon-only, outdoor-spawn exempt).
|
|
961
|
+
// Rebuilt when zones are fixed at construction; zone.spawns come from the bake.
|
|
962
|
+
const { outdoorSpawned, spawnZonesByEntry } = buildOutdoorSpawnIndex(b.zones ?? []);
|
|
963
|
+
const censusByMap = (b as { dungeonCensus?: Record<string, { entries?: Record<string, unknown> }> })
|
|
964
|
+
.dungeonCensus;
|
|
965
|
+
const raidHuntableBlock = instanceCreatureEntrySet(censusByMap, { outdoorSpawnedEntries: outdoorSpawned });
|
|
966
|
+
const bgHuntableBlock = battlegroundCreatureEntrySet(censusByMap, { outdoorSpawnedEntries: outdoorSpawned });
|
|
967
|
+
// The outdoor prune blocks BOTH — a BG creature is no more reachable from a zone than a
|
|
968
|
+
// dungeon one. The two sets stay separate only so the skip line can name the right place.
|
|
969
|
+
const outdoorHuntableBlock = new Set([...raidHuntableBlock, ...bgHuntableBlock]);
|
|
970
|
+
// Bosses the authored instance graphs fight that dungeonCensus never saw because they are
|
|
971
|
+
// summoned, not spawned: Ragnaros, Nefarian, Majordomo, Sapphiron, Ouro, Arlokk. 14 of 881.
|
|
972
|
+
for (const inst of b.instances ?? []) {
|
|
973
|
+
for (const enc of inst.encounters) {
|
|
974
|
+
const members = [
|
|
975
|
+
enc.creatureEntry,
|
|
976
|
+
...(enc.poolCandidates ?? []),
|
|
977
|
+
...(enc.pack ?? []).flatMap((m) => [m.creatureEntry, ...(m.poolCandidates ?? [])]),
|
|
978
|
+
];
|
|
979
|
+
for (const m of members) {
|
|
980
|
+
const e = m | 0;
|
|
981
|
+
if (e > 0 && !outdoorSpawned.has(e)) raidHuntableBlock.add(e);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
// creature entry -> instance mapId, built on first ask (only the honest-skip line needs it).
|
|
986
|
+
let creatureMapId: Map<number, number> | null = null;
|
|
987
|
+
const instanceMapOfCreature = (entry: number): number | undefined => {
|
|
988
|
+
if (!creatureMapId) {
|
|
989
|
+
creatureMapId = new Map();
|
|
990
|
+
for (const [mapKey, row] of Object.entries(censusByMap ?? {})) {
|
|
991
|
+
const mid = Number(mapKey);
|
|
992
|
+
if (!Number.isFinite(mid)) continue;
|
|
993
|
+
for (const k of Object.keys(row?.entries ?? {})) {
|
|
994
|
+
const n = Number(k);
|
|
995
|
+
if (Number.isFinite(n) && !creatureMapId.has(n)) creatureMapId.set(n, mid);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return creatureMapId.get(entry);
|
|
1000
|
+
};
|
|
1001
|
+
/** zoneId → local outdoor spawn entries */
|
|
1002
|
+
const localSpawnsByZone = new Map<string, Set<number>>();
|
|
1003
|
+
/** zoneId → AreaTable alliance/horde/contested (same-side noXp prune). */
|
|
1004
|
+
const zoneFactionById = new Map<string, ZoneDef['faction']>();
|
|
1005
|
+
/** zoneId → p10–p90 level band from local spawns */
|
|
1006
|
+
const levelBandByZone = new Map<string, [number, number] | null>();
|
|
1007
|
+
const spawnRankOf = (e: number): number => {
|
|
1008
|
+
const c = creatures.get(e | 0) as { rank?: number; scaling?: { rank?: number } } | undefined;
|
|
1009
|
+
if (!c) return 0;
|
|
1010
|
+
const r = c.rank ?? c.scaling?.rank;
|
|
1011
|
+
return (typeof r === 'number' && Number.isFinite(r) ? r : 0) | 0;
|
|
1012
|
+
};
|
|
1013
|
+
for (const z of b.zones ?? []) {
|
|
1014
|
+
const zid = z.id;
|
|
1015
|
+
if (!zid) continue;
|
|
1016
|
+
const local = new Set<number>();
|
|
1017
|
+
for (const s of z.spawns ?? []) {
|
|
1018
|
+
const e = (s.entry ?? 0) | 0;
|
|
1019
|
+
if (e > 0) local.add(e);
|
|
1020
|
+
}
|
|
1021
|
+
localSpawnsByZone.set(zid, local);
|
|
1022
|
+
if (z.faction) zoneFactionById.set(zid, z.faction);
|
|
1023
|
+
levelBandByZone.set(
|
|
1024
|
+
zid,
|
|
1025
|
+
zoneSpawnLevelBand(local, (e) => creatures.get(e)?.level ?? 0, { rankOf: spawnRankOf }),
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
// Quest kill targets AND baked item droppers (quest.farm) that still pass isOutdoorNonGrindCreature.
|
|
1029
|
+
// Faction-35 Binding / Garments heal proxies are rebuilt out after patchGarmentHealQuests. Cite: KI-1037.
|
|
1030
|
+
const questTargetHuntable = new Set<number>();
|
|
1031
|
+
/** zoneId → kill-target entries for quests listed on that zone */
|
|
1032
|
+
const killByZone = new Map<string, Set<number>>();
|
|
1033
|
+
const rebuildKillByZone = () => {
|
|
1034
|
+
killByZone.clear();
|
|
1035
|
+
for (const z of b.zones ?? []) {
|
|
1036
|
+
const zid = z.id;
|
|
1037
|
+
if (!zid) continue;
|
|
1038
|
+
const ks = new Set<number>();
|
|
1039
|
+
for (const qe of z.quests ?? []) {
|
|
1040
|
+
const q = quests.get(qe | 0);
|
|
1041
|
+
if (!q) continue;
|
|
1042
|
+
for (const kt of q.killTargets ?? []) {
|
|
1043
|
+
const e = kt.creatureEntry | 0;
|
|
1044
|
+
if (e > 0) ks.add(e);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
killByZone.set(zid, ks);
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
const ingestQuestTargets = (list: Iterable<QuestDef>) => {
|
|
1051
|
+
for (const q of list) {
|
|
1052
|
+
for (const kt of q.killTargets ?? []) {
|
|
1053
|
+
const e = kt.creatureEntry | 0;
|
|
1054
|
+
if (e > 0) questTargetHuntable.add(e);
|
|
1055
|
+
}
|
|
1056
|
+
// The reverse-loot dropper list is the same objective in item form; the prune dropped it for
|
|
1057
|
+
// NO_XP, which only zeroes XP. Cite: Formulas.h:368 Gain; Player.cpp:18727 RewardHonor.
|
|
1058
|
+
for (const ce of q.farm ?? []) {
|
|
1059
|
+
const e = ce | 0;
|
|
1060
|
+
if (e > 0) questTargetHuntable.add(e);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
/** Clear + rebuild after patches that strip bogus killTargets / absurd farms. */
|
|
1065
|
+
const rebuildQuestTargetHuntable = () => {
|
|
1066
|
+
questTargetHuntable.clear();
|
|
1067
|
+
ingestQuestTargets(quests.values());
|
|
1068
|
+
questKillHuntableGen = questTargetHuntable.size;
|
|
1069
|
+
};
|
|
1070
|
+
let questKillHuntableGen = 0;
|
|
1071
|
+
rebuildQuestTargetHuntable();
|
|
1072
|
+
rebuildKillByZone();
|
|
1073
|
+
|
|
1074
|
+
// Keep zone.huntable entries at least one player race can attack, and drop instance/summon-only,
|
|
1075
|
+
// civilians, guards and out-of-band leaks. FAILS OPEN on an unknown entry: `creatures` is LAZY.
|
|
1076
|
+
const huntableEntryOk = (entry: number, zoneId: string): boolean => {
|
|
1077
|
+
const e = entry | 0;
|
|
1078
|
+
const c = creatures.get(e);
|
|
1079
|
+
// Instance / rank-3 / AQ / guard / noXp — instance block wins over quest-kill allowlist.
|
|
1080
|
+
if (
|
|
1081
|
+
isExcludedFromOutdoorHuntable(e, c, {
|
|
1082
|
+
blockSet: outdoorHuntableBlock,
|
|
1083
|
+
questTargetSet: questTargetHuntable,
|
|
1084
|
+
outdoorSpawned,
|
|
1085
|
+
})
|
|
1086
|
+
) {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
// Zone-aware wrong-zone + level-band (needs spawn index; safe before creatures attach
|
|
1090
|
+
// for spawn-local rows; level band re-evaluates after attach when levels exist).
|
|
1091
|
+
const localSpawns = localSpawnsByZone.get(zoneId) ?? new Set<number>();
|
|
1092
|
+
// Recompute level band once creatures are attached (construction-time band is empty).
|
|
1093
|
+
// Prefer rank-0 normals so elite islands do not inflate p90 (KI-842/865/867/885/886).
|
|
1094
|
+
let band = levelBandByZone.get(zoneId) ?? null;
|
|
1095
|
+
if (creatures.size > 0 && localSpawns.size > 0) {
|
|
1096
|
+
band = zoneSpawnLevelBand(
|
|
1097
|
+
localSpawns,
|
|
1098
|
+
(id) => creatures.get(id)?.level ?? 0,
|
|
1099
|
+
{ rankOf: spawnRankOf },
|
|
1100
|
+
);
|
|
1101
|
+
levelBandByZone.set(zoneId, band);
|
|
1102
|
+
}
|
|
1103
|
+
if (
|
|
1104
|
+
!shouldKeepOutdoorHuntableEntry(e, zoneId, {
|
|
1105
|
+
blockSet: outdoorHuntableBlock,
|
|
1106
|
+
questTargetSet: questTargetHuntable,
|
|
1107
|
+
outdoorSpawned,
|
|
1108
|
+
spawnZonesByEntry,
|
|
1109
|
+
localKillSet: killByZone.get(zoneId) ?? new Set(),
|
|
1110
|
+
localSpawnSet: localSpawns,
|
|
1111
|
+
levelBand: band,
|
|
1112
|
+
creature: c,
|
|
1113
|
+
})
|
|
1114
|
+
) {
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
if (!c) return true;
|
|
1118
|
+
const a = c.factionId != null ? factionTemplatesEarly.get(c.factionId) : undefined;
|
|
1119
|
+
const zoneFaction = zoneFactionById.get(zoneId);
|
|
1120
|
+
// Same-side noXp / CIVILIAN / faction-35 / CanAttack mask — no quest-kill bypass.
|
|
1121
|
+
if (isOutdoorNonGrindCreature(c, a, zoneFaction)) return false;
|
|
1122
|
+
// Hostile quest targets stay listable even when the template is Neutral to some races.
|
|
1123
|
+
if (questTargetHuntable.has(e)) return true;
|
|
1124
|
+
if (!a) return true; // fail open when faction bake missing
|
|
1125
|
+
for (const race of racesList) {
|
|
1126
|
+
const bft = race.factionTemplate != null
|
|
1127
|
+
? factionTemplatesEarly.get(race.factionTemplate)
|
|
1128
|
+
: undefined;
|
|
1129
|
+
if (!bft) continue;
|
|
1130
|
+
// getFactionReaction(a=creature, b=player) — attackable when < FRIENDLY.
|
|
1131
|
+
if (getFactionReaction(a, bft) < REP_RANK.FRIENDLY) return true;
|
|
1132
|
+
}
|
|
1133
|
+
return false;
|
|
1134
|
+
};
|
|
1135
|
+
// Zone/catalog load-time patches (KI-429/467/478/670/730/873/879/916) — sourced ids only; no re-bake.
|
|
1136
|
+
patchBeliaPetTrainer(b.zones ?? [], creatures);
|
|
1137
|
+
for (const z of b.zones ?? []) {
|
|
1138
|
+
patchZoneInnkeepers(z);
|
|
1139
|
+
patchBlastedLandsVendors(z);
|
|
1140
|
+
// core.zones dropped Druid (cls 11) from faction class lists; restore from race unions.
|
|
1141
|
+
patchZoneClassTrainers(z, (e) => trainers.has(e));
|
|
1142
|
+
for (const tr of z.trainers ?? []) trainerClass.set(tr.entry | 0, tr.cls | 0);
|
|
1143
|
+
}
|
|
1144
|
+
// KI-873: starter-hub quests (Camp Narache, …) have starterZones but ZoneOrSort is a
|
|
1145
|
+
// sub-area id, so bake zone.quests omitted them. Union before kill-by-zone rebuild.
|
|
1146
|
+
if (quests.size > 0) {
|
|
1147
|
+
patchZoneQuestsFromStarterZones(b.zones ?? [], quests.values());
|
|
1148
|
+
rebuildKillByZone();
|
|
1149
|
+
}
|
|
1150
|
+
const zones = new Map(b.zones.map((z) => [z.id, z]));
|
|
1151
|
+
// Non-lazy callers (probes, dev drivers) pass creatures/quests eagerly and never call
|
|
1152
|
+
// attach*, so patch here too — but ONLY when the table is really populated. Cite: KI-478/670/916.
|
|
1153
|
+
if (creatures.size > 0) {
|
|
1154
|
+
ensureFlightMasterCreatures(b.zones ?? [], creatures);
|
|
1155
|
+
patchWindshearVerminDamage(creatures);
|
|
1156
|
+
}
|
|
1157
|
+
if (quests.size > 0) {
|
|
1158
|
+
patchGarmentHealQuests(quests.values());
|
|
1159
|
+
patchBogusQuestMinRepGates(quests.values());
|
|
1160
|
+
patchLegendsOfTheEarthQuest(quests.values());
|
|
1161
|
+
// Garments cleared killTargets — drop heal-proxy entries from the huntable allowlist.
|
|
1162
|
+
rebuildQuestTargetHuntable();
|
|
1163
|
+
rebuildKillByZone();
|
|
1164
|
+
}
|
|
1165
|
+
// KI-826: scrub absurd reverse-loot farm[] once both catalogs exist (eager bundles).
|
|
1166
|
+
if (quests.size > 0 && creatures.size > 0) {
|
|
1167
|
+
patchAbsurdQuestFarms(quests.values(), creatures, zones.values());
|
|
1168
|
+
// The allowlist reads farm[] — re-ingest so a scrubbed dropper loses its bypass too.
|
|
1169
|
+
rebuildQuestTargetHuntable();
|
|
1170
|
+
}
|
|
1171
|
+
// Zone with the huntable prune applied, evaluated on READ because `creatures` only fills when
|
|
1172
|
+
// boot.ts attaches the lazy table. Memoized on `creatures.size` + the quest-kill set gen.
|
|
1173
|
+
let huntableMemo = new Map<string, ZoneDef>();
|
|
1174
|
+
let huntableMemoKey = -1;
|
|
1175
|
+
/** The zone walk's own memo key, published so callers can key their derived maps on it. */
|
|
1176
|
+
const zoneWorldGen = (): number => (creatures.size > 0
|
|
1177
|
+
? creatures.size * 1_000_003 + questKillHuntableGen
|
|
1178
|
+
: questKillHuntableGen);
|
|
1179
|
+
const zonePruned = (z: ZoneDef | undefined): ZoneDef | undefined => {
|
|
1180
|
+
if (!z) return z;
|
|
1181
|
+
// Raid-map filter is safe before creatures attach (uses dungeonCensus only). Faction
|
|
1182
|
+
// prune still waits for the lazy catalog — same gate as huntable-lazy.test.ts.
|
|
1183
|
+
const canFactionPrune = creatures.size > 0;
|
|
1184
|
+
const memoKey = zoneWorldGen();
|
|
1185
|
+
if (huntableMemoKey !== memoKey) {
|
|
1186
|
+
huntableMemo = new Map();
|
|
1187
|
+
huntableMemoKey = memoKey;
|
|
1188
|
+
}
|
|
1189
|
+
const hit = huntableMemo.get(z.id);
|
|
1190
|
+
if (hit) return hit;
|
|
1191
|
+
const raw = z.huntable ?? [];
|
|
1192
|
+
// Inject zone quest killTargets omitted from bake (KI-888 Highvale). Skip spawn-home elsewhere.
|
|
1193
|
+
let candidates = raw;
|
|
1194
|
+
if (canFactionPrune) {
|
|
1195
|
+
const localKills = killByZone.get(z.id);
|
|
1196
|
+
if (localKills?.size) {
|
|
1197
|
+
const set = new Set(raw);
|
|
1198
|
+
let grew = false;
|
|
1199
|
+
for (const e of localKills) {
|
|
1200
|
+
if (set.has(e)) continue;
|
|
1201
|
+
const homes = spawnZonesByEntry.get(e);
|
|
1202
|
+
if (homes && homes.size > 0 && !homes.has(z.id)) continue;
|
|
1203
|
+
// localKillSet bypasses every band/rank check, so guard here: a level-1 row is a
|
|
1204
|
+
// trigger/doodad/structure, never a grind target (14 of 109). Real targets are level 2+.
|
|
1205
|
+
if ((creatures.get(e)?.level ?? 0) <= 1) continue;
|
|
1206
|
+
set.add(e);
|
|
1207
|
+
grew = true;
|
|
1208
|
+
}
|
|
1209
|
+
if (grew) candidates = [...set];
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
const hunt = canFactionPrune
|
|
1213
|
+
? candidates.filter((e) => huntableEntryOk(e, z.id))
|
|
1214
|
+
: outdoorHuntableBlock.size
|
|
1215
|
+
? raw.filter((e) => !outdoorHuntableBlock.has(e | 0))
|
|
1216
|
+
: raw;
|
|
1217
|
+
// Surface capital weapon masters on zone.trainers (cls=0) so discovery scans that only walk
|
|
1218
|
+
// trainers still find One-Handed Swords (KI-573). zoneTrainer(cls) still keys on cls.
|
|
1219
|
+
let trainersList = z.trainers ?? [];
|
|
1220
|
+
const wms = z.weaponTrainers ?? [];
|
|
1221
|
+
if (wms.length) {
|
|
1222
|
+
const have = new Set(trainersList.map((t) => t.entry | 0));
|
|
1223
|
+
const extra = wms
|
|
1224
|
+
.map((e) => e | 0)
|
|
1225
|
+
.filter((e) => e > 0 && !have.has(e) && trainers.has(e))
|
|
1226
|
+
.map((entry) => ({ cls: 0, entry }));
|
|
1227
|
+
if (extra.length) trainersList = [...trainersList, ...extra];
|
|
1228
|
+
}
|
|
1229
|
+
const trainersChanged = trainersList !== z.trainers && trainersList !== (z.trainers ?? []);
|
|
1230
|
+
const huntableChanged =
|
|
1231
|
+
hunt.length !== raw.length || !raw.every((e, i) => e === hunt[i]);
|
|
1232
|
+
// Recompute levelRange when huntable shrank so L40–46 instance/AQ rows no longer inflate it.
|
|
1233
|
+
let levelRange = z.levelRange;
|
|
1234
|
+
let levelRangeChanged = false;
|
|
1235
|
+
if (huntableChanged && canFactionPrune) {
|
|
1236
|
+
const lvls: number[] = [];
|
|
1237
|
+
for (const e of hunt) {
|
|
1238
|
+
const c = creatures.get(e | 0);
|
|
1239
|
+
const lvl = c?.level ?? 0;
|
|
1240
|
+
if (lvl > 0) lvls.push(lvl);
|
|
1241
|
+
}
|
|
1242
|
+
if (lvls.length) {
|
|
1243
|
+
const nextRange: [number, number] = [Math.min(...lvls), Math.max(...lvls)];
|
|
1244
|
+
const prev = z.levelRange;
|
|
1245
|
+
if (!prev || prev[0] !== nextRange[0] || prev[1] !== nextRange[1]) {
|
|
1246
|
+
levelRange = nextRange;
|
|
1247
|
+
levelRangeChanged = true;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
const out =
|
|
1252
|
+
!huntableChanged && !trainersChanged && !levelRangeChanged
|
|
1253
|
+
? z
|
|
1254
|
+
: {
|
|
1255
|
+
...z,
|
|
1256
|
+
...(huntableChanged ? { huntable: hunt } : {}),
|
|
1257
|
+
...(trainersChanged ? { trainers: trainersList } : {}),
|
|
1258
|
+
...(levelRangeChanged ? { levelRange } : {}),
|
|
1259
|
+
};
|
|
1260
|
+
huntableMemo.set(z.id, out);
|
|
1261
|
+
return out;
|
|
1262
|
+
};
|
|
1263
|
+
// Professions (guard each with `?? []`: a pre-Phase-4 bundle lacks them). Usually EMPTY at
|
|
1264
|
+
// construction and attached later; `let` so attachProfessions can swap the array bindings.
|
|
1265
|
+
let professionList = b.professions ?? [];
|
|
1266
|
+
// Key by plain string so lookups by an arbitrary id (the `profession(id: string)`
|
|
1267
|
+
// contract) type-check — the keys are the SkillLine union, which widens to string.
|
|
1268
|
+
const professions = new Map<string, ProfessionDef>(professionList.map((p) => [p.id, p]));
|
|
1269
|
+
let recipeList = b.recipes ?? [];
|
|
1270
|
+
const recipes = new Map(recipeList.map((r) => [r.id, r]));
|
|
1271
|
+
let gatherNodeList = b.gatherNodes ?? [];
|
|
1272
|
+
const gatherNodes = new Map(gatherNodeList.map((n) => [n.id, n]));
|
|
1273
|
+
const profTrainers = new Map((b.profTrainers ?? []).map((t) => [t.entry, t]));
|
|
1274
|
+
const skinnable = new Map((b.skinnable ?? []).map((s) => [s.creature, s.node])); const skinGap = createSkinGapFill(skinnable, gatherNodes, (e) => creatures.get(e)); // KI-904/905
|
|
1275
|
+
// Enchanting (guard with `?? []`: a bundle baked before seed-enchanting.mjs lacks them).
|
|
1276
|
+
let enchantList = b.enchants ?? [];
|
|
1277
|
+
const enchants = new Map(enchantList.map((e) => [e.id, e]));
|
|
1278
|
+
let enchantRodList = b.enchantRods ?? [];
|
|
1279
|
+
const enchantRods = new Map(enchantRodList.map((r) => [r.entry, r.tier]));
|
|
1280
|
+
let disenchantRules = b.disenchantRules ?? [];
|
|
1281
|
+
// Instances (guard with `?? []`: a bundle baked before Phase 5 lacks them).
|
|
1282
|
+
const instanceList = b.instances ?? [];
|
|
1283
|
+
const instances = new Map(instanceList.map((d) => [d.id, d]));
|
|
1284
|
+
const encounterHasCreature = (inst: InstanceDef, e: number): boolean =>
|
|
1285
|
+
inst.encounters.some((enc) =>
|
|
1286
|
+
enc.creatureEntry === e
|
|
1287
|
+
|| (enc.poolCandidates ?? []).includes(e)
|
|
1288
|
+
|| (enc.pack ?? []).some((m) => m.creatureEntry === e || (m.poolCandidates ?? []).includes(e)));
|
|
1289
|
+
// Talents (guard `?? []`)
|
|
1290
|
+
const talentTreesAll: TalentTreeDef[] = (b.talents ?? []).map(t => ({...t, talents: t.talents ?? [] }));
|
|
1291
|
+
const talentById = new Map<number, TalentDef>();
|
|
1292
|
+
for (const tree of talentTreesAll) {
|
|
1293
|
+
for (const tal of (tree.talents ?? [])) talentById.set(tal.id, tal);
|
|
1294
|
+
}
|
|
1295
|
+
const treesByClass = new Map<number, TalentTreeDef[]>();
|
|
1296
|
+
for (const t of talentTreesAll) {
|
|
1297
|
+
if (!treesByClass.has(t.classId)) treesByClass.set(t.classId, []);
|
|
1298
|
+
treesByClass.get(t.classId)!.push(t);
|
|
1299
|
+
}
|
|
1300
|
+
// GameObjects: a slim set (quest/current zone) may be in the core bundle; attachGameObjects
|
|
1301
|
+
// merges the bulk from lazy gameobject-data.json. Use `let` list so getter returns attached.
|
|
1302
|
+
let gameObjectList: GameObjectDef[] = (b.gameObjects ?? []).slice();
|
|
1303
|
+
const gameObjects = new Map<number, GameObjectDef>(gameObjectList.map((g) => [g.entry, g]));
|
|
1304
|
+
patchLegendsOfTheEarthQuest(quests.values(), gameObjects);
|
|
1305
|
+
// Lock.dbc (P0.3) — resolve GameObjectDef.lockId for canOpenLock / useGameObject.
|
|
1306
|
+
// Built before quest-loot GO bridge so KI-775 free-open plant promote can read locks.
|
|
1307
|
+
const locksById = new Map<number, LockDataDef>((b.locks ?? []).map((l) => [l.id, l]));
|
|
1308
|
+
// Built on first read: these two ship as boot tables, but a fixture bundle omits them and
|
|
1309
|
+
// the spell-list gate must then default open rather than silence every gated row.
|
|
1310
|
+
let combatCondIdx: Map<number, CombatConditionDef> | null = null;
|
|
1311
|
+
const combatConditionById = (): Map<number, CombatConditionDef> => {
|
|
1312
|
+
combatCondIdx ??= new Map((b.combatConditions ?? []).map((c) => [c.id | 0, c]));
|
|
1313
|
+
return combatCondIdx;
|
|
1314
|
+
};
|
|
1315
|
+
let npcTextIdx: Map<number, NpcTextDef> | null = null;
|
|
1316
|
+
const npcTextById = (): Map<number, NpcTextDef> => {
|
|
1317
|
+
npcTextIdx ??= new Map((b.npcTexts ?? []).map((t) => [t.id | 0, t]));
|
|
1318
|
+
return npcTextIdx;
|
|
1319
|
+
};
|
|
1320
|
+
let unitCondIdx: Map<number, UnitConditionDef> | null = null;
|
|
1321
|
+
const unitConditionById = (): Map<number, UnitConditionDef> => {
|
|
1322
|
+
unitCondIdx ??= new Map((b.unitConditions ?? []).map((c) => [c.id | 0, c]));
|
|
1323
|
+
return unitCondIdx;
|
|
1324
|
+
};
|
|
1325
|
+
// Inline-boot GOs + quests: bridge SrcItem goStarters (KI-502 Menethil Statuette pattern).
|
|
1326
|
+
bridgeGoStarterSrcItemLoot(loot, gameObjects, quests.values(), zones);
|
|
1327
|
+
dualWriteGoLootDiscoverability(loot);
|
|
1328
|
+
// KI-689 / KI-775: quest itemTarget GO loot → zone.objects (name co-location + free-open nodes).
|
|
1329
|
+
// Re-run on attachLoot / attachGameObjects / attachCreatures / attachProfessions.
|
|
1330
|
+
const runQuestLootGoBridge = () => {
|
|
1331
|
+
const nodeNames = new Set<string>();
|
|
1332
|
+
for (const n of gatherNodes.values()) {
|
|
1333
|
+
if (n.name) nodeNames.add(String(n.name).toLowerCase());
|
|
1334
|
+
}
|
|
1335
|
+
// spellOf after attachSpells fills lazy spell-data (Summon Rusty Chest 6464 etc.).
|
|
1336
|
+
bridgeQuestItemLootGoZones(
|
|
1337
|
+
loot,
|
|
1338
|
+
gameObjects,
|
|
1339
|
+
creatures,
|
|
1340
|
+
quests.values(),
|
|
1341
|
+
zones,
|
|
1342
|
+
nodeNames.size ? nodeNames : undefined,
|
|
1343
|
+
(id) => spells.get(id),
|
|
1344
|
+
locksById,
|
|
1345
|
+
ensureLoot,
|
|
1346
|
+
);
|
|
1347
|
+
};
|
|
1348
|
+
runQuestLootGoBridge();
|
|
1349
|
+
|
|
1350
|
+
// Item sets (small; for E1 set bonuses)
|
|
1351
|
+
const itemSetsList: ItemSetDef[] = (b.itemSets ?? []).slice();
|
|
1352
|
+
const itemSets = new Map<number, ItemSetDef>(itemSetsList.map((s) => [s.id, s]));
|
|
1353
|
+
|
|
1354
|
+
// Playerbot companion name pool (optional; pre-seed bundles lack it)
|
|
1355
|
+
const botNamePool = { male: b.botNames?.male ?? [], female: b.botNames?.female ?? [] };
|
|
1356
|
+
|
|
1357
|
+
// Premade playerbot specs by class (optional; pre-seed bundles lack it)
|
|
1358
|
+
const botSpecsByClass = new Map<number, BotSpecDef[]>(
|
|
1359
|
+
Object.entries(b.botSpecs ?? {}).map(([k, v]) => [Number(k), v]),
|
|
1360
|
+
);
|
|
1361
|
+
|
|
1362
|
+
// R8b sounds (optional; pre-bake bundles may lack the keys)
|
|
1363
|
+
const spellSounds = new Map<number, string>(Object.entries(b.spellSounds ?? {}).map(([k, v]) => [Number(k), v]));
|
|
1364
|
+
const spellImpactSounds = new Map<number, string>(Object.entries(b.spellImpactSounds ?? {}).map(([k, v]) => [Number(k), v]));
|
|
1365
|
+
const creatureSounds = new Map<number, {
|
|
1366
|
+
aggro?: string; death?: string; attack?: string; greeting?: string; farewell?: string;
|
|
1367
|
+
}>(
|
|
1368
|
+
Object.entries(b.creatureSounds ?? {}).map(([k, v]) => [Number(k), v || {}])
|
|
1369
|
+
);
|
|
1370
|
+
const spellSoundStages = new Map<number, SpellSoundStages>(
|
|
1371
|
+
Object.entries(b.spellSoundStages ?? {}).map(([k, v]) => [Number(k), v])
|
|
1372
|
+
);
|
|
1373
|
+
// Normalized: entry -> CreatureSoundData id -> the shared set (263 sets for 5474 creatures).
|
|
1374
|
+
const creatureSoundSets = new Map<number, CreatureSoundStages>(
|
|
1375
|
+
Object.entries(b.creatureSoundSets ?? {}).map(([k, v]) => [Number(k), v])
|
|
1376
|
+
);
|
|
1377
|
+
const creatureSoundRef = new Map<number, number>(
|
|
1378
|
+
Object.entries(b.creatureSoundRef ?? {}).map(([k, v]) => [Number(k), Number(v)])
|
|
1379
|
+
);
|
|
1380
|
+
// `<race>-<gender>` -> the same CreatureSoundData ids, so the player shares creatureSoundSets.
|
|
1381
|
+
const raceSoundRef = new Map<string, number>(
|
|
1382
|
+
Object.entries(b.raceSoundRef ?? {}).map(([k, v]) => [k, Number(v)])
|
|
1383
|
+
);
|
|
1384
|
+
// NPCSounds is its own chain (gossip voice), normalized the same way: 156 sets, entry → id.
|
|
1385
|
+
const npcVoiceSets = new Map<number, NpcVoiceStages>(
|
|
1386
|
+
Object.entries(b.npcVoiceSets ?? {}).map(([k, v]) => [Number(k), v])
|
|
1387
|
+
);
|
|
1388
|
+
const npcVoiceRef = new Map<number, number>(
|
|
1389
|
+
Object.entries(b.npcVoiceRef ?? {}).map(([k, v]) => [Number(k), Number(v)])
|
|
1390
|
+
);
|
|
1391
|
+
const textVoice = new Map<number, string[]>(
|
|
1392
|
+
Object.entries(b.textVoice ?? {}).map(([k, v]) => [Number(k), v])
|
|
1393
|
+
);
|
|
1394
|
+
const weaponSounds = new Map<string, WeaponSoundSet>(Object.entries(b.weaponSounds ?? {}));
|
|
1395
|
+
const creatureWeapons = new Map<number, CreatureWeapon>(
|
|
1396
|
+
Object.entries(b.creatureWeapons ?? {}).map(([k, v]) => [Number(k), v])
|
|
1397
|
+
);
|
|
1398
|
+
const soundClipVolume = b.soundClipVolume ?? {};
|
|
1399
|
+
|
|
1400
|
+
// Combat skill meta: normalize JSON string keys → number keys for Record lookups.
|
|
1401
|
+
const rawMeta = b.combatSkillMeta;
|
|
1402
|
+
let combatMeta: CombatSkillMeta | undefined;
|
|
1403
|
+
if (rawMeta) {
|
|
1404
|
+
const numRec = <T>(r?: Record<number, T> | Record<string, T>): Record<number, T> | undefined => {
|
|
1405
|
+
if (!r) return undefined;
|
|
1406
|
+
const out: Record<number, T> = {};
|
|
1407
|
+
for (const [k, v] of Object.entries(r)) out[Number(k)] = v as T;
|
|
1408
|
+
return out;
|
|
1409
|
+
};
|
|
1410
|
+
combatMeta = {
|
|
1411
|
+
categories: numRec(rawMeta.categories),
|
|
1412
|
+
skillNames: numRec(rawMeta.skillNames),
|
|
1413
|
+
tiers: numRec(rawMeta.tiers),
|
|
1414
|
+
raceClass: rawMeta.raceClass,
|
|
1415
|
+
spellGrants: numRec(rawMeta.spellGrants),
|
|
1416
|
+
spellLearnSkills: numRec(rawMeta.spellLearnSkills),
|
|
1417
|
+
// SkillLineAbility rows for IsSpellFitByClassAndRace (weapon masters / trainers).
|
|
1418
|
+
spellAbilities: numRec(rawMeta.spellAbilities),
|
|
1419
|
+
// spell_learn_spell graph (Poisons 2842 → Instant Poison 8681, Beast Training → Growl, …).
|
|
1420
|
+
// Dropped here previously → train() never auto-taught dependents (KI-640).
|
|
1421
|
+
spellLearnSpells: numRec(rawMeta.spellLearnSpells),
|
|
1422
|
+
};
|
|
1423
|
+
// SPELL_EFFECT_PARRY (22): EffectMiscValue is the mono skill line (Parry 3127 → 90). SLA
|
|
1424
|
+
// learnOnGetSkill never maps it, so combatSkills[90] stayed undefined and CanParry never armed.
|
|
1425
|
+
const grants = combatMeta.spellGrants ?? (combatMeta.spellGrants = {});
|
|
1426
|
+
for (const s of allSpellDefs) {
|
|
1427
|
+
if (grants[s.id] != null) continue;
|
|
1428
|
+
for (const re of s.rawEffects ?? []) {
|
|
1429
|
+
if ((re.effect | 0) !== 22) continue;
|
|
1430
|
+
const skillId = (re.miscValue | 0);
|
|
1431
|
+
if (skillId > 0) {
|
|
1432
|
+
grants[s.id] = skillId;
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// Reputation factions / ranks / rates / spillover (Wave C). Guard `?? []` for pre-E1 bundles.
|
|
1440
|
+
const factionsList: FactionDef[] = b.factions ?? [];
|
|
1441
|
+
const factionsById = new Map(factionsList.map((f) => [f.id, f]));
|
|
1442
|
+
const factionTemplatesById = new Map((b.factionTemplates ?? []).map((t) => [t.id, t]));
|
|
1443
|
+
const reputationRanksList: ReputationRankDef[] = b.reputationRanks ?? [];
|
|
1444
|
+
const reputationRatesList: ReputationRateDef[] = b.reputationRates ?? [];
|
|
1445
|
+
const reputationSpilloverList: ReputationSpilloverDef[] = b.reputationSpillover ?? [];
|
|
1446
|
+
|
|
1447
|
+
// Materialize hollow percent-stat effects (aura 80/137) once, then cache — `settled` is what
|
|
1448
|
+
// keeps this a Map lookup at ~10k/s in a pull. Both mutations are idempotent and one-shot.
|
|
1449
|
+
const settled = new WeakSet<SpellDef>();
|
|
1450
|
+
const materializeSpell = (s: SpellDef): SpellDef => {
|
|
1451
|
+
if (settled.has(s)) return s;
|
|
1452
|
+
// Food/drink tooltips: per-tick → total (KI-147). Safe when effects already filled.
|
|
1453
|
+
if ((s.effects?.length ?? 0) > 0) {
|
|
1454
|
+
patchFoodDrinkDescriptions(s);
|
|
1455
|
+
settled.add(s);
|
|
1456
|
+
return s;
|
|
1457
|
+
}
|
|
1458
|
+
if (!(s.rawEffects?.length)) { settled.add(s); return s; }
|
|
1459
|
+
const filled = fillEmptyEffectsFromRaw(s);
|
|
1460
|
+
if (filled === s || !(filled.effects?.length)) { settled.add(s); return s; }
|
|
1461
|
+
patchFoodDrinkDescriptions(filled);
|
|
1462
|
+
settled.add(filled);
|
|
1463
|
+
spells.set(s.id, filled);
|
|
1464
|
+
// Keep allSpellDefs in sync for allSpells() / census iterators.
|
|
1465
|
+
const idx = spellIndexById.get(s.id);
|
|
1466
|
+
if (idx != null && idx >= 0) allSpellDefs[idx] = filled;
|
|
1467
|
+
return filled;
|
|
1468
|
+
};
|
|
1469
|
+
|
|
1470
|
+
return {
|
|
1471
|
+
spell: (id) => {
|
|
1472
|
+
const s = spells.get(id);
|
|
1473
|
+
return s ? materializeSpell(s) : undefined;
|
|
1474
|
+
},
|
|
1475
|
+
creature: (entry) => creatures.get(entry),
|
|
1476
|
+
creatures: () => [...creatures.values()],
|
|
1477
|
+
item: (entry) => items.get(entry),
|
|
1478
|
+
items: () => allItemDefs,
|
|
1479
|
+
allItems: () => allItemDefs,
|
|
1480
|
+
itemCatalogComplete: () => bulkItemsPresent,
|
|
1481
|
+
lootTable: (lootId) => ensureLoot(normalizeLootKey(lootId)),
|
|
1482
|
+
zoneWorldGen: () => zoneWorldGen(),
|
|
1483
|
+
lootKeysForItem: (itemId) => lootKeysForItemImpl(itemId),
|
|
1484
|
+
lootHasItem: (lootId, itemId) => {
|
|
1485
|
+
if (lootId == null) return false;
|
|
1486
|
+
const id = itemId | 0;
|
|
1487
|
+
let set = itemLootSets.get(id);
|
|
1488
|
+
if (set === undefined) {
|
|
1489
|
+
set = new Set(lootKeysForItemImpl(id));
|
|
1490
|
+
itemLootSets.set(id, set);
|
|
1491
|
+
}
|
|
1492
|
+
return set.has(normalizeLootKey(lootId));
|
|
1493
|
+
},
|
|
1494
|
+
lootTemplateIds: () => {
|
|
1495
|
+
if (!compactLoot.size) lootLoader?.();
|
|
1496
|
+
if (!compactLoot.size) return [...loot.keys()];
|
|
1497
|
+
const ids = [...compactLoot.keys()];
|
|
1498
|
+
for (const k of loot.keys()) if (!compactLoot.has(k)) ids.push(k);
|
|
1499
|
+
return ids;
|
|
1500
|
+
},
|
|
1501
|
+
lock: (lockId) => locksById.get(lockId),
|
|
1502
|
+
setLootLoader: (fn) => { lootLoader = fn; },
|
|
1503
|
+
lootAttached: () => attachedCompact != null,
|
|
1504
|
+
ensureLoot: () => {
|
|
1505
|
+
if (attachedCompact) return Promise.resolve();
|
|
1506
|
+
lootLoader?.();
|
|
1507
|
+
if (attachedCompact) return Promise.resolve();
|
|
1508
|
+
return new Promise<void>((resolve) => { lootWaiters.push(resolve); });
|
|
1509
|
+
},
|
|
1510
|
+
attachLoot: (compact) => {
|
|
1511
|
+
attachedCompact = compact;
|
|
1512
|
+
invalidateItemLootIndex();
|
|
1513
|
+
for (const id in compact) {
|
|
1514
|
+
const k = normalizeLootKey(id);
|
|
1515
|
+
const rows = compact[id]!;
|
|
1516
|
+
compactLoot.set(k, rows);
|
|
1517
|
+
if (typeof k === 'string' && k.startsWith('go:')) loot.set(k, decodeLootTable(rows));
|
|
1518
|
+
}
|
|
1519
|
+
sanitizeAttachedLoot();
|
|
1520
|
+
dualWriteGoLootDiscoverability(loot, ensureLoot);
|
|
1521
|
+
bridgeGoStarterSrcItemLoot(loot, gameObjects, quests.values(), zones, ensureLoot);
|
|
1522
|
+
runQuestLootGoBridge();
|
|
1523
|
+
// Again after the bridges: sanitizeAttachedLoot's invalidate ran before them.
|
|
1524
|
+
invalidateItemLootIndex();
|
|
1525
|
+
if (lootWaiters.length) {
|
|
1526
|
+
const waiters = lootWaiters;
|
|
1527
|
+
lootWaiters = [];
|
|
1528
|
+
for (const w of waiters) w();
|
|
1529
|
+
}
|
|
1530
|
+
},
|
|
1531
|
+
attachProfessions: (chunk) => {
|
|
1532
|
+
if (chunk.professions) { professionList = chunk.professions; professions.clear(); for (const p of professionList) professions.set(p.id, p); }
|
|
1533
|
+
if (chunk.recipes) { recipeList = chunk.recipes; recipes.clear(); for (const r of recipeList) recipes.set(r.id, r); }
|
|
1534
|
+
{
|
|
1535
|
+
const sliced = ensurePoisonsProfessionSlice({
|
|
1536
|
+
professions: professionList,
|
|
1537
|
+
recipes: recipeList,
|
|
1538
|
+
spellOf: (id) => spells.get(id),
|
|
1539
|
+
});
|
|
1540
|
+
if (sliced.professions !== professionList) {
|
|
1541
|
+
professionList = sliced.professions;
|
|
1542
|
+
professions.clear();
|
|
1543
|
+
for (const p of professionList) professions.set(p.id, p);
|
|
1544
|
+
}
|
|
1545
|
+
if (sliced.recipes !== recipeList) {
|
|
1546
|
+
recipeList = sliced.recipes;
|
|
1547
|
+
recipes.clear();
|
|
1548
|
+
for (const r of recipeList) recipes.set(r.id, r);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
if (chunk.gatherNodes) {
|
|
1552
|
+
gatherNodeList = chunk.gatherNodes;
|
|
1553
|
+
gatherNodes.clear();
|
|
1554
|
+
for (const n of chunk.gatherNodes) gatherNodes.set(n.id, n);
|
|
1555
|
+
skinGap.invalidate();
|
|
1556
|
+
}
|
|
1557
|
+
if (chunk.profTrainers) { profTrainers.clear(); for (const t of chunk.profTrainers) profTrainers.set(t.entry, t); }
|
|
1558
|
+
if (chunk.skinnable) { skinnable.clear(); for (const s of chunk.skinnable) skinnable.set(s.creature, s.node); skinGap.invalidate(); }
|
|
1559
|
+
if (chunk.enchants) { enchantList = chunk.enchants; enchants.clear(); for (const e of enchantList) enchants.set(e.id, e); }
|
|
1560
|
+
if (chunk.enchantRods) { enchantRodList = chunk.enchantRods; enchantRods.clear(); for (const r of enchantRodList) enchantRods.set(r.entry, r.tier); }
|
|
1561
|
+
if (chunk.disenchantRules) disenchantRules = chunk.disenchantRules;
|
|
1562
|
+
runQuestLootGoBridge();
|
|
1563
|
+
},
|
|
1564
|
+
attachSpells: (chunk) => {
|
|
1565
|
+
if (!chunk?.spells?.length) return;
|
|
1566
|
+
const added: SpellDef[] = [];
|
|
1567
|
+
for (const raw of chunk.spells) {
|
|
1568
|
+
if (!spells.has(raw.id)) {
|
|
1569
|
+
// Same construction materializers, in the same order.
|
|
1570
|
+
const s = withPetLearnTeachesIfEmpty(withResurrectEffectsIfEmpty(withRuntimeSpellFlags(raw)));
|
|
1571
|
+
hydrateSpellFormAttrs(s);
|
|
1572
|
+
retagBreakableStunOnSpell(s);
|
|
1573
|
+
retagDisorientOnSpell(s);
|
|
1574
|
+
hydrateSchoolImmunityOnSpell(s);
|
|
1575
|
+
stampMissingSpeedFromRaw(s);
|
|
1576
|
+
stampMissingRatingBuffsFromRaw(s);
|
|
1577
|
+
stampMissingCharmFromRaw(s);
|
|
1578
|
+
materializeModHealthRegenInCombat(s);
|
|
1579
|
+
materializeModDamagePercentTaken(s);
|
|
1580
|
+
materializeSpellReputation(s);
|
|
1581
|
+
patchCannibalizeHeal(s);
|
|
1582
|
+
const normalized = normalizeIceBlock(s);
|
|
1583
|
+
spells.set(normalized.id, normalized);
|
|
1584
|
+
added.push(normalized);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
if (added.length > 0) {
|
|
1588
|
+
const from = allSpellDefs.length;
|
|
1589
|
+
allSpellDefs = allSpellDefs.concat(added);
|
|
1590
|
+
reindexSpells(from);
|
|
1591
|
+
}
|
|
1592
|
+
},
|
|
1593
|
+
attachGameObjects: (chunk) => {
|
|
1594
|
+
if (!chunk?.gameObjects?.length) return;
|
|
1595
|
+
for (const g of chunk.gameObjects) {
|
|
1596
|
+
if (!gameObjects.has(g.entry)) {
|
|
1597
|
+
gameObjects.set(g.entry, g);
|
|
1598
|
+
gameObjectList.push(g);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
// SrcItem ↔ goStarter bridge once GO defs exist (loot may already be attached).
|
|
1602
|
+
bridgeGoStarterSrcItemLoot(loot, gameObjects, quests.values(), zones, ensureLoot);
|
|
1603
|
+
dualWriteGoLootDiscoverability(loot, ensureLoot);
|
|
1604
|
+
patchLegendsOfTheEarthQuest(quests.values(), gameObjects);
|
|
1605
|
+
runQuestLootGoBridge();
|
|
1606
|
+
invalidateItemLootIndex();
|
|
1607
|
+
},
|
|
1608
|
+
attachItems: (chunk) => {
|
|
1609
|
+
if (!chunk?.items?.length) return;
|
|
1610
|
+
const added: ItemDef[] = [];
|
|
1611
|
+
for (const it of chunk.items) {
|
|
1612
|
+
if (!items.has(it.entry)) {
|
|
1613
|
+
items.set(it.entry, it);
|
|
1614
|
+
added.push(it);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if (added.length > 0) {
|
|
1618
|
+
allItemDefs = allItemDefs.concat(added);
|
|
1619
|
+
// Taming rods may land only in the lazy chunk — stamp useSpell on the new set.
|
|
1620
|
+
applyTamingRodUseSpells(added);
|
|
1621
|
+
}
|
|
1622
|
+
if (allItemDefs.length >= BULK_ITEM_FLOOR) bulkItemsPresent = true;
|
|
1623
|
+
},
|
|
1624
|
+
attachCreatures: (list) => {
|
|
1625
|
+
for (const c of list ?? []) {
|
|
1626
|
+
if (creatures.has(c.entry)) continue;
|
|
1627
|
+
const name = stripInternalNameTags(c.name);
|
|
1628
|
+
creatures.set(c.entry, name === c.name ? c : { ...c, name });
|
|
1629
|
+
}
|
|
1630
|
+
skinGap.invalidate(); // KI-904/905
|
|
1631
|
+
// AFTER the real rows, never at construction: `creatures` is the empty stub there, so
|
|
1632
|
+
// stubbing every flight master up front made all 61 hp-1/faction-0. Cite: KI-670.
|
|
1633
|
+
ensureFlightMasterCreatures(b.zones ?? [], creatures);
|
|
1634
|
+
patchBeliaPetTrainer(b.zones ?? [], creatures);
|
|
1635
|
+
patchWindshearVerminDamage(creatures);
|
|
1636
|
+
// Creature names power KI-689 quest-loot GO zone co-location.
|
|
1637
|
+
runQuestLootGoBridge();
|
|
1638
|
+
invalidateItemLootIndex();
|
|
1639
|
+
// KI-826 farm scrub needs creature levels — re-run after lazy creatures attach.
|
|
1640
|
+
if (quests.size > 0) {
|
|
1641
|
+
patchAbsurdQuestFarms(quests.values(), creatures, zones.values());
|
|
1642
|
+
rebuildQuestTargetHuntable();
|
|
1643
|
+
}
|
|
1644
|
+
},
|
|
1645
|
+
attachQuests: (list) => {
|
|
1646
|
+
for (const q of list ?? []) if (!quests.has(q.entry)) quests.set(q.entry, q);
|
|
1647
|
+
sanitizeAttachedLoot();
|
|
1648
|
+
// Garment heal quests live in the lazy chunk — patching b.quests at construction ran
|
|
1649
|
+
// against the empty stub and never fired. Cite: KI-478.
|
|
1650
|
+
patchGarmentHealQuests(quests.values());
|
|
1651
|
+
// KI-784: strip classic-db bogus IF Honored gate on Young Crocolisk Skins (484).
|
|
1652
|
+
patchBogusQuestMinRepGates(quests.values());
|
|
1653
|
+
patchLegendsOfTheEarthQuest(quests.values(), gameObjects);
|
|
1654
|
+
// KI-826: strip L42 reverse-loot farms from low-level quests once creatures are present.
|
|
1655
|
+
if (creatures.size > 0) patchAbsurdQuestFarms(quests.values(), creatures, zones.values());
|
|
1656
|
+
// KI-873: lazy quest rows may carry starterZones the boot stub lacked — re-union.
|
|
1657
|
+
if ((list ?? []).length) patchZoneQuestsFromStarterZones(zones.values(), list ?? []);
|
|
1658
|
+
// Rebuild after garment heal-proxy killTargets are cleared (KI-1037/1041/1049).
|
|
1659
|
+
rebuildQuestTargetHuntable();
|
|
1660
|
+
rebuildKillByZone();
|
|
1661
|
+
questKillHuntableGen += 1;
|
|
1662
|
+
// Lazy quest chunk may arrive after loot/GOs — re-bridge SrcItem goStarters.
|
|
1663
|
+
bridgeGoStarterSrcItemLoot(loot, gameObjects, quests.values(), zones, ensureLoot);
|
|
1664
|
+
dualWriteGoLootDiscoverability(loot, ensureLoot);
|
|
1665
|
+
runQuestLootGoBridge();
|
|
1666
|
+
invalidateItemLootIndex();
|
|
1667
|
+
},
|
|
1668
|
+
attachWorldTables: (extra) => {
|
|
1669
|
+
if (!extra) return;
|
|
1670
|
+
Object.assign(b, extra);
|
|
1671
|
+
npcTextIdx = null; // the only derived read; rebuilt lazily on the next npcText()
|
|
1672
|
+
},
|
|
1673
|
+
attachSounds: (chunk) => {
|
|
1674
|
+
if (!chunk) return;
|
|
1675
|
+
// Filled in place so every accessor closure createDataStore already handed out sees them.
|
|
1676
|
+
const fill = <V>(m: Map<number, V>, r: Record<number, V> | undefined): void => {
|
|
1677
|
+
for (const [k, v] of Object.entries(r ?? {})) m.set(Number(k), v);
|
|
1678
|
+
};
|
|
1679
|
+
fill(spellSounds, chunk.spellSounds);
|
|
1680
|
+
fill(spellImpactSounds, chunk.spellImpactSounds);
|
|
1681
|
+
for (const [k, v] of Object.entries(chunk.creatureSounds ?? {})) {
|
|
1682
|
+
creatureSounds.set(Number(k), v || {});
|
|
1683
|
+
}
|
|
1684
|
+
fill(spellSoundStages, chunk.spellSoundStages);
|
|
1685
|
+
fill(creatureSoundSets, chunk.creatureSoundSets);
|
|
1686
|
+
fill(creatureSoundRef, chunk.creatureSoundRef);
|
|
1687
|
+
for (const [k, v] of Object.entries(chunk.raceSoundRef ?? {})) raceSoundRef.set(k, Number(v));
|
|
1688
|
+
fill(npcVoiceSets, chunk.npcVoiceSets);
|
|
1689
|
+
fill(npcVoiceRef, chunk.npcVoiceRef);
|
|
1690
|
+
fill(textVoice, chunk.textVoice);
|
|
1691
|
+
fill(creatureWeapons, chunk.creatureWeapons);
|
|
1692
|
+
for (const [k, v] of Object.entries(chunk.weaponSounds ?? {})) weaponSounds.set(k, v);
|
|
1693
|
+
},
|
|
1694
|
+
quest: (entry) => quests.get(entry),
|
|
1695
|
+
quests: () => [...quests.values()],
|
|
1696
|
+
trainer: (entry) => {
|
|
1697
|
+
// Zone lists carry `{ cls, entry }` refs, so accept the bare entry number or the ref object
|
|
1698
|
+
// and let a caller passing a zone list element through resolve the same catalog.
|
|
1699
|
+
const id =
|
|
1700
|
+
typeof entry === 'object' && entry != null && 'entry' in (entry as object)
|
|
1701
|
+
? Number((entry as { entry: number }).entry)
|
|
1702
|
+
: Number(entry);
|
|
1703
|
+
// Legacy synthetic class-trainer ids (1000+cls) still resolve: KI-918 moved them to
|
|
1704
|
+
// 1000*1000+cls, and src_npc_trainer has no rows in 1001-1012, so the old id shadows nothing.
|
|
1705
|
+
const t = trainers.get(id) ?? trainers.get(normalizeSyntheticTrainerEntry(id));
|
|
1706
|
+
return t ? withCoreTrainerSpells(t) : undefined;
|
|
1707
|
+
},
|
|
1708
|
+
trainers: () => [...trainers.values()].map((t) => withCoreTrainerSpells(t)),
|
|
1709
|
+
vendor: (entry) => vendors.get(entry),
|
|
1710
|
+
vendors: () => [...vendors.values()],
|
|
1711
|
+
createInfo: (race, cls) => createInfo.get(duoKey(race, cls)),
|
|
1712
|
+
levelStats: (race, cls, level) => levelStats.get(triKey(race, cls, level)),
|
|
1713
|
+
classLevel: (cls, level) => {
|
|
1714
|
+
const r = classLevel.get(duoKey(cls, level));
|
|
1715
|
+
return r ? { baseHp: r.baseHp, baseMana: r.baseMana } : undefined;
|
|
1716
|
+
},
|
|
1717
|
+
creatureClassLvlStats: (level, unitClass) => {
|
|
1718
|
+
// ObjectMgr::GetCreatureClassLvlStats validity (ObjectMgr.cpp ~795–796)
|
|
1719
|
+
const r = creatureCls.get(duoKey(level, unitClass));
|
|
1720
|
+
if (!r) return undefined;
|
|
1721
|
+
const stamina = r.stamina ?? 0;
|
|
1722
|
+
if ((r.baseHealth !== 0 || stamina > 0) && r.baseDamage > 0.1) return r;
|
|
1723
|
+
return undefined;
|
|
1724
|
+
},
|
|
1725
|
+
petLevelStats: (creatureEntry, level) => {
|
|
1726
|
+
const L = Math.max(1, Math.min(60, level | 0));
|
|
1727
|
+
const direct = petLvl.get(duoKey(creatureEntry, L));
|
|
1728
|
+
if (direct) return direct;
|
|
1729
|
+
// Gap-fill: walk down to the nearest lower level present (ObjectMgr.cpp ~3030–3038).
|
|
1730
|
+
const levels = petLvlByEntry.get(creatureEntry);
|
|
1731
|
+
if (!levels || levels.length === 0) return undefined;
|
|
1732
|
+
let best: number | undefined;
|
|
1733
|
+
for (const lv of levels) {
|
|
1734
|
+
if (lv <= L) best = lv;
|
|
1735
|
+
else break;
|
|
1736
|
+
}
|
|
1737
|
+
return best != null ? petLvl.get(duoKey(creatureEntry, best)) : undefined;
|
|
1738
|
+
},
|
|
1739
|
+
petAbilityTP: (spellId) => {
|
|
1740
|
+
const v = petAbilityTpMap[String(spellId)];
|
|
1741
|
+
return typeof v === 'number' && v > 0 ? v : 0;
|
|
1742
|
+
},
|
|
1743
|
+
petGenericSpells: () => petGenericSpellList,
|
|
1744
|
+
weaponEnchantProc: (enchantId: number) => weaponEnchantProcMap[String(enchantId | 0)],
|
|
1745
|
+
itemEnchant: (enchantId: number) => itemEnchantMap[String(enchantId | 0)],
|
|
1746
|
+
enchantApplicator: (spellId: number) => enchantApplicatorMap[String(spellId | 0)],
|
|
1747
|
+
xpToNext: (level) => xp.get(level) ?? Infinity,
|
|
1748
|
+
zone: (id) => zonePruned(zones.get(id)),
|
|
1749
|
+
// Prune here too — `zones()` used to bypass the filter entirely, so browse and autopilot
|
|
1750
|
+
// disagreed about what was fightable in a zone.
|
|
1751
|
+
zones: () => b.zones.map((z) => zonePruned(z) as ZoneDef),
|
|
1752
|
+
race: (id) => races.get(id),
|
|
1753
|
+
races: () => racesList,
|
|
1754
|
+
allSpells: () => {
|
|
1755
|
+
// Materialize on demand so census / kit builders see filled effects.
|
|
1756
|
+
for (let i = 0; i < allSpellDefs.length; i++) {
|
|
1757
|
+
allSpellDefs[i] = materializeSpell(allSpellDefs[i]!);
|
|
1758
|
+
}
|
|
1759
|
+
return allSpellDefs;
|
|
1760
|
+
},
|
|
1761
|
+
profession: (id) => professions.get(id),
|
|
1762
|
+
professions: () => professionList,
|
|
1763
|
+
recipe: (id) => recipes.get(id),
|
|
1764
|
+
recipes: () => recipeList,
|
|
1765
|
+
gatherNode: (id) => gatherNodes.get(id),
|
|
1766
|
+
gatherNodes: () => gatherNodeList,
|
|
1767
|
+
profTrainer: (entry) => profTrainers.get(entry),
|
|
1768
|
+
skinNode: (creatureEntry) => skinGap.resolve(creatureEntry),
|
|
1769
|
+
enchant: (id) => enchants.get(id),
|
|
1770
|
+
enchants: () => enchantList,
|
|
1771
|
+
enchantRodTier: (entry) => enchantRods.get(entry),
|
|
1772
|
+
enchantRods: () => enchantRodList,
|
|
1773
|
+
disenchantRules: () => disenchantRules,
|
|
1774
|
+
instance: (id) => instances.get(id),
|
|
1775
|
+
instances: () => instanceList,
|
|
1776
|
+
isInstanceCreature: (entry) => raidHuntableBlock.has(entry | 0),
|
|
1777
|
+
isBattlegroundCreature: (entry) => bgHuntableBlock.has(entry | 0),
|
|
1778
|
+
battlegroundForCreature: (entry) => {
|
|
1779
|
+
const e = entry | 0;
|
|
1780
|
+
if (!bgHuntableBlock.has(e)) return undefined;
|
|
1781
|
+
for (const mapId of CLASSIC_BG_MAP_IDS) {
|
|
1782
|
+
if (censusByMap?.[String(mapId)]?.entries?.[String(e)] != null) {
|
|
1783
|
+
return CLASSIC_BG_NAME_BY_MAP[mapId as 30 | 489 | 529];
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
return undefined;
|
|
1787
|
+
},
|
|
1788
|
+
instanceForCreature: (entry) => {
|
|
1789
|
+
const e = entry | 0;
|
|
1790
|
+
// Authored encounter first: it is the only signal that knows which WING fights this
|
|
1791
|
+
// creature, and shared maps (SM, BRS, Strat, Dire Maul) would otherwise name the wrong door.
|
|
1792
|
+
const byEncounter = instanceList.find((i) => encounterHasCreature(i, e));
|
|
1793
|
+
if (byEncounter) return byEncounter;
|
|
1794
|
+
const mapId = instanceMapOfCreature(e);
|
|
1795
|
+
if (mapId == null) return undefined;
|
|
1796
|
+
const onMap = instanceList.filter((i) => i.mapId === mapId);
|
|
1797
|
+
if (onMap.length <= 1) return onMap[0];
|
|
1798
|
+
// Trash / mid-fight adds no encounter names: fall back to the whole instance, built by
|
|
1799
|
+
// cutting the wing suffix off ("Stratholme (Live)" -> "Stratholme"). No suffix, no name.
|
|
1800
|
+
const cut = onMap[0]!.name.search(/ - | \(/);
|
|
1801
|
+
if (cut <= 0) return undefined;
|
|
1802
|
+
const shared = onMap[0]!.name.slice(0, cut);
|
|
1803
|
+
return onMap.every((i) => i.name.startsWith(shared)) ? { ...onMap[0]!, name: shared } : undefined;
|
|
1804
|
+
},
|
|
1805
|
+
talentTrees: (classId) => (treesByClass.get(classId) ?? []).slice().sort((a, b) => a.order - b.order),
|
|
1806
|
+
talent: (id) => talentById.get(id),
|
|
1807
|
+
trainerClass: (entry) => trainerClass.get(entry | 0) ?? 0,
|
|
1808
|
+
// GameObjects (chests/containers/quest nodes) — merge from attach if split
|
|
1809
|
+
gameObject: (entry) => gameObjects.get(entry),
|
|
1810
|
+
gameObjects: () => gameObjectList,
|
|
1811
|
+
itemSet: (id) => itemSets.get(id),
|
|
1812
|
+
itemSets: () => itemSetsList,
|
|
1813
|
+
botNames: () => botNamePool,
|
|
1814
|
+
botSpecs: (cls) => botSpecsByClass.get(cls) ?? [],
|
|
1815
|
+
botPopulation: () => b.botPopulation?.classRaceProb ?? {},
|
|
1816
|
+
spellStages: (id) => spellSoundStages.get(id),
|
|
1817
|
+
creatureSoundSet: (entry) => {
|
|
1818
|
+
const sd = creatureSoundRef.get(entry);
|
|
1819
|
+
return sd == null ? undefined : creatureSoundSets.get(sd);
|
|
1820
|
+
},
|
|
1821
|
+
raceSoundSet: (race, gender) => {
|
|
1822
|
+
const sd = raceSoundRef.get(`${race}-${gender === 1 ? 1 : 0}`);
|
|
1823
|
+
return sd == null ? undefined : creatureSoundSets.get(sd);
|
|
1824
|
+
},
|
|
1825
|
+
npcVoice: (entry) => {
|
|
1826
|
+
const ns = npcVoiceRef.get(entry);
|
|
1827
|
+
return ns == null ? undefined : npcVoiceSets.get(ns);
|
|
1828
|
+
},
|
|
1829
|
+
textVoice: (textId) => textVoice.get(textId),
|
|
1830
|
+
clipVolume: (key) => soundClipVolume[key] ?? 1,
|
|
1831
|
+
weaponSounds: (subclass, metal) => weaponSounds.get(`${subclass}-${metal ? 1 : 0}`),
|
|
1832
|
+
creatureWeapon: (entry) => creatureWeapons.get(entry),
|
|
1833
|
+
spellSound: (id) => spellSounds.get(id),
|
|
1834
|
+
spellImpact: (id) => spellImpactSounds.get(id),
|
|
1835
|
+
creatureSound: (entry) => creatureSounds.get(entry),
|
|
1836
|
+
combatSkillMeta: () => combatMeta,
|
|
1837
|
+
factions: () => factionsList,
|
|
1838
|
+
faction: (id) => factionsById.get(id),
|
|
1839
|
+
factionTemplate: (id) => factionTemplatesById.get(id),
|
|
1840
|
+
reputationRanks: () => reputationRanksList,
|
|
1841
|
+
reputationRates: () => reputationRatesList,
|
|
1842
|
+
reputationSpillover: () => reputationSpilloverList,
|
|
1843
|
+
explorationBasexp: () => b.explorationBasexp ?? [],
|
|
1844
|
+
graveyards: () => b.graveyards ?? [],
|
|
1845
|
+
gameEvents: () => b.gameEvents ?? [],
|
|
1846
|
+
eventScripts: (eventId: number) => {
|
|
1847
|
+
if (!eventId) return [];
|
|
1848
|
+
return b.dbScripts?.event?.[String(eventId)] ?? [];
|
|
1849
|
+
},
|
|
1850
|
+
// GameObject.cpp Use → dbscripts_on_go_template_use (Dark Keeper portraits, …).
|
|
1851
|
+
goUseScripts: (goEntry: number) => {
|
|
1852
|
+
if (!goEntry) return [];
|
|
1853
|
+
return b.dbScripts?.goTemplateUse?.[String(goEntry)] ?? [];
|
|
1854
|
+
},
|
|
1855
|
+
goGuidUseScripts: (goGuid: number) => {
|
|
1856
|
+
if (!goGuid) return [];
|
|
1857
|
+
return b.dbScripts?.goUse?.[String(goGuid)] ?? [];
|
|
1858
|
+
},
|
|
1859
|
+
gossipScripts: (scriptId: number) => {
|
|
1860
|
+
if (!scriptId) return [];
|
|
1861
|
+
return b.dbScripts?.gossip?.[String(scriptId)] ?? [];
|
|
1862
|
+
},
|
|
1863
|
+
// Cite: ScriptMgr.cpp:2938–2948 ScriptsStart(SCRIPT_TYPE_RELAY, chosenId).
|
|
1864
|
+
relayScripts: (relayId: number) => {
|
|
1865
|
+
if (!relayId) return [];
|
|
1866
|
+
return b.dbScripts?.relay?.[String(relayId)] ?? [];
|
|
1867
|
+
},
|
|
1868
|
+
// Cite: WaypointMovementGenerator.cpp:183–186 ScriptsStart(SCRIPT_TYPE_CREATURE_MOVEMENT, …).
|
|
1869
|
+
creatureMovementScripts: (scriptId: number) => {
|
|
1870
|
+
if (!scriptId) return [];
|
|
1871
|
+
return b.dbScripts?.creatureMovement?.[String(scriptId)] ?? [];
|
|
1872
|
+
},
|
|
1873
|
+
// Cite: Unit.cpp:1382 ScriptsStart(SCRIPT_TYPE_CREATURE_DEATH, victim->GetEntry(), …).
|
|
1874
|
+
creatureDeathScripts: (entry: number) => {
|
|
1875
|
+
if (!entry) return [];
|
|
1876
|
+
return b.dbScripts?.creatureDeath?.[String(entry)] ?? [];
|
|
1877
|
+
},
|
|
1878
|
+
// Cite: SpellEffects.cpp:1529–1531 / :4189–4191 ScriptsStart(SCRIPT_TYPE_SPELL, …).
|
|
1879
|
+
spellScripts: (spellId: number) => {
|
|
1880
|
+
if (!spellId) return [];
|
|
1881
|
+
return b.dbScripts?.spell?.[String(spellId)] ?? [];
|
|
1882
|
+
},
|
|
1883
|
+
pools: () => b.pools ?? [],
|
|
1884
|
+
zoneWeather: () => b.zoneWeather ?? [],
|
|
1885
|
+
conditionsList: () => b.conditions ?? [],
|
|
1886
|
+
combatCondition: (id: number) => combatConditionById().get(id | 0),
|
|
1887
|
+
unitCondition: (id: number) => unitConditionById().get(id | 0),
|
|
1888
|
+
gossipMenus: () => b.gossipMenus ?? [],
|
|
1889
|
+
npcText: (id: number) => npcTextById().get(id | 0),
|
|
1890
|
+
entranceYards: () => b.entranceYards ?? [],
|
|
1891
|
+
itemEnchantmentTemplate: (entry) => b.itemEnchantmentTemplates?.[entry],
|
|
1892
|
+
itemRandomProperty: (id) => b.itemRandomProperties?.[id],
|
|
1893
|
+
tavernTriggers: () => b.tavernTriggers ?? [],
|
|
1894
|
+
areatriggerTeleports: () => b.areatriggerTeleports ?? [],
|
|
1895
|
+
ridingSpells: () => b.ridingSpells ?? [],
|
|
1896
|
+
spellChain: (spellId) => b.spellChain?.[spellId | 0],
|
|
1897
|
+
travelEdges: () => b.travelEdges ?? [],
|
|
1898
|
+
travelAnchors: () => b.travelAnchors ?? {},
|
|
1899
|
+
taxiNodes: () => b.taxiNodes ?? [],
|
|
1900
|
+
taxiRoutes: () => b.taxiRoutes ?? [],
|
|
1901
|
+
transports: () => b.transports ?? [],
|
|
1902
|
+
spellTeleports: () => b.spellTeleports ?? [],
|
|
1903
|
+
};
|
|
1904
|
+
}
|