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,1553 @@
|
|
|
1
|
+
// Geometry motion phase for a live combat tick, peeled from stepSession. Owns charge travel, gap-close
|
|
2
|
+
// into melee or cast range, and the area-aura pass. Extracted modules must not import combat.ts.
|
|
3
|
+
|
|
4
|
+
import type { SpellDef } from '../../data/schema.ts';
|
|
5
|
+
import { isEnemySide, isTotemSide, type Side } from '../core/events.ts';
|
|
6
|
+
import {
|
|
7
|
+
moveStepYd, stepToward, MELEE_REACH_YD, MELEE_PARK_SLACK_YD,
|
|
8
|
+
MELEE_CONTACT_YD, PARTY_FOLLOW_YD,
|
|
9
|
+
combatDistanceYd, STOREY_DZ_YD,
|
|
10
|
+
planarRangeHonestYd,
|
|
11
|
+
type CombatPosYd,
|
|
12
|
+
type PullGeometryAnchor,
|
|
13
|
+
} from './combatGeometry.ts';
|
|
14
|
+
import { combatToWorld, worldToCombat } from './worldFrame.ts';
|
|
15
|
+
import {
|
|
16
|
+
walkLineClearOnSurface,
|
|
17
|
+
motionSurfacesForMap,
|
|
18
|
+
stepOnSurface,
|
|
19
|
+
} from './walkSurface.ts';
|
|
20
|
+
import { type NavPos } from '../../domain/nav/nav.ts';
|
|
21
|
+
import {
|
|
22
|
+
JUMP_REACH_MAX_YD,
|
|
23
|
+
WALKABLE_CLIMB_YD,
|
|
24
|
+
findNearestPoly,
|
|
25
|
+
followPathNavMesh,
|
|
26
|
+
isWalkableNavMesh,
|
|
27
|
+
navMeshForMap,
|
|
28
|
+
sidestepNavMesh,
|
|
29
|
+
snapOntoNavMesh,
|
|
30
|
+
stepWalkableNavMesh,
|
|
31
|
+
} from '../../domain/nav/navmesh.ts';
|
|
32
|
+
import { createMotionKit } from './sessionMotionKit.ts';
|
|
33
|
+
import { createMechanicMove } from './sessionMechanicMove.ts';
|
|
34
|
+
import { createChargeArm } from './sessionCharge.ts';
|
|
35
|
+
import { createFollowArms } from './sessionFollow.ts';
|
|
36
|
+
import { createChaseArm } from './sessionChase.ts';
|
|
37
|
+
import { createHomeArm } from './sessionHome.ts';
|
|
38
|
+
import { createMoveOrderArm } from './sessionMoveOrder.ts';
|
|
39
|
+
import { createStepBackArm } from './sessionStepBack.ts';
|
|
40
|
+
import { createMoveIntentArm } from './sessionMoveIntent.ts';
|
|
41
|
+
import { detHypot, detHypot3 } from '../core/detMath.ts';
|
|
42
|
+
import { createFearFlee } from './fearFlee.ts';
|
|
43
|
+
import {
|
|
44
|
+
clampDisplacementToFloor,
|
|
45
|
+
displaceFloorOf,
|
|
46
|
+
stepDisplacement,
|
|
47
|
+
} from './displaceFloor.ts';
|
|
48
|
+
import { channelHoldsStill, channelInterruptCtx, interruptChannel } from './channel.ts';
|
|
49
|
+
import { runAreaAuraMaintain } from './areaAura.ts';
|
|
50
|
+
import { runEvadeLifecycle } from './evadeLifecycle.ts';
|
|
51
|
+
import { createMotionClaims, type MotionOwner } from './motionClaim.ts';
|
|
52
|
+
import {
|
|
53
|
+
createMotionGates,
|
|
54
|
+
type MotionGateInfo,
|
|
55
|
+
type MotionGateName,
|
|
56
|
+
} from './motionGate.ts';
|
|
57
|
+
import { emptySlowDiag } from './slowUpdate.ts';
|
|
58
|
+
import type { CombatContext } from './context.ts';
|
|
59
|
+
import type { ActiveEffect, Fighter } from './types.ts';
|
|
60
|
+
import {
|
|
61
|
+
bindPathTo,
|
|
62
|
+
drainNavSearchQueue,
|
|
63
|
+
dropNavSearch,
|
|
64
|
+
dropStaleCorridor as dropStaleCorridorAt,
|
|
65
|
+
followLeadCorridor,
|
|
66
|
+
tryAdoptSharedPath,
|
|
67
|
+
upsertNavSearch,
|
|
68
|
+
type NavSearchReq,
|
|
69
|
+
type PathToOpts,
|
|
70
|
+
type SharedPath,
|
|
71
|
+
type UnitNavPath,
|
|
72
|
+
} from './sessionPathTo.ts';
|
|
73
|
+
import { BODY_SEP_YD, applyLocalSteer, bodyInWay } from './sessionSteer.ts';
|
|
74
|
+
import { navSightClear } from './navSight.ts';
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
/** Closures the motion phase needs from installStepSession / the session bag. */
|
|
78
|
+
export interface SessionMotionDeps {
|
|
79
|
+
ctx: CombatContext;
|
|
80
|
+
dead: (f: Fighter) => boolean;
|
|
81
|
+
/** Threat-id → fighter (charge retarget). */
|
|
82
|
+
fighterByThreatId: (id: string) => Fighter | null;
|
|
83
|
+
hostileTargetOf: (f: Fighter) => Fighter | null;
|
|
84
|
+
foeOf: (f: Fighter) => Fighter | null;
|
|
85
|
+
isChargeRole: (spell: SpellDef) => boolean;
|
|
86
|
+
onCooldown: (f: Fighter, spell: SpellDef) => boolean;
|
|
87
|
+
canAfford: (f: Fighter, spell: SpellDef) => boolean;
|
|
88
|
+
weaponStrike: (f: Fighter, tgt: Fighter, spell: SpellDef) => unknown;
|
|
89
|
+
castOnTargets: (f: Fighter, tgt: Fighter, spell: SpellDef) => void;
|
|
90
|
+
posOf: (side: Side | string) => CombatPosYd | undefined;
|
|
91
|
+
setPos: (side: Side | string, p: CombatPosYd) => void;
|
|
92
|
+
fightersOrder: () => Fighter[];
|
|
93
|
+
bySide: (s: Side) => Fighter;
|
|
94
|
+
effects: ActiveEffect[];
|
|
95
|
+
tickMs: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface SessionMotionApi {
|
|
99
|
+
finishChargeArrival: (f: Fighter, tgt: Fighter, spell: SpellDef) => void;
|
|
100
|
+
advanceChargeMove: (f: Fighter) => void;
|
|
101
|
+
/** Non-empty threat list for enemies (also gates white-swing engagement in step). */
|
|
102
|
+
enemyEngaged: (f: Fighter) => boolean;
|
|
103
|
+
runMotionPhase: () => void;
|
|
104
|
+
areaAuraMaintainPhase: () => void;
|
|
105
|
+
pathTo: (side: string, dest: CombatPosYd, opts?: PathToOpts) => boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Install charge / gap-close / area-aura maintain on the session bag and return the same functions for
|
|
109
|
+
// startOrCast / step closures.
|
|
110
|
+
export function installSessionMotion(deps: SessionMotionDeps): SessionMotionApi {
|
|
111
|
+
const {
|
|
112
|
+
ctx, dead, fighterByThreatId, hostileTargetOf, foeOf, isChargeRole,
|
|
113
|
+
onCooldown, canAfford, weaponStrike, castOnTargets,
|
|
114
|
+
posOf, setPos: setPosRaw, fightersOrder, bySide, effects, tickMs,
|
|
115
|
+
} = deps;
|
|
116
|
+
const lastMoveFrom = new Map<string, CombatPosYd>();
|
|
117
|
+
// One motion owner per unit. Every arm asks for the unit before it steps, and a producer outside the
|
|
118
|
+
// engine asks through `pathTo`. See motionClaim.ts for why the early-return order is not enough.
|
|
119
|
+
const claims = ctx.motionClaims ?? createMotionClaims(tickMs);
|
|
120
|
+
ctx.motionClaims = claims;
|
|
121
|
+
// Which of `advanceGapClose`'s early returns resolved each unit, so "the boss just stands there" has one
|
|
122
|
+
// name and its inputs. Same diagnosis contract as `navDiag` below.
|
|
123
|
+
const motionGates = ctx.motionGates ?? createMotionGates();
|
|
124
|
+
ctx.motionGates = motionGates;
|
|
125
|
+
const gate = (side: string, name: MotionGateName, info?: MotionGateInfo): void => {
|
|
126
|
+
motionGates.mark(side, name, ctx.t, info);
|
|
127
|
+
};
|
|
128
|
+
// The goal this arm may walk this tick, or null when someone with priority owns the unit. A
|
|
129
|
+
// same-owner refusal is the dead band and hands back the LIVE goal: half a yard is not a new dest.
|
|
130
|
+
const takeMotion = (
|
|
131
|
+
side: string,
|
|
132
|
+
owner: MotionOwner,
|
|
133
|
+
goal: CombatPosYd | null,
|
|
134
|
+
opts?: { force?: boolean; holdMs?: number },
|
|
135
|
+
): CombatPosYd | null => {
|
|
136
|
+
if (claims.take(side, owner, goal, ctx.t, opts)) return goal;
|
|
137
|
+
if (claims.ownerOf(side, ctx.t) === owner) return claims.goalOf(side, ctx.t) ?? goal;
|
|
138
|
+
return null;
|
|
139
|
+
};
|
|
140
|
+
// Who owns a stored dest: whoever issued it, so a re-plan keeps its hold window and a floor click
|
|
141
|
+
// keeps its precedence. Nothing holding it means the queue outlived its issuer — treat it as a click.
|
|
142
|
+
const orderOwnerOf = (side: string): MotionOwner =>
|
|
143
|
+
claims.ownerOf(side, ctx.t)
|
|
144
|
+
?? ((ctx as { unitHoldCourse?: Set<string> }).unitHoldCourse?.has(side) ? 'course' : 'click');
|
|
145
|
+
const setPos = (side: string, p: CombatPosYd): void => {
|
|
146
|
+
const prev = posOf(side);
|
|
147
|
+
setPosRaw(side, p);
|
|
148
|
+
if (prev && detHypot(p.x - prev.x, p.y - prev.y) > 0.05) {
|
|
149
|
+
lastMoveFrom.set(side, { x: prev.x, y: prev.y });
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Geometry motion — Euclidean stepToward without a mesh, corridor-following with one, mask-clamped
|
|
154
|
+
// greedy slide when clamp-only. Speeds: run 7.0 (Unit.cpp:66), charge 27 (Unit.h).
|
|
155
|
+
const anchorOf = (): PullGeometryAnchor | undefined => ctx.cfg.geometryAnchor;
|
|
156
|
+
const floorOf = () => displaceFloorOf({
|
|
157
|
+
mapId: anchorOf()?.mapId,
|
|
158
|
+
origin: anchorOf()?.frame === 'world-translated' ? anchorOf()?.origin : undefined,
|
|
159
|
+
});
|
|
160
|
+
// Per-side cached path, recomputed when the goal cell changes and when the unit stops closing on its
|
|
161
|
+
// own next waypoint: a stale path aims through a wall, and the graze along it read as progress.
|
|
162
|
+
const unitNavPaths = new Map<string, UnitNavPath>();
|
|
163
|
+
// A blocked WAYPOINT is not a wrong CORRIDOR: drop that waypoint and aim at the next one. Deleting
|
|
164
|
+
// the whole path re-drew a route up to three times a second on a formation move.
|
|
165
|
+
const WP_SKIP_MAX = 4;
|
|
166
|
+
/** Per-side floor on full search. Allies follow the player; they do not A* a second route. */
|
|
167
|
+
const REPATH_COOLDOWN_MS = 1_500;
|
|
168
|
+
/** Re-aim the last waypoint when the live goal is still this close to the tail. */
|
|
169
|
+
const TAIL_REAIM_YD = 10;
|
|
170
|
+
const lastSearchAt = new Map<string, number>();
|
|
171
|
+
const orderStall = new Map<string, { ms: number; hops: number; best: number }>();
|
|
172
|
+
// side -> goalKey the unit walks on the layer-aware stack, armed only after the overview corridor
|
|
173
|
+
// wedges. Keyed by goalKey so a new goal reverts to overview-primary.
|
|
174
|
+
const stackRecover = new Map<string, string>();
|
|
175
|
+
// Shared corridor for a goal key — a group move order pathfinds once, not per ally. Without it the
|
|
176
|
+
// player, four allies and a pet each burned an A*, so half slid a tick under the search budget.
|
|
177
|
+
const sharedPathByGoal = new Map<string, SharedPath>();
|
|
178
|
+
const SHARE_START_YD = 10;
|
|
179
|
+
const dropStaleCorridor = (side: string): void =>
|
|
180
|
+
dropStaleCorridorAt(side, unitNavPaths, sharedPathByGoal);
|
|
181
|
+
// How far apart two goals may be and still be "the same order". A formation gives every member its
|
|
182
|
+
// own slot, so the exact-key lookup this replaces was settled by a rounding boundary.
|
|
183
|
+
const SHARE_GOAL_YD = 8;
|
|
184
|
+
// Allies that wedged on a follow-the-lead line toward this goal key. Their next corridor to the same
|
|
185
|
+
// goal is their own search: re-adopting the line that failed is what pinned a unit behind a wall.
|
|
186
|
+
const followLeadFailed = new Map<string, string>();
|
|
187
|
+
/** Ticks of wall-graze before a stale path is thrown away and re-drawn from here. */
|
|
188
|
+
const REPATH_NO_GAIN_TICKS = 3;
|
|
189
|
+
// Enemy chase is clamp-only, escalating only when geometry blocks on the surface the mob steps on.
|
|
190
|
+
// Another body in the way is a push, not an A*.
|
|
191
|
+
const navEscalateUntil = new Map<string, number>();
|
|
192
|
+
const clampProgress = new Map<string, { d: number; miss: number }>();
|
|
193
|
+
const CLAMP_MISS_TICKS = 5;
|
|
194
|
+
const NAV_ESCALATE_MS = 4_000;
|
|
195
|
+
// Goal quantisation for the path cache key. Keying on the goal CELL threw the corridor away every
|
|
196
|
+
// tick a target moved: 36 main-thread stalls over 25 ms in 68 s of auto-running Deadmines.
|
|
197
|
+
const GOAL_REGRID_YD = 4;
|
|
198
|
+
// Full searches allowed in one sim tick. One, not three: three searches a tick was the 25-46 ms
|
|
199
|
+
// combat bill on Dire Maul North. Denied units keep the last corridor or clamp-slide.
|
|
200
|
+
const NAV_SEARCH_BUDGET = 1;
|
|
201
|
+
let navSearchTick = -1;
|
|
202
|
+
let navSearchesThisTick = 0;
|
|
203
|
+
const navSearchQueue: NavSearchReq[] = [];
|
|
204
|
+
|
|
205
|
+
// Per-side tally of how each motion tick resolved. A unit walking back at 3% of run speed looks the
|
|
206
|
+
// same from outside whether it is starved of A*, off-mesh, or pathing into a wall.
|
|
207
|
+
const navDiag = new Map<string, Record<string, number>>();
|
|
208
|
+
const diag = (side: string, k: string): void => {
|
|
209
|
+
let m = navDiag.get(side);
|
|
210
|
+
if (!m) { m = {}; navDiag.set(side, m); }
|
|
211
|
+
m[k] = (m[k] ?? 0) + 1;
|
|
212
|
+
};
|
|
213
|
+
ctx.navDiag = navDiag;
|
|
214
|
+
|
|
215
|
+
const skipSteerBody = (s: string): boolean => {
|
|
216
|
+
if (isTotemSide(s)) return true;
|
|
217
|
+
const f = bySide(s as Side); return !f || dead(f);
|
|
218
|
+
};
|
|
219
|
+
const localSteerAim = (side: string, from: CombatPosYd, to: CombatPosYd, step: number) => {
|
|
220
|
+
const unit = bySide(side as Side).side === side ? bySide(side as Side) : undefined;
|
|
221
|
+
const r = applyLocalSteer({
|
|
222
|
+
side, from, to, hopYd: Math.max(step, BODY_SEP_YD), others: fightersOrder(),
|
|
223
|
+
posOf, skip: skipSteerBody, tickIndex: Math.floor(ctx.t / Math.max(1, tickMs)), unit,
|
|
224
|
+
hardCC: !!(unit && (
|
|
225
|
+
(ctx.isControlledHard as ((u: Fighter) => boolean) | undefined)?.(unit)
|
|
226
|
+
|| unit.rootedUntil > ctx.t
|
|
227
|
+
)),
|
|
228
|
+
});
|
|
229
|
+
if (r?.steered) diag(side, 'steerBody');
|
|
230
|
+
return r?.aim ?? null;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const navStepToward = (
|
|
234
|
+
side: string,
|
|
235
|
+
from: CombatPosYd,
|
|
236
|
+
to: CombatPosYd,
|
|
237
|
+
step: number,
|
|
238
|
+
stopAtDist: number,
|
|
239
|
+
// May this unit pay for a corridor SEARCH this tick: true is a full A*, false is steer and clamp
|
|
240
|
+
// only, omitted takes the session switch. It does NOT pick the surface — the map answers that.
|
|
241
|
+
searchCorridor?: boolean,
|
|
242
|
+
): CombatPosYd => {
|
|
243
|
+
const anchor = anchorOf();
|
|
244
|
+
// No mesh, no geometry: walk the straight line. This gate used to read `if (!nav)` and then
|
|
245
|
+
// `mesh or raster` (E4.6 finding 1); with the raster gone the mesh is the whole question.
|
|
246
|
+
if (anchor?.mapId == null || !navMeshForMap(anchor.mapId)) {
|
|
247
|
+
return stepToward(from, to, step, stopAtDist);
|
|
248
|
+
}
|
|
249
|
+
const fw = combatToWorld(from, anchor);
|
|
250
|
+
// An aim built without a z means "over there on MY floor", never "whichever floor is nearest in
|
|
251
|
+
// plan": handing the mesh a z-less dest snaps it to an arbitrary storey of the same column.
|
|
252
|
+
const destHasZ = to.z != null && Number.isFinite(to.z);
|
|
253
|
+
const tw = combatToWorld(!destHasZ && from.z != null ? { ...to, z: from.z } : to, anchor);
|
|
254
|
+
let slideTw = tw;
|
|
255
|
+
const stay = (): CombatPosYd => (from.z != null ? { x: from.x, y: from.y, z: from.z } : { x: from.x, y: from.y });
|
|
256
|
+
const dist = detHypot(tw.x - fw.x, tw.y - fw.y);
|
|
257
|
+
if (stopAtDist > 0 && dist <= stopAtDist) return stay();
|
|
258
|
+
|
|
259
|
+
// Surface, not search — the map answers it for every unit, party and mob alike. Resolved before
|
|
260
|
+
// every stepper: the overview raster calls half a real mesh corridor solid rock.
|
|
261
|
+
const { mesh } = motionSurfacesForMap(anchor?.mapId);
|
|
262
|
+
|
|
263
|
+
// Manual walk / party approach needs corridors. Enemy AI chase uses clamp economics.
|
|
264
|
+
// Do NOT gate the whole session on living hostiles — that froze floor travel mid-run.
|
|
265
|
+
let clampOnly = searchCorridor === true ? false
|
|
266
|
+
: searchCorridor === false ? true
|
|
267
|
+
: ctx.cfg.navClampOnly === true;
|
|
268
|
+
if (clampOnly) {
|
|
269
|
+
if ((navEscalateUntil.get(side) ?? 0) > ctx.t) {
|
|
270
|
+
clampOnly = false;
|
|
271
|
+
} else {
|
|
272
|
+
const aim = localSteerAim(side, from, to, step);
|
|
273
|
+
if (aim) slideTw = combatToWorld(aim, anchor);
|
|
274
|
+
const prev = clampProgress.get(side);
|
|
275
|
+
if (prev && dist > prev.d - 0.05) {
|
|
276
|
+
prev.d = dist;
|
|
277
|
+
prev.miss += 1;
|
|
278
|
+
// Geometry only. A body in the door is a push; if it also stalls for the miss count the wall
|
|
279
|
+
// is real and this side escalates. On the SAME surface the mob steps on, at the same width.
|
|
280
|
+
if (!walkLineClearOnSurface(mesh, fw, tw, 0.55)
|
|
281
|
+
&& (prev.miss >= CLAMP_MISS_TICKS
|
|
282
|
+
|| !bodyInWay(side, from, to, fightersOrder(), posOf, skipSteerBody))) {
|
|
283
|
+
navEscalateUntil.set(side, ctx.t + NAV_ESCALATE_MS);
|
|
284
|
+
clampProgress.delete(side);
|
|
285
|
+
clampOnly = false;
|
|
286
|
+
// The one place clamp becomes A*. Without a counter here, "enemies do not path"
|
|
287
|
+
// and "enemies path and it does not help" read the same in every run output.
|
|
288
|
+
diag(side, 'clampEscalate');
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
clampProgress.set(side, { d: dist, miss: 0 });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const ownDest = (ctx as { unitDirectMove?: Set<string> }).unitDirectMove?.has(side) === true;
|
|
297
|
+
// One greedy step toward the goal on the SERVER mesh, or null. A raster step is a 2-D collapse
|
|
298
|
+
// whose soft height physics legalises the void; the mesh stepper cannot cross a storey.
|
|
299
|
+
const slideOnMesh = (toward: NavPos = tw): NavPos | null => {
|
|
300
|
+
if (!mesh) return null;
|
|
301
|
+
const s = stepWalkableNavMesh(mesh, fw, toward, step);
|
|
302
|
+
return detHypot(s.x - fw.x, s.y - fw.y) > 1e-6 ? s : null;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Floor is part of the goal — two stacked dests must not share one corridor.
|
|
306
|
+
// Pack chase still sees a sibling's corridor while clamp-only (C0).
|
|
307
|
+
const shareTeam = isEnemySide(side) ? 'enemy' : 'friend';
|
|
308
|
+
const packMayShare = clampOnly && shareTeam === 'enemy';
|
|
309
|
+
const goalKey = clampOnly && !packMayShare
|
|
310
|
+
? ''
|
|
311
|
+
: `${Math.round(tw.x / GOAL_REGRID_YD)},${Math.round(tw.y / GOAL_REGRID_YD)}`
|
|
312
|
+
+ (destHasZ ? `,${Math.round(to.z! / STOREY_DZ_YD)}` : '');
|
|
313
|
+
let entry = (clampOnly && !packMayShare) ? undefined : unitNavPaths.get(side);
|
|
314
|
+
const livePath = !!(entry && entry.pts.length > 0);
|
|
315
|
+
const haveCorridor = !!(livePath && entry!.goalKey === goalKey);
|
|
316
|
+
// Hop is a first-mile shortcut only. A live corridor already went around the last wall, and mesh
|
|
317
|
+
// LOS at 1.6 yd leaks through door lips — it stole that route whenever dest came inside 12 yd.
|
|
318
|
+
// The mesh LOS walk is 5% of live JS and all three readers below sit behind other gates,
|
|
319
|
+
// so it is paid only once, and only when one of them actually asks.
|
|
320
|
+
let hopClearMemo: boolean | undefined;
|
|
321
|
+
const hopClear = (): boolean => (hopClearMemo ??= walkLineClearOnSurface(mesh, fw, tw, 0.55));
|
|
322
|
+
const hopOtherFloor = tw.z != null && fw.z != null && Math.abs(tw.z - fw.z) > 3;
|
|
323
|
+
// C4: no corridor + open floor + a body → push. Do not A* a teammate.
|
|
324
|
+
// A live corridor still follows; stacked formation must not sidestep forever.
|
|
325
|
+
if (!clampOnly && !livePath && !hopOtherFloor && hopClear()) {
|
|
326
|
+
const aim = localSteerAim(side, from, to, step);
|
|
327
|
+
if (aim) {
|
|
328
|
+
const aimW = combatToWorld(aim, anchor);
|
|
329
|
+
// Was `slideOnMesh(aimW) ?? stepWalkable(...)`: a raster second opinion after the mesh
|
|
330
|
+
// had refused, ten lines above the same hop taken mesh-exclusively. One policy now.
|
|
331
|
+
const nextW = stepOnSurface(mesh, fw, aimW, step);
|
|
332
|
+
if (nextW && detHypot(nextW.x - fw.x, nextW.y - fw.y) > 1e-6) {
|
|
333
|
+
return worldToCombat(nextW, anchor, from.z);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// After A* has run, dest needs the corridor — hop is only the no-search shortcut.
|
|
338
|
+
if (!clampOnly && !livePath && lastSearchAt.get(side) == null
|
|
339
|
+
&& dist <= 12 && !hopOtherFloor && hopClear()) {
|
|
340
|
+
const nextW = stepOnSurface(mesh, fw, tw, step);
|
|
341
|
+
if (nextW && detHypot(nextW.x - fw.x, nextW.y - fw.y) > 1e-6) {
|
|
342
|
+
const closer = detHypot(tw.x - nextW.x, tw.y - nextW.y);
|
|
343
|
+
const mx = nextW.x - fw.x;
|
|
344
|
+
const my = nextW.y - fw.y;
|
|
345
|
+
const md = detHypot(mx, my);
|
|
346
|
+
const along = md > 1e-6 ? (mx * (tw.x - fw.x) + my * (tw.y - fw.y)) / (md * dist) : 0;
|
|
347
|
+
if (closer < dist - 1e-3 && along >= 0.85) {
|
|
348
|
+
diag(side, 'hop');
|
|
349
|
+
return worldToCombat(nextW, anchor, from.z);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const takeShared = (shared: { pts: NavPos[]; viaMesh: boolean } | null): boolean => {
|
|
354
|
+
if (!shared || shared.pts.length === 0) return false;
|
|
355
|
+
entry = {
|
|
356
|
+
goalKey, pts: shared.pts, stuck: 0, wp: null, wpD: Infinity,
|
|
357
|
+
noGain: 0, skipped: 0, viaMesh: shared.viaMesh,
|
|
358
|
+
};
|
|
359
|
+
unitNavPaths.set(side, entry);
|
|
360
|
+
lastSearchAt.set(side, ctx.t);
|
|
361
|
+
dropNavSearch(navSearchQueue, side);
|
|
362
|
+
diag(side, 'sharedAdopt');
|
|
363
|
+
return true;
|
|
364
|
+
};
|
|
365
|
+
// Clamp chase: take a sibling's corridor. Search stays behind escalate (C0).
|
|
366
|
+
if (packMayShare && goalKey && (!entry || entry.goalKey !== goalKey)
|
|
367
|
+
&& lastSearchAt.get(side) == null) {
|
|
368
|
+
if (!takeShared(tryAdoptSharedPath(sharedPathByGoal, fw, tw, {
|
|
369
|
+
team: 'enemy',
|
|
370
|
+
shareStartYd: SHARE_START_YD,
|
|
371
|
+
shareGoalYd: SHARE_GOAL_YD,
|
|
372
|
+
}))) diag(side, 'shareMiss');
|
|
373
|
+
}
|
|
374
|
+
if (!clampOnly && (!entry || entry.goalKey !== goalKey)) {
|
|
375
|
+
// Party group-move and pack chase reuse a corridor a nearby unit already paid for, matched by
|
|
376
|
+
// PROXIMITY of the two goals rather than an exact key. Split-now slots keep their own dest.
|
|
377
|
+
const shared = !ownDest && goalKey ? tryAdoptSharedPath(sharedPathByGoal, fw, tw, {
|
|
378
|
+
team: shareTeam,
|
|
379
|
+
shareStartYd: SHARE_START_YD,
|
|
380
|
+
shareGoalYd: SHARE_GOAL_YD,
|
|
381
|
+
}) : null;
|
|
382
|
+
// A unit that already searched this walk must not re-adopt the start corridor
|
|
383
|
+
// — after 11 yd they are still inside SHARE_START and loop the failed path.
|
|
384
|
+
if (shared && lastSearchAt.get(side) == null && takeShared(shared)) {
|
|
385
|
+
// adopted
|
|
386
|
+
} else {
|
|
387
|
+
const since = ctx.t - (lastSearchAt.get(side) ?? -Infinity);
|
|
388
|
+
let holdCorridor = false;
|
|
389
|
+
const wp0 = entry?.pts[0];
|
|
390
|
+
const storeyOff = (a?: number, b?: number) => a != null && b != null && Math.abs(a - b) > 3;
|
|
391
|
+
// Other-floor dest, or first wp already a storey jump: the old corridor is the ledge.
|
|
392
|
+
if (entry && entry.pts.length > 0 && since < REPATH_COOLDOWN_MS
|
|
393
|
+
&& !storeyOff(fw.z, tw.z) && !storeyOff(fw.z, wp0?.z)) {
|
|
394
|
+
const tail = entry.pts[entry.pts.length - 1]!;
|
|
395
|
+
const tailOff = detHypot(tw.x - tail.x, tw.y - tail.y);
|
|
396
|
+
if (tailOff <= GOAL_REGRID_YD) {
|
|
397
|
+
holdCorridor = true;
|
|
398
|
+
diag(side, 'searchCooldown');
|
|
399
|
+
} else if (tailOff <= TAIL_REAIM_YD) {
|
|
400
|
+
// New object on purpose: the walker tracks its waypoint by identity, so this
|
|
401
|
+
// resets the no-gain counter when the tail IS the waypoint being closed on.
|
|
402
|
+
entry.pts[entry.pts.length - 1] = tw.z != null
|
|
403
|
+
? { x: tw.x, y: tw.y, z: tw.z }
|
|
404
|
+
: { x: tw.x, y: tw.y };
|
|
405
|
+
holdCorridor = true;
|
|
406
|
+
diag(side, 'tailReaim');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (holdCorridor) {
|
|
410
|
+
entry!.goalKey = goalKey;
|
|
411
|
+
dropNavSearch(navSearchQueue, side);
|
|
412
|
+
} else if (ownDest || !takeShared(tryAdoptSharedPath(sharedPathByGoal, fw, tw, {
|
|
413
|
+
team: shareTeam,
|
|
414
|
+
shareStartYd: SHARE_START_YD,
|
|
415
|
+
shareGoalYd: SHARE_GOAL_YD,
|
|
416
|
+
}))) {
|
|
417
|
+
// Friends follow the player; they do not draw a second corridor.
|
|
418
|
+
// Own-slot dests (Move now) walk theirs — following the player is the centre pile-in.
|
|
419
|
+
let queued = true;
|
|
420
|
+
if (!ownDest && !isEnemySide(side) && side !== 'player'
|
|
421
|
+
&& followLeadFailed.get(side) !== goalKey) {
|
|
422
|
+
const lead = unitNavPaths.get('player');
|
|
423
|
+
const leadP = posOf('player');
|
|
424
|
+
const leadW = leadP ? combatToWorld(leadP, anchor) : null;
|
|
425
|
+
const tail = lead && lead.pts.length > 0 ? lead.pts[lead.pts.length - 1] : undefined;
|
|
426
|
+
const sameDest = !tail || detHypot(tail.x - tw.x, tail.y - tw.y) <= SHARE_GOAL_YD;
|
|
427
|
+
if (lead && lead.pts.length > 0 && leadW && sameDest) {
|
|
428
|
+
queued = !takeShared({
|
|
429
|
+
pts: followLeadCorridor(lead.pts, leadW, fw, tw),
|
|
430
|
+
viaMesh: lead.viaMesh,
|
|
431
|
+
});
|
|
432
|
+
if (!queued) diag(side, 'followLead');
|
|
433
|
+
} else if (leadW && detHypot(fw.x - leadW.x, fw.y - leadW.y) > SHARE_START_YD) {
|
|
434
|
+
queued = !takeShared({
|
|
435
|
+
pts: followLeadCorridor([], leadW, fw, tw),
|
|
436
|
+
viaMesh: false,
|
|
437
|
+
});
|
|
438
|
+
if (!queued) diag(side, 'followLead');
|
|
439
|
+
}
|
|
440
|
+
} else if (!ownDest && !isEnemySide(side) && side !== 'player') {
|
|
441
|
+
diag(side, 'followLeadRefused');
|
|
442
|
+
}
|
|
443
|
+
if (queued && (!ownDest || !hopClear())) {
|
|
444
|
+
upsertNavSearch(navSearchQueue, { side, from: fw, to: tw, goalKey, team: shareTeam });
|
|
445
|
+
diag(side, 'searchQueued');
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// Player already stands on this dest: a leftover ally corridor is the scenic A* (230 yd for a
|
|
451
|
+
// 34 yd pin). Follow the player. Split-now dests stay on their own slot even so.
|
|
452
|
+
if (!clampOnly && !isEnemySide(side) && side !== 'player' && entry && entry.pts.length > 0
|
|
453
|
+
&& !ownDest && followLeadFailed.get(side) !== goalKey) {
|
|
454
|
+
const leadP = posOf('player');
|
|
455
|
+
const leadBusy = (unitNavPaths.get('player')?.pts.length ?? 0) > 0;
|
|
456
|
+
if (!leadBusy && leadP) {
|
|
457
|
+
const dPlayer = combatDistanceYd(leadP, to);
|
|
458
|
+
const dSelf = combatDistanceYd(from, to);
|
|
459
|
+
if (dPlayer != null && dPlayer <= 1.5 && (dSelf ?? 0) > 8) {
|
|
460
|
+
dropStaleCorridor(side);
|
|
461
|
+
const leadW = combatToWorld(leadP, anchor);
|
|
462
|
+
takeShared({
|
|
463
|
+
pts: followLeadCorridor([], leadW, fw, tw),
|
|
464
|
+
viaMesh: !!mesh,
|
|
465
|
+
});
|
|
466
|
+
diag(side, 'followLead');
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const doFollow = (pts: NavPos[]): NavPos => {
|
|
471
|
+
// Match the stepper to the router that drew THIS corridor. Walking a raster corridor with the
|
|
472
|
+
// mesh stepper wedges on the first cell the mesh has no polygon for.
|
|
473
|
+
if (mesh && entry?.viaMesh) return followPathNavMesh(mesh, fw, pts, step);
|
|
474
|
+
|
|
475
|
+
// A raster corridor cannot exist without a raster: every solver is mesh-only when one is absent.
|
|
476
|
+
// Reachable only for injected waypoints marked otherwise, and the mesh stepper walks those.
|
|
477
|
+
return mesh ? followPathNavMesh(mesh, fw, pts, step) : fw;
|
|
478
|
+
};
|
|
479
|
+
// Greedy step, mesh first, with the raster lane as the fallback it was built to be. Aim at the
|
|
480
|
+
// WAYPOINT: a slide aimed at the goal walks away from the route, and a graze reads as progress.
|
|
481
|
+
const doSlide = (toward: NavPos = slideTw): NavPos => {
|
|
482
|
+
const meshStep = slideOnMesh(toward);
|
|
483
|
+
if (meshStep) return meshStep;
|
|
484
|
+
// Raster stepWalkable legalises the void (navmesh.ts:4-9). Own-slot dests stay
|
|
485
|
+
// on the mesh rather than walking off a wall toward a split ring in the pit.
|
|
486
|
+
if (ownDest && mesh) return fw;
|
|
487
|
+
|
|
488
|
+
// Mesh refused and there is no raster to ask instead: the refusal stands and the unit
|
|
489
|
+
// holds, which is `stepOnSurface`'s stated policy rather than a new one.
|
|
490
|
+
return stepOnSurface(mesh, fw, toward, step) ?? fw;
|
|
491
|
+
};
|
|
492
|
+
if (entry && entry.pts.length > 0) {
|
|
493
|
+
// Progress is measured against the CURRENT waypoint, not the goal: a corridor turn walks away
|
|
494
|
+
// from the goal in straight-line terms but always closes on its own next waypoint.
|
|
495
|
+
const wp = entry.pts[0]!;
|
|
496
|
+
const storeyWp = !!(entry.viaMesh && wp.z != null && fw.z != null
|
|
497
|
+
&& Math.abs(wp.z - fw.z) > 3);
|
|
498
|
+
const d0 = storeyWp
|
|
499
|
+
? detHypot3(wp.x - fw.x, wp.y - fw.y, wp.z! - fw.z!)
|
|
500
|
+
: detHypot(wp.x - fw.x, wp.y - fw.y);
|
|
501
|
+
if (entry.wp !== wp) {
|
|
502
|
+
entry.wp = wp;
|
|
503
|
+
entry.wpD = d0;
|
|
504
|
+
entry.noGain = 0;
|
|
505
|
+
} else if (d0 > entry.wpD - 0.05) {
|
|
506
|
+
entry.noGain += 1;
|
|
507
|
+
entry.wpD = Math.min(entry.wpD, d0);
|
|
508
|
+
if (entry.noGain >= REPATH_NO_GAIN_TICKS) {
|
|
509
|
+
const planWp = detHypot(wp.x - fw.x, wp.y - fw.y);
|
|
510
|
+
// At a ramp wp in plan: follow climbs. Mesh corners ARE the corridor —
|
|
511
|
+
// skipping them is the chord through the wall (Stockade 10 yd pair).
|
|
512
|
+
if (storeyWp && planWp <= 1) {
|
|
513
|
+
entry.noGain = 0;
|
|
514
|
+
} else if (entry.viaMesh) {
|
|
515
|
+
// Do not skip mesh waypoints (that is the wall chord). Allies still
|
|
516
|
+
// drop and follow the player instead of hugging the same cell forever.
|
|
517
|
+
if (!isEnemySide(side) && side !== 'player' && entry.noGain >= 12) {
|
|
518
|
+
followLeadFailed.set(side, entry.goalKey);
|
|
519
|
+
dropStaleCorridor(side);
|
|
520
|
+
diag(side, 'dropNoGainFollow');
|
|
521
|
+
entry = undefined;
|
|
522
|
+
}
|
|
523
|
+
} else if (entry.pts.length > 1 && entry.skipped < WP_SKIP_MAX) {
|
|
524
|
+
entry.pts.shift();
|
|
525
|
+
entry.skipped += 1;
|
|
526
|
+
entry.wp = null;
|
|
527
|
+
entry.wpD = Infinity;
|
|
528
|
+
entry.noGain = 0;
|
|
529
|
+
diag(side, storeyWp ? 'wpSkipStorey' : 'wpSkip');
|
|
530
|
+
} else {
|
|
531
|
+
dropStaleCorridor(side);
|
|
532
|
+
diag(side, 'dropNoGain');
|
|
533
|
+
entry = undefined;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
} else {
|
|
537
|
+
entry.wpD = d0;
|
|
538
|
+
entry.noGain = 0;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (!entry || entry.pts.length === 0) {
|
|
542
|
+
const otherFloor = tw.z != null && fw.z != null && Math.abs(tw.z - fw.z) > 3;
|
|
543
|
+
if (otherFloor && mesh) {
|
|
544
|
+
const sideHop = sidestepNavMesh(mesh, fw, tw, Math.max(step, 2));
|
|
545
|
+
if (sideHop) {
|
|
546
|
+
diag(side, 'unstickSidestep');
|
|
547
|
+
return worldToCombat(sideHop, anchor, from.z);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Refuse only a stacked-column dest-slide (ledge). A slope dest 10 yd
|
|
551
|
+
// away still needs a mesh step toward the ramp.
|
|
552
|
+
if (!(otherFloor && dist <= 2)) {
|
|
553
|
+
const nextW = doSlide();
|
|
554
|
+
const slid = detHypot(nextW.x - fw.x, nextW.y - fw.y);
|
|
555
|
+
if (slid > 1e-6) { diag(side, 'slideNoCorridor'); return worldToCombat(nextW, anchor, from.z); }
|
|
556
|
+
}
|
|
557
|
+
diag(side, 'frozenNoSlide');
|
|
558
|
+
return stay();
|
|
559
|
+
}
|
|
560
|
+
const nextW = doFollow(entry.pts);
|
|
561
|
+
const wpNow = entry.pts[0];
|
|
562
|
+
// An in-place ramp climb is progress only when it CLOSES on the waypoint's z — an absolute delta
|
|
563
|
+
// would count falls and the filmed half-yard rubber-band as progress.
|
|
564
|
+
const zGain = wpNow?.z != null && fw.z != null && nextW.z != null
|
|
565
|
+
? Math.max(0, Math.abs(wpNow.z - fw.z) - Math.abs(wpNow.z - nextW.z))
|
|
566
|
+
: 0;
|
|
567
|
+
const moved = Math.max(detHypot(nextW.x - fw.x, nextW.y - fw.y), zGain);
|
|
568
|
+
const wpPlan = wpNow ? detHypot(wpNow.x - fw.x, wpNow.y - fw.y) : 0;
|
|
569
|
+
// Tiny wall-graze still counts as followOk and reset stuck — crawl forever.
|
|
570
|
+
const stillFar = wpPlan > 1;
|
|
571
|
+
const closed = wpNow != null
|
|
572
|
+
&& detHypot(wpNow.x - nextW.x, wpNow.y - nextW.y) < wpPlan - 0.05;
|
|
573
|
+
const crawled = moved < 0.2 && stillFar && !closed;
|
|
574
|
+
diag(side, moved > 1e-6 && !crawled ? 'followOk' : 'followWedged');
|
|
575
|
+
if (moved <= 1e-6 || crawled) {
|
|
576
|
+
entry.stuck += 1;
|
|
577
|
+
// Toward the WAYPOINT, not the goal — the corridor is the plan, and a wedged unit needs to
|
|
578
|
+
// rejoin it rather than push at whatever the corridor was avoiding. See doSlide.
|
|
579
|
+
const slide = doSlide(entry.pts[0] ?? tw);
|
|
580
|
+
if (detHypot(slide.x - fw.x, slide.y - fw.y) > 1e-6) {
|
|
581
|
+
// Sliding IS progress, so the route is fine — it is this waypoint the unit cannot
|
|
582
|
+
// reach from here. Skip it rather than paying for a fresh search (WP_SKIP_MAX).
|
|
583
|
+
if (entry.stuck >= 4 && !entry.viaMesh) {
|
|
584
|
+
const swp = entry.pts[0];
|
|
585
|
+
const planWp = swp ? detHypot(swp.x - fw.x, swp.y - fw.y) : Infinity;
|
|
586
|
+
if (entry.pts.length > 1 && entry.skipped < WP_SKIP_MAX && planWp > 1) {
|
|
587
|
+
entry.pts.shift();
|
|
588
|
+
entry.skipped += 1;
|
|
589
|
+
entry.wp = null;
|
|
590
|
+
entry.wpD = Infinity;
|
|
591
|
+
entry.noGain = 0;
|
|
592
|
+
entry.stuck = 0;
|
|
593
|
+
diag(side, 'wpSkipStuck');
|
|
594
|
+
} else {
|
|
595
|
+
dropStaleCorridor(side);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return worldToCombat(slide, anchor, from.z);
|
|
599
|
+
}
|
|
600
|
+
if (entry.stuck >= 4) {
|
|
601
|
+
let swp = entry.pts[0];
|
|
602
|
+
if (mesh && swp) {
|
|
603
|
+
const sideHop = sidestepNavMesh(mesh, fw, swp, Math.max(step, 2));
|
|
604
|
+
if (sideHop) {
|
|
605
|
+
entry.stuck = 0;
|
|
606
|
+
diag(side, 'unstickSidestep');
|
|
607
|
+
return worldToCombat(sideHop, anchor, from.z);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
if (!entry.viaMesh && entry.pts.length > 1 && entry.skipped < WP_SKIP_MAX) {
|
|
611
|
+
entry.pts.shift();
|
|
612
|
+
entry.skipped += 1;
|
|
613
|
+
entry.wp = null;
|
|
614
|
+
entry.wpD = Infinity;
|
|
615
|
+
entry.noGain = 0;
|
|
616
|
+
entry.stuck = 0;
|
|
617
|
+
diag(side, 'wpSkipStuck');
|
|
618
|
+
} else if (!entry.viaMesh || entry.stuck >= 12) {
|
|
619
|
+
dropStaleCorridor(side);
|
|
620
|
+
}
|
|
621
|
+
// Seam-gap hop: coarse A* legally bridges a fine void run of one coarse cell, so cross it along
|
|
622
|
+
// the corridor ray and land on the first walkable sample. Without it the walker wedged.
|
|
623
|
+
swp = entry.pts[0];
|
|
624
|
+
// Wedged with nothing to slide to. The overview seam-gap hop and the storey-stack recover arm
|
|
625
|
+
// both read raster cell geometry and went with it in E7.6; the mesh's own push is the ladder.
|
|
626
|
+
const pushAt = swp ?? tw;
|
|
627
|
+
if (mesh) {
|
|
628
|
+
const push = stepWalkableNavMesh(mesh, fw, pushAt, 3);
|
|
629
|
+
if (detHypot(push.x - fw.x, push.y - fw.y) > 0.3) {
|
|
630
|
+
diag(side, 'unstickPush');
|
|
631
|
+
return worldToCombat(push, anchor, from.z);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return stay();
|
|
636
|
+
}
|
|
637
|
+
entry.stuck = 0;
|
|
638
|
+
entry.skipped = 0;
|
|
639
|
+
if (entry.pts.length === 0) unitNavPaths.delete(side);
|
|
640
|
+
return worldToCombat(nextW, anchor, from.z);
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const pathTo = bindPathTo({
|
|
644
|
+
ctx, dead, bySide, posOf, anchorOf, combatToWorld,
|
|
645
|
+
unitNavPaths, sharedPathByGoal, lastSearchAt, orderStall, navSearchQueue, diag,
|
|
646
|
+
goalRegridYd: GOAL_REGRID_YD,
|
|
647
|
+
shareStartYd: SHARE_START_YD,
|
|
648
|
+
shareGoalYd: SHARE_GOAL_YD,
|
|
649
|
+
});
|
|
650
|
+
ctx.pathTo = pathTo;
|
|
651
|
+
|
|
652
|
+
/** Off every walkable layer — void, roofed black, inside a wall column. */
|
|
653
|
+
const offMesh = (w: NavPos): boolean => {
|
|
654
|
+
const mapId = anchorOf()?.mapId;
|
|
655
|
+
// Loaded mesh is the floor. Raster fallback legalises a pit whose 2-D column is walkable on
|
|
656
|
+
// another storey. Door-lip erosion is the 1.6 yd poly tolerance, not a raster vote.
|
|
657
|
+
const mesh = mapId != null ? navMeshForMap(mapId) : null;
|
|
658
|
+
if (mesh) {
|
|
659
|
+
const hit = findNearestPoly(mesh, w.x, w.y, w.z, 2);
|
|
660
|
+
return !(hit && hit.dist <= 1.6);
|
|
661
|
+
}
|
|
662
|
+
return true;
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
/** Void / wrong-storey rescue — nearest floor, or the player's stand. */
|
|
666
|
+
const NUDGE_SEARCH_YD = 20;
|
|
667
|
+
/** Crumb = last stand reached under own power on a real floor; recall target of last resort. */
|
|
668
|
+
const CRUMB_STEP_YD = 5;
|
|
669
|
+
const CRUMB_MAX_DZ_YD = 4;
|
|
670
|
+
const lastGoodStand = new Map<string, CombatPosYd>();
|
|
671
|
+
/** Whole ladder found nothing for this long ⇒ hard recall to the crumb. */
|
|
672
|
+
const NUDGE_RECALL_MS = 2_000;
|
|
673
|
+
const nudgeFail = new Map<string, { t: number; ms: number }>();
|
|
674
|
+
const dropCrumb = (f: Fighter, at: CombatPosYd): void => {
|
|
675
|
+
if (isEnemySide(f.side)) return;
|
|
676
|
+
// Inside any stuck window the spot is suspect — a trap must not stamp itself good.
|
|
677
|
+
const stall = orderStall.get(f.side);
|
|
678
|
+
if ((stall != null && (stall.ms > 0 || stall.hops > 0))
|
|
679
|
+
|| (stuckState.get(f.side)?.ms ?? 0) > 0
|
|
680
|
+
|| (partyStuckWatch.get(f.side)?.ms ?? 0) > 0) return;
|
|
681
|
+
const prev = lastGoodStand.get(f.side);
|
|
682
|
+
if (!prev) { lastGoodStand.set(f.side, { ...at }); return; }
|
|
683
|
+
if (detHypot(at.x - prev.x, at.y - prev.y) < CRUMB_STEP_YD) return;
|
|
684
|
+
// A storey-sized z jump counts only off a live corridor: stairs walk corridors, falls don't.
|
|
685
|
+
const dz = at.z != null && prev.z != null ? Math.abs(at.z - prev.z) : 0;
|
|
686
|
+
const live = unitNavPaths.get(f.side);
|
|
687
|
+
if (dz > CRUMB_MAX_DZ_YD && !(live && live.pts.length > 0)) return;
|
|
688
|
+
lastGoodStand.set(f.side, { ...at });
|
|
689
|
+
};
|
|
690
|
+
const nudgeOntoMesh = (f: Fighter): boolean => {
|
|
691
|
+
if (f.rootedUntil > ctx.t || f.stunnedUntil > ctx.t || f.incapUntil > ctx.t) return false;
|
|
692
|
+
const at = posOf(f.side);
|
|
693
|
+
const anchor = anchorOf();
|
|
694
|
+
// Every rung below is mesh-exclusive wherever a mesh is loaded. A raster-only gate kept the whole
|
|
695
|
+
// void rescue shut for a unit the mesh could have recovered. E4.6 finding 1.
|
|
696
|
+
if (!at || anchor?.mapId == null || !navMeshForMap(anchor.mapId)) return false;
|
|
697
|
+
const w = combatToWorld(at, anchor);
|
|
698
|
+
const lead = !isEnemySide(f.side) ? posOf('player') : undefined;
|
|
699
|
+
const live = unitNavPaths.get(f.side);
|
|
700
|
+
const climbing = !!(live && live.pts.length > 0);
|
|
701
|
+
const otherStorey = !!(lead?.z != null && at.z != null && Math.abs(at.z - lead.z) > 6);
|
|
702
|
+
// Airborne on a bake jump link: the corridor's next waypoint is a lower floor inside a running
|
|
703
|
+
// jump's reach, so the fall IS the route (MoveFall); the snap below would yank it back onto the lip.
|
|
704
|
+
if (climbing && live!.viaMesh && w.z != null) {
|
|
705
|
+
const wp = live!.pts[0]!;
|
|
706
|
+
if (wp.z != null && wp.z < w.z - WALKABLE_CLIMB_YD
|
|
707
|
+
&& detHypot(wp.x - w.x, wp.y - w.y) <= JUMP_REACH_MAX_YD + 1.6) {
|
|
708
|
+
const land = findNearestPoly(navMeshForMap(anchor.mapId)!, wp.x, wp.y, wp.z, 2);
|
|
709
|
+
if (land && land.dist <= 1.6 && offMesh(w)) return false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (!offMesh(w)) {
|
|
713
|
+
nudgeFail.delete(f.side);
|
|
714
|
+
dropCrumb(f, at);
|
|
715
|
+
if (!(otherStorey && !climbing && f.side !== 'player')) return false;
|
|
716
|
+
}
|
|
717
|
+
const mapId = anchor?.mapId;
|
|
718
|
+
const mesh = mapId != null ? navMeshForMap(mapId) : null;
|
|
719
|
+
let s: NavPos | null = null;
|
|
720
|
+
if (mesh) {
|
|
721
|
+
// Mesh exclusive: raster nearestWalkableNearZ collapses stacked floors into one
|
|
722
|
+
// height byte and will "recover" a void pit onto the storey below.
|
|
723
|
+
s = snapOntoNavMesh(mesh, w.x, w.y, at.z, NUDGE_SEARCH_YD, 4);
|
|
724
|
+
if (!s && at.z != null) {
|
|
725
|
+
s = snapOntoNavMesh(mesh, w.x, w.y, at.z, NUDGE_SEARCH_YD, STOREY_DZ_YD);
|
|
726
|
+
}
|
|
727
|
+
if (!s && lead?.z != null && f.side !== 'player') {
|
|
728
|
+
const lw = combatToWorld(lead, anchor);
|
|
729
|
+
s = snapOntoNavMesh(mesh, lw.x, lw.y, lead.z, 8, 4)
|
|
730
|
+
?? { x: lw.x, y: lw.y, z: lead.z };
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (!s) {
|
|
734
|
+
// No floor found anywhere near HERE (the frozen-player shape, ZG bridge underside).
|
|
735
|
+
const nf = nudgeFail.get(f.side);
|
|
736
|
+
const acc = !nf ? tickMs : (nf.t === ctx.t ? nf.ms : nf.ms + tickMs);
|
|
737
|
+
nudgeFail.set(f.side, { t: ctx.t, ms: acc });
|
|
738
|
+
if (acc >= NUDGE_RECALL_MS && recallToLastGood(f)) {
|
|
739
|
+
nudgeFail.delete(f.side);
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
nudgeFail.delete(f.side);
|
|
745
|
+
unstuckFails.delete(f.side);
|
|
746
|
+
setPos(f.side, worldToCombat(s, anchor, s.z ?? at.z));
|
|
747
|
+
unitNavPaths.delete(f.side);
|
|
748
|
+
stackRecover.delete(f.side);
|
|
749
|
+
stuckState.delete(f.side);
|
|
750
|
+
return true;
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
// Charge travel — peeled to sessionCharge.ts (filesize ratchet).
|
|
754
|
+
const { finishChargeArrival, advanceChargeMove, charge } = createChargeArm({
|
|
755
|
+
ctx, claims, tickMs, posOf, setPos, dead,
|
|
756
|
+
fighterByThreatId, hostileTargetOf, foeOf,
|
|
757
|
+
weaponStrike, castOnTargets, dropStaleCorridor, floorOf,
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
// Kit shortlists + live speed — peeled to sessionMotionKit.ts (filesize ratchet).
|
|
761
|
+
const {
|
|
762
|
+
kitRangedAuto, isThreatVictimOf, pbaoeRadiusYd, longestOffenseRangeYd,
|
|
763
|
+
shortOffenseList, wantsMeleeBand, runSpeedYdPerSec, enemyIsFleeing,
|
|
764
|
+
} = createMotionKit({ ctx, onCooldown, canAfford, isChargeRole });
|
|
765
|
+
|
|
766
|
+
// Enemy combat engagement is a non-empty threat list. Until then no gap-close and no white, so a
|
|
767
|
+
// Frostbolt open stands still while the missile is in flight.
|
|
768
|
+
const enemyEngaged = (f: Fighter): boolean => {
|
|
769
|
+
if (!isEnemySide(f.side)) return true;
|
|
770
|
+
const tm = ctx.threatOf(f);
|
|
771
|
+
return !!tm && !tm.isEmpty();
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
/** Within this many yards of a dest the walk has arrived: move orders, charge, follow. */
|
|
775
|
+
const ARRIVE_YD = 0.25;
|
|
776
|
+
|
|
777
|
+
/** True while the player (or a companion) owns this unit's motion — no AI gap-close. */
|
|
778
|
+
const manualMotion = (side: string): boolean =>
|
|
779
|
+
(ctx as { unitManualMotion?: Set<string> }).unitManualMotion?.has(side) === true;
|
|
780
|
+
|
|
781
|
+
// ChaseMovementGenerator distance recheck: four times a second the core re-asks whether the dest is
|
|
782
|
+
// still a melee spot on this body, and re-splines when not. Cite: TargetedMovementGenerator.cpp:242.
|
|
783
|
+
const CHASE_RECHECK_MS = 250;
|
|
784
|
+
/** side -> earliest t this unit may re-aim its chase dest. */
|
|
785
|
+
const chaseRecheckAt = new Map<string, number>();
|
|
786
|
+
// A positional slide in progress, as an offset from the focus so it translates but does not rotate,
|
|
787
|
+
// held until the unit STANDS there. Un-latched it walked 26-43 yd for 9-12 net, 3-6% less damage.
|
|
788
|
+
const posSlideHold = new Map<string, { dx: number; dy: number; tgt: string }>();
|
|
789
|
+
/** Arrived: no new slide for this long, so a still-exposed spot cannot re-solve every tick. */
|
|
790
|
+
const POS_SLIDE_SETTLE_MS = 1_500;
|
|
791
|
+
const posSlideSettled = new Map<string, number>();
|
|
792
|
+
// Live destination of a held slide, or null when nothing is held for this focus. Arrival (and a focus
|
|
793
|
+
// change) drops the hold and starts the settle window.
|
|
794
|
+
const heldSlideDest = (
|
|
795
|
+
side: string,
|
|
796
|
+
tgtSide: string,
|
|
797
|
+
focusAt: CombatPosYd,
|
|
798
|
+
from: CombatPosYd,
|
|
799
|
+
arriveYd: number,
|
|
800
|
+
): CombatPosYd | null => {
|
|
801
|
+
const held = posSlideHold.get(side);
|
|
802
|
+
if (!held) return null;
|
|
803
|
+
if (held.tgt !== tgtSide) {
|
|
804
|
+
posSlideHold.delete(side);
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
const at: CombatPosYd = {
|
|
808
|
+
x: focusAt.x + held.dx,
|
|
809
|
+
y: focusAt.y + held.dy,
|
|
810
|
+
...(from.z != null ? { z: from.z } : {}),
|
|
811
|
+
};
|
|
812
|
+
const d = combatDistanceYd(from, at);
|
|
813
|
+
if (d != null && d > arriveYd) return at;
|
|
814
|
+
posSlideHold.delete(side);
|
|
815
|
+
posSlideSettled.set(side, ctx.t + POS_SLIDE_SETTLE_MS);
|
|
816
|
+
claims.release(side, 'flank', ctx.t);
|
|
817
|
+
return null;
|
|
818
|
+
};
|
|
819
|
+
const holdSlide = (side: string, tgtSide: string, focusAt: CombatPosYd, dest: CombatPosYd): void => {
|
|
820
|
+
posSlideHold.set(side, { dx: dest.x - focusAt.x, dy: dest.y - focusAt.y, tgt: tgtSide });
|
|
821
|
+
};
|
|
822
|
+
// Friendly melee sharing one focus, in session unit order so a rank does not reshuffle into a new
|
|
823
|
+
// destination each tick, and when each focus last MOVED — you cannot flank a body that is turning.
|
|
824
|
+
const focusStillSince = new Map<string, { x: number; y: number; since: number }>();
|
|
825
|
+
// How long a focus must hold its spot before anyone flanks it. Three ticks: a chasing boss never
|
|
826
|
+
// qualifies, and at 1 s the behind-gate fixture ran out of its 20 ticks before the rogue arrived.
|
|
827
|
+
const FOCUS_STILL_MS = 300;
|
|
828
|
+
/** Yards of drift that count as the focus moving (below this is float noise). */
|
|
829
|
+
const FOCUS_STILL_EPS_YD = 0.35;
|
|
830
|
+
const focusIsStill = (side: string, at: CombatPosYd): boolean => {
|
|
831
|
+
const prev = focusStillSince.get(side);
|
|
832
|
+
if (!prev) {
|
|
833
|
+
focusStillSince.set(side, { x: at.x, y: at.y, since: ctx.t });
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
if (detHypot(at.x - prev.x, at.y - prev.y) > FOCUS_STILL_EPS_YD) {
|
|
837
|
+
prev.x = at.x;
|
|
838
|
+
prev.y = at.y;
|
|
839
|
+
prev.since = ctx.t;
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
return ctx.t - prev.since >= FOCUS_STILL_MS;
|
|
843
|
+
};
|
|
844
|
+
let meleePeerAt = -1;
|
|
845
|
+
const meleePeers = new Map<string, string[]>();
|
|
846
|
+
const meleePeersOf = (tgtSide: string): string[] => {
|
|
847
|
+
if (meleePeerAt !== ctx.t) {
|
|
848
|
+
meleePeerAt = ctx.t;
|
|
849
|
+
meleePeers.clear();
|
|
850
|
+
for (const u of fightersOrder()) {
|
|
851
|
+
if (isEnemySide(u.side) || dead(u) || isTotemSide(u.side)) continue;
|
|
852
|
+
if (!(u.mode === 'melee' || wantsMeleeBand(u))) continue;
|
|
853
|
+
const focus = foeOf(u);
|
|
854
|
+
if (!focus || dead(focus)) continue;
|
|
855
|
+
const list = meleePeers.get(focus.side);
|
|
856
|
+
if (list) list.push(u.side);
|
|
857
|
+
else meleePeers.set(focus.side, [u.side]);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return meleePeers.get(tgtSide) ?? [];
|
|
861
|
+
};
|
|
862
|
+
// Evade an enemy that cannot reach its victim: empty threat, full vitals, passive on the floor. One
|
|
863
|
+
// unreachable Defias Miner held the party in combat from 42.9 yd for 60 s and blocked auto-run.
|
|
864
|
+
const evadeUnreachable = (f: Fighter): void => {
|
|
865
|
+
const tm = ctx.threatOf(f);
|
|
866
|
+
if (tm) tm.clear();
|
|
867
|
+
f.hp = Math.max(1, f.spec.maxHealth);
|
|
868
|
+
f.power = f.spec.powerType === 'rage' ? 0 : f.spec.maxPower;
|
|
869
|
+
f.castingSpell = null;
|
|
870
|
+
if (f.channeling) interruptChannel(f, channelInterruptCtx(ctx), { quiet: true });
|
|
871
|
+
f.pendingCharge = null;
|
|
872
|
+
claims.breakAll(f.side);
|
|
873
|
+
unitNavPaths.delete(f.side);
|
|
874
|
+
stuckState.delete(f.side);
|
|
875
|
+
// Finalize the chase generator: its watchdog and park timer are the only things that
|
|
876
|
+
// know this unit was chasing at all (MovementGenerator.h:39).
|
|
877
|
+
chase.finalize?.(f);
|
|
878
|
+
navEscalateUntil.delete(f.side);
|
|
879
|
+
clampProgress.delete(f.side);
|
|
880
|
+
// Named in navDiag because a mob that resets mid-chase and one that never chased look
|
|
881
|
+
// identical from outside, and this counter is what tells a wall apart from a bad corridor.
|
|
882
|
+
diag(f.side, 'evadeUnreachable');
|
|
883
|
+
ctx.push({ t: ctx.t, kind: 'flavor', text: `${f.spec.name} cannot reach you and resets.` });
|
|
884
|
+
// Same scripted half as releaseEnemyToPassive: the instance OnCreatureEvade hook, then the
|
|
885
|
+
// script's own EnterEvadeMode and Reset. Cite: Unit.cpp:578-592; CreatureAI.cpp:66-71.
|
|
886
|
+
runEvadeLifecycle(ctx, f);
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// Gap-close at effective run speed when out of band, with a stuck guard on NET close to the goal:
|
|
890
|
+
// displacement alone never latched, because wall-graze thrash zigzags in place at moved > eps.
|
|
891
|
+
const stuckState = new Map<string, {
|
|
892
|
+
gx: number; gy: number;
|
|
893
|
+
/** Distance-to-goal when this window opened. */
|
|
894
|
+
startD: number;
|
|
895
|
+
/** Best (shortest) distance-to-goal seen in the window. */
|
|
896
|
+
bestD: number;
|
|
897
|
+
ms: number;
|
|
898
|
+
}>();
|
|
899
|
+
const STUCK_GIVE_UP_MS = 1000;
|
|
900
|
+
/** Need this much net close on the goal to clear the window (zigzag nets ~0). */
|
|
901
|
+
const STUCK_PROGRESS_YD = 1.5;
|
|
902
|
+
/** A re-aim this far is a fresh attempt, not the same blocked one. */
|
|
903
|
+
const STUCK_GOAL_EPS_YD = 2;
|
|
904
|
+
const distToGoal = (a: CombatPosYd, b: CombatPosYd): number =>
|
|
905
|
+
a.z != null && b.z != null
|
|
906
|
+
? detHypot3(a.x - b.x, a.y - b.y, a.z - b.z)
|
|
907
|
+
: detHypot(a.x - b.x, a.y - b.y);
|
|
908
|
+
const noProgressStuck = (f: Fighter, goal: CombatPosYd): boolean => {
|
|
909
|
+
const p = posOf(f.side);
|
|
910
|
+
if (!p) return false;
|
|
911
|
+
const goalD = distToGoal(p, goal);
|
|
912
|
+
const prev = stuckState.get(f.side);
|
|
913
|
+
if (!prev
|
|
914
|
+
|| distToGoal({ x: goal.x, y: goal.y }, { x: prev.gx, y: prev.gy }) > STUCK_GOAL_EPS_YD
|
|
915
|
+
|| goalD <= prev.startD - STUCK_PROGRESS_YD) {
|
|
916
|
+
stuckState.set(f.side, {
|
|
917
|
+
gx: goal.x, gy: goal.y, startD: goalD, bestD: goalD, ms: 0,
|
|
918
|
+
});
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
if (goalD < prev.bestD) prev.bestD = goalD;
|
|
922
|
+
// Real progress mid-window: restart from the new best (steady walk clears forever).
|
|
923
|
+
if (prev.bestD <= prev.startD - STUCK_PROGRESS_YD) {
|
|
924
|
+
stuckState.set(f.side, {
|
|
925
|
+
gx: goal.x, gy: goal.y, startD: prev.bestD, bestD: prev.bestD, ms: 0,
|
|
926
|
+
});
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
prev.ms += tickMs;
|
|
930
|
+
return prev.ms >= STUCK_GIVE_UP_MS;
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
// Mild rescue, never the player's own click: thrashing in place with no net close gets a small
|
|
934
|
+
// walkable hop, never a snap to the destination — that read as a teleport to the click.
|
|
935
|
+
const PARTY_STUCK_MS = 2_500;
|
|
936
|
+
/** Net close on goal required to clear; thrash nets ~0. */
|
|
937
|
+
const PARTY_STUCK_PROGRESS_YD = 1.5;
|
|
938
|
+
const PARTY_STUCK_GOAL_EPS_YD = 2;
|
|
939
|
+
/** Must still be within this of the window origin — left it ⇒ walking, not stuck. */
|
|
940
|
+
const PARTY_STUCK_CLUSTER_YD = 4;
|
|
941
|
+
/** Small hop only; repeated rescues can close a gap without one-shot teleports. */
|
|
942
|
+
const PARTY_UNSTUCK_HOP_YD = 5;
|
|
943
|
+
const partyStuckWatch = new Map<string, {
|
|
944
|
+
ax: number; ay: number; az?: number;
|
|
945
|
+
gx: number; gy: number;
|
|
946
|
+
startD: number;
|
|
947
|
+
bestD: number;
|
|
948
|
+
ms: number;
|
|
949
|
+
}>();
|
|
950
|
+
|
|
951
|
+
/** Clear path caches after a rescue hop so the next tick re-routes from the new cell. */
|
|
952
|
+
const clearMotionCaches = (side: string): void => {
|
|
953
|
+
claims.breakAll(side);
|
|
954
|
+
unitNavPaths.delete(side);
|
|
955
|
+
stackRecover.delete(side);
|
|
956
|
+
stuckState.delete(side);
|
|
957
|
+
partyStuckWatch.delete(side);
|
|
958
|
+
clearChaseParkTimer(side);
|
|
959
|
+
posSlideHold.delete(side);
|
|
960
|
+
posSlideSettled.delete(side);
|
|
961
|
+
followLeadFailed.delete(side);
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
/** Consecutive soft-rescue failures per side; any successful reposition resets it. */
|
|
965
|
+
const unstuckFails = new Map<string, number>();
|
|
966
|
+
const UNSTUCK_FAILS_RECALL = 3;
|
|
967
|
+
// Terminal rung: every hop, sidestep and snap failed repeatedly — put the unit back on the last ground it
|
|
968
|
+
// walked itself. Party only; a wedged enemy evades instead.
|
|
969
|
+
const recallToLastGood = (f: Fighter): boolean => {
|
|
970
|
+
if (isEnemySide(f.side)) return false;
|
|
971
|
+
const crumb = lastGoodStand.get(f.side);
|
|
972
|
+
const at = posOf(f.side);
|
|
973
|
+
if (!crumb || !at) return false;
|
|
974
|
+
if (detHypot(crumb.x - at.x, crumb.y - at.y) < 1
|
|
975
|
+
&& (crumb.z == null || at.z == null || Math.abs(crumb.z - at.z) < 2)) return false;
|
|
976
|
+
setPos(f.side, { ...crumb });
|
|
977
|
+
(ctx as { unitMoveOrders?: Map<string, CombatPosYd[]> }).unitMoveOrders?.delete(f.side);
|
|
978
|
+
orderStall.delete(f.side);
|
|
979
|
+
unstuckFails.delete(f.side);
|
|
980
|
+
clearMotionCaches(f.side);
|
|
981
|
+
f.decideDirty = true;
|
|
982
|
+
diag(f.side, 'stuckRecall');
|
|
983
|
+
return true;
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
// Small walkable hop toward `goal` — never goal-snap, never long-range hard teleport. If nothing walkable
|
|
987
|
+
// along a short ray: clear path cache only (repath next tick).
|
|
988
|
+
const softUnstuckToward = (f: Fighter, from: CombatPosYd, goal: CombatPosYd): boolean => {
|
|
989
|
+
const moved = softUnstuckHop(f, from, goal);
|
|
990
|
+
if (moved) unstuckFails.delete(f.side);
|
|
991
|
+
else if (!isEnemySide(f.side)) unstuckFails.set(f.side, (unstuckFails.get(f.side) ?? 0) + 1);
|
|
992
|
+
return moved;
|
|
993
|
+
};
|
|
994
|
+
const softUnstuckHop = (f: Fighter, from: CombatPosYd, goal: CombatPosYd): boolean => {
|
|
995
|
+
if (isEnemySide(f.side)) return false;
|
|
996
|
+
unitNavPaths.delete(f.side);
|
|
997
|
+
stackRecover.delete(f.side);
|
|
998
|
+
stuckState.delete(f.side);
|
|
999
|
+
partyStuckWatch.delete(f.side);
|
|
1000
|
+
const dx = goal.x - from.x;
|
|
1001
|
+
const dy = goal.y - from.y;
|
|
1002
|
+
const d = detHypot(dx, dy);
|
|
1003
|
+
const anchor = anchorOf();
|
|
1004
|
+
if (!(d > 0.5)) {
|
|
1005
|
+
const moved = nudgeOntoMesh(f);
|
|
1006
|
+
if (moved) diag(f.side, 'stuckUnstuck');
|
|
1007
|
+
return moved;
|
|
1008
|
+
}
|
|
1009
|
+
const ux = dx / d;
|
|
1010
|
+
const uy = dy / d;
|
|
1011
|
+
const hop = Math.min(PARTY_UNSTUCK_HOP_YD, d);
|
|
1012
|
+
const mapId = anchor?.mapId;
|
|
1013
|
+
const mesh = mapId != null ? navMeshForMap(mapId) : null;
|
|
1014
|
+
if (mesh) {
|
|
1015
|
+
const fw = combatToWorld(from, anchor);
|
|
1016
|
+
const tw = combatToWorld(
|
|
1017
|
+
goal.z == null && from.z != null ? { ...goal, z: from.z } : goal,
|
|
1018
|
+
anchor,
|
|
1019
|
+
);
|
|
1020
|
+
const sideHop = sidestepNavMesh(mesh, fw, tw, hop);
|
|
1021
|
+
if (sideHop) {
|
|
1022
|
+
setPos(f.side, worldToCombat(sideHop, anchor, from.z));
|
|
1023
|
+
diag(f.side, 'unstickSidestep');
|
|
1024
|
+
return true;
|
|
1025
|
+
}
|
|
1026
|
+
const s = stepWalkableNavMesh(mesh, fw, tw, hop);
|
|
1027
|
+
const other = goal.z != null && from.z != null && Math.abs(goal.z - from.z) > 3;
|
|
1028
|
+
const zGain = other && s.z != null && Math.abs(s.z - goal.z!) < Math.abs(from.z! - goal.z!) - 0.2;
|
|
1029
|
+
if (zGain || (!other && detHypot(s.x - fw.x, s.y - fw.y) > 0.4)) {
|
|
1030
|
+
setPos(f.side, worldToCombat(s, anchor, from.z));
|
|
1031
|
+
diag(f.side, 'stuckUnstuck');
|
|
1032
|
+
return true;
|
|
1033
|
+
}
|
|
1034
|
+
if (other) {
|
|
1035
|
+
// In place, a storey change is a fall (MotionMaster.cpp:575 MoveFall), never a climb.
|
|
1036
|
+
const hit = findNearestPoly(mesh, fw.x, fw.y, goal.z, hop);
|
|
1037
|
+
if (hit && hit.dist <= 1 && Math.abs(hit.z - goal.z!) <= 3 && fw.z != null && hit.z < fw.z) {
|
|
1038
|
+
setPos(f.side, worldToCombat({ x: fw.x, y: fw.y, z: hit.z }, anchor, goal.z));
|
|
1039
|
+
diag(f.side, 'stuckUnstuck');
|
|
1040
|
+
return true;
|
|
1041
|
+
}
|
|
1042
|
+
diag(f.side, 'stuckRepathOnly');
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
// Same storey and the mesh had nothing. Falls through to the raster arm, which nudgeOntoMesh:866
|
|
1046
|
+
// refuses BY NAME. Counted before it is cut: cutting it changes motion on all 26 mesh maps.
|
|
1047
|
+
diag(f.side, 'stuckRasterFallthrough');
|
|
1048
|
+
}
|
|
1049
|
+
if (mesh) {
|
|
1050
|
+
// The mesh is the authority over every spot this arm proposes: up is walkableClimb
|
|
1051
|
+
// (MapBuilder.cpp:358), down is a fall (MoveFall) — the symmetric 4 let a hop mount a wall.
|
|
1052
|
+
const meshAccepts = (w: NavPos, z: number | null | undefined): boolean => {
|
|
1053
|
+
if (!mesh) return true;
|
|
1054
|
+
if (z == null || !Number.isFinite(z)) return isWalkableNavMesh(mesh, w.x, w.y);
|
|
1055
|
+
const hit = findNearestPoly(mesh, w.x, w.y, z, 2);
|
|
1056
|
+
return !!hit && hit.dist <= 0.75 && hit.z - z <= WALKABLE_CLIMB_YD && z - hit.z <= 4;
|
|
1057
|
+
};
|
|
1058
|
+
for (const frac of [1, 0.7, 0.4]) {
|
|
1059
|
+
const dist = hop * frac;
|
|
1060
|
+
if (dist < 0.75) continue;
|
|
1061
|
+
const cand: CombatPosYd = {
|
|
1062
|
+
x: from.x + ux * dist,
|
|
1063
|
+
y: from.y + uy * dist,
|
|
1064
|
+
...(from.z != null ? { z: from.z } : {}),
|
|
1065
|
+
};
|
|
1066
|
+
const w = combatToWorld(cand, anchor);
|
|
1067
|
+
if (meshAccepts(w, from.z)) {
|
|
1068
|
+
setPos(f.side, worldToCombat(w, anchor, from.z));
|
|
1069
|
+
nudgeOntoMesh(f);
|
|
1070
|
+
diag(f.side, 'stuckUnstuck');
|
|
1071
|
+
return true;
|
|
1072
|
+
}
|
|
1073
|
+
// The raster proposed these and the mesh vetoed them (E4.9). With the raster gone the
|
|
1074
|
+
// mesh proposes its own, which is the same accepted set without the round trip.
|
|
1075
|
+
const s = from.z != null
|
|
1076
|
+
? snapOntoNavMesh(mesh, w.x, w.y, from.z, 3, STOREY_DZ_YD)
|
|
1077
|
+
: snapOntoNavMesh(mesh, w.x, w.y, undefined, 3);
|
|
1078
|
+
if (s) {
|
|
1079
|
+
const placed = worldToCombat(s, anchor, from.z);
|
|
1080
|
+
// Reject snaps that would jump farther than the intended hop.
|
|
1081
|
+
if (detHypot(placed.x - from.x, placed.y - from.y) <= hop + 1) {
|
|
1082
|
+
// Judged from the unit's own z: at the snap's z this was always true, a 10 yd lift.
|
|
1083
|
+
if (!meshAccepts(s, from.z)) {
|
|
1084
|
+
diag(f.side, 'unstickMeshRejected');
|
|
1085
|
+
} else {
|
|
1086
|
+
setPos(f.side, placed);
|
|
1087
|
+
nudgeOntoMesh(f);
|
|
1088
|
+
diag(f.side, 'stuckUnstuck');
|
|
1089
|
+
return true;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
const fw = combatToWorld(from, anchor);
|
|
1095
|
+
const tw = combatToWorld(
|
|
1096
|
+
goal.z == null && from.z != null ? { ...goal, z: from.z } : goal,
|
|
1097
|
+
anchor,
|
|
1098
|
+
);
|
|
1099
|
+
const hopW = sidestepNavMesh(mesh, fw, tw, Math.min(hop, 2));
|
|
1100
|
+
if (hopW && meshAccepts(hopW, hopW.z ?? from.z)) {
|
|
1101
|
+
setPos(f.side, worldToCombat(hopW, anchor, from.z));
|
|
1102
|
+
diag(f.side, 'stuckUnstuck');
|
|
1103
|
+
return true;
|
|
1104
|
+
}
|
|
1105
|
+
if (hopW) diag(f.side, 'unstickMeshRejected');
|
|
1106
|
+
}
|
|
1107
|
+
diag(f.side, 'stuckRepathOnly');
|
|
1108
|
+
return false;
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// AI thrash rescue: only while the unit stays in a small cluster with no net goal close. An ALLY under
|
|
1112
|
+
// a move order calls it too (sessionMoveOrder.ts); the player's own click never does — that hop read as
|
|
1113
|
+
// a teleport to the click. Snared/rooted callers must not invoke this.
|
|
1114
|
+
const watchPartyStuck = (
|
|
1115
|
+
f: Fighter,
|
|
1116
|
+
_before: CombatPosYd,
|
|
1117
|
+
after: CombatPosYd,
|
|
1118
|
+
goal: CombatPosYd,
|
|
1119
|
+
): boolean => {
|
|
1120
|
+
if (isEnemySide(f.side)) return false;
|
|
1121
|
+
if (f.rootedUntil > ctx.t || f.stunnedUntil > ctx.t || f.incapUntil > ctx.t) {
|
|
1122
|
+
partyStuckWatch.delete(f.side);
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
const goalD = distToGoal(after, goal);
|
|
1126
|
+
const prev = partyStuckWatch.get(f.side);
|
|
1127
|
+
const goalMoved = prev != null
|
|
1128
|
+
&& distToGoal({ x: goal.x, y: goal.y }, { x: prev.gx, y: prev.gy }) > PARTY_STUCK_GOAL_EPS_YD;
|
|
1129
|
+
if (!prev || goalMoved) {
|
|
1130
|
+
partyStuckWatch.set(f.side, {
|
|
1131
|
+
ax: after.x, ay: after.y, az: after.z,
|
|
1132
|
+
gx: goal.x, gy: goal.y,
|
|
1133
|
+
startD: goalD, bestD: goalD, ms: 0,
|
|
1134
|
+
});
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
if (goalD < prev.bestD) prev.bestD = goalD;
|
|
1138
|
+
// Steady walk: net close clears.
|
|
1139
|
+
if (prev.bestD <= prev.startD - PARTY_STUCK_PROGRESS_YD) {
|
|
1140
|
+
partyStuckWatch.set(f.side, {
|
|
1141
|
+
ax: after.x, ay: after.y, az: after.z,
|
|
1142
|
+
gx: goal.x, gy: goal.y,
|
|
1143
|
+
startD: prev.bestD, bestD: prev.bestD, ms: 0,
|
|
1144
|
+
});
|
|
1145
|
+
return false;
|
|
1146
|
+
}
|
|
1147
|
+
// Left the thrash spot ⇒ a real walk (detour), not stuck. Full rebaseline.
|
|
1148
|
+
const fromOrigin = distToGoal(after, { x: prev.ax, y: prev.ay, z: prev.az });
|
|
1149
|
+
if (fromOrigin > PARTY_STUCK_CLUSTER_YD) {
|
|
1150
|
+
partyStuckWatch.set(f.side, {
|
|
1151
|
+
ax: after.x, ay: after.y, az: after.z,
|
|
1152
|
+
gx: goal.x, gy: goal.y,
|
|
1153
|
+
startD: goalD, bestD: goalD, ms: 0,
|
|
1154
|
+
});
|
|
1155
|
+
return false;
|
|
1156
|
+
}
|
|
1157
|
+
prev.ms += tickMs;
|
|
1158
|
+
if (prev.ms < PARTY_STUCK_MS) return false;
|
|
1159
|
+
if (softUnstuckToward(f, after, goal)) return true;
|
|
1160
|
+
// Pinned through 3 whole rescue windows (~7.5 s): hard recall to the last good stand.
|
|
1161
|
+
if ((unstuckFails.get(f.side) ?? 0) >= UNSTUCK_FAILS_RECALL) return recallToLastGood(f);
|
|
1162
|
+
return false;
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
// A companion that HAS A DESTINATION, never gets nearer it, and is nowhere near the party is stuck —
|
|
1166
|
+
// however much it moves. The rungs above measure a standstill or a 4 yd cluster, and a wedged walker
|
|
1167
|
+
// swinging 6 yd defeats both (liveDungeonController's filmed ping-pong).
|
|
1168
|
+
//
|
|
1169
|
+
// Progress is measured against the unit's OWN goal, never against the player. Measured: judging it on
|
|
1170
|
+
// distance-to-player teleported a melee that was legitimately fighting a boss on the far side of the
|
|
1171
|
+
// room, because it never closes on the player while doing so (`twinUppercut.uppercutsOnMelee` 12 -> 7).
|
|
1172
|
+
const GROUP_RECALL_MS = 8_000;
|
|
1173
|
+
/** Leaving this disc around where the window opened is a walk, not a swing. */
|
|
1174
|
+
const GROUP_RECALL_ROAM_YD = 12;
|
|
1175
|
+
const groupWatch = new Map<string, {
|
|
1176
|
+
ox: number; oy: number; oz?: number;
|
|
1177
|
+
gx: number; gy: number;
|
|
1178
|
+
/** Distance to its own goal when this window opened, and the best since. */
|
|
1179
|
+
startD: number; bestD: number; since: number;
|
|
1180
|
+
}>();
|
|
1181
|
+
// Queued dest, else corridor tail, else the leader on `partyFollow`. A stand gate (inRange / hold)
|
|
1182
|
+
// is the job, not a walk: the corridor often still points at the body the melee parked 5 yd short of.
|
|
1183
|
+
const liveGoalOf = (side: string, lead: CombatPosYd): CombatPosYd | null => {
|
|
1184
|
+
const g = motionGates.read(side)?.gate;
|
|
1185
|
+
if (g === 'inRange' || g === 'policyHold' || g === 'stance' || g === 'holdGround') return null;
|
|
1186
|
+
const order = ctx.unitMoveOrders?.get(side)?.[0];
|
|
1187
|
+
if (order) return order;
|
|
1188
|
+
const pts = unitNavPaths.get(side)?.pts;
|
|
1189
|
+
const tail = pts && pts.length > 0 ? pts[pts.length - 1] : undefined;
|
|
1190
|
+
if (tail) return worldToCombat(tail, anchorOf(), posOf(side)?.z);
|
|
1191
|
+
return g === 'partyFollow' ? lead : null;
|
|
1192
|
+
};
|
|
1193
|
+
// The last rung: put the unit on the player's cell. Same snap `nudgeOntoMesh` takes for a body the
|
|
1194
|
+
// mesh lost; the wipe path places the party by fiat the same way (worldPose.setPartyWorldPos).
|
|
1195
|
+
const recallToGroup = (f: Fighter, lead: CombatPosYd): void => {
|
|
1196
|
+
const anchor = anchorOf();
|
|
1197
|
+
const mesh = anchor?.mapId != null ? navMeshForMap(anchor.mapId) : null;
|
|
1198
|
+
let dest: CombatPosYd = lead.z != null ? { x: lead.x, y: lead.y, z: lead.z } : { x: lead.x, y: lead.y };
|
|
1199
|
+
if (mesh) {
|
|
1200
|
+
const lw = combatToWorld(lead, anchor);
|
|
1201
|
+
const s = snapOntoNavMesh(mesh, lw.x, lw.y, lead.z, 8, 4);
|
|
1202
|
+
if (s) dest = worldToCombat(s, anchor, lead.z);
|
|
1203
|
+
}
|
|
1204
|
+
setPos(f.side, dest);
|
|
1205
|
+
(ctx as { unitMoveOrders?: Map<string, CombatPosYd[]> }).unitMoveOrders?.delete(f.side);
|
|
1206
|
+
orderStall.delete(f.side);
|
|
1207
|
+
unstuckFails.delete(f.side);
|
|
1208
|
+
clearMotionCaches(f.side);
|
|
1209
|
+
groupWatch.delete(f.side);
|
|
1210
|
+
f.decideDirty = true;
|
|
1211
|
+
diag(f.side, 'stuckRecallGroup');
|
|
1212
|
+
ctx.push({ t: ctx.t, kind: 'flavor', text: `${f.spec.name} rejoins the party.` });
|
|
1213
|
+
};
|
|
1214
|
+
const watchGroupRecall = (f: Fighter): void => {
|
|
1215
|
+
if (f.side === 'player') return;
|
|
1216
|
+
// The BG / RTS bubble spreads the party on purpose, so the player's spot is not "the group" there.
|
|
1217
|
+
if (ctx.cfg.friendlyHoldYd != null) { groupWatch.delete(f.side); return; }
|
|
1218
|
+
const stance = (ctx as { unitStance?: Map<string, string> }).unitStance?.get(f.side);
|
|
1219
|
+
const parked = stance === 'hold' || stance === 'passive'
|
|
1220
|
+
|| (f.side === 'pet' && ctx.petCommand === 'stay')
|
|
1221
|
+
|| manualMotion(f.side)
|
|
1222
|
+
|| f.rootedUntil > ctx.t || f.stunnedUntil > ctx.t || f.incapUntil > ctx.t || f.fearedUntil > ctx.t;
|
|
1223
|
+
const at = posOf(f.side);
|
|
1224
|
+
const lead = posOf('player');
|
|
1225
|
+
if (parked || !at || !lead || dead(bySide('player'))) { groupWatch.delete(f.side); return; }
|
|
1226
|
+
// Standing in the party bubble: no recall, and no foeOf. Idle 40-man is this case every tick.
|
|
1227
|
+
if (distToGoal(at, lead) <= (ctx.cfg.partyFollowYd ?? PARTY_FOLLOW_YD)) {
|
|
1228
|
+
groupWatch.delete(f.side);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
const focus = foeOf(f);
|
|
1232
|
+
if (focus && focus !== f && !dead(focus)) {
|
|
1233
|
+
const there = posOf(focus.side);
|
|
1234
|
+
const gap = there ? combatDistanceYd(at, there) : null;
|
|
1235
|
+
const reach = wantsMeleeBand(f)
|
|
1236
|
+
? MELEE_CONTACT_YD
|
|
1237
|
+
: Math.max(MELEE_CONTACT_YD, longestOffenseRangeYd(f));
|
|
1238
|
+
if (gap != null && gap <= reach && planarRangeHonestYd(at, there)) {
|
|
1239
|
+
groupWatch.delete(f.side);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
// Far from the party AND holding a goal it is not reaching. Near the party it is in the fight, and
|
|
1244
|
+
// with no goal it is standing on purpose — neither is this rung's business.
|
|
1245
|
+
const goal = distToGoal(at, lead) > (ctx.cfg.partyFollowYd ?? PARTY_FOLLOW_YD)
|
|
1246
|
+
? liveGoalOf(f.side, lead)
|
|
1247
|
+
: null;
|
|
1248
|
+
if (!goal) { groupWatch.delete(f.side); return; }
|
|
1249
|
+
const d = distToGoal(at, goal);
|
|
1250
|
+
const w = groupWatch.get(f.side);
|
|
1251
|
+
// A new goal, or a real walk out of the swing disc, is a fresh window rather than a stuck one.
|
|
1252
|
+
if (!w
|
|
1253
|
+
|| distToGoal(at, { x: w.ox, y: w.oy, ...(w.oz != null ? { z: w.oz } : {}) }) > GROUP_RECALL_ROAM_YD
|
|
1254
|
+
|| distToGoal({ x: goal.x, y: goal.y }, { x: w.gx, y: w.gy }) > PARTY_STUCK_GOAL_EPS_YD) {
|
|
1255
|
+
groupWatch.set(f.side, {
|
|
1256
|
+
ox: at.x, oy: at.y, ...(at.z != null ? { oz: at.z } : {}),
|
|
1257
|
+
gx: goal.x, gy: goal.y, startD: d, bestD: d, since: ctx.t,
|
|
1258
|
+
});
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
if (d < w.bestD) w.bestD = d;
|
|
1262
|
+
// Only monotone improvement counts, the rule the autopilot stall watchdog already applies.
|
|
1263
|
+
if (w.bestD <= w.startD - PARTY_STUCK_PROGRESS_YD) {
|
|
1264
|
+
w.startD = w.bestD;
|
|
1265
|
+
w.since = ctx.t;
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
if (ctx.t - w.since >= GROUP_RECALL_MS) recallToGroup(f, lead);
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
// Fear source position for forced flee: the live fear aura's caster, else current threat or foe.
|
|
1272
|
+
// Cite: Unit.cpp SetFleeing → MoveFleeing(source).
|
|
1273
|
+
const fearSourcePos = (f: Fighter): CombatPosYd | null => {
|
|
1274
|
+
let bestUntil = 0;
|
|
1275
|
+
let srcSide: string | null = null;
|
|
1276
|
+
for (const e of ctx.effectsOn(f.side)) {
|
|
1277
|
+
if (e.kind !== 'cc' || e.control !== 'fear' || e.broken) continue;
|
|
1278
|
+
if (e.expiresAt <= ctx.t) continue;
|
|
1279
|
+
if (e.expiresAt >= bestUntil) {
|
|
1280
|
+
bestUntil = e.expiresAt;
|
|
1281
|
+
srcSide = e.source;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (srcSide) {
|
|
1285
|
+
const p = posOf(srcSide);
|
|
1286
|
+
if (p) return p;
|
|
1287
|
+
}
|
|
1288
|
+
const threat = isEnemySide(f.side) ? hostileTargetOf(f) : foeOf(f);
|
|
1289
|
+
if (threat && threat !== f && !dead(threat)) {
|
|
1290
|
+
const p = posOf(threat.side);
|
|
1291
|
+
if (p) return p;
|
|
1292
|
+
}
|
|
1293
|
+
return null;
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
// Forced flee lives in fearFlee.ts. The dest is GetHitPosition on the loaded floor, so a random
|
|
1297
|
+
// quiet-band point through a wall is not a void run. A 100% snare still freezes the unit.
|
|
1298
|
+
const fearFlee = createFearFlee({
|
|
1299
|
+
claims,
|
|
1300
|
+
rng: ctx.rng,
|
|
1301
|
+
now: () => ctx.t,
|
|
1302
|
+
posOf: (f) => posOf(f.side),
|
|
1303
|
+
setPos: (f, p) => setPos(f.side, p),
|
|
1304
|
+
sourcePos: fearSourcePos,
|
|
1305
|
+
clampDest: (from, dest) => clampDisplacementToFloor(from, dest, floorOf()),
|
|
1306
|
+
step: (f, from, to) => {
|
|
1307
|
+
// SetCombatSlowed is ONE multiply and the speed helper already carries it. A second copy here
|
|
1308
|
+
// squared it to 0.25x at 0 hp and ignored the opt-out flag 369 templates carry.
|
|
1309
|
+
const speed = runSpeedYdPerSec(f);
|
|
1310
|
+
if (!(speed > 0)) return from;
|
|
1311
|
+
return stepDisplacement(from, to, moveStepYd(speed, tickMs), floorOf());
|
|
1312
|
+
},
|
|
1313
|
+
arriveYd: ARRIVE_YD,
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
// Held key / virtual stick — peeled to sessionMoveIntent.ts (E4.2 arm 5).
|
|
1317
|
+
const { moveIntent } = createMoveIntentArm({
|
|
1318
|
+
ctx, tickMs, posOf, setPos, takeMotion, navStepToward, runSpeedYdPerSec,
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
// After-cast retreat (`after.stepBackYd`) — peeled to sessionStepBack.ts (E4.2 arm 4).
|
|
1322
|
+
const { stepBack } = createStepBackArm({
|
|
1323
|
+
ctx, tickMs, posOf, setPos, dead, foeOf, takeMotion, dropStaleCorridor,
|
|
1324
|
+
runSpeedYdPerSec, lastMoveFrom,
|
|
1325
|
+
clampDest: (from, dest) => clampDisplacementToFloor(from, dest, floorOf()),
|
|
1326
|
+
});
|
|
1327
|
+
|
|
1328
|
+
// Manual move orders (click-to-move / RTS group order) — peeled to sessionMoveOrder.ts
|
|
1329
|
+
// (E4.2 arm 3). Built here rather than earlier because its unstuck ladder is declared above.
|
|
1330
|
+
const { moveOrder, advanceHeld: advanceHeldOrder } = createMoveOrderArm({
|
|
1331
|
+
ctx, tickMs, claims, arriveYd: ARRIVE_YD, unstuckFailsRecall: UNSTUCK_FAILS_RECALL,
|
|
1332
|
+
posOf, setPos, diag, takeMotion, orderOwnerOf, navStepToward, dropStaleCorridor,
|
|
1333
|
+
unitNavPaths, orderStall, nudgeOntoMesh, softUnstuckToward, watchPartyStuck, recallToLastGood,
|
|
1334
|
+
unstuckFails, runSpeedYdPerSec,
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
// Pet / guardian / Flamelash follows — peeled to sessionFollow.ts (filesize ratchet).
|
|
1338
|
+
const { petCommand, guardianFollow, spiritFollow, partyFollow } =
|
|
1339
|
+
createFollowArms({
|
|
1340
|
+
ctx, tickMs, posOf, setPos, dead, foeOf, bySide, enemyEngaged,
|
|
1341
|
+
takeMotion, navStepToward, runSpeedYdPerSec,
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
// Mechanic moves (rotation `kind:'move'` rows) — peeled to sessionMechanicMove.ts.
|
|
1345
|
+
const { mechanicMove } = createMechanicMove({
|
|
1346
|
+
ctx, claims, tickMs, posOf, setPos, fightersOrder, dead, foeOf,
|
|
1347
|
+
navStepToward, runSpeedYdPerSec, longestOffenseRangeYd, dropStaleCorridor,
|
|
1348
|
+
pathTo, advanceHeldOrder,
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
// Hold-ground return — peeled to sessionHome.ts (E4.2 arm 2).
|
|
1352
|
+
const { home } = createHomeArm({
|
|
1353
|
+
ctx, tickMs, posOf, setPos, gate, takeMotion, navStepToward, watchPartyStuck,
|
|
1354
|
+
runSpeedYdPerSec,
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
// Gap-close, the melee flank slot and the ranged stand band — peeled to sessionChase.ts
|
|
1358
|
+
// (E4.2). It is the floor of the claim order and the last arm in this chain.
|
|
1359
|
+
const { chase, clearChaseParkTimer, markNoPath, standsInLiveCone } = createChaseArm({
|
|
1360
|
+
ctx, tickMs, posOf, setPos, dead, foeOf, hostileTargetOf, enemyEngaged, fightersOrder,
|
|
1361
|
+
gate, diag, claimOwnerOf: (side) => claims.ownerOf(side, ctx.t),
|
|
1362
|
+
takeMotion, navStepToward, pathTo, advanceHeldOrder, dropStaleCorridor,
|
|
1363
|
+
unitNavPaths, stuckState, noProgressStuck, watchPartyStuck, evadeUnreachable,
|
|
1364
|
+
runSpeedYdPerSec, longestOffenseRangeYd, shortOffenseList, onCooldown, canAfford,
|
|
1365
|
+
kitRangedAuto, wantsMeleeBand, pbaoeRadiusYd, isThreatVictimOf,
|
|
1366
|
+
heldSlideDest, holdSlide, posSlideSettled, focusIsStill, meleePeersOf,
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
const advanceGapClose = (f: Fighter): void => {
|
|
1370
|
+
if (!ctx.unitPositionsMutable) { gate(f.side, 'noPositions'); return; }
|
|
1371
|
+
// A cast bar parks a unit, except one standing in a live wedge: a player moves and eats the lost
|
|
1372
|
+
// cast (the order cancels it, sessionPathTo.ts:481) rather than finishing the bar in a breath.
|
|
1373
|
+
const coneOverride = f.castingSpell != null && !dead(f) && standsInLiveCone(f);
|
|
1374
|
+
if (dead(f) || (f.castingSpell && !coneOverride) || f.pendingCharge) {
|
|
1375
|
+
gate(f.side, dead(f) ? 'dead' : f.castingSpell ? 'casting' : 'charging');
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
// A channel stands its caster still for the whole duration, like a cast bar. Only the cast was
|
|
1379
|
+
// checked here, so the player channelled and ran. A deliberate walk cancels the channel first.
|
|
1380
|
+
if (channelHoldsStill(f, ctx.t)) { gate(f.side, 'channeling'); return; }
|
|
1381
|
+
// Root, stun, incapacitate and SESSILE mean no voluntary motion; the last clause is EventAI's own
|
|
1382
|
+
// root, whose flag nothing read. NOT ported: the action's combatOnly param, so it persists here.
|
|
1383
|
+
if (
|
|
1384
|
+
f.rootedUntil > ctx.t || f.stunnedUntil > ctx.t || f.incapUntil > ctx.t
|
|
1385
|
+
|| (((f.spec.staticFlags ?? 0) & 0x0100) !== 0)
|
|
1386
|
+
|| (isEnemySide(f.side) && ctx.enemyCastBySide.get(f.side)?.eventAi.immobilized === true)
|
|
1387
|
+
) { gate(f.side, 'immobile'); return; }
|
|
1388
|
+
// Fear is forced flee, not a freeze, and DoFlee takes the same generator, so a flee-for-assist row
|
|
1389
|
+
// and a no-melee critter both run here. Reading the flag only in the swing left them standing.
|
|
1390
|
+
if (f.fearedUntil > ctx.t || enemyIsFleeing(f)) {
|
|
1391
|
+
// Forced flight outranks every decision below it; only a knockback displaces it.
|
|
1392
|
+
fearFlee.generator.update(f, tickMs);
|
|
1393
|
+
gate(f.side, 'fleeing');
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
// Not fleeing: Finalize() drops the claim and the pinned source, so a later fear pins
|
|
1397
|
+
// its own. Cite: MovementGenerator.h:39.
|
|
1398
|
+
fearFlee.generator.finalize?.(f);
|
|
1399
|
+
if (isTotemSide(f.side)) { gate(f.side, 'totem'); return; }
|
|
1400
|
+
// PLAN §7.5: mechanic move BEFORE held-key / step-back / engage / leash.
|
|
1401
|
+
if (mechanicMove.update(f, tickMs)) { gate(f.side, 'mechanicMove'); return; }
|
|
1402
|
+
// Held key / stick outranks the click route it interrupted.
|
|
1403
|
+
if (moveIntent.update(f, tickMs)) { gate(f.side, 'moveIntent'); return; }
|
|
1404
|
+
// After-cast retreat: before stance / engage / leftover walk-in. Cast-time waits
|
|
1405
|
+
// (stepSession + combatView) so this actually covers the authored yards.
|
|
1406
|
+
if (stepBack.update(f, tickMs)) { gate(f.side, 'stepBack'); return; }
|
|
1407
|
+
// A ranged dest is "stand short of where the mob was", so once the mob walks in the move-order arm
|
|
1408
|
+
// walks you onto it. A player "go here" dest is not that park and is kept.
|
|
1409
|
+
if (!isEnemySide(f.side)
|
|
1410
|
+
&& !(ctx as { unitHoldCourse?: Set<string> }).unitHoldCourse?.has(f.side)
|
|
1411
|
+
&& (((ctx as { unitMoveOrders?: Map<string, unknown> }).unitMoveOrders?.has(f.side) ?? false)
|
|
1412
|
+
|| unitNavPaths.has(f.side))) {
|
|
1413
|
+
const focus = foeOf(f);
|
|
1414
|
+
if (focus && focus !== f && !dead(focus)) {
|
|
1415
|
+
// A POLICY travel order does not survive the pull it walked into; a person's click does.
|
|
1416
|
+
// Cite: PlayerbotAI.cpp:2394 DoCombatMovement — MotionMaster::Clear(false) + MoveChase.
|
|
1417
|
+
if (orderOwnerOf(f.side) === 'route' && enemyEngaged(focus)) {
|
|
1418
|
+
(ctx as { unitMoveOrders?: Map<string, unknown> }).unitMoveOrders?.delete(f.side);
|
|
1419
|
+
dropStaleCorridor(f.side);
|
|
1420
|
+
claims.release(f.side, 'route', ctx.t);
|
|
1421
|
+
}
|
|
1422
|
+
const here = posOf(f.side);
|
|
1423
|
+
const there = posOf(focus.side);
|
|
1424
|
+
const gap = here && there ? combatDistanceYd(here, there) : null;
|
|
1425
|
+
const stand = f.spec.standYards ?? 0;
|
|
1426
|
+
let park = stand > MELEE_REACH_YD + 1
|
|
1427
|
+
? stand
|
|
1428
|
+
: (f.side === 'pet' ? MELEE_REACH_YD + MELEE_PARK_SLACK_YD : 0);
|
|
1429
|
+
// A ranged kit with no stamped stand parks at cast range: the storey/LOS fallback
|
|
1430
|
+
// (tail of advanceGapClose) queues the BODY, and an order is walked to its end.
|
|
1431
|
+
if (park <= 0 && !wantsMeleeBand(f)) {
|
|
1432
|
+
const r = longestOffenseRangeYd(f);
|
|
1433
|
+
if (r > MELEE_REACH_YD + 1) park = r;
|
|
1434
|
+
}
|
|
1435
|
+
// Sight is part of parked — the tail hold needs range + storey + LOS at once.
|
|
1436
|
+
if (park > 0 && gap != null && gap <= park && planarRangeHonestYd(here, there)
|
|
1437
|
+
&& (!ctx.cfg.navLos || navSightClear(ctx.cfg, here, there))) {
|
|
1438
|
+
ctx.unitMoveOrders?.delete(f.side);
|
|
1439
|
+
dropStaleCorridor(f.side);
|
|
1440
|
+
}
|
|
1441
|
+
// A chase dest is aimed at a BODY, and a queued dest is walked to its END, so melee trailed a
|
|
1442
|
+
// leg behind a runner. Re-spline instead, on a 250 ms recheck, once the dest stops fitting.
|
|
1443
|
+
if (gap != null && gap > MELEE_CONTACT_YD
|
|
1444
|
+
&& ctx.t >= (chaseRecheckAt.get(f.side) ?? 0)
|
|
1445
|
+
&& claims.ownerOf(f.side, ctx.t) === 'chase'
|
|
1446
|
+
&& (f.mode === 'melee' || wantsMeleeBand(f))) {
|
|
1447
|
+
chaseRecheckAt.set(f.side, ctx.t + CHASE_RECHECK_MS);
|
|
1448
|
+
const dest = (ctx as { unitMoveOrders?: Map<string, CombatPosYd[]> })
|
|
1449
|
+
.unitMoveOrders?.get(f.side)?.[0];
|
|
1450
|
+
const off = dest && there ? combatDistanceYd(there, dest) : null;
|
|
1451
|
+
if (off != null && off > MELEE_CONTACT_YD) {
|
|
1452
|
+
(ctx as { unitMoveOrders?: Map<string, unknown> }).unitMoveOrders?.delete(f.side);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
// Manual move order wins over AI gap-close (party click-to-move / multi-select).
|
|
1458
|
+
if (moveOrder.update(f, tickMs)) { gate(f.side, 'moveOrder'); return; }
|
|
1459
|
+
// Motion is the player's while the latch is held: no key down and no order left means STAND.
|
|
1460
|
+
// Letting gap-close take over here is what makes kiting impossible.
|
|
1461
|
+
if (manualMotion(f.side)) { gate(f.side, 'manualMotion'); return; }
|
|
1462
|
+
// Stay parks the pet; Follow heels the owner at PET_FOLLOW_DIST. Guardians use 1.5.
|
|
1463
|
+
if (petCommand.update(f, tickMs)) { gate(f.side, 'petCommand'); return; }
|
|
1464
|
+
// Flamelash Burning Spirit MoveFollow runs while the encounter is live.
|
|
1465
|
+
if (spiritFollow.update(f, tickMs)) { gate(f.side, 'spiritFollow'); return; }
|
|
1466
|
+
if (guardianFollow.update(f, tickMs)) { gate(f.side, 'guardianFollow'); return; }
|
|
1467
|
+
// RTS stance: hold / passive stand after the walk; patrol is handled by the move order.
|
|
1468
|
+
const stance = (ctx as { unitStance?: Map<string, string> }).unitStance?.get(f.side);
|
|
1469
|
+
if (stance === 'hold' || stance === 'passive') { gate(f.side, 'stance'); return; }
|
|
1470
|
+
// Not engaged yet — stand until first threat (missile land) or AttackStart seed.
|
|
1471
|
+
// threat 0 is the gate's own condition — carried so a report reads as a measurement.
|
|
1472
|
+
if (!enemyEngaged(f)) { gate(f.side, 'notEngaged', { threat: 0 }); return; }
|
|
1473
|
+
// Outside its hold-ground bubble a friendly walks home rather than picking a target at all.
|
|
1474
|
+
// The arm stamps `holdGround` itself — it is the only caller that knows the distance.
|
|
1475
|
+
if (home.update(f, tickMs)) return;
|
|
1476
|
+
// Nothing inside acquire range: walk back to the party instead of standing where the fight
|
|
1477
|
+
// left us. Below `home` because the RTS bubble is the same question with its own anchor.
|
|
1478
|
+
if (partyFollow.update(f, tickMs)) { gate(f.side, 'partyFollow'); return; }
|
|
1479
|
+
chase.update(f, tickMs);
|
|
1480
|
+
};
|
|
1481
|
+
|
|
1482
|
+
// Per-tick motion for every living unit once geometry is seeded, each checked against the mesh first:
|
|
1483
|
+
// a knockback or an off-mesh spawn leaves a unit with no floor to slide on and no cell to path from.
|
|
1484
|
+
const drainNavSearches = (): void => {
|
|
1485
|
+
if (navSearchTick !== ctx.t) {
|
|
1486
|
+
navSearchTick = ctx.t;
|
|
1487
|
+
navSearchesThisTick = 0;
|
|
1488
|
+
}
|
|
1489
|
+
const left = NAV_SEARCH_BUDGET - navSearchesThisTick;
|
|
1490
|
+
if (left <= 0 || navSearchQueue.length === 0) return;
|
|
1491
|
+
const anchor = anchorOf();
|
|
1492
|
+
navSearchesThisTick += drainNavSearchQueue({
|
|
1493
|
+
queue: navSearchQueue,
|
|
1494
|
+
budget: left,
|
|
1495
|
+
mapId: anchor?.mapId,
|
|
1496
|
+
unitNavPaths,
|
|
1497
|
+
sharedPathByGoal,
|
|
1498
|
+
lastSearchAt,
|
|
1499
|
+
now: ctx.t,
|
|
1500
|
+
diag,
|
|
1501
|
+
liveFrom: (s) => {
|
|
1502
|
+
const p = posOf(s);
|
|
1503
|
+
return p ? combatToWorld(p, anchor) : undefined;
|
|
1504
|
+
},
|
|
1505
|
+
onNoPath: markNoPath,
|
|
1506
|
+
skip: (s) => {
|
|
1507
|
+
const f = bySide(s as Side); return !f || dead(f);
|
|
1508
|
+
},
|
|
1509
|
+
});
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
const runMotionPhase = (): void => {
|
|
1513
|
+
if (!ctx.unitPositionsMutable) return;
|
|
1514
|
+
drainNavSearches();
|
|
1515
|
+
const endless = ctx.cfg.endless === true;
|
|
1516
|
+
for (const f of fightersOrder()) {
|
|
1517
|
+
if (dead(f)) continue;
|
|
1518
|
+
if (isTotemSide(f.side)) continue; // totems are placed, never walk
|
|
1519
|
+
// Stream passives never path — skip mesh nudge + gap-close (BRD 60+ injects/tick).
|
|
1520
|
+
// Detection/aggro is floor-side; once threat seeds they join this loop.
|
|
1521
|
+
if (endless && isEnemySide(f.side) && !enemyEngaged(f)) continue;
|
|
1522
|
+
nudgeOntoMesh(f);
|
|
1523
|
+
if (f.pendingCharge) charge.update(f, tickMs);
|
|
1524
|
+
else advanceGapClose(f);
|
|
1525
|
+
if (!isEnemySide(f.side)) watchGroupRecall(f);
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
/** AreaAura::Update after motion. Radius is C2; death/unsummon is not. */
|
|
1530
|
+
const areaAuraMaintainPhase = (): void => {
|
|
1531
|
+
runAreaAuraMaintain({
|
|
1532
|
+
sources: ctx.areaAuraSources,
|
|
1533
|
+
posOf,
|
|
1534
|
+
bySide: (side) => bySide(side as Side),
|
|
1535
|
+
dead,
|
|
1536
|
+
hasGeometry: () => ctx.unitPositionsMutable != null,
|
|
1537
|
+
t: ctx.t,
|
|
1538
|
+
tickMs,
|
|
1539
|
+
diag: (ctx.slowDiag ??= emptySlowDiag()),
|
|
1540
|
+
});
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
ctx.clearMotionCaches = clearMotionCaches;
|
|
1544
|
+
|
|
1545
|
+
return {
|
|
1546
|
+
finishChargeArrival,
|
|
1547
|
+
advanceChargeMove,
|
|
1548
|
+
enemyEngaged,
|
|
1549
|
+
runMotionPhase,
|
|
1550
|
+
areaAuraMaintainPhase,
|
|
1551
|
+
pathTo,
|
|
1552
|
+
};
|
|
1553
|
+
}
|