azeroth-shell 0.0.0-stage → 0.1.1
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 +35 -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 +144 -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,1572 @@
|
|
|
1
|
+
// Instanced-dungeon rules (Phase 5), pure and deterministic. Given an InstanceDef
|
|
2
|
+
// (static data) and an InstanceRun (per-character progress) it answers: what can I
|
|
3
|
+
// fight next, is the dungeon cleared, am I locked out? No DOM, no clock — any "now"
|
|
4
|
+
// is injected by the caller (the UI boundary), so this stays testable and the
|
|
5
|
+
// arch.test purity guard is satisfied.
|
|
6
|
+
//
|
|
7
|
+
// D-4 event primitives (flags, gates, wave/phase progress) live here as pure
|
|
8
|
+
// helpers; game.ts is the wiring layer only.
|
|
9
|
+
|
|
10
|
+
import type { DataStore } from '../data/dataStore.ts';
|
|
11
|
+
import type {
|
|
12
|
+
InstanceDef,
|
|
13
|
+
EncounterDef,
|
|
14
|
+
EncounterEvent,
|
|
15
|
+
EncounterPackMember,
|
|
16
|
+
} from '../data/schema.ts';
|
|
17
|
+
import {
|
|
18
|
+
checkAreaTriggerLock,
|
|
19
|
+
findEntranceTeleport,
|
|
20
|
+
type AreaTriggerTeleportRow,
|
|
21
|
+
} from '../domain/world/areaTriggerLocks.ts';
|
|
22
|
+
import type { PullFusion, RouteDensity } from './dungeonRoute.ts';
|
|
23
|
+
import {
|
|
24
|
+
DEFAULT_PULL_FUSION,
|
|
25
|
+
DEFAULT_ROUTE_DENSITY,
|
|
26
|
+
selectRouteEncounters,
|
|
27
|
+
} from './dungeonRoute.ts';
|
|
28
|
+
import type { CreatureLookup, SpellLookup, PackBudgetPreset } from './packBudget.ts';
|
|
29
|
+
import {
|
|
30
|
+
expandRouteByPackBudget,
|
|
31
|
+
packBudgetPresetFor,
|
|
32
|
+
} from './packBudget.ts';
|
|
33
|
+
import {
|
|
34
|
+
filterRouteByPoolRoll,
|
|
35
|
+
orderRouteByGeometry,
|
|
36
|
+
routeItemSources,
|
|
37
|
+
} from './dungeonRouteOrder.ts';
|
|
38
|
+
import type { NavFloorDoc, NavGrid } from './dungeonNav.ts';
|
|
39
|
+
import { dungeonGameObjectsForMap } from '../data/dungeonGameObjects.ts';
|
|
40
|
+
import {
|
|
41
|
+
STRATH_LIVE_POSTBOX,
|
|
42
|
+
postboxOrigin,
|
|
43
|
+
} from '../data/dungeons/stratholme-live.ts';
|
|
44
|
+
import type { DungeonPoolRoll, PoolMapSpawn } from './rareRoll.ts';
|
|
45
|
+
import { INSTANCE_DATA } from '../domain/scriptdev/types.ts';
|
|
46
|
+
import {
|
|
47
|
+
applyMajordomoSpawnOnPlayerEnter,
|
|
48
|
+
applyMoltenCoreRuneSpecial,
|
|
49
|
+
moltenCoreRuneField,
|
|
50
|
+
} from '../domain/scriptdev/moltenCore.ts';
|
|
51
|
+
|
|
52
|
+
/** Per-character progress through one active dungeon run. */
|
|
53
|
+
export interface InstanceRun {
|
|
54
|
+
dungeonId: string;
|
|
55
|
+
defeated: string[]; // encounter ids cleared so far this run
|
|
56
|
+
/**
|
|
57
|
+
* Free-walk floor progress: spawn guids already killed. `defeated` tracks ENCOUNTER ids,
|
|
58
|
+
* which the continuous floor does not use — so without this every mob you killed came back
|
|
59
|
+
* alive the moment the controller re-mounted ("Continue run" rebuilds the floor from the
|
|
60
|
+
* spawn bake). Optional: absent ≡ nothing cleared, so old saves stay valid.
|
|
61
|
+
*/
|
|
62
|
+
floorClearedGuids?: number[];
|
|
63
|
+
/**
|
|
64
|
+
* Guids that won dungeon `pool_creature` rolls at first mount. Restored on re-mount so
|
|
65
|
+
* rares / substitutes do not re-roll mid-run. Absent ≡ not rolled yet (legacy) — mount
|
|
66
|
+
* rolls once and stamps this. Empty array = map had no relevant pools (or all failed).
|
|
67
|
+
* Cite: MapPersistentStateMgr.cpp InitPools ~198-207; pools.json member chances only.
|
|
68
|
+
*/
|
|
69
|
+
floorPoolSpawnedGuids?: number[];
|
|
70
|
+
/**
|
|
71
|
+
* Guids that won the map's GAMEOBJECT pool rolls at first mount — the herb, vein, crate and
|
|
72
|
+
* chest positions that hold an object this run. Losers are candidate spots the DB allows and
|
|
73
|
+
* nothing stands on. Absent ≡ not rolled yet (legacy save): every candidate stays, which is
|
|
74
|
+
* what every surface did before the pool bake. Cite: PoolManager.cpp:1087 SpawnPool.
|
|
75
|
+
*/
|
|
76
|
+
floorPoolSpawnedGoGuids?: number[];
|
|
77
|
+
// Where the party stands on the free-walk floor. Absent ≡ start at the instance entrance. Restored on
|
|
78
|
+
// re-mount so a Continue does not teleport you back to the door.
|
|
79
|
+
floorPartyPos?: { x: number; y: number; z?: number };
|
|
80
|
+
/**
|
|
81
|
+
* Deaths this run has taken, for Alterac Valley's reinforcement race — one death is one
|
|
82
|
+
* reinforcement (BattleGroundAV.cpp:38-45 HandleKillPlayer). Counted in liveFloorRecovery's
|
|
83
|
+
* `chargeDeaths`, which is already the once-per-death moment. Optional: absent ≡ 0, so every
|
|
84
|
+
* existing save stays valid and every non-AV run simply never writes it.
|
|
85
|
+
*/
|
|
86
|
+
avDeaths?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Alterac Valley's capture board — node id -> owner/state/assault clock, mine id -> owner, and
|
|
89
|
+
* the mines' banked reinforcement income. State, not a read, because a node has an assault
|
|
90
|
+
* clock; see src/domain/bg/avInstanceNodes.ts. Optional throughout: absent ≡ the DB's own
|
|
91
|
+
* starting board, so every existing save stays valid and no non-AV run ever writes it.
|
|
92
|
+
*/
|
|
93
|
+
avNodes?: Record<string, { o: 'alliance' | 'horde' | null; s: 'owned' | 'assaulted'; m?: number }>;
|
|
94
|
+
avMines?: Record<string, 'alliance' | 'horde'>;
|
|
95
|
+
avMineTickAtMs?: number;
|
|
96
|
+
avMineIncome?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Instance flags set by boss-death `setsFlag`, object-use gates, HP phases, etc.
|
|
99
|
+
* Optional for save back-compat (absent ≡ []). Survives mid-run save without a
|
|
100
|
+
* SAVE_VERSION bump (migrate treats missing as empty).
|
|
101
|
+
*/
|
|
102
|
+
flags?: string[];
|
|
103
|
+
/**
|
|
104
|
+
* SPELL_AURA_FORCE_REACTION (139) map — Faction.dbc id → ReputationRank. Lives on the run
|
|
105
|
+
* because the crown is stripped on leaving the instance. Optional (absent ≡ none).
|
|
106
|
+
* Cite: instance_dire_maul.cpp:75-79; domain/creature/forcedReaction.ts.
|
|
107
|
+
*/
|
|
108
|
+
forcedReactions?: import('../domain/creature/forcedReaction.ts').ForcedReactionMap;
|
|
109
|
+
/**
|
|
110
|
+
* ScriptDev numeric instance data store (TYPE_* → NOT_STARTED/IN_PROGRESS/…).
|
|
111
|
+
* Written by GO-use / boss SetData paths. Optional — absent ≡ empty (all NOT_STARTED).
|
|
112
|
+
* Cite: sc_instance.h; stratholme.h TYPE_*; host handleGoUse / applyInstanceSetData.
|
|
113
|
+
*/
|
|
114
|
+
scriptInstanceData?: Record<number, number>;
|
|
115
|
+
// Stratholme postbox open counter (m_postboxesUsed). After the 2nd IN_PROGRESS write to TYPE_POSTMASTER
|
|
116
|
+
// the store promotes to SPECIAL. Cite: stratholme.cpp:463-471 TYPE_POSTMASTER SetData.
|
|
117
|
+
postboxesUsed?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Gnomeregan bomb faces (`instance_gnomeregan::m_asBombFaces`). On the RUN because the C++
|
|
120
|
+
* keeps it on the instance, not on the boss AI: `DoActivateBombFace` is reached from the boss
|
|
121
|
+
* spell script and `DoDeactivateBombFace` from `GOUse_go_gnomeface_button`, and both go through
|
|
122
|
+
* the instance. It lived on the UI-layer dungeon ctx here, so a click that was not a hand click
|
|
123
|
+
* could not see it. Absent = no run has engaged him (all faces off).
|
|
124
|
+
* Cite: instance_gnomeregan.cpp:250-274; gnomeregan.h:47-51 sBombFace; boss_thermaplugg.cpp:263-281.
|
|
125
|
+
*/
|
|
126
|
+
thermapluggFaces?: import('../data/dungeons/gnomeregan.ts').ThermapluggFightState;
|
|
127
|
+
// Multi-step objectGate progress: setsFlag → successful use count toward needUses. Optional — absent ≡
|
|
128
|
+
// {}. Survives mid-run save without SAVE_VERSION bump.
|
|
129
|
+
objectGateUses?: Record<string, number>;
|
|
130
|
+
// Multi-entry objectGate: setsFlag → goEntries already counted (each entry ≤1). Optional — absent ≡ {}.
|
|
131
|
+
// Used when gate.goEntries has length > 1.
|
|
132
|
+
objectGateUsedEntries?: Record<string, number[]>;
|
|
133
|
+
/**
|
|
134
|
+
* ScriptDev summon_count queue from GO-use (e.g. 3× Undead Postman per postbox).
|
|
135
|
+
* Optional coords = authored GO origin for floor inject; encounterId pins the
|
|
136
|
+
* summoned-body guid (Malown → postmaster-malown). Absent ≡ none pending.
|
|
137
|
+
* Cite: stratholmeScripts.cpp:110,118-122; docs/dungeons/malown/README.md.
|
|
138
|
+
*/
|
|
139
|
+
scriptPendingSummons?: Array<{
|
|
140
|
+
creatureEntry: number;
|
|
141
|
+
count: number;
|
|
142
|
+
x?: number;
|
|
143
|
+
y?: number;
|
|
144
|
+
z?: number;
|
|
145
|
+
/** Floor guid = summonedBodyGuid(encounterId) for the first body of this row. */
|
|
146
|
+
encounterId?: string;
|
|
147
|
+
}>;
|
|
148
|
+
/**
|
|
149
|
+
* Path-dependent encounter origins stamped at gate complete (Strat Malown 3rd box).
|
|
150
|
+
* Optional — absent ≡ use EncounterDef.origin. Survives mid-run save without bump.
|
|
151
|
+
* Cite: stratholmeScripts.cpp:108-110; docs/dungeons/malown/README.md.
|
|
152
|
+
*/
|
|
153
|
+
encounterOrigins?: Record<string, { x: number; y: number; z?: number }>;
|
|
154
|
+
// DR route density for this run (PACK-COMBAT-VISION §8b). Absent ≡ `bosses` for new runs; legacy mid-run
|
|
155
|
+
// saves without the field still use full def order when `routeEncounterIds` is also absent.
|
|
156
|
+
routeDensity?: RouteDensity;
|
|
157
|
+
// DR pull fusion for this run. Absent ≡ `single`. Rest pacing stays GameState.squadStrategy /
|
|
158
|
+
// SQUAD_PACING (careful|aggressive|sip).
|
|
159
|
+
pullFusion?: PullFusion;
|
|
160
|
+
/**
|
|
161
|
+
* Materialized ordered encounter ids for this run's route (enterInstance).
|
|
162
|
+
* When set, unlock / next / complete drive off this list; non-route packs are
|
|
163
|
+
* never required. Absent ≡ full `def.encounters` order (legacy saves).
|
|
164
|
+
*/
|
|
165
|
+
routeEncounterIds?: string[];
|
|
166
|
+
/**
|
|
167
|
+
* Budget-split route atoms (COMBAT-INTERACTION-V2 §Pack-strength budget).
|
|
168
|
+
* Materialized at enter when pack-budget context is supplied. Synthetic
|
|
169
|
+
* sub-pull ids (e.g. `pack__budget2`) resolve from here, not the static def.
|
|
170
|
+
* Absent ≡ lookup encounters from InstanceDef only (legacy / no budget).
|
|
171
|
+
*/
|
|
172
|
+
routeEncounters?: EncounterDef[];
|
|
173
|
+
/** Pack-budget preset stamped at enter (optional; for UI / score tags). */
|
|
174
|
+
packBudgetPreset?: PackBudgetPreset;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** dungeonId -> epoch ms at which a timed lockout expires (5-mans never add one). */
|
|
178
|
+
export type LockoutMap = Record<string, number>;
|
|
179
|
+
|
|
180
|
+
export interface EnterRunOpts {
|
|
181
|
+
routeDensity?: RouteDensity;
|
|
182
|
+
pullFusion?: PullFusion;
|
|
183
|
+
/** Precomputed route; when omitted, density is applied against a def if provided. */
|
|
184
|
+
routeEncounterIds?: string[];
|
|
185
|
+
/** Full encounter defs for budget-split atoms (same order as routeEncounterIds). */
|
|
186
|
+
routeEncounters?: EncounterDef[];
|
|
187
|
+
packBudgetPreset?: PackBudgetPreset;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Begin a fresh run of a dungeon (no progress yet). */
|
|
191
|
+
export function enterRun(dungeonId: string, opts?: EnterRunOpts): InstanceRun {
|
|
192
|
+
const routeDensity = opts?.routeDensity ?? DEFAULT_ROUTE_DENSITY;
|
|
193
|
+
const pullFusion = opts?.pullFusion ?? DEFAULT_PULL_FUSION;
|
|
194
|
+
const run: InstanceRun = {
|
|
195
|
+
dungeonId,
|
|
196
|
+
defeated: [],
|
|
197
|
+
flags: [],
|
|
198
|
+
routeDensity,
|
|
199
|
+
pullFusion,
|
|
200
|
+
};
|
|
201
|
+
if (opts?.routeEncounterIds?.length) {
|
|
202
|
+
run.routeEncounterIds = [...opts.routeEncounterIds];
|
|
203
|
+
}
|
|
204
|
+
if (opts?.routeEncounters?.length) {
|
|
205
|
+
run.routeEncounters = opts.routeEncounters.map((e) => ({ ...e }));
|
|
206
|
+
}
|
|
207
|
+
if (opts?.packBudgetPreset) {
|
|
208
|
+
run.packBudgetPreset = opts.packBudgetPreset;
|
|
209
|
+
}
|
|
210
|
+
return run;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface EnterRunWithRouteOpts {
|
|
214
|
+
routeDensity?: RouteDensity;
|
|
215
|
+
pullFusion?: PullFusion;
|
|
216
|
+
// When set with creature/spell lookups, over-budget trash packs are split into sequential sub-pulls
|
|
217
|
+
// (COMBAT-INTERACTION-V2 §Pack-strength budget). Omitted → density filter only (tests / legacy).
|
|
218
|
+
packBudget?: {
|
|
219
|
+
partyLevel: number;
|
|
220
|
+
creatureOf: CreatureLookup;
|
|
221
|
+
spellOf: SpellLookup;
|
|
222
|
+
/** Override preset; default derived from density + fusion. */
|
|
223
|
+
preset?: PackBudgetPreset;
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Nav grid / floorplan for geometric route order (entrance-first, A* distance).
|
|
227
|
+
* When omitted, uses the instance nav registry (primed when a floorplan loads).
|
|
228
|
+
* Without any grid the orderer still applies flag/boss constraints and Euclidean
|
|
229
|
+
* nearest-neighbour as a last-resort rank — better than declaration order, but
|
|
230
|
+
* wall-aware only once a floorplan is registered.
|
|
231
|
+
*/
|
|
232
|
+
navGrid?: NavGrid | null;
|
|
233
|
+
floorplan?: NavFloorDoc | null;
|
|
234
|
+
// Skip geometric reorder (tests that assert declaration / density relative order). Production enter
|
|
235
|
+
// always orders geometrically.
|
|
236
|
+
skipGeometricOrder?: boolean;
|
|
237
|
+
/** Bags at enter — a carried 13523 sends the tour to Kirtonos, not Reliquary. */
|
|
238
|
+
hasItem?: (itemEntry: number) => boolean;
|
|
239
|
+
// True when the party can loot this inside-key NOW (quest conditions). 13523 is only farmable after 5384
|
|
240
|
+
// is rewarded; otherwise skip Kirtonos.
|
|
241
|
+
itemObtainable?: (itemEntry: number) => boolean;
|
|
242
|
+
// Once-per-run pool roll (liveDungeonController.ts:252-291). When set with mapSpawns,
|
|
243
|
+
// orderRouteByGeometry strips pool-absent rares + multi-slot dups.
|
|
244
|
+
poolRoll?: DungeonPoolRoll | null;
|
|
245
|
+
mapSpawns?: readonly PoolMapSpawn[];
|
|
246
|
+
/** Persist winners on the run (MapPersistentStateMgr InitPools). */
|
|
247
|
+
floorPoolSpawnedGuids?: readonly number[];
|
|
248
|
+
/** Same, for the map's gameobject pools (goPoolRoll.rollDungeonGoPools). */
|
|
249
|
+
floorPoolSpawnedGoGuids?: readonly number[];
|
|
250
|
+
/** Alternate world door; route orders from that areatrigger dest. */
|
|
251
|
+
entranceTriggerId?: number;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Begin a run and materialize the density-filtered route from `def`.
|
|
256
|
+
* Preferred entry from game.enterInstance.
|
|
257
|
+
* Optional pack-budget context splits over-strong trash (never nerfs stats).
|
|
258
|
+
* Route ids are ordered geometrically (nav-graph from entrance), not def order.
|
|
259
|
+
*/
|
|
260
|
+
export function enterRunWithRoute(
|
|
261
|
+
def: InstanceDef,
|
|
262
|
+
opts?: EnterRunWithRouteOpts,
|
|
263
|
+
): InstanceRun {
|
|
264
|
+
const routeDensity = opts?.routeDensity ?? DEFAULT_ROUTE_DENSITY;
|
|
265
|
+
const pullFusion = opts?.pullFusion ?? DEFAULT_PULL_FUSION;
|
|
266
|
+
let route = selectRouteEncounters(def, routeDensity);
|
|
267
|
+
let packBudgetPreset: PackBudgetPreset | undefined;
|
|
268
|
+
if (opts?.packBudget) {
|
|
269
|
+
packBudgetPreset =
|
|
270
|
+
opts.packBudget.preset
|
|
271
|
+
?? packBudgetPresetFor(routeDensity, pullFusion);
|
|
272
|
+
// def.maxPlayers picks the table: a 40-man keeps the legacy raid budget, a 5/10-man gets
|
|
273
|
+
// the one measured against probe-dungeon-completion at band (packBudget.ts).
|
|
274
|
+
route = expandRouteByPackBudget(
|
|
275
|
+
route,
|
|
276
|
+
opts.packBudget.partyLevel,
|
|
277
|
+
opts.packBudget.creatureOf,
|
|
278
|
+
opts.packBudget.spellOf,
|
|
279
|
+
packBudgetPreset,
|
|
280
|
+
def.maxPlayers,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
// Geometric order: declaration order was the player-visible scatter bug.
|
|
284
|
+
// Pool strip + multi-slot rare collapse ride the same path as the live floor
|
|
285
|
+
// (liveDungeonController.ts:271 stripAbsentPoolEncountersFromRoute).
|
|
286
|
+
if (!opts?.skipGeometricOrder) {
|
|
287
|
+
route = orderRouteByGeometry(def, route, {
|
|
288
|
+
grid: opts?.navGrid,
|
|
289
|
+
floorplan: opts?.floorplan,
|
|
290
|
+
hasItem: opts?.hasItem,
|
|
291
|
+
itemObtainable: opts?.itemObtainable,
|
|
292
|
+
poolRoll: opts?.poolRoll,
|
|
293
|
+
mapSpawns: opts?.mapSpawns,
|
|
294
|
+
entranceTriggerId: opts?.entranceTriggerId,
|
|
295
|
+
});
|
|
296
|
+
} else if (opts?.mapSpawns) {
|
|
297
|
+
route = filterRouteByPoolRoll(route, opts.poolRoll, opts.mapSpawns);
|
|
298
|
+
}
|
|
299
|
+
const run = enterRun(def.id, {
|
|
300
|
+
routeDensity,
|
|
301
|
+
pullFusion,
|
|
302
|
+
routeEncounterIds: route.map((e) => e.id),
|
|
303
|
+
...(opts?.packBudget
|
|
304
|
+
? { routeEncounters: route, packBudgetPreset }
|
|
305
|
+
: {}),
|
|
306
|
+
});
|
|
307
|
+
if (opts?.floorPoolSpawnedGuids) {
|
|
308
|
+
run.floorPoolSpawnedGuids = [...opts.floorPoolSpawnedGuids];
|
|
309
|
+
} else if (opts?.poolRoll) {
|
|
310
|
+
run.floorPoolSpawnedGuids = [...opts.poolRoll.spawnedGuids];
|
|
311
|
+
}
|
|
312
|
+
if (opts?.floorPoolSpawnedGoGuids) {
|
|
313
|
+
run.floorPoolSpawnedGoGuids = [...opts.floorPoolSpawnedGoGuids];
|
|
314
|
+
}
|
|
315
|
+
// molten_core.cpp:62-65 OnPlayerEnter → DoSpawnMajordomoIfCan(true).
|
|
316
|
+
return applyMajordomoSpawnOnPlayerEnter(run);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Re-select and re-order the route once the nav bake lands.
|
|
321
|
+
*
|
|
322
|
+
* Selection itself is nav-free now that density is `bosses`/`full` — the corridor spine
|
|
323
|
+
* that needed the bake was deleted 2026-08-17. What still needs it is `orderRouteByGeometry`:
|
|
324
|
+
* at enter time it ranks stops without a mesh, so the itinerary can start in walking order
|
|
325
|
+
* and settle once the bake is in.
|
|
326
|
+
*
|
|
327
|
+
* Fresh runs only, and never over budget-split atoms: `routeEncounters` was materialized
|
|
328
|
+
* from the route this would replace, so in practice this is the legacy-save path —
|
|
329
|
+
* `enterInstance` always passes a packBudget and `reapplyGeometricRouteOrder` handles the rest.
|
|
330
|
+
*/
|
|
331
|
+
/**
|
|
332
|
+
* Bring an IN-FLIGHT run route in line with its density.
|
|
333
|
+
*
|
|
334
|
+
* `reapplyRouteSelection` below returns early on `run.routeEncounters`, and EVERY live run has
|
|
335
|
+
* that field (enterInstance always passes packBudget), so a run entered before a density change
|
|
336
|
+
* keeps its old stop list for the rest of its life. `state.instance` is persisted, so that means
|
|
337
|
+
* across reloads too: a blackrock-depths run entered under the old `normal` default still walks
|
|
338
|
+
* its 235 trash stops after the default moved to `bosses`, and the autopilot still names them.
|
|
339
|
+
*
|
|
340
|
+
* Cleared stops are KEPT so isCleared / isComplete / routeBossProgress stay coherent; only
|
|
341
|
+
* UNCLEARED stops the current density would not select are dropped. Budget-split atoms
|
|
342
|
+
* (`<id>__budgetN`, packBudget.ts:526) resolve to their base encounter before the test.
|
|
343
|
+
*/
|
|
344
|
+
export function pruneRouteToDensity(def: InstanceDef, run: InstanceRun): InstanceRun {
|
|
345
|
+
const ids = run.routeEncounterIds;
|
|
346
|
+
if (!ids?.length) return run;
|
|
347
|
+
const density = run.routeDensity ?? DEFAULT_ROUTE_DENSITY;
|
|
348
|
+
const keep = new Set(selectRouteEncounters(def, density).map((e) => e.id));
|
|
349
|
+
const baseOf = (id: string): string => id.split('__budget')[0] ?? id;
|
|
350
|
+
const next = ids.filter((id) => isCleared(run, id) || keep.has(baseOf(id)));
|
|
351
|
+
if (next.length === ids.length) return run;
|
|
352
|
+
const out: InstanceRun = { ...run, routeEncounterIds: next };
|
|
353
|
+
if (run.routeEncounters?.length) {
|
|
354
|
+
const kept = new Set(next);
|
|
355
|
+
out.routeEncounters = run.routeEncounters.filter((e) => kept.has(e.id));
|
|
356
|
+
}
|
|
357
|
+
return out;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function reapplyRouteSelection(
|
|
361
|
+
def: InstanceDef,
|
|
362
|
+
run: InstanceRun,
|
|
363
|
+
opts?: {
|
|
364
|
+
navGrid?: NavGrid | null;
|
|
365
|
+
floorplan?: NavFloorDoc | null;
|
|
366
|
+
hasItem?: (itemEntry: number) => boolean;
|
|
367
|
+
itemObtainable?: (itemEntry: number) => boolean;
|
|
368
|
+
},
|
|
369
|
+
): InstanceRun {
|
|
370
|
+
if (run.defeated.length > 0 || (run.flags?.length ?? 0) > 0) return run;
|
|
371
|
+
if (run.routeEncounters?.length) return run;
|
|
372
|
+
const density = run.routeDensity ?? DEFAULT_ROUTE_DENSITY;
|
|
373
|
+
const ids = orderRouteByGeometry(def, selectRouteEncounters(def, density), {
|
|
374
|
+
grid: opts?.navGrid,
|
|
375
|
+
floorplan: opts?.floorplan,
|
|
376
|
+
hasItem: opts?.hasItem,
|
|
377
|
+
itemObtainable: opts?.itemObtainable,
|
|
378
|
+
}).map((e) => e.id);
|
|
379
|
+
const prev = run.routeEncounterIds ?? [];
|
|
380
|
+
if (prev.length === ids.length && prev.every((id, i) => id === ids[i])) return run;
|
|
381
|
+
return { ...run, routeEncounterIds: ids };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Re-apply geometric route order on a fresh run (defeated empty) once a floorplan nav grid becomes
|
|
385
|
+
// available. No-op when progress has started.
|
|
386
|
+
export function reapplyGeometricRouteOrder(
|
|
387
|
+
def: InstanceDef,
|
|
388
|
+
run: InstanceRun,
|
|
389
|
+
opts?: {
|
|
390
|
+
navGrid?: NavGrid | null;
|
|
391
|
+
floorplan?: NavFloorDoc | null;
|
|
392
|
+
hasItem?: (itemEntry: number) => boolean;
|
|
393
|
+
itemObtainable?: (itemEntry: number) => boolean;
|
|
394
|
+
},
|
|
395
|
+
): InstanceRun {
|
|
396
|
+
if (run.defeated.length > 0) return run;
|
|
397
|
+
// Resolve current route atoms (budget splits live on run.routeEncounters).
|
|
398
|
+
let route: EncounterDef[];
|
|
399
|
+
if (run.routeEncounters?.length) {
|
|
400
|
+
const byId = new Map(run.routeEncounters.map((e) => [e.id, e]));
|
|
401
|
+
const ids = run.routeEncounterIds ?? run.routeEncounters.map((e) => e.id);
|
|
402
|
+
route = [];
|
|
403
|
+
for (const id of ids) {
|
|
404
|
+
const e = byId.get(id);
|
|
405
|
+
if (e) route.push(e);
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
const ids = run.routeEncounterIds ?? def.encounters.map((e) => e.id);
|
|
409
|
+
route = [];
|
|
410
|
+
for (const id of ids) {
|
|
411
|
+
const e = encounterOf(def, id, run);
|
|
412
|
+
if (e) route.push(e);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const ordered = orderRouteByGeometry(def, route, {
|
|
416
|
+
grid: opts?.navGrid,
|
|
417
|
+
floorplan: opts?.floorplan,
|
|
418
|
+
hasItem: opts?.hasItem,
|
|
419
|
+
itemObtainable: opts?.itemObtainable,
|
|
420
|
+
});
|
|
421
|
+
const ids = ordered.map((e) => e.id);
|
|
422
|
+
const prev = run.routeEncounterIds ?? [];
|
|
423
|
+
if (
|
|
424
|
+
prev.length === ids.length
|
|
425
|
+
&& prev.every((id, i) => id === ids[i])
|
|
426
|
+
&& (!run.routeEncounters?.length
|
|
427
|
+
|| ordered.every((e, i) => !e.optional || run.routeEncounters?.[i]?.optional === e.optional))
|
|
428
|
+
) {
|
|
429
|
+
return run;
|
|
430
|
+
}
|
|
431
|
+
const next: InstanceRun = {
|
|
432
|
+
...run,
|
|
433
|
+
routeEncounterIds: ids,
|
|
434
|
+
};
|
|
435
|
+
if (run.routeEncounters?.length) {
|
|
436
|
+
next.routeEncounters = ordered;
|
|
437
|
+
}
|
|
438
|
+
return next;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** Wipe progress for a fresh attempt — keeps density / fusion / route list. */
|
|
442
|
+
export function resetRun(run: InstanceRun): InstanceRun {
|
|
443
|
+
return {
|
|
444
|
+
dungeonId: run.dungeonId,
|
|
445
|
+
defeated: [],
|
|
446
|
+
flags: [],
|
|
447
|
+
...(run.routeDensity != null ? { routeDensity: run.routeDensity } : {}),
|
|
448
|
+
...(run.pullFusion != null ? { pullFusion: run.pullFusion } : {}),
|
|
449
|
+
...(run.routeEncounterIds?.length
|
|
450
|
+
? { routeEncounterIds: [...run.routeEncounterIds] }
|
|
451
|
+
: {}),
|
|
452
|
+
...(run.routeEncounters?.length
|
|
453
|
+
? { routeEncounters: run.routeEncounters.map((e) => ({ ...e })) }
|
|
454
|
+
: {}),
|
|
455
|
+
...(run.packBudgetPreset != null ? { packBudgetPreset: run.packBudgetPreset } : {}),
|
|
456
|
+
};
|
|
457
|
+
// objectGateUses / objectGateUsedEntries intentionally dropped (fresh attempt).
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Ordered encounter ids for progress checks. Prefers the run's materialized route; falls back to full def
|
|
461
|
+
// order (legacy saves without DR fields).
|
|
462
|
+
export function routeEncounterIdList(def: InstanceDef, run: InstanceRun): string[] {
|
|
463
|
+
if (run.routeEncounterIds?.length) return run.routeEncounterIds;
|
|
464
|
+
return def.encounters.map((e) => e.id);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** Encounter defs on the active route (lookup from full def; preserves route order). */
|
|
468
|
+
export function routeEncounterList(def: InstanceDef, run: InstanceRun): EncounterDef[] {
|
|
469
|
+
// Prefer budget-materialized atoms (may include synthetic sub-pull ids).
|
|
470
|
+
if (run.routeEncounters?.length) {
|
|
471
|
+
const byId = new Map(run.routeEncounters.map((e) => [e.id, e]));
|
|
472
|
+
const out: EncounterDef[] = [];
|
|
473
|
+
for (const id of routeEncounterIdList(def, run)) {
|
|
474
|
+
const e = byId.get(id) ?? encounterOf(def, id);
|
|
475
|
+
if (e) out.push(e);
|
|
476
|
+
}
|
|
477
|
+
return out;
|
|
478
|
+
}
|
|
479
|
+
const out: EncounterDef[] = [];
|
|
480
|
+
for (const id of routeEncounterIdList(def, run)) {
|
|
481
|
+
const e = encounterOf(def, id, run);
|
|
482
|
+
if (e) out.push(e);
|
|
483
|
+
}
|
|
484
|
+
return out;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Resolve an encounter by id. When `run` carries budget-split `routeEncounters`, those take precedence
|
|
488
|
+
// (synthetic `__budgetN` atoms are not on InstanceDef).
|
|
489
|
+
export function encounterOf(
|
|
490
|
+
def: InstanceDef,
|
|
491
|
+
encounterId: string,
|
|
492
|
+
run?: InstanceRun | null,
|
|
493
|
+
): EncounterDef | undefined {
|
|
494
|
+
if (run?.routeEncounters?.length) {
|
|
495
|
+
const fromRun = run.routeEncounters.find((e) => e.id === encounterId);
|
|
496
|
+
if (fromRun) return fromRun;
|
|
497
|
+
}
|
|
498
|
+
return def.encounters.find((e) => e.id === encounterId);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function isCleared(run: InstanceRun, encounterId: string): boolean {
|
|
502
|
+
return run.defeated.includes(encounterId);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ---- flags -----------------------------------------------------------------
|
|
506
|
+
|
|
507
|
+
/** Flags present on this run (empty when unset). */
|
|
508
|
+
export function runFlags(run: InstanceRun): readonly string[] {
|
|
509
|
+
return run.flags ?? [];
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export function hasFlag(run: InstanceRun, flag: string): boolean {
|
|
513
|
+
return (run.flags ?? []).includes(flag);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Add a flag (pure; idempotent — same reference when already present). */
|
|
517
|
+
export function setFlag(run: InstanceRun, flag: string): InstanceRun {
|
|
518
|
+
if ((run.flags ?? []).includes(flag)) return run;
|
|
519
|
+
return { ...run, flags: [...(run.flags ?? []), flag] };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** `requiresFlag` entries on an encounter (empty when none). */
|
|
523
|
+
export function requiredFlags(enc: EncounterDef): string[] {
|
|
524
|
+
const out: string[] = [];
|
|
525
|
+
for (const ev of enc.events ?? []) {
|
|
526
|
+
if (ev.kind === 'requiresFlag') out.push(ev.flag);
|
|
527
|
+
}
|
|
528
|
+
return out;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** `setsFlag` entries applied when the encounter fully clears. */
|
|
532
|
+
export function flagsSetOnClear(enc: EncounterDef): string[] {
|
|
533
|
+
const out: string[] = [];
|
|
534
|
+
for (const ev of enc.events ?? []) {
|
|
535
|
+
if (ev.kind === 'setsFlag') out.push(ev.flag);
|
|
536
|
+
}
|
|
537
|
+
return out;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* An encounter is engageable once every `requiresFlag` on it is satisfied. Encounters
|
|
542
|
+
* not on the run's route are never unlocked.
|
|
543
|
+
*
|
|
544
|
+
* THE ROUTE ORDER IS AN ITINERARY, NOT A LOCK. This used to also require every
|
|
545
|
+
* non-optional route stop listed before it to be cleared, and that prefix was invented
|
|
546
|
+
* scaffolding — vanilla gates a dungeon with doors, keys and script flags, never with
|
|
547
|
+
* "kill the pack in front first". Three measurements against it:
|
|
548
|
+
*
|
|
549
|
+
* - blackrock-depths 2026-08-17 (probe-run-trace, band L55, 300 s at 8x): the route was
|
|
550
|
+
* 257 stops, so boss 4 sat behind 235 trash prerequisites. 3/22 bosses, 8/257 stops,
|
|
551
|
+
* and the autopilot's target at the end was `230-3fe13518` — a trash pack, because
|
|
552
|
+
* `nextAutoBossId` is forced to walk to "the first uncleared non-optional predecessor".
|
|
553
|
+
* - scholomance (dungeonRouteOrder.ts:531): one mis-ordered key gate "blocked all 49
|
|
554
|
+
* stops behind it: 9/58 cleared, 0/13 bosses".
|
|
555
|
+
* - UBRS / SFK (liveAutopilot.ts:472): the prefix read as sealed for nearby trash, so
|
|
556
|
+
* 8-10 of ~10 packs never streamed and chips never painted.
|
|
557
|
+
*
|
|
558
|
+
* The order still decides what the party WALKS to (`nextEncounter`, `routeEncounterList`,
|
|
559
|
+
* `nextAutoBossId`); it no longer decides what it may pull once it is standing there.
|
|
560
|
+
*/
|
|
561
|
+
export function isUnlocked(def: InstanceDef, run: InstanceRun, encounterId: string): boolean {
|
|
562
|
+
const seq = routeEncounterIdList(def, run);
|
|
563
|
+
if (!seq.includes(encounterId)) return false;
|
|
564
|
+
const enc = encounterOf(def, encounterId, run);
|
|
565
|
+
if (!enc) return false;
|
|
566
|
+
for (const flag of requiredFlags(enc)) {
|
|
567
|
+
if (!hasFlag(run, flag)) return false;
|
|
568
|
+
}
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Optional route encounter that drops an inside-key for a missing gate flag.
|
|
574
|
+
* Same "sealed boss is not a destination — walk to what opens it" rule as
|
|
575
|
+
* liveAutopilot, for keys that only drop from optional pulls (DMN Inner Door
|
|
576
|
+
* key 18268 ← Guard Mol'dar). Kromcrush hosts the GO but is not the dropper.
|
|
577
|
+
*/
|
|
578
|
+
function optionalKeyOpenerForFlags(
|
|
579
|
+
def: InstanceDef,
|
|
580
|
+
run: InstanceRun,
|
|
581
|
+
seq: readonly string[],
|
|
582
|
+
missingFlags: readonly string[],
|
|
583
|
+
): EncounterDef | undefined {
|
|
584
|
+
if (missingFlags.length === 0) return undefined;
|
|
585
|
+
const need = new Set(missingFlags);
|
|
586
|
+
const items = new Set<number>();
|
|
587
|
+
for (const other of def.encounters) {
|
|
588
|
+
for (const gate of objectGateEvents(other)) {
|
|
589
|
+
if (!need.has(gate.setsFlag) || gate.requiredItem == null) continue;
|
|
590
|
+
items.add(gate.requiredItem);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
for (const item of items) {
|
|
594
|
+
const sources = routeItemSources(def.id, item);
|
|
595
|
+
// Empty ⇒ outside key; autoAdvanceRouteGates bypasses those — not a dest.
|
|
596
|
+
if (sources.length === 0) continue;
|
|
597
|
+
for (const id of seq) {
|
|
598
|
+
if (!sources.includes(id)) continue;
|
|
599
|
+
if (isCleared(run, id) || !isUnlocked(def, run, id)) continue;
|
|
600
|
+
const enc = encounterOf(def, id, run);
|
|
601
|
+
// Only the optional exception — non-optional sources are primary dests above.
|
|
602
|
+
if (!enc || enc.optional !== true) continue;
|
|
603
|
+
return enc;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* The next engageable uncleared **non-optional** route encounter, or undefined
|
|
611
|
+
* if none remain (route complete, only optionals left, or remaining pulls are
|
|
612
|
+
* flag-gated). Matches `isComplete` and liveAutopilot destination skips — an
|
|
613
|
+
* optional boss is skippable, not deleted; `beginEncounter(def, optionalId)`
|
|
614
|
+
* still works when the player pulls it by hand.
|
|
615
|
+
*
|
|
616
|
+
* Exception: when a required encounter is sealed on a flag whose objectGate key
|
|
617
|
+
* drops only from an optional encounter still on the route, dest that opener
|
|
618
|
+
* (e.g. Guard Mol'dar for Gordok Inner Door key 18268) so the door can fire
|
|
619
|
+
* without pulling unrelated optionals like Captain Kromcrush.
|
|
620
|
+
*/
|
|
621
|
+
export function nextEncounter(def: InstanceDef, run: InstanceRun): EncounterDef | undefined {
|
|
622
|
+
const seq = routeEncounterIdList(def, run);
|
|
623
|
+
for (const id of seq) {
|
|
624
|
+
if (isCleared(run, id) || !isUnlocked(def, run, id)) continue;
|
|
625
|
+
const enc = encounterOf(def, id, run);
|
|
626
|
+
// Optional tribute / skippable rares are not auto-run destinations.
|
|
627
|
+
if (enc?.optional === true) continue;
|
|
628
|
+
return enc;
|
|
629
|
+
}
|
|
630
|
+
// No unlocked required stop — look for an optional key-dropper that opens one.
|
|
631
|
+
for (const id of seq) {
|
|
632
|
+
if (isCleared(run, id)) continue;
|
|
633
|
+
const enc = encounterOf(def, id, run);
|
|
634
|
+
if (!enc || enc.optional === true) continue;
|
|
635
|
+
const missing = requiredFlags(enc).filter((f) => !hasFlag(run, f));
|
|
636
|
+
const opener = optionalKeyOpenerForFlags(def, run, seq, missing);
|
|
637
|
+
if (opener) return opener;
|
|
638
|
+
}
|
|
639
|
+
return undefined;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** True once every non-optional route encounter has been cleared this run. */
|
|
643
|
+
export function isComplete(def: InstanceDef, run: InstanceRun): boolean {
|
|
644
|
+
return routeEncounterIdList(def, run).every((id) => {
|
|
645
|
+
if (isCleared(run, id)) return true;
|
|
646
|
+
return encounterOf(def, id, run)?.optional === true;
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** Record an encounter as defeated (returns a new run; idempotent). */
|
|
651
|
+
export function markDefeated(run: InstanceRun, encounterId: string): InstanceRun {
|
|
652
|
+
if (run.defeated.includes(encounterId)) return run;
|
|
653
|
+
return { ...run, defeated: [...run.defeated, encounterId] };
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Mark an encounter cleared and apply its `setsFlag` events (and optional extra
|
|
658
|
+
* flags, e.g. HP phase markers). Pure; idempotent for the defeat list.
|
|
659
|
+
*
|
|
660
|
+
* Object gates are **not** applied here — use `useObjectGate` (manual, item
|
|
661
|
+
* checked) or `autoAdvanceRouteGates` (auto-run, item-bypassed + orphan flags).
|
|
662
|
+
*/
|
|
663
|
+
export function applyEncounterCleared(
|
|
664
|
+
run: InstanceRun,
|
|
665
|
+
enc: EncounterDef,
|
|
666
|
+
extraFlags?: readonly string[],
|
|
667
|
+
): InstanceRun {
|
|
668
|
+
let next = markDefeated(run, enc.id);
|
|
669
|
+
for (const f of flagsSetOnClear(enc)) next = setFlag(next, f);
|
|
670
|
+
if (extraFlags) {
|
|
671
|
+
for (const f of extraFlags) next = setFlag(next, f);
|
|
672
|
+
}
|
|
673
|
+
return next;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** How many bosses this dungeon has, and how many are down — for a progress hint. */
|
|
677
|
+
export function bossProgress(def: InstanceDef, run: InstanceRun): { down: number; total: number } {
|
|
678
|
+
const bosses = def.encounters.filter((e) => e.kind === 'boss');
|
|
679
|
+
return { down: bosses.filter((e) => isCleared(run, e.id)).length, total: bosses.length };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Boss progress on the **active route only** (density-filtered). Use for run scoring / "route cleared" UI
|
|
683
|
+
// so Normal/Speedrun aren't graded against Full.
|
|
684
|
+
export function routeBossProgress(def: InstanceDef, run: InstanceRun): { down: number; total: number } {
|
|
685
|
+
// Optional tribute guards do not count toward "route cleared" progress.
|
|
686
|
+
const bosses = routeEncounterList(def, run).filter((e) => e.kind === 'boss' && !e.optional);
|
|
687
|
+
return { down: bosses.filter((e) => isCleared(run, e.id)).length, total: bosses.length };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// ---- event DSL: waves / death-summon / HP thresholds / object gates --------
|
|
691
|
+
|
|
692
|
+
/** First `waves` event on the encounter, if any. */
|
|
693
|
+
export function wavesEvent(
|
|
694
|
+
enc: EncounterDef,
|
|
695
|
+
): Extract<EncounterEvent, { kind: 'waves' }> | undefined {
|
|
696
|
+
return (enc.events ?? []).find((e): e is Extract<EncounterEvent, { kind: 'waves' }> => e.kind === 'waves');
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Waves this encounter can run ON THE FLOOR: authored `waves` whose primary
|
|
701
|
+
* appears on the final wave (Ring / Doom'rel / Grubbis) OR on the first wave
|
|
702
|
+
* with a later follow-up pack (SFK Fenrus → voidwalkers after death). Flattened
|
|
703
|
+
* to entry/count for the live controller sequencer.
|
|
704
|
+
* Cite: instance_shadowfang_keep.cpp:184-191 TYPE_FENRUS DONE → Arugal + voidwalkers;
|
|
705
|
+
* shadowfang_keep.cpp:443-460 ARCHMAGE_VOIDWALKERS summon NPC_VOIDWALKER×4.
|
|
706
|
+
* Null when there are no waves or neither accepted shape matches.
|
|
707
|
+
*/
|
|
708
|
+
export function floorGauntletWaves(
|
|
709
|
+
enc: EncounterDef,
|
|
710
|
+
): { entry: number; count: number }[][] | null {
|
|
711
|
+
const w = wavesEvent(enc);
|
|
712
|
+
if (!w?.waves.length) return null;
|
|
713
|
+
const primary = enc.creatureEntry;
|
|
714
|
+
if (!(primary > 0)) return null;
|
|
715
|
+
const first = w.waves[0]!.pack ?? [];
|
|
716
|
+
const last = w.waves[w.waves.length - 1]!.pack ?? [];
|
|
717
|
+
const lastHas = last.some((m) => m.creatureEntry === primary);
|
|
718
|
+
const firstHas = first.some((m) => m.creatureEntry === primary);
|
|
719
|
+
if (!lastHas && !firstHas) return null;
|
|
720
|
+
return w.waves.map((wave) =>
|
|
721
|
+
(wave.pack ?? []).map((m) => ({ entry: m.creatureEntry, count: m.count })),
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/** Ordered `onDeathSummon` events (phase-chain after primary). */
|
|
726
|
+
export function onDeathSummons(
|
|
727
|
+
enc: EncounterDef,
|
|
728
|
+
): Extract<EncounterEvent, { kind: 'onDeathSummon' }>[] {
|
|
729
|
+
return (enc.events ?? []).filter(
|
|
730
|
+
(e): e is Extract<EncounterEvent, { kind: 'onDeathSummon' }> => e.kind === 'onDeathSummon',
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/** Ordered `hpThreshold` events (stable authored order). */
|
|
735
|
+
export function hpThresholdEvents(
|
|
736
|
+
enc: EncounterDef,
|
|
737
|
+
): Extract<EncounterEvent, { kind: 'hpThreshold' }>[] {
|
|
738
|
+
return (enc.events ?? []).filter(
|
|
739
|
+
(e): e is Extract<EncounterEvent, { kind: 'hpThreshold' }> => e.kind === 'hpThreshold',
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Object-gate events on this encounter (and, when scanning a dungeon, callers iterate all encounters).
|
|
744
|
+
// `once` defaults to true.
|
|
745
|
+
export function objectGateEvents(
|
|
746
|
+
enc: EncounterDef,
|
|
747
|
+
): Extract<EncounterEvent, { kind: 'objectGate' }>[] {
|
|
748
|
+
return (enc.events ?? []).filter(
|
|
749
|
+
(e): e is Extract<EncounterEvent, { kind: 'objectGate' }> => e.kind === 'objectGate',
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** Entries that satisfy an objectGate (`goEntries` or sole `goEntry`). */
|
|
754
|
+
export function objectGateAcceptedEntries(
|
|
755
|
+
gate: Extract<EncounterEvent, { kind: 'objectGate' }>,
|
|
756
|
+
): number[] {
|
|
757
|
+
if (gate.goEntries?.length) return [...gate.goEntries];
|
|
758
|
+
return [gate.goEntry];
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/** Uses required before setsFlag (default 1). */
|
|
762
|
+
export function objectGateNeedUses(
|
|
763
|
+
gate: Extract<EncounterEvent, { kind: 'objectGate' }>,
|
|
764
|
+
): number {
|
|
765
|
+
const n = gate.needUses ?? 1;
|
|
766
|
+
return n > 0 ? n : 1;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** Find the first objectGate in the instance matching `goEntry` (primary or goEntries). */
|
|
770
|
+
export function findObjectGate(
|
|
771
|
+
def: InstanceDef,
|
|
772
|
+
goEntry: number,
|
|
773
|
+
): { encounter: EncounterDef; gate: Extract<EncounterEvent, { kind: 'objectGate' }> } | undefined {
|
|
774
|
+
for (const enc of def.encounters) {
|
|
775
|
+
for (const g of objectGateEvents(enc)) {
|
|
776
|
+
if (g.goEntry === goEntry || g.goEntries?.includes(goEntry)) {
|
|
777
|
+
return { encounter: enc, gate: g };
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return undefined;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* How many auto-chained phases this encounter has (including the initial phase 0).
|
|
786
|
+
* - waves: waves.length
|
|
787
|
+
* - onDeathSummon: 1 + summons.length
|
|
788
|
+
* - hpThreshold with spawn: 1 (primary) + number of thresholds that have spawn
|
|
789
|
+
* - otherwise: 1
|
|
790
|
+
*
|
|
791
|
+
* When both waves and onDeathSummon are authored, waves win (mutually exclusive
|
|
792
|
+
* authoring intent for Wave 1).
|
|
793
|
+
*/
|
|
794
|
+
export function encounterPhaseCount(enc: EncounterDef): number {
|
|
795
|
+
const waves = wavesEvent(enc);
|
|
796
|
+
if (waves && waves.waves.length > 0) return waves.waves.length;
|
|
797
|
+
|
|
798
|
+
const summons = onDeathSummons(enc);
|
|
799
|
+
const hpSpawns = hpThresholdEvents(enc).filter((t) => (t.spawn?.length ?? 0) > 0);
|
|
800
|
+
// Primary + each death-summon + each HP-spawn phase (HP spawns chain after summons).
|
|
801
|
+
return 1 + summons.length + hpSpawns.length;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Uniform CSE roll over survey/DB candidates only.
|
|
806
|
+
* CMaNGOS pattern: Creature::ResetEntry / GetRandomCreatureEntry (creature_spawn_entry).
|
|
807
|
+
* `rng` must return [0, 1) — inject mulberry32 / LOCK 4 (never Math.random / wall clock).
|
|
808
|
+
*/
|
|
809
|
+
export function pickPoolCandidate(candidates: readonly number[], rng: () => number): number {
|
|
810
|
+
if (candidates.length === 0) {
|
|
811
|
+
throw new Error('pickPoolCandidate: empty candidates');
|
|
812
|
+
}
|
|
813
|
+
if (candidates.length === 1) return candidates[0]!;
|
|
814
|
+
const u = rng();
|
|
815
|
+
const idx = Math.min(candidates.length - 1, Math.floor(u * candidates.length));
|
|
816
|
+
return candidates[idx]!;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Resolve CSE placeholders on one pack row to a concrete creatureEntry. Non-pool rows pass through. count
|
|
820
|
+
// preserved.
|
|
821
|
+
export function materializePackMember(
|
|
822
|
+
row: EncounterPackMember,
|
|
823
|
+
rng: () => number,
|
|
824
|
+
): EncounterPackMember {
|
|
825
|
+
if (!row.poolCandidates?.length) {
|
|
826
|
+
return { creatureEntry: row.creatureEntry, count: row.count };
|
|
827
|
+
}
|
|
828
|
+
// One CSE slot per guid: count must be 1; still materialize count times if mis-authored.
|
|
829
|
+
if (row.count <= 1) {
|
|
830
|
+
return { creatureEntry: pickPoolCandidate(row.poolCandidates, rng), count: 1 };
|
|
831
|
+
}
|
|
832
|
+
// Degenerate: expand multi-count CSE into one roll each (should not be authored).
|
|
833
|
+
return {
|
|
834
|
+
creatureEntry: pickPoolCandidate(row.poolCandidates, rng),
|
|
835
|
+
count: row.count,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Materialize CSE on an encounter (primary poolCandidates + pack rows). Returns a concrete pull safe for
|
|
840
|
+
// combat / kill credit. Injected RNG only.
|
|
841
|
+
export function materializeEncounterCse(
|
|
842
|
+
enc: EncounterDef,
|
|
843
|
+
rng: () => number,
|
|
844
|
+
): EncounterDef {
|
|
845
|
+
const next: EncounterDef = {
|
|
846
|
+
...enc,
|
|
847
|
+
creatureEntry: enc.poolCandidates?.length
|
|
848
|
+
? pickPoolCandidate(enc.poolCandidates, rng)
|
|
849
|
+
: enc.creatureEntry,
|
|
850
|
+
};
|
|
851
|
+
delete next.poolCandidates;
|
|
852
|
+
if (enc.pack?.length) {
|
|
853
|
+
next.pack = enc.pack.map((row) => materializePackMember(row, rng));
|
|
854
|
+
}
|
|
855
|
+
return next;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Resolved pull for one phase of a multi-phase encounter.
|
|
860
|
+
* Returns null when `phaseIndex` is out of range.
|
|
861
|
+
*
|
|
862
|
+
* Phase layout:
|
|
863
|
+
* waves: phase i → waves[i].pack (first row is primary)
|
|
864
|
+
* else: phase 0 → primary + pack
|
|
865
|
+
* next phases → each onDeathSummon in order
|
|
866
|
+
* then each hpThreshold with spawn (pack becomes the pull)
|
|
867
|
+
*
|
|
868
|
+
* Optional `rng`: when provided, CSE poolCandidates are rolled to concrete entries
|
|
869
|
+
* (run/fight start — CMaNGOS ResetEntry). Without rng, placeholders remain
|
|
870
|
+
* (first candidate) for pure graph inspection.
|
|
871
|
+
*/
|
|
872
|
+
export function resolveEncounterPhase(
|
|
873
|
+
enc: EncounterDef,
|
|
874
|
+
phaseIndex: number,
|
|
875
|
+
rng?: () => number,
|
|
876
|
+
): {
|
|
877
|
+
creatureEntry: number;
|
|
878
|
+
bossSpells?: number[];
|
|
879
|
+
pack?: EncounterPackMember[];
|
|
880
|
+
} | null {
|
|
881
|
+
if (phaseIndex < 0) return null;
|
|
882
|
+
|
|
883
|
+
const base = rng ? materializeEncounterCse(enc, rng) : enc;
|
|
884
|
+
|
|
885
|
+
const waves = wavesEvent(base);
|
|
886
|
+
if (waves && waves.waves.length > 0) {
|
|
887
|
+
const w = waves.waves[phaseIndex];
|
|
888
|
+
if (!w) return null;
|
|
889
|
+
const pack = rng
|
|
890
|
+
? w.pack.map((row) => materializePackMember(row, rng))
|
|
891
|
+
: w.pack;
|
|
892
|
+
return packToPull(pack);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (phaseIndex === 0) {
|
|
896
|
+
return {
|
|
897
|
+
creatureEntry: base.creatureEntry,
|
|
898
|
+
bossSpells: base.bossSpells,
|
|
899
|
+
pack: base.pack,
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const summons = onDeathSummons(base);
|
|
904
|
+
if (phaseIndex <= summons.length) {
|
|
905
|
+
const s = summons[phaseIndex - 1]!;
|
|
906
|
+
return { creatureEntry: s.creatureEntry, bossSpells: s.bossSpells };
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const hpSpawns = hpThresholdEvents(base).filter((t) => (t.spawn?.length ?? 0) > 0);
|
|
910
|
+
const hi = phaseIndex - 1 - summons.length;
|
|
911
|
+
const thr = hpSpawns[hi];
|
|
912
|
+
if (!thr?.spawn) return null;
|
|
913
|
+
const spawn = rng
|
|
914
|
+
? thr.spawn.map((row) => materializePackMember(row, rng))
|
|
915
|
+
: thr.spawn;
|
|
916
|
+
return packToPull(spawn);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/** Expand a pack table into primary + remaining pack members (first count>0 row wins as primary). */
|
|
920
|
+
export function packToPull(
|
|
921
|
+
pack: EncounterPackMember[],
|
|
922
|
+
): { creatureEntry: number; pack?: EncounterPackMember[] } | null {
|
|
923
|
+
const expanded: EncounterPackMember[] = [];
|
|
924
|
+
for (const row of pack) {
|
|
925
|
+
if (row.count <= 0) continue;
|
|
926
|
+
// Drop poolCandidates from combat pull (callers should materialize first).
|
|
927
|
+
expanded.push({ creatureEntry: row.creatureEntry, count: row.count });
|
|
928
|
+
}
|
|
929
|
+
if (expanded.length === 0) return null;
|
|
930
|
+
const head = expanded[0]!;
|
|
931
|
+
const rest: EncounterPackMember[] = [];
|
|
932
|
+
if (head.count > 1) rest.push({ creatureEntry: head.creatureEntry, count: head.count - 1 });
|
|
933
|
+
for (let i = 1; i < expanded.length; i++) rest.push(expanded[i]!);
|
|
934
|
+
return {
|
|
935
|
+
creatureEntry: head.creatureEntry,
|
|
936
|
+
pack: rest.length ? rest : undefined,
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Which HP thresholds fire at the given primary HP percent (0–100).
|
|
942
|
+
* `fired` is the set of threshold keys already consumed (`once` default true).
|
|
943
|
+
* Key format: `hp:<pct>` or `hp:<pct>:phase:<n>` when phase is authored.
|
|
944
|
+
*/
|
|
945
|
+
export function hpThresholdKey(t: Extract<EncounterEvent, { kind: 'hpThreshold' }>): string {
|
|
946
|
+
return t.phase != null ? `hp:${t.pct}:phase:${t.phase}` : `hp:${t.pct}`;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
export function thresholdsAtHp(
|
|
950
|
+
thresholds: readonly Extract<EncounterEvent, { kind: 'hpThreshold' }>[],
|
|
951
|
+
hpPct: number,
|
|
952
|
+
fired: ReadonlySet<string> | readonly string[],
|
|
953
|
+
): Extract<EncounterEvent, { kind: 'hpThreshold' }>[] {
|
|
954
|
+
const done = fired instanceof Set ? fired : new Set(fired);
|
|
955
|
+
const out: Extract<EncounterEvent, { kind: 'hpThreshold' }>[] = [];
|
|
956
|
+
for (const t of thresholds) {
|
|
957
|
+
if (hpPct > t.pct) continue;
|
|
958
|
+
const key = hpThresholdKey(t);
|
|
959
|
+
const once = t.once !== false;
|
|
960
|
+
if (once && done.has(key)) continue;
|
|
961
|
+
out.push(t);
|
|
962
|
+
}
|
|
963
|
+
return out;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Flags to set when the primary has been reduced to `hpPct` (and/or killed at 0). Includes `phase:<n>` for
|
|
967
|
+
// authored phase numbers and the stable `hpThresholdKey`.
|
|
968
|
+
export function flagsForHpThresholds(
|
|
969
|
+
thresholds: readonly Extract<EncounterEvent, { kind: 'hpThreshold' }>[],
|
|
970
|
+
hpPct: number,
|
|
971
|
+
already: ReadonlySet<string> | readonly string[],
|
|
972
|
+
): string[] {
|
|
973
|
+
const fired = thresholdsAtHp(thresholds, hpPct, already);
|
|
974
|
+
const out: string[] = [];
|
|
975
|
+
for (const t of fired) {
|
|
976
|
+
out.push(hpThresholdKey(t));
|
|
977
|
+
if (t.phase != null) out.push(`phase:${t.phase}`);
|
|
978
|
+
}
|
|
979
|
+
return out;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Result of attempting to use an instance object (GO entry). Pure: does not touch inventory (caller checks
|
|
983
|
+
// item presence and spends `consumeItem`).
|
|
984
|
+
export type UseObjectResult =
|
|
985
|
+
| {
|
|
986
|
+
ok: true;
|
|
987
|
+
run: InstanceRun;
|
|
988
|
+
flag: string;
|
|
989
|
+
already: boolean;
|
|
990
|
+
/** Uses counted toward needUses after this call (0 when already). */
|
|
991
|
+
uses: number;
|
|
992
|
+
needUses: number;
|
|
993
|
+
/** True when this call set (or already had) setsFlag. */
|
|
994
|
+
completed: boolean;
|
|
995
|
+
/** Entry to destroy one of (successful use, spellcharges_1 < 0). Cite: Spell.cpp:4209 TakeCastItem. */
|
|
996
|
+
consumeItem?: number;
|
|
997
|
+
}
|
|
998
|
+
| { ok: false; reason: string };
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Apply an object-use gate. `hasItem` is true when the optional required item is
|
|
1002
|
+
* available (caller owns inventory). When `once` (default true) and the flag is
|
|
1003
|
+
* already set, returns ok with `already: true` and an unchanged run (no-op).
|
|
1004
|
+
*
|
|
1005
|
+
* Multi-step: increments objectGateUses[setsFlag]; sets flag only when uses ≥ needUses
|
|
1006
|
+
* (default 1). Multi-entry gates (goEntries length > 1) count each entry at most once.
|
|
1007
|
+
* Each successful use reports `consumeItem` independently (ST 4× Hakkari Blood).
|
|
1008
|
+
*/
|
|
1009
|
+
export function useObjectGate(
|
|
1010
|
+
run: InstanceRun,
|
|
1011
|
+
gate: Extract<EncounterEvent, { kind: 'objectGate' }>,
|
|
1012
|
+
hasItem: boolean,
|
|
1013
|
+
opts?: { goEntry?: number; /** ItemDef.charges for gate.requiredItem (0 = keep). */ itemCharges?: number },
|
|
1014
|
+
): UseObjectResult {
|
|
1015
|
+
const need = objectGateNeedUses(gate);
|
|
1016
|
+
const allowed = objectGateAcceptedEntries(gate);
|
|
1017
|
+
const usedEntry = opts?.goEntry ?? gate.goEntry;
|
|
1018
|
+
if (!allowed.includes(usedEntry)) {
|
|
1019
|
+
return { ok: false, reason: 'Nothing happens' };
|
|
1020
|
+
}
|
|
1021
|
+
// already before the item check: a consumed key must not turn a no-op into "you lack".
|
|
1022
|
+
const once = gate.once !== false;
|
|
1023
|
+
if (once && hasFlag(run, gate.setsFlag)) {
|
|
1024
|
+
return {
|
|
1025
|
+
ok: true,
|
|
1026
|
+
run,
|
|
1027
|
+
flag: gate.setsFlag,
|
|
1028
|
+
already: true,
|
|
1029
|
+
uses: need,
|
|
1030
|
+
needUses: need,
|
|
1031
|
+
completed: true,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const multiEntry = allowed.length > 1;
|
|
1036
|
+
const prevUsed = run.objectGateUsedEntries?.[gate.setsFlag] ?? [];
|
|
1037
|
+
if (multiEntry && prevUsed.includes(usedEntry)) {
|
|
1038
|
+
const uses = run.objectGateUses?.[gate.setsFlag] ?? prevUsed.length;
|
|
1039
|
+
return {
|
|
1040
|
+
ok: true,
|
|
1041
|
+
run,
|
|
1042
|
+
flag: gate.setsFlag,
|
|
1043
|
+
already: true,
|
|
1044
|
+
uses,
|
|
1045
|
+
needUses: need,
|
|
1046
|
+
completed: hasFlag(run, gate.setsFlag),
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (gate.requiredItem != null && !hasItem) {
|
|
1051
|
+
return { ok: false, reason: 'You lack the required item' };
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
let next: InstanceRun = run;
|
|
1055
|
+
if (multiEntry) {
|
|
1056
|
+
const usedEntries = [...prevUsed, usedEntry];
|
|
1057
|
+
next = {
|
|
1058
|
+
...next,
|
|
1059
|
+
objectGateUsedEntries: {
|
|
1060
|
+
...(next.objectGateUsedEntries ?? {}),
|
|
1061
|
+
[gate.setsFlag]: usedEntries,
|
|
1062
|
+
},
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
const prevCount = next.objectGateUses?.[gate.setsFlag] ?? 0;
|
|
1066
|
+
const count = prevCount + 1;
|
|
1067
|
+
next = {
|
|
1068
|
+
...next,
|
|
1069
|
+
objectGateUses: { ...(next.objectGateUses ?? {}), [gate.setsFlag]: count },
|
|
1070
|
+
};
|
|
1071
|
+
if (count >= need) {
|
|
1072
|
+
next = setFlag(next, gate.setsFlag);
|
|
1073
|
+
// Strat postboxes: Malown spawns at the box that completed needUses=3.
|
|
1074
|
+
// Cite: stratholmeScripts.cpp:108-110; docs/dungeons/malown/README.md.
|
|
1075
|
+
if (gate.setsFlag === STRATH_LIVE_POSTBOX.setsFlag) {
|
|
1076
|
+
const origin = postboxOrigin(usedEntry);
|
|
1077
|
+
if (origin) {
|
|
1078
|
+
next = {
|
|
1079
|
+
...next,
|
|
1080
|
+
encounterOrigins: {
|
|
1081
|
+
...(next.encounterOrigins ?? {}),
|
|
1082
|
+
[STRATH_LIVE_POSTBOX.encId]: { x: origin.x, y: origin.y, z: origin.z },
|
|
1083
|
+
},
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
// TakeCastItem destroys only when SpellCharges < 0; bake stamps 0 and omits −1.
|
|
1089
|
+
const consumeItem =
|
|
1090
|
+
gate.requiredItem != null && opts?.itemCharges !== 0 ? gate.requiredItem : undefined;
|
|
1091
|
+
return {
|
|
1092
|
+
ok: true,
|
|
1093
|
+
run: next,
|
|
1094
|
+
flag: gate.setsFlag,
|
|
1095
|
+
already: false,
|
|
1096
|
+
uses: count,
|
|
1097
|
+
needUses: need,
|
|
1098
|
+
completed: count >= need,
|
|
1099
|
+
...(consumeItem != null ? { consumeItem } : {}),
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/** Flags an encounter sets on clear (setsFlag events + hosted objectGate.setsFlag). */
|
|
1104
|
+
export function flagsSetByEncounter(enc: EncounterDef): string[] {
|
|
1105
|
+
const out: string[] = [...flagsSetOnClear(enc)];
|
|
1106
|
+
for (const gate of objectGateEvents(enc)) {
|
|
1107
|
+
if (!out.includes(gate.setsFlag)) out.push(gate.setsFlag);
|
|
1108
|
+
}
|
|
1109
|
+
return out;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Auto-run helper: for every uncleared encounter that is sequentially reachable
|
|
1114
|
+
* but missing flags, try matching objectGates anywhere in the instance.
|
|
1115
|
+
*
|
|
1116
|
+
* `hasItem` gates real inventory for UI-driven use. Pass
|
|
1117
|
+
* `opts.ignoreRequiredItem: true` on squad/live/headless auto-runs so OUTSIDE key
|
|
1118
|
+
* items (Seal of Ascension, Staff of Prehistoria, …) do not soft-lock the route.
|
|
1119
|
+
* Keys that drop inside this instance stay honest via `opts.keyDropsInside`.
|
|
1120
|
+
* Manual object-use still enforces items via `useObjectGate`.
|
|
1121
|
+
*/
|
|
1122
|
+
export function autoFireObjectGates(
|
|
1123
|
+
def: InstanceDef,
|
|
1124
|
+
run: InstanceRun,
|
|
1125
|
+
hasItem: (itemEntry: number) => boolean,
|
|
1126
|
+
opts?: {
|
|
1127
|
+
ignoreRequiredItem?: boolean;
|
|
1128
|
+
/** True when the key drops from creatures/GOs in this instance (plan 4d). */
|
|
1129
|
+
keyDropsInside?: (itemEntry: number) => boolean;
|
|
1130
|
+
},
|
|
1131
|
+
): { run: InstanceRun; fired: number; consumedItems: number[] } {
|
|
1132
|
+
let next = run;
|
|
1133
|
+
let fired = 0;
|
|
1134
|
+
const consumedItems: number[] = [];
|
|
1135
|
+
const seq = routeEncounterIdList(def, run);
|
|
1136
|
+
const ignoreItem = opts?.ignoreRequiredItem === true;
|
|
1137
|
+
const dropsInside = opts?.keyDropsInside;
|
|
1138
|
+
// Iterate until no new gates fire (flags can unlock further gated uses).
|
|
1139
|
+
for (let guard = 0; guard < 32; guard++) {
|
|
1140
|
+
let progressed = false;
|
|
1141
|
+
for (const encId of seq) {
|
|
1142
|
+
const enc = encounterOf(def, encId, next);
|
|
1143
|
+
if (!enc) continue;
|
|
1144
|
+
if (isCleared(next, enc.id)) continue;
|
|
1145
|
+
// Sequential **route** prefix must be clear before we auto-open gates.
|
|
1146
|
+
// Optional tribute guards do not block gate firing for later pulls.
|
|
1147
|
+
const idx = seq.indexOf(enc.id);
|
|
1148
|
+
let seqOk = true;
|
|
1149
|
+
for (let i = 0; i < idx; i++) {
|
|
1150
|
+
const prev = encounterOf(def, seq[i]!, next);
|
|
1151
|
+
if (prev?.optional) continue;
|
|
1152
|
+
if (!isCleared(next, seq[i]!)) {
|
|
1153
|
+
seqOk = false;
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (!seqOk) continue;
|
|
1158
|
+
|
|
1159
|
+
const missing = requiredFlags(enc).filter((f) => !hasFlag(next, f));
|
|
1160
|
+
if (missing.length === 0) continue;
|
|
1161
|
+
|
|
1162
|
+
// Search whole instance for objectGates that set a missing flag.
|
|
1163
|
+
for (const other of def.encounters) {
|
|
1164
|
+
for (const gate of objectGateEvents(other)) {
|
|
1165
|
+
if (!missing.includes(gate.setsFlag)) continue;
|
|
1166
|
+
const req = gate.requiredItem;
|
|
1167
|
+
const honest = req != null && dropsInside?.(req) === true;
|
|
1168
|
+
const owned = req == null || hasItem(req);
|
|
1169
|
+
const okItem = owned || (ignoreItem && !honest);
|
|
1170
|
+
// Multi-step: walk accepted entries (or repeat sole entry needUses times).
|
|
1171
|
+
const allowed = objectGateAcceptedEntries(gate);
|
|
1172
|
+
const need = objectGateNeedUses(gate);
|
|
1173
|
+
if (allowed.length > 1) {
|
|
1174
|
+
for (const entry of allowed) {
|
|
1175
|
+
if (hasFlag(next, gate.setsFlag)) break;
|
|
1176
|
+
const field = moltenCoreRuneField(entry);
|
|
1177
|
+
// molten_coreScripts.cpp:47-52 GetData != DONE → no SetData SPECIAL.
|
|
1178
|
+
if (
|
|
1179
|
+
field != null
|
|
1180
|
+
&& (next.scriptInstanceData?.[field] ?? INSTANCE_DATA.NOT_STARTED)
|
|
1181
|
+
!== INSTANCE_DATA.DONE
|
|
1182
|
+
) {
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
const r = useObjectGate(next, gate, okItem, { goEntry: entry });
|
|
1186
|
+
if (r.ok && !r.already) {
|
|
1187
|
+
next = r.run;
|
|
1188
|
+
if (field != null) next = applyMoltenCoreRuneSpecial(next, field);
|
|
1189
|
+
fired++;
|
|
1190
|
+
progressed = true;
|
|
1191
|
+
// Spend only when the party actually held the item (not outside-key bypass).
|
|
1192
|
+
if (r.consumeItem != null && owned) consumedItems.push(r.consumeItem);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
} else {
|
|
1196
|
+
for (let i = 0; i < need; i++) {
|
|
1197
|
+
if (hasFlag(next, gate.setsFlag)) break;
|
|
1198
|
+
const entry = allowed[0]!;
|
|
1199
|
+
const field = moltenCoreRuneField(entry);
|
|
1200
|
+
if (
|
|
1201
|
+
field != null
|
|
1202
|
+
&& (next.scriptInstanceData?.[field] ?? INSTANCE_DATA.NOT_STARTED)
|
|
1203
|
+
!== INSTANCE_DATA.DONE
|
|
1204
|
+
) {
|
|
1205
|
+
break;
|
|
1206
|
+
}
|
|
1207
|
+
const r = useObjectGate(next, gate, okItem, { goEntry: entry });
|
|
1208
|
+
if (r.ok && !r.already) {
|
|
1209
|
+
next = r.run;
|
|
1210
|
+
if (field != null) next = applyMoltenCoreRuneSpecial(next, field);
|
|
1211
|
+
fired++;
|
|
1212
|
+
progressed = true;
|
|
1213
|
+
if (r.consumeItem != null && owned) consumedItems.push(r.consumeItem);
|
|
1214
|
+
} else {
|
|
1215
|
+
break;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (!progressed) break;
|
|
1223
|
+
}
|
|
1224
|
+
return { run: next, fired, consumedItems };
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* When route density skips the packs that would `setsFlag` a gate (e.g. SFK
|
|
1229
|
+
* top-floor worgs, Strat courtyard band, UBRS rune rooms), auto-set those flags
|
|
1230
|
+
* so the run does not soft-lock on Normal/Speedrun.
|
|
1231
|
+
*
|
|
1232
|
+
* Density orphans are `setsFlag`-on-clear only. Never stamp a flag that any
|
|
1233
|
+
* `objectGate.setsFlag` produces — key doors stay on autoFire honesty + manual use
|
|
1234
|
+
* (Deadmines iron-clad / Defias Gunpowder 5397).
|
|
1235
|
+
*/
|
|
1236
|
+
export function autoSatisfyOrphanRouteFlags(
|
|
1237
|
+
def: InstanceDef,
|
|
1238
|
+
run: InstanceRun,
|
|
1239
|
+
): { run: InstanceRun; fired: number } {
|
|
1240
|
+
let next = run;
|
|
1241
|
+
let fired = 0;
|
|
1242
|
+
const seq = routeEncounterIdList(def, run);
|
|
1243
|
+
|
|
1244
|
+
const objectGateFlags = new Set<string>();
|
|
1245
|
+
for (const enc of def.encounters) {
|
|
1246
|
+
for (const gate of objectGateEvents(enc)) objectGateFlags.add(gate.setsFlag);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
for (let guard = 0; guard < 32; guard++) {
|
|
1250
|
+
let progressed = false;
|
|
1251
|
+
for (const encId of seq) {
|
|
1252
|
+
const enc = encounterOf(def, encId, next);
|
|
1253
|
+
if (!enc || isCleared(next, enc.id)) continue;
|
|
1254
|
+
const idx = seq.indexOf(enc.id);
|
|
1255
|
+
let seqOk = true;
|
|
1256
|
+
for (let i = 0; i < idx; i++) {
|
|
1257
|
+
// Optional stops are skippable, not prerequisites — the same read `diagnose` and
|
|
1258
|
+
// `nextEncounter` take. Counting them here meant one un-pulled optional boss held the
|
|
1259
|
+
// stamp forever, so `wc-disciple-ritual` (no setter anywhere) read as an authoring gap.
|
|
1260
|
+
if (encounterOf(def, seq[i]!, next)?.optional) continue;
|
|
1261
|
+
if (!isCleared(next, seq[i]!)) {
|
|
1262
|
+
seqOk = false;
|
|
1263
|
+
break;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (!seqOk) continue;
|
|
1267
|
+
|
|
1268
|
+
for (const flag of requiredFlags(enc)) {
|
|
1269
|
+
if (hasFlag(next, flag)) continue;
|
|
1270
|
+
// Key / GO doors: never density-orphan-stamp (KI-940).
|
|
1271
|
+
if (objectGateFlags.has(flag)) continue;
|
|
1272
|
+
|
|
1273
|
+
// Route encounters that set this flag on clear (setsFlag events only).
|
|
1274
|
+
const setters: string[] = [];
|
|
1275
|
+
for (const sid of seq) {
|
|
1276
|
+
const sEnc = encounterOf(def, sid, next);
|
|
1277
|
+
if (!sEnc) continue;
|
|
1278
|
+
if (flagsSetOnClear(sEnc).includes(flag)) setters.push(sid);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Orphan: nothing on this route sets the flag (density skipped them).
|
|
1282
|
+
// Or all setters already cleared but flag still missing (authoring gap).
|
|
1283
|
+
const orphan =
|
|
1284
|
+
setters.length === 0
|
|
1285
|
+
|| setters.every((sid) => isCleared(next, sid));
|
|
1286
|
+
if (!orphan) continue;
|
|
1287
|
+
|
|
1288
|
+
next = setFlag(next, flag);
|
|
1289
|
+
fired++;
|
|
1290
|
+
progressed = true;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (!progressed) break;
|
|
1294
|
+
}
|
|
1295
|
+
return { run: next, fired };
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Full auto-run unlock pass: object gates (outside keys bypassed; in-instance keys honest) + orphan density
|
|
1299
|
+
// flags. Call before every `nextEncounter` in live / headless / runFullDungeon.
|
|
1300
|
+
export function autoAdvanceRouteGates(
|
|
1301
|
+
def: InstanceDef,
|
|
1302
|
+
run: InstanceRun,
|
|
1303
|
+
hasItem: (itemEntry: number) => boolean = () => false,
|
|
1304
|
+
opts?: { keyDropsInside?: (itemEntry: number) => boolean },
|
|
1305
|
+
): { run: InstanceRun; fired: number; consumedItems: number[] } {
|
|
1306
|
+
let next = run;
|
|
1307
|
+
let fired = 0;
|
|
1308
|
+
const consumedItems: number[] = [];
|
|
1309
|
+
for (let guard = 0; guard < 8; guard++) {
|
|
1310
|
+
const a = autoFireObjectGates(def, next, hasItem, {
|
|
1311
|
+
ignoreRequiredItem: true,
|
|
1312
|
+
keyDropsInside: opts?.keyDropsInside,
|
|
1313
|
+
});
|
|
1314
|
+
next = a.run;
|
|
1315
|
+
fired += a.fired;
|
|
1316
|
+
consumedItems.push(...a.consumedItems);
|
|
1317
|
+
const b = autoSatisfyOrphanRouteFlags(def, next);
|
|
1318
|
+
next = b.run;
|
|
1319
|
+
fired += b.fired;
|
|
1320
|
+
if (a.fired + b.fired === 0) break;
|
|
1321
|
+
}
|
|
1322
|
+
return { run: next, fired, consumedItems };
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
type LootRow = { item: number; conditionId?: number };
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* True when `itemEntry` appears on a creature or GO loot table for this instance def.
|
|
1329
|
+
* Used to keep auto-run honest for keys that drop inside (Gordok Courtyard/Inner keys).
|
|
1330
|
+
*
|
|
1331
|
+
* Sources: encounter creature loot, objectGate GO lootIds, `def.lootObjects`, and
|
|
1332
|
+
* floor GOs from `dungeonGameObjectsForMap(def.mapId)` (Defias Gunpowder GO 17155).
|
|
1333
|
+
*
|
|
1334
|
+
* `rowAllowed` filters condition-gated rows (13523 / condition 213 QUESTREWARDED 5384).
|
|
1335
|
+
* Omit it to ask "does any row exist"; pass lootDropAllowed to ask "can THIS party loot it".
|
|
1336
|
+
*/
|
|
1337
|
+
export function itemDropsInsideInstance(
|
|
1338
|
+
def: InstanceDef,
|
|
1339
|
+
itemEntry: number,
|
|
1340
|
+
lootTable: (lootId: number | string) => readonly LootRow[],
|
|
1341
|
+
goLootId?: (goEntry: number) => number | undefined,
|
|
1342
|
+
rowAllowed?: (row: LootRow) => boolean,
|
|
1343
|
+
): boolean {
|
|
1344
|
+
const seen = new Set<number>();
|
|
1345
|
+
const seenGoLoot = new Set<number>();
|
|
1346
|
+
const hits = (rows: readonly LootRow[]): boolean =>
|
|
1347
|
+
rows.some((r) => r.item === itemEntry && (rowAllowed ? rowAllowed(r) : true));
|
|
1348
|
+
const checkCreature = (entry: number): boolean => {
|
|
1349
|
+
if (seen.has(entry)) return false;
|
|
1350
|
+
seen.add(entry);
|
|
1351
|
+
return hits(lootTable(entry));
|
|
1352
|
+
};
|
|
1353
|
+
const checkGoLoot = (lid: number | undefined): boolean => {
|
|
1354
|
+
if (lid == null || !(lid > 0) || seenGoLoot.has(lid)) return false;
|
|
1355
|
+
seenGoLoot.add(lid);
|
|
1356
|
+
return hits(lootTable(`go:${lid}`));
|
|
1357
|
+
};
|
|
1358
|
+
for (const enc of def.encounters) {
|
|
1359
|
+
if (checkCreature(enc.creatureEntry)) return true;
|
|
1360
|
+
for (const p of enc.pack ?? []) {
|
|
1361
|
+
if (checkCreature(p.creatureEntry)) return true;
|
|
1362
|
+
}
|
|
1363
|
+
for (const ev of enc.events ?? []) {
|
|
1364
|
+
if (ev.kind !== 'objectGate') continue;
|
|
1365
|
+
if (checkGoLoot(goLootId?.(ev.goEntry))) return true;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
for (const lo of def.lootObjects ?? []) {
|
|
1369
|
+
if (checkGoLoot(goLootId?.(lo.goEntry))) return true;
|
|
1370
|
+
}
|
|
1371
|
+
// Floor chests / powder piles are not on encounters or lootObjects (KI-940).
|
|
1372
|
+
for (const go of dungeonGameObjectsForMap(def.mapId)) {
|
|
1373
|
+
if (checkGoLoot(go.lootId)) return true;
|
|
1374
|
+
}
|
|
1375
|
+
return false;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// ---- instance save (5-mans: 30 min; raids: until lockout / reset_delay) ------
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* How long a normal (5-man) instance keeps its state after you walk out.
|
|
1382
|
+
* Cite: MapPersistentStateMgr.h:41 `#define NORMAL_INSTANCE_RESET_TIME 30 * MINUTE`.
|
|
1383
|
+
* Raid/heroic saves are NOT this macro — they use `def.lockoutMs` (instance_template
|
|
1384
|
+
* reset_delay); do not reuse this constant for them.
|
|
1385
|
+
*/
|
|
1386
|
+
export const NORMAL_INSTANCE_RESET_TIME_MS = 30 * 60 * 1000;
|
|
1387
|
+
|
|
1388
|
+
/** A walked-out-of run, held until `expiresAtMs` (30 min or lockout). Cite: MapPersistentState unload-or-reset. */
|
|
1389
|
+
export interface InstanceSave {
|
|
1390
|
+
run: InstanceRun;
|
|
1391
|
+
expiresAtMs: number;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
/**
|
|
1395
|
+
* dungeonId -> held instance state. A map, not one slot: saves are per-instance in
|
|
1396
|
+
* CMaNGOS (`MapPersistentStateManager` keyed by instance id), so entering Deadmines
|
|
1397
|
+
* must not reset your Wailing Caverns clear. Raids land here too — the lockout is
|
|
1398
|
+
* the bind clock, this map is the instance itself.
|
|
1399
|
+
*/
|
|
1400
|
+
export type InstanceSaveMap = Record<string, InstanceSave>;
|
|
1401
|
+
|
|
1402
|
+
/** Does this run hold progress worth keeping across a step outside? */
|
|
1403
|
+
function runHasProgress(run: InstanceRun): boolean {
|
|
1404
|
+
return run.defeated.length > 0
|
|
1405
|
+
|| (run.floorClearedGuids?.length ?? 0) > 0
|
|
1406
|
+
|| (run.flags?.length ?? 0) > 0;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/** Drop saves whose `expiresAtMs` has passed — those instances reset. */
|
|
1410
|
+
export function pruneInstanceSaves(saves: InstanceSaveMap, nowMs: number): InstanceSaveMap {
|
|
1411
|
+
const out: InstanceSaveMap = {};
|
|
1412
|
+
for (const [id, s] of Object.entries(saves)) {
|
|
1413
|
+
if (s.expiresAtMs > nowMs) out[id] = s;
|
|
1414
|
+
}
|
|
1415
|
+
return out;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/** 5-man: 30 min. Raid: existing lockout, else now + reset_delay. Cite: MapPersistentStateMgr.cpp:348-352. */
|
|
1419
|
+
function saveExpiresAtMs(def: InstanceDef, nowMs: number, lockouts?: LockoutMap): number {
|
|
1420
|
+
if (def.lockoutMs > 0) {
|
|
1421
|
+
const until = lockouts?.[def.id];
|
|
1422
|
+
if (until != null && until > nowMs) return until;
|
|
1423
|
+
return nowMs + def.lockoutMs;
|
|
1424
|
+
}
|
|
1425
|
+
return nowMs + NORMAL_INSTANCE_RESET_TIME_MS;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// File the run on leaving, pruning expired ones. No progress → drop. 5-mans expire in 30 min; raids expire
|
|
1429
|
+
// at the lockout (reset_delay), so a mid-raid leave is not a reset.
|
|
1430
|
+
export function saveRunOnLeave(
|
|
1431
|
+
saves: InstanceSaveMap,
|
|
1432
|
+
run: InstanceRun,
|
|
1433
|
+
def: InstanceDef,
|
|
1434
|
+
nowMs: number,
|
|
1435
|
+
lockouts?: LockoutMap,
|
|
1436
|
+
): InstanceSaveMap {
|
|
1437
|
+
const pruned = pruneInstanceSaves(saves, nowMs);
|
|
1438
|
+
if (!runHasProgress(run)) {
|
|
1439
|
+
delete pruned[def.id];
|
|
1440
|
+
return pruned;
|
|
1441
|
+
}
|
|
1442
|
+
return { ...pruned, [def.id]: { run, expiresAtMs: saveExpiresAtMs(def, nowMs, lockouts) } };
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// The saved run to re-enter with, or null when there is none for this dungeon or `expiresAtMs` has passed
|
|
1446
|
+
// (the instance reset while you were out).
|
|
1447
|
+
export function restoreSavedRun(
|
|
1448
|
+
saves: InstanceSaveMap | undefined,
|
|
1449
|
+
dungeonId: string,
|
|
1450
|
+
nowMs: number,
|
|
1451
|
+
): InstanceRun | null {
|
|
1452
|
+
const save = saves?.[dungeonId];
|
|
1453
|
+
if (!save || save.expiresAtMs <= nowMs) return null;
|
|
1454
|
+
if (save.run.dungeonId !== dungeonId) return null;
|
|
1455
|
+
// molten_core.cpp:62-65 OnPlayerEnter on a saved instance.
|
|
1456
|
+
return applyMajordomoSpawnOnPlayerEnter(save.run);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// ---- lockouts (raid/heroic; 5-mans use lockoutMs 0 and never lock) ----------
|
|
1460
|
+
|
|
1461
|
+
export function isLocked(lockouts: LockoutMap, dungeonId: string, nowMs: number): boolean {
|
|
1462
|
+
const until = lockouts[dungeonId];
|
|
1463
|
+
return until != null && until > nowMs;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
export function lockRemainingMs(lockouts: LockoutMap, dungeonId: string, nowMs: number): number {
|
|
1467
|
+
const until = lockouts[dungeonId];
|
|
1468
|
+
return until != null && until > nowMs ? until - nowMs : 0;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
/** Save the player to a dungeon on completion (no-op for freely-resettable 5-mans). */
|
|
1472
|
+
export function applyLockout(lockouts: LockoutMap, def: InstanceDef, nowMs: number): LockoutMap {
|
|
1473
|
+
if (def.lockoutMs <= 0) return lockouts;
|
|
1474
|
+
return { ...lockouts, [def.id]: nowMs + def.lockoutMs };
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// ---- portals ----------------------------------------------------------------
|
|
1478
|
+
|
|
1479
|
+
/** Dungeons whose entrance portal is in the given world zone. */
|
|
1480
|
+
export function instancePortals(ds: DataStore, zoneId: string): InstanceDef[] {
|
|
1481
|
+
return ds.instances().filter((d) => d.portalZone === zoneId);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
export interface EnterCheck {
|
|
1485
|
+
ok: boolean;
|
|
1486
|
+
reason?: string;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* Optional bag for areatrigger_teleport lock chain (GetAreaTriggerLockStatus).
|
|
1491
|
+
* When `teleports` is provided and an entrance row matches `def.portalZone`,
|
|
1492
|
+
* the trigger's requiredLevel/item/quest gates replace authored minLevel.
|
|
1493
|
+
* When no matching row (or teleports omitted), authored `def.minLevel` remains.
|
|
1494
|
+
*
|
|
1495
|
+
* Wiring (game.ts enterInstance — do not implement here):
|
|
1496
|
+
* canEnter(def, level, lockouts, nowMs, {
|
|
1497
|
+
* teleports: b.areatriggerTeleports,
|
|
1498
|
+
* ownedItems: inventory item entry set,
|
|
1499
|
+
* completedQuests: turned-in quest id set,
|
|
1500
|
+
* nameFragment: optional dungeon name hint,
|
|
1501
|
+
* })
|
|
1502
|
+
*/
|
|
1503
|
+
export interface CanEnterContext {
|
|
1504
|
+
/** Baked areatrigger_teleport rows (DataBundle.areatriggerTeleports). */
|
|
1505
|
+
teleports?: readonly AreaTriggerTeleportRow[];
|
|
1506
|
+
/** Narrow entrance match by name substring (e.g. "Deadmines", "Onyxia"). */
|
|
1507
|
+
nameFragment?: string;
|
|
1508
|
+
ownedItems?: ReadonlySet<number> | readonly number[];
|
|
1509
|
+
hasItem?: (entry: number) => boolean;
|
|
1510
|
+
completedQuests?: ReadonlySet<number> | readonly number[];
|
|
1511
|
+
hasQuestRewarded?: (questId: number) => boolean;
|
|
1512
|
+
/** Filed run(s). Lockout + a live save is re-entry of YOUR instance, not a bar. */
|
|
1513
|
+
instanceSaves?: InstanceSaveMap;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* Whether a character may enter a dungeon right now.
|
|
1518
|
+
* Lockout without a filed save refuses a *fresh* raid; lockout + save allows
|
|
1519
|
+
* re-entry of that same instance. Level/item/quest: sourced lock chain when a
|
|
1520
|
+
* matching entrance teleport exists; otherwise authored `def.minLevel`.
|
|
1521
|
+
*/
|
|
1522
|
+
export function canEnter(
|
|
1523
|
+
def: InstanceDef,
|
|
1524
|
+
level: number,
|
|
1525
|
+
lockouts: LockoutMap,
|
|
1526
|
+
nowMs: number,
|
|
1527
|
+
ctx?: CanEnterContext,
|
|
1528
|
+
): EnterCheck {
|
|
1529
|
+
// Sourced lock chain when teleports supplied and an entrance matches portalZone.
|
|
1530
|
+
// requiredLevel on the trigger replaces authored minLevel when present;
|
|
1531
|
+
// if the trigger has no requiredLevel, fall back to def.minLevel.
|
|
1532
|
+
if (ctx?.teleports) {
|
|
1533
|
+
const entrance = def.entranceTriggerId != null
|
|
1534
|
+
? ctx.teleports.find((t) => t.id === def.entranceTriggerId)
|
|
1535
|
+
: findEntranceTeleport(def.portalZone, ctx.teleports, ctx.nameFragment);
|
|
1536
|
+
if (entrance) {
|
|
1537
|
+
const teleport = {
|
|
1538
|
+
requiredLevel:
|
|
1539
|
+
(entrance.requiredLevel ?? 0) > 0 ? entrance.requiredLevel : def.minLevel,
|
|
1540
|
+
requiredItem: entrance.requiredItem,
|
|
1541
|
+
requiredItem2: entrance.requiredItem2,
|
|
1542
|
+
requiredQuest: entrance.requiredQuest,
|
|
1543
|
+
statusFailedText: entrance.statusFailedText,
|
|
1544
|
+
};
|
|
1545
|
+
const lock = checkAreaTriggerLock({
|
|
1546
|
+
level,
|
|
1547
|
+
ownedItems: ctx.ownedItems,
|
|
1548
|
+
hasItem: ctx.hasItem,
|
|
1549
|
+
completedQuests: ctx.completedQuests,
|
|
1550
|
+
hasQuestRewarded: ctx.hasQuestRewarded,
|
|
1551
|
+
teleport,
|
|
1552
|
+
});
|
|
1553
|
+
if (!lock.ok) return { ok: false, reason: lock.reason };
|
|
1554
|
+
} else if (level < def.minLevel) {
|
|
1555
|
+
// No matching trigger row → keep authored minLevel (jasperlode / unbaked)
|
|
1556
|
+
return { ok: false, reason: `Requires level ${def.minLevel}` };
|
|
1557
|
+
}
|
|
1558
|
+
} else if (level < def.minLevel) {
|
|
1559
|
+
return { ok: false, reason: `Requires level ${def.minLevel}` };
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// Perm bind is to YOUR instance. HAS_BIND only fires when the target is a *different*
|
|
1563
|
+
// instance. Cite: Player.cpp:19990-19994; Map.cpp:1797-1812.
|
|
1564
|
+
if (isLocked(lockouts, def.id, nowMs) && !restoreSavedRun(ctx?.instanceSaves, def.id, nowMs)) {
|
|
1565
|
+
return { ok: false, reason: 'You are saved to this instance' };
|
|
1566
|
+
}
|
|
1567
|
+
return { ok: true };
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
/** Re-export for callers that only import world/instances. */
|
|
1571
|
+
export { findEntranceTeleport, checkAreaTriggerLock } from '../domain/world/areaTriggerLocks.ts';
|
|
1572
|
+
export type { AreaTriggerTeleportRow, AreaTriggerLockResult } from '../domain/world/areaTriggerLocks.ts';
|