azeroth-shell 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -2
- package/bin/azsh.mjs +19 -0
- package/docs/aifid/eventai-texts.json +4414 -0
- package/docs/dungeons/atlas/atlas-rows-dungeons.json +8989 -0
- package/docs/dungeons/survey/pack-id-migration.json +2418 -0
- package/package.json +26 -4
- package/src/autopilot/bossSkip.ts +26 -0
- package/src/autopilot/combatRotation.ts +824 -0
- package/src/autopilot/config.ts +533 -0
- package/src/autopilot/consumables.ts +58 -0
- package/src/autopilot/encounterStand.ts +121 -0
- package/src/autopilot/objectGatePlan.ts +53 -0
- package/src/autopilot/pathEngage.ts +229 -0
- package/src/autopilot/plan.ts +1643 -0
- package/src/autopilot/questPolicy.ts +274 -0
- package/src/autopilot/routeStep.ts +69 -0
- package/src/autopilot/sessionReport.ts +88 -0
- package/src/autopilot/topOffPower.ts +26 -0
- package/src/autopilot/view.ts +476 -0
- package/src/cli/bootCheck.mjs +34 -0
- package/src/cli/bootData.mjs +57 -0
- package/src/cli/combatLogFilter.ts +153 -0
- package/src/cli/combatLogFormat.ts +613 -0
- package/src/cli/fileRepository.mjs +105 -0
- package/src/cli/gzLoader.mjs +31 -0
- package/src/cli/liveGauntletEscort.ts +978 -0
- package/src/cli/liveStratUndead.ts +305 -0
- package/src/cli/macroHostCombat.ts +545 -0
- package/src/cli/macroHostWorld.ts +330 -0
- package/src/cli/prefs.mjs +31 -0
- package/src/cli/register.mjs +4 -0
- package/src/cli/repl.mjs +693 -0
- package/src/cli/rotationText.mjs +226 -0
- package/src/cli/squad.mjs +105 -0
- package/src/cli/term.mjs +126 -0
- package/src/cli/text.mjs +122 -0
- package/src/cli/tui.mjs +626 -0
- package/src/cli/tuiArena.mjs +109 -0
- package/src/cli/tuiAutopilotSettings.mjs +93 -0
- package/src/cli/tuiBags.mjs +180 -0
- package/src/cli/tuiBars.mjs +133 -0
- package/src/cli/tuiBg.mjs +243 -0
- package/src/cli/tuiBulk.mjs +162 -0
- package/src/cli/tuiCheck.mjs +1882 -0
- package/src/cli/tuiCity.mjs +396 -0
- package/src/cli/tuiCompanionKit.mjs +300 -0
- package/src/cli/tuiCompanions.mjs +471 -0
- package/src/cli/tuiDev.mjs +62 -0
- package/src/cli/tuiDungeon.mjs +606 -0
- package/src/cli/tuiEnchantTarget.mjs +120 -0
- package/src/cli/tuiFight.mjs +577 -0
- package/src/cli/tuiFightPanes.mjs +228 -0
- package/src/cli/tuiFloor.mjs +506 -0
- package/src/cli/tuiGear.mjs +158 -0
- package/src/cli/tuiHonor.mjs +74 -0
- package/src/cli/tuiLoot.mjs +97 -0
- package/src/cli/tuiMacros.mjs +144 -0
- package/src/cli/tuiOutfitter.mjs +115 -0
- package/src/cli/tuiPet.mjs +116 -0
- package/src/cli/tuiProfessions.mjs +800 -0
- package/src/cli/tuiRaid.mjs +127 -0
- package/src/cli/tuiRep.mjs +65 -0
- package/src/cli/tuiSaves.mjs +69 -0
- package/src/cli/tuiSearch.mjs +363 -0
- package/src/cli/tuiSettings.mjs +151 -0
- package/src/cli/tuiSimLab.mjs +80 -0
- package/src/cli/tuiTalents.mjs +60 -0
- package/src/cli/tuiViews.mjs +1178 -0
- package/src/cli/tuiWiki.mjs +181 -0
- package/src/cli/tuiWorld.mjs +361 -0
- package/src/cli/wikiIndex.ts +259 -0
- package/src/cli/wikiSources.ts +355 -0
- package/src/data/alteracValley.ts +176 -0
- package/src/data/appearanceOptions.ts +534 -0
- package/src/data/applyLootChanceSanity.ts +21 -0
- package/src/data/createFlavor.ts +96 -0
- package/src/data/customItems.ts +33 -0
- package/src/data/dataStore.ts +1904 -0
- package/src/data/dungeonGameObjects.ts +84 -0
- package/src/data/dungeonPlateCover.ts +66 -0
- package/src/data/dungeonSpawns.ts +294 -0
- package/src/data/dungeons/alterac-valley.ts +135 -0
- package/src/data/dungeons/apply-dungeon-rooms.ts +29 -0
- package/src/data/dungeons/apply-encounter-outdoors.ts +72 -0
- package/src/data/dungeons/apply-instance-geometry.ts +65 -0
- package/src/data/dungeons/apply-spawn-placement.ts +171 -0
- package/src/data/dungeons/blackfathom-deeps.ts +539 -0
- package/src/data/dungeons/blackrock-depths.ts +1410 -0
- package/src/data/dungeons/blackwing-lair.ts +129 -0
- package/src/data/dungeons/boss-eventai.ts +8 -0
- package/src/data/dungeons/deadmines.ts +410 -0
- package/src/data/dungeons/dire-maul-east.ts +980 -0
- package/src/data/dungeons/dire-maul-north.ts +661 -0
- package/src/data/dungeons/dire-maul-west.ts +785 -0
- package/src/data/dungeons/dungeon-rooms.json +5479 -0
- package/src/data/dungeons/encounter-outdoors.json +359 -0
- package/src/data/dungeons/eventai/blackfathom-deeps.ts +308 -0
- package/src/data/dungeons/eventai/blackrock-depths.ts +1257 -0
- package/src/data/dungeons/eventai/deadmines.ts +264 -0
- package/src/data/dungeons/eventai/dire-maul-east.ts +320 -0
- package/src/data/dungeons/eventai/gnomeregan.ts +213 -0
- package/src/data/dungeons/eventai/helpers.ts +79 -0
- package/src/data/dungeons/eventai/index.ts +133 -0
- package/src/data/dungeons/eventai/lbrs.ts +372 -0
- package/src/data/dungeons/eventai/maraudon.ts +323 -0
- package/src/data/dungeons/eventai/ragefire-chasm.ts +108 -0
- package/src/data/dungeons/eventai/razorfen-downs.ts +207 -0
- package/src/data/dungeons/eventai/razorfen-kraul.ts +326 -0
- package/src/data/dungeons/eventai/scholomance.ts +680 -0
- package/src/data/dungeons/eventai/shadowfang-keep.ts +230 -0
- package/src/data/dungeons/eventai/sm-cathedral.ts +75 -0
- package/src/data/dungeons/eventai/sm-graveyard.ts +150 -0
- package/src/data/dungeons/eventai/sm-library.ts +55 -0
- package/src/data/dungeons/eventai/stratholme-undead.ts +127 -0
- package/src/data/dungeons/eventai/sunken-temple.ts +582 -0
- package/src/data/dungeons/eventai/the-stockade.ts +228 -0
- package/src/data/dungeons/eventai/ubrs.ts +225 -0
- package/src/data/dungeons/eventai/uldaman.ts +355 -0
- package/src/data/dungeons/eventai/wailing-caverns.ts +396 -0
- package/src/data/dungeons/eventai/zul-farrak.ts +591 -0
- package/src/data/dungeons/gnomeregan.ts +909 -0
- package/src/data/dungeons/index.ts +126 -0
- package/src/data/dungeons/instance-geometry.json +5233 -0
- package/src/data/dungeons/key-sources.json +169 -0
- package/src/data/dungeons/lbrs.ts +996 -0
- package/src/data/dungeons/maraudon.ts +786 -0
- package/src/data/dungeons/molten-core.ts +194 -0
- package/src/data/dungeons/naxxramas.ts +173 -0
- package/src/data/dungeons/onyxias-lair.ts +71 -0
- package/src/data/dungeons/pack-from-clusters.ts +1256 -0
- package/src/data/dungeons/ragefire-chasm.ts +238 -0
- package/src/data/dungeons/razorfen-downs.ts +698 -0
- package/src/data/dungeons/razorfen-kraul.ts +766 -0
- package/src/data/dungeons/ruins-of-ahnqiraj.ts +121 -0
- package/src/data/dungeons/scholomance.ts +1076 -0
- package/src/data/dungeons/shadowfang-keep.ts +740 -0
- package/src/data/dungeons/sm-armory.ts +266 -0
- package/src/data/dungeons/sm-cathedral.ts +430 -0
- package/src/data/dungeons/sm-graveyard.ts +482 -0
- package/src/data/dungeons/sm-library.ts +337 -0
- package/src/data/dungeons/stratholme-live.ts +808 -0
- package/src/data/dungeons/stratholme-undead.ts +1164 -0
- package/src/data/dungeons/sunken-temple.ts +931 -0
- package/src/data/dungeons/temple-of-ahnqiraj.ts +119 -0
- package/src/data/dungeons/the-stockade.ts +282 -0
- package/src/data/dungeons/ubrs.ts +1286 -0
- package/src/data/dungeons/uldaman.ts +685 -0
- package/src/data/dungeons/wailing-caverns.ts +356 -0
- package/src/data/dungeons/zul-farrak.ts +686 -0
- package/src/data/dungeons/zul-gurub.ts +142 -0
- package/src/data/dungeons.ts +42 -0
- package/src/data/fixtures.ts +446 -0
- package/src/data/gameobject-data.json.gz +0 -0
- package/src/data/item-data.json.gz +0 -0
- package/src/data/loot-data.json.gz +0 -0
- package/src/data/maps/atlas-approach-maps.json +1425 -0
- package/src/data/maps/atlas-landmarks.json +1711 -0
- package/src/data/maps/dungeon-viewports.json +392 -0
- package/src/data/materializeModDamagePercentTaken.ts +70 -0
- package/src/data/materializeModHealthRegen.ts +89 -0
- package/src/data/materializeSpellReputation.ts +26 -0
- package/src/data/pageTexts.ts +19 -0
- package/src/data/profession-data.json.gz +0 -0
- package/src/data/raid-plans/blackfathom-deeps.json +86 -0
- package/src/data/raid-plans/blackrock-depths.json +419 -0
- package/src/data/raid-plans/blackwing-lair.json +328 -0
- package/src/data/raid-plans/deadmines.json +55 -0
- package/src/data/raid-plans/dire-maul-east.json +94 -0
- package/src/data/raid-plans/dire-maul-north.json +70 -0
- package/src/data/raid-plans/dire-maul-west.json +75 -0
- package/src/data/raid-plans/gnomeregan.json +131 -0
- package/src/data/raid-plans/lbrs.json +176 -0
- package/src/data/raid-plans/maraudon.json +102 -0
- package/src/data/raid-plans/molten-core.json +169 -0
- package/src/data/raid-plans/naxxramas.json +354 -0
- package/src/data/raid-plans/onyxias-lair.json +18 -0
- package/src/data/raid-plans/ragefire-chasm.json +30 -0
- package/src/data/raid-plans/razorfen-downs.json +75 -0
- package/src/data/raid-plans/razorfen-kraul.json +53 -0
- package/src/data/raid-plans/ruins-of-ahnqiraj.json +110 -0
- package/src/data/raid-plans/scholomance.json +170 -0
- package/src/data/raid-plans/shadowfang-keep.json +87 -0
- package/src/data/raid-plans/sm-armory.json +53 -0
- package/src/data/raid-plans/sm-cathedral.json +72 -0
- package/src/data/raid-plans/sm-graveyard.json +63 -0
- package/src/data/raid-plans/sm-library.json +23 -0
- package/src/data/raid-plans/stratholme-live.json +129 -0
- package/src/data/raid-plans/stratholme-undead.json +73 -0
- package/src/data/raid-plans/sunken-temple.json +159 -0
- package/src/data/raid-plans/temple-of-ahnqiraj.json +163 -0
- package/src/data/raid-plans/the-stockade.json +42 -0
- package/src/data/raid-plans/ubrs.json +53 -0
- package/src/data/raid-plans/uldaman.json +131 -0
- package/src/data/raid-plans/wailing-caverns.json +90 -0
- package/src/data/raid-plans/zul-farrak.json +156 -0
- package/src/data/raid-plans/zul-gurub.json +307 -0
- package/src/data/raidInstanceCreatures.mjs +473 -0
- package/src/data/rotations/index.ts +152 -0
- package/src/data/rotations/layout.mjs +72 -0
- package/src/data/rotations/pve/druid/balance.json +754 -0
- package/src/data/rotations/pve/druid/feral-tank.json +2448 -0
- package/src/data/rotations/pve/druid/feral.json +2660 -0
- package/src/data/rotations/pve/druid/restoration.json +424 -0
- package/src/data/rotations/pve/hunter/beast-mastery.json +1288 -0
- package/src/data/rotations/pve/hunter/marksmanship.json +1260 -0
- package/src/data/rotations/pve/hunter/survival.json +1373 -0
- package/src/data/rotations/pve/mage/arcane-leveling.json +1798 -0
- package/src/data/rotations/pve/mage/fire-leveling.json +2003 -0
- package/src/data/rotations/pve/mage/frost-leveling.json +1912 -0
- package/src/data/rotations/pve/paladin/holy.json +755 -0
- package/src/data/rotations/pve/paladin/protection.json +962 -0
- package/src/data/rotations/pve/paladin/retribution.json +924 -0
- package/src/data/rotations/pve/priest/discipline.json +772 -0
- package/src/data/rotations/pve/priest/holy.json +707 -0
- package/src/data/rotations/pve/priest/shadow.json +787 -0
- package/src/data/rotations/pve/rogue/assassination.json +2477 -0
- package/src/data/rotations/pve/rogue/combat.json +2483 -0
- package/src/data/rotations/pve/rogue/subtlety.json +2523 -0
- package/src/data/rotations/pve/shaman/elemental.json +1157 -0
- package/src/data/rotations/pve/shaman/enhancement.json +1085 -0
- package/src/data/rotations/pve/shaman/restoration.json +592 -0
- package/src/data/rotations/pve/warlock/affliction.json +1185 -0
- package/src/data/rotations/pve/warlock/demonology.json +1165 -0
- package/src/data/rotations/pve/warlock/destruction.json +1177 -0
- package/src/data/rotations/pve/warrior/arms.json +3020 -0
- package/src/data/rotations/pve/warrior/fury.json +2996 -0
- package/src/data/rotations/pve/warrior/protection.json +2509 -0
- package/src/data/rotations/pvp/druid/balance.json +593 -0
- package/src/data/rotations/pvp/druid/feral.json +3276 -0
- package/src/data/rotations/pvp/druid/restoration.json +662 -0
- package/src/data/rotations/pvp/hunter/beast-mastery.json +665 -0
- package/src/data/rotations/pvp/hunter/marksmanship.json +2406 -0
- package/src/data/rotations/pvp/hunter/survival.json +635 -0
- package/src/data/rotations/pvp/mage/arcane.json +574 -0
- package/src/data/rotations/pvp/mage/fire.json +570 -0
- package/src/data/rotations/pvp/mage/frost.json +4048 -0
- package/src/data/rotations/pvp/paladin/holy.json +1498 -0
- package/src/data/rotations/pvp/paladin/protection.json +567 -0
- package/src/data/rotations/pvp/paladin/retribution.json +1110 -0
- package/src/data/rotations/pvp/priest/discipline.json +1848 -0
- package/src/data/rotations/pvp/priest/holy.json +6340 -0
- package/src/data/rotations/pvp/priest/shadow.json +2329 -0
- package/src/data/rotations/pvp/rogue/assassination.json +1322 -0
- package/src/data/rotations/pvp/rogue/combat.json +5258 -0
- package/src/data/rotations/pvp/rogue/subtlety.json +586 -0
- package/src/data/rotations/pvp/shaman/elemental.json +1404 -0
- package/src/data/rotations/pvp/shaman/enhancement.json +1277 -0
- package/src/data/rotations/pvp/shaman/restoration.json +2490 -0
- package/src/data/rotations/pvp/warlock/affliction.json +1017 -0
- package/src/data/rotations/pvp/warlock/demonology.json +1503 -0
- package/src/data/rotations/pvp/warlock/destruction.json +638 -0
- package/src/data/rotations/pvp/warrior/arms.json +5665 -0
- package/src/data/rotations/pvp/warrior/fury.json +850 -0
- package/src/data/rotations/pvp/warrior/protection.json +777 -0
- package/src/data/schema/bundle.ts +511 -0
- package/src/data/schema/constants.ts +49 -0
- package/src/data/schema/creature.ts +454 -0
- package/src/data/schema/encounter.ts +155 -0
- package/src/data/schema/faction.ts +48 -0
- package/src/data/schema/item.ts +297 -0
- package/src/data/schema/itemEnchant.ts +105 -0
- package/src/data/schema/npc.ts +44 -0
- package/src/data/schema/primitives.ts +295 -0
- package/src/data/schema/profession.ts +226 -0
- package/src/data/schema/quest.ts +154 -0
- package/src/data/schema/spell.ts +1149 -0
- package/src/data/schema/talent.ts +74 -0
- package/src/data/schema/world.ts +124 -0
- package/src/data/schema.ts +19 -0
- package/src/data/skinNodeGapFill.ts +68 -0
- package/src/data/spell-data.json.gz +0 -0
- package/src/data/spell-script-targets.json +1 -0
- package/src/data/tables/ahItemValues.json.gz +0 -0
- package/src/data/tables/alteracValley.json.gz +0 -0
- package/src/data/tables/areatriggerTeleports.json.gz +0 -0
- package/src/data/tables/botNames.json.gz +0 -0
- package/src/data/tables/botPopulation.json.gz +0 -0
- package/src/data/tables/botSpecs.json.gz +0 -0
- package/src/data/tables/classLevelStats.json.gz +0 -0
- package/src/data/tables/combatConditions.json.gz +0 -0
- package/src/data/tables/combatSkillMeta.json.gz +0 -0
- package/src/data/tables/conditions.json.gz +0 -0
- package/src/data/tables/createInfo.json.gz +0 -0
- package/src/data/tables/creatureAggroGroups.json.gz +0 -0
- package/src/data/tables/creatureClassLevelStats.json.gz +0 -0
- package/src/data/tables/creaturePatrolPacks.json.gz +0 -0
- package/src/data/tables/creatureWeapons.json.gz +0 -0
- package/src/data/tables/creatures.json.gz +0 -0
- package/src/data/tables/dbScripts.json.gz +0 -0
- package/src/data/tables/dungeonAuthoredRoutes.json.gz +0 -0
- package/src/data/tables/dungeonCensus.json.gz +0 -0
- package/src/data/tables/dungeonGameObjects.json.gz +0 -0
- package/src/data/tables/dungeonPacks.json.gz +0 -0
- package/src/data/tables/dungeonPlateCover.json.gz +0 -0
- package/src/data/tables/dungeonSpawns.json.gz +0 -0
- package/src/data/tables/dungeonSurvey.json.gz +0 -0
- package/src/data/tables/durabilityCosts.json.gz +0 -0
- package/src/data/tables/durabilityQuality.json.gz +0 -0
- package/src/data/tables/enchantApplicators.json.gz +0 -0
- package/src/data/tables/entranceYards.json.gz +0 -0
- package/src/data/tables/explorationBasexp.json.gz +0 -0
- package/src/data/tables/factionTemplates.json.gz +0 -0
- package/src/data/tables/factions.json.gz +0 -0
- package/src/data/tables/gameEvents.json.gz +0 -0
- package/src/data/tables/gameObjects.json.gz +0 -0
- package/src/data/tables/gossipMenus.json.gz +0 -0
- package/src/data/tables/graveyards.json.gz +0 -0
- package/src/data/tables/instanceMaps.json.gz +0 -0
- package/src/data/tables/itemEnchantmentTemplates.json.gz +0 -0
- package/src/data/tables/itemEnchants.json.gz +0 -0
- package/src/data/tables/itemRandomProperties.json.gz +0 -0
- package/src/data/tables/itemSets.json.gz +0 -0
- package/src/data/tables/items.json.gz +0 -0
- package/src/data/tables/levelStats.json.gz +0 -0
- package/src/data/tables/locks.json.gz +0 -0
- package/src/data/tables/map-pin-overrides.json.gz +0 -0
- package/src/data/tables/nonCombatTrash.json.gz +0 -0
- package/src/data/tables/npcTexts.json.gz +0 -0
- package/src/data/tables/pageTexts.json.gz +0 -0
- package/src/data/tables/petAbilityTP.json.gz +0 -0
- package/src/data/tables/petGenericSpells.json.gz +0 -0
- package/src/data/tables/petLevelStats.json.gz +0 -0
- package/src/data/tables/playerbot-weightscales.json.gz +0 -0
- package/src/data/tables/pools.json.gz +0 -0
- package/src/data/tables/quests.json.gz +0 -0
- package/src/data/tables/races.json.gz +0 -0
- package/src/data/tables/reputationRanks.json.gz +0 -0
- package/src/data/tables/reputationRates.json.gz +0 -0
- package/src/data/tables/reputationSpillover.json.gz +0 -0
- package/src/data/tables/ridingSpells.json.gz +0 -0
- package/src/data/tables/scriptTexts.json.gz +0 -0
- package/src/data/tables/soundClipVolume.json.gz +0 -0
- package/src/data/tables/spellChain.json.gz +0 -0
- package/src/data/tables/spellTeleports.json.gz +0 -0
- package/src/data/tables/spells.json.gz +0 -0
- package/src/data/tables/talents.json.gz +0 -0
- package/src/data/tables/tavernTriggers.json.gz +0 -0
- package/src/data/tables/taxiNodes.json.gz +0 -0
- package/src/data/tables/taxiRoutes.json.gz +0 -0
- package/src/data/tables/trainers.json.gz +0 -0
- package/src/data/tables/transports.json.gz +0 -0
- package/src/data/tables/travelAnchors.json.gz +0 -0
- package/src/data/tables/travelEdges.json.gz +0 -0
- package/src/data/tables/unitConditions.json.gz +0 -0
- package/src/data/tables/vendors.json.gz +0 -0
- package/src/data/tables/weaponEnchantProcs.json.gz +0 -0
- package/src/data/tables/xp.json.gz +0 -0
- package/src/data/tables/zoneWeather.json.gz +0 -0
- package/src/data/tables/zones.json.gz +0 -0
- package/src/data/world-data.ts +56 -0
- package/src/data/zone-music-sets.json +2751 -0
- package/src/dev/etl/lib/loot-chance-sanity.mjs +180 -0
- package/src/domain/audioQuality.mjs +145 -0
- package/src/domain/bg/ab.ts +433 -0
- package/src/domain/bg/arenaComps.ts +644 -0
- package/src/domain/bg/arenaLab.ts +695 -0
- package/src/domain/bg/arenaPace.ts +31 -0
- package/src/domain/bg/arenaRating.ts +110 -0
- package/src/domain/bg/av.ts +758 -0
- package/src/domain/bg/avEnemyPush.ts +219 -0
- package/src/domain/bg/avField.ts +283 -0
- package/src/domain/bg/avInstanceNodes.ts +197 -0
- package/src/domain/bg/avInstanceScore.ts +123 -0
- package/src/domain/bg/avQuestItems.ts +55 -0
- package/src/domain/bg/avQuests.ts +210 -0
- package/src/domain/bg/avSession.ts +196 -0
- package/src/domain/bg/bgConsumes.ts +269 -0
- package/src/domain/bg/bgPrebuff.ts +153 -0
- package/src/domain/bg/bgRest.ts +91 -0
- package/src/domain/bg/bgRoster.ts +305 -0
- package/src/domain/bg/bgTier.ts +61 -0
- package/src/domain/bg/bgWorld.ts +862 -0
- package/src/domain/bg/match.ts +215 -0
- package/src/domain/bg/strategies.ts +552 -0
- package/src/domain/bg/types.ts +291 -0
- package/src/domain/bg/wsg.ts +264 -0
- package/src/domain/bots/actionProviderSpec.ts +38 -0
- package/src/domain/bots/autoConsumables.ts +285 -0
- package/src/domain/bots/betweenPullRest.ts +239 -0
- package/src/domain/bots/botGear.ts +248 -0
- package/src/domain/bots/botInventory.ts +486 -0
- package/src/domain/bots/botPet.ts +535 -0
- package/src/domain/bots/botPool.ts +291 -0
- package/src/domain/bots/botProfessions.ts +334 -0
- package/src/domain/bots/companionActionBar.ts +209 -0
- package/src/domain/bots/companionHireCost.ts +236 -0
- package/src/domain/bots/companionMissions.ts +582 -0
- package/src/domain/bots/companionPrep.ts +254 -0
- package/src/domain/bots/companionQuests.ts +270 -0
- package/src/domain/bots/companionRep.ts +75 -0
- package/src/domain/bots/companionRotation.ts +708 -0
- package/src/domain/bots/companionXp.ts +66 -0
- package/src/domain/bots/consumableShare.ts +811 -0
- package/src/domain/bots/dpsMate.ts +1072 -0
- package/src/domain/bots/groupCompReport.ts +132 -0
- package/src/domain/bots/healthstoneSupply.ts +198 -0
- package/src/domain/bots/outfitter.ts +1056 -0
- package/src/domain/bots/paladinPrePull.ts +231 -0
- package/src/domain/bots/partyRole.ts +40 -0
- package/src/domain/bots/playerbotEquipCache.ts +412 -0
- package/src/domain/bots/playerbotItemEligibility.ts +60 -0
- package/src/domain/bots/playerbotStatWeight.ts +853 -0
- package/src/domain/bots/playerbotUnavailableItems.ts +147 -0
- package/src/domain/bots/prePullBuffs.ts +219 -0
- package/src/domain/bots/prePullStealth.ts +82 -0
- package/src/domain/bots/runSupply.ts +251 -0
- package/src/domain/bots/runSupplyPrefs.ts +28 -0
- package/src/domain/bots/sendTradeGoods.ts +394 -0
- package/src/domain/bots/soulShardCap.ts +35 -0
- package/src/domain/bots/squad.ts +842 -0
- package/src/domain/bots/squadRun.ts +81 -0
- package/src/domain/character/actionbar.ts +346 -0
- package/src/domain/character/appearance.ts +165 -0
- package/src/domain/character/buildCombatant.ts +1726 -0
- package/src/domain/character/character.ts +356 -0
- package/src/domain/character/characterName.ts +43 -0
- package/src/domain/character/characterRatings.ts +424 -0
- package/src/domain/character/characterSpells.ts +282 -0
- package/src/domain/character/characterStats.ts +1559 -0
- package/src/domain/character/characterTalents.ts +398 -0
- package/src/domain/character/combatSkills.ts +994 -0
- package/src/domain/character/displayForm.ts +72 -0
- package/src/domain/character/formTalentAuras.ts +171 -0
- package/src/domain/character/forms.ts +499 -0
- package/src/domain/character/races.ts +40 -0
- package/src/domain/character/resurrectionSickness.ts +49 -0
- package/src/domain/character/spellbook.ts +169 -0
- package/src/domain/character/stanceBar.ts +354 -0
- package/src/domain/character/stancePrereq.ts +70 -0
- package/src/domain/character/talentBuilds.ts +63 -0
- package/src/domain/character/talentImport.ts +130 -0
- package/src/domain/character/talentLink.ts +123 -0
- package/src/domain/character/talents.ts +201 -0
- package/src/domain/core/conditionSnapshot.ts +92 -0
- package/src/domain/core/hover.ts +14 -0
- package/src/domain/core/ids.ts +58 -0
- package/src/domain/core/internalNameTags.ts +5 -0
- package/src/domain/core/runtimeDataPatches.ts +357 -0
- package/src/domain/creature/aiTargeting.ts +588 -0
- package/src/domain/creature/combatCondition.ts +157 -0
- package/src/domain/creature/creatureAggro.ts +804 -0
- package/src/domain/creature/creatureAi.ts +206 -0
- package/src/domain/creature/creatureAiConstants.ts +661 -0
- package/src/domain/creature/creatureCastList.ts +1007 -0
- package/src/domain/creature/creatureEventAi.ts +1409 -0
- package/src/domain/creature/creatureGroups.ts +144 -0
- package/src/domain/creature/creatureLinking.ts +333 -0
- package/src/domain/creature/creatureScaling.ts +320 -0
- package/src/domain/creature/creatureTrigger.ts +53 -0
- package/src/domain/creature/creatureWander.ts +74 -0
- package/src/domain/creature/eventAiTexts.ts +145 -0
- package/src/domain/creature/faction.ts +287 -0
- package/src/domain/creature/forcedReaction.ts +48 -0
- package/src/domain/creature/outdoorHuntable.ts +74 -0
- package/src/domain/creature/patrolPacks.ts +210 -0
- package/src/domain/creature/reputation.ts +967 -0
- package/src/domain/creature/unitFlags.ts +74 -0
- package/src/domain/creature/worldBosses.ts +393 -0
- package/src/domain/dungeonMusic.mjs +48 -0
- package/src/domain/economy/auction.ts +282 -0
- package/src/domain/economy/auctionBot.ts +1060 -0
- package/src/domain/economy/honorPrice.ts +100 -0
- package/src/domain/economy/mail.ts +958 -0
- package/src/domain/economy/money.ts +42 -0
- package/src/domain/economy/syntheticClassTrainers.ts +62 -0
- package/src/domain/economy/trading.ts +296 -0
- package/src/domain/economy/vendorSellAll.ts +252 -0
- package/src/domain/economy/vendorStock.ts +146 -0
- package/src/domain/items/ammo.ts +817 -0
- package/src/domain/items/bank.ts +162 -0
- package/src/domain/items/consumableFamily.ts +205 -0
- package/src/domain/items/craftUpgrades.ts +83 -0
- package/src/domain/items/createItem.ts +93 -0
- package/src/domain/items/durability.ts +581 -0
- package/src/domain/items/equipment.ts +441 -0
- package/src/domain/items/equipmentHelper.ts +767 -0
- package/src/domain/items/inventory.ts +561 -0
- package/src/domain/items/itemBonding.ts +93 -0
- package/src/domain/items/itemContainer.ts +245 -0
- package/src/domain/items/itemDisplayName.ts +33 -0
- package/src/domain/items/itemInstance.ts +181 -0
- package/src/domain/items/itemRandomEnchantOutcomes.ts +315 -0
- package/src/domain/items/itemRandomProps.ts +77 -0
- package/src/domain/items/itemRestrictions.ts +329 -0
- package/src/domain/items/itemTeaches.ts +82 -0
- package/src/domain/items/itemUseCooldown.ts +52 -0
- package/src/domain/items/itemUseTarget.ts +90 -0
- package/src/domain/items/junkCategories.ts +174 -0
- package/src/domain/items/junkFilter.ts +368 -0
- package/src/domain/items/keyring.ts +131 -0
- package/src/domain/items/loot.ts +221 -0
- package/src/domain/items/mounts.ts +190 -0
- package/src/domain/items/onUsePayload.ts +102 -0
- package/src/domain/items/pendingLoot.ts +107 -0
- package/src/domain/items/relic.ts +105 -0
- package/src/domain/items/rocketBoots.ts +36 -0
- package/src/domain/items/sortContainer.ts +139 -0
- package/src/domain/items/spareContainers.ts +71 -0
- package/src/domain/items/spellReagentIndex.ts +131 -0
- package/src/domain/items/staleConsumables.ts +66 -0
- package/src/domain/items/weaponSpender.ts +110 -0
- package/src/domain/listDungeonIds.mjs +28 -0
- package/src/domain/macro/compose.ts +239 -0
- package/src/domain/macro/conditions.ts +260 -0
- package/src/domain/macro/creatureTypes.ts +40 -0
- package/src/domain/macro/describe.ts +169 -0
- package/src/domain/macro/icon.ts +60 -0
- package/src/domain/macro/index.ts +20 -0
- package/src/domain/macro/normalize.ts +112 -0
- package/src/domain/macro/parse.ts +316 -0
- package/src/domain/macro/plan.ts +288 -0
- package/src/domain/macro/rotation.ts +590 -0
- package/src/domain/macro/rotationEnv.ts +173 -0
- package/src/domain/macro/slots.ts +101 -0
- package/src/domain/macro/snapshot.ts +114 -0
- package/src/domain/macro/templates.ts +446 -0
- package/src/domain/macro/types.ts +70 -0
- package/src/domain/macro/units.ts +93 -0
- package/src/domain/macro/weaponTypes.ts +83 -0
- package/src/domain/nav/cityTeleports.ts +10 -0
- package/src/domain/nav/mounting.ts +113 -0
- package/src/domain/nav/nav.ts +43 -0
- package/src/domain/nav/navCollision.ts +606 -0
- package/src/domain/nav/navOutdoors.ts +91 -0
- package/src/domain/nav/navRegistry.ts +179 -0
- package/src/domain/nav/navRoomGraph.ts +302 -0
- package/src/domain/nav/navTypes.ts +43 -0
- package/src/domain/nav/navmesh.ts +1715 -0
- package/src/domain/nav/speed.ts +200 -0
- package/src/domain/nav/storey.ts +10 -0
- package/src/domain/nav/travel.ts +725 -0
- package/src/domain/nav/travelTypes.ts +75 -0
- package/src/domain/pet/pet.ts +1150 -0
- package/src/domain/pet/petActionBar.ts +180 -0
- package/src/domain/pet/petSpellbook.ts +39 -0
- package/src/domain/pet/petStable.ts +81 -0
- package/src/domain/professions/autoDisenchant.ts +236 -0
- package/src/domain/professions/characterEnchants.ts +176 -0
- package/src/domain/professions/craftCooldown.ts +52 -0
- package/src/domain/professions/enchantApply.ts +62 -0
- package/src/domain/professions/enchantTargets.ts +114 -0
- package/src/domain/professions/enchanting.ts +236 -0
- package/src/domain/professions/gatherFilter.ts +149 -0
- package/src/domain/professions/gearEnchant.ts +137 -0
- package/src/domain/professions/heldTools.ts +29 -0
- package/src/domain/professions/learnAllRecipes.ts +146 -0
- package/src/domain/professions/miningHarvest.ts +69 -0
- package/src/domain/professions/partyDisenchant.ts +294 -0
- package/src/domain/professions/partyWork.ts +83 -0
- package/src/domain/professions/pickpocket.ts +172 -0
- package/src/domain/professions/poisons.ts +240 -0
- package/src/domain/professions/professions.ts +677 -0
- package/src/domain/professions/rareNodes.ts +45 -0
- package/src/domain/professions/skinLoot.ts +27 -0
- package/src/domain/professions/vendorReagents.ts +152 -0
- package/src/domain/professions/weaponImbueHand.ts +171 -0
- package/src/domain/questFarmFilter.mjs +42 -0
- package/src/domain/quests/doQuest.ts +1204 -0
- package/src/domain/quests/gossip.ts +308 -0
- package/src/domain/quests/gossipTalk.ts +298 -0
- package/src/domain/quests/interpreter/canComplete.ts +150 -0
- package/src/domain/quests/interpreter/claim.ts +137 -0
- package/src/domain/quests/interpreter/dbscript/handlers.ts +1371 -0
- package/src/domain/quests/interpreter/dbscript/handlersBatch.ts +1251 -0
- package/src/domain/quests/interpreter/dbscript/hooks.ts +331 -0
- package/src/domain/quests/interpreter/dbscript/index.ts +212 -0
- package/src/domain/quests/interpreter/dbscript/opcodes.ts +257 -0
- package/src/domain/quests/interpreter/dbscript/registry.ts +1194 -0
- package/src/domain/quests/interpreter/dbscript/registryAreatriggerScripts.ts +539 -0
- package/src/domain/quests/interpreter/dbscript/registryAshenvale.ts +553 -0
- package/src/domain/quests/interpreter/dbscript/registryAzshara.ts +272 -0
- package/src/domain/quests/interpreter/dbscript/registryDarkshore.ts +637 -0
- package/src/domain/quests/interpreter/dbscript/registryDesolace.ts +501 -0
- package/src/domain/quests/interpreter/dbscript/registryDustwallowMarsh.ts +735 -0
- package/src/domain/quests/interpreter/dbscript/registryFelwood.ts +493 -0
- package/src/domain/quests/interpreter/dbscript/registryFeralas.ts +485 -0
- package/src/domain/quests/interpreter/dbscript/registryGoScripts.ts +203 -0
- package/src/domain/quests/interpreter/dbscript/registryMoonglade.ts +288 -0
- package/src/domain/quests/interpreter/dbscript/registryNpcProfessions.ts +230 -0
- package/src/domain/quests/interpreter/dbscript/registryNpcsSpecial.ts +545 -0
- package/src/domain/quests/interpreter/dbscript/registryRazorfenDowns.ts +293 -0
- package/src/domain/quests/interpreter/dbscript/registryRazorfenKraul.ts +221 -0
- package/src/domain/quests/interpreter/dbscript/registryRedridge.ts +112 -0
- package/src/domain/quests/interpreter/dbscript/registrySearingGorge.ts +125 -0
- package/src/domain/quests/interpreter/dbscript/registrySiblings.ts +51 -0
- package/src/domain/quests/interpreter/dbscript/registrySilverpineForest.ts +172 -0
- package/src/domain/quests/interpreter/dbscript/registryStonetalonMountains.ts +180 -0
- package/src/domain/quests/interpreter/dbscript/registryStormwindCity.ts +369 -0
- package/src/domain/quests/interpreter/dbscript/registryStranglethornVale.ts +115 -0
- package/src/domain/quests/interpreter/dbscript/registryStratholme.ts +146 -0
- package/src/domain/quests/interpreter/dbscript/registrySunkenTemple.ts +149 -0
- package/src/domain/quests/interpreter/dbscript/registrySwampOfSorrows.ts +163 -0
- package/src/domain/quests/interpreter/dbscript/registryTanaris.ts +318 -0
- package/src/domain/quests/interpreter/dbscript/registryTeldrassil.ts +120 -0
- package/src/domain/quests/interpreter/dbscript/registryTheBarrens.ts +603 -0
- package/src/domain/quests/interpreter/dbscript/registryThousandNeedles.ts +499 -0
- package/src/domain/quests/interpreter/dbscript/registryTirisfalGlades.ts +222 -0
- package/src/domain/quests/interpreter/dbscript/registryUngoroCrater.ts +291 -0
- package/src/domain/quests/interpreter/dbscript/registryWarEffort.ts +773 -0
- package/src/domain/quests/interpreter/dbscript/registryWesternPlaguelands.ts +206 -0
- package/src/domain/quests/interpreter/dbscript/registryWestfall.ts +190 -0
- package/src/domain/quests/interpreter/dbscript/registryWetlands.ts +186 -0
- package/src/domain/quests/interpreter/dbscript/registryWinterspring.ts +235 -0
- package/src/domain/quests/interpreter/dbscript/types.ts +817 -0
- package/src/domain/quests/interpreter/dbscript/vm.ts +171 -0
- package/src/domain/quests/interpreter/diff.ts +349 -0
- package/src/domain/quests/interpreter/fixtures/oracle-questlog-qi2.json +2417 -0
- package/src/domain/quests/interpreter/flags.ts +101 -0
- package/src/domain/quests/interpreter/index.ts +113 -0
- package/src/domain/quests/interpreter/live.ts +178 -0
- package/src/domain/quests/interpreter/types.ts +81 -0
- package/src/domain/quests/interpreter/updaters.ts +412 -0
- package/src/domain/quests/questCorpseUse.ts +155 -0
- package/src/domain/quests/questDialog.ts +43 -0
- package/src/domain/quests/questFight.ts +34 -0
- package/src/domain/quests/questObjectiveLabel.ts +39 -0
- package/src/domain/quests/questRewardChoice.ts +85 -0
- package/src/domain/quests/questScriptCredit.ts +26 -0
- package/src/domain/quests/questScriptItemUse.ts +74 -0
- package/src/domain/quests/questTameRods.ts +79 -0
- package/src/domain/quests/questlog.ts +1545 -0
- package/src/domain/quests/talkDeliver.ts +170 -0
- package/src/domain/raid/globalActionBar.ts +195 -0
- package/src/domain/raid/instanceRoster.ts +194 -0
- package/src/domain/raid/partySummon.ts +198 -0
- package/src/domain/raid/partyVault.ts +320 -0
- package/src/domain/raid/raidBossAbilities.ts +268 -0
- package/src/domain/raid/raidComp.ts +335 -0
- package/src/domain/raid/raidCustomRows.ts +453 -0
- package/src/domain/raid/raidEncounterGear.ts +298 -0
- package/src/domain/raid/raidEncounterTactics.ts +412 -0
- package/src/domain/raid/raidPlan.ts +509 -0
- package/src/domain/raid/raidPlanEdits.ts +98 -0
- package/src/domain/raid/raidPlanPacks.ts +480 -0
- package/src/domain/raid/raidRecovery.ts +183 -0
- package/src/domain/raid/raidRosterTemplate.ts +138 -0
- package/src/domain/rotation/auraSide.ts +68 -0
- package/src/domain/rotation/build.ts +3031 -0
- package/src/domain/rotation/classes/druid.ts +58 -0
- package/src/domain/rotation/classes/hooks.ts +59 -0
- package/src/domain/rotation/classes/hunter.ts +156 -0
- package/src/domain/rotation/classes/index.ts +104 -0
- package/src/domain/rotation/classes/mage.ts +99 -0
- package/src/domain/rotation/classes/paladin.ts +296 -0
- package/src/domain/rotation/classes/priest.ts +94 -0
- package/src/domain/rotation/classes/pullOpener.ts +92 -0
- package/src/domain/rotation/classes/rogue.ts +69 -0
- package/src/domain/rotation/classes/shaman.ts +29 -0
- package/src/domain/rotation/classes/supportSpec.ts +94 -0
- package/src/domain/rotation/classes/warlock.ts +81 -0
- package/src/domain/rotation/classes/warrior.ts +402 -0
- package/src/domain/rotation/classify.ts +1381 -0
- package/src/domain/rotation/condMetricPicker.ts +379 -0
- package/src/domain/rotation/consumables.ts +242 -0
- package/src/domain/rotation/evaluate.ts +1115 -0
- package/src/domain/rotation/finisherLadder.ts +145 -0
- package/src/domain/rotation/invent.ts +938 -0
- package/src/domain/rotation/killOrder.ts +30 -0
- package/src/domain/rotation/openerForm.ts +22 -0
- package/src/domain/rotation/paladinBuffs.ts +568 -0
- package/src/domain/rotation/prebuff.ts +283 -0
- package/src/domain/rotation/raidRowTuning.ts +34 -0
- package/src/domain/rotation/rotation.ts +11 -0
- package/src/domain/rotation/rotationProfiles.ts +180 -0
- package/src/domain/rotation/rotationSuggestions.ts +851 -0
- package/src/domain/rotation/ruleAfterChain.ts +49 -0
- package/src/domain/rotation/spec.ts +70 -0
- package/src/domain/rotation/spellPicker.ts +140 -0
- package/src/domain/rotation/stand.ts +143 -0
- package/src/domain/rotation/types.ts +718 -0
- package/src/domain/run/battlegroundRun.ts +111 -0
- package/src/domain/run/canEnterContext.ts +32 -0
- package/src/domain/run/death.ts +561 -0
- package/src/domain/run/deathRecovery.ts +289 -0
- package/src/domain/run/duel.ts +141 -0
- package/src/domain/run/dungeonRun.ts +344 -0
- package/src/domain/run/floorRest.ts +159 -0
- package/src/domain/run/honor.ts +557 -0
- package/src/domain/run/honorWeek.ts +180 -0
- package/src/domain/run/whenMap.ts +977 -0
- package/src/domain/scriptdev/arathiHighlands.ts +445 -0
- package/src/domain/scriptdev/ashenvale.ts +635 -0
- package/src/domain/scriptdev/ashenvaleHelpers.ts +1215 -0
- package/src/domain/scriptdev/azshara.ts +892 -0
- package/src/domain/scriptdev/blackfathomDeeps.ts +212 -0
- package/src/domain/scriptdev/blackrockDepths.ts +1434 -0
- package/src/domain/scriptdev/blackrockSpire.ts +730 -0
- package/src/domain/scriptdev/blackwingLair.ts +521 -0
- package/src/domain/scriptdev/burningSteppes.ts +62 -0
- package/src/domain/scriptdev/classFilter.ts +43 -0
- package/src/domain/scriptdev/darkshore.ts +817 -0
- package/src/domain/scriptdev/darkshoreHelpers.ts +853 -0
- package/src/domain/scriptdev/deadmines.ts +331 -0
- package/src/domain/scriptdev/desolace.ts +1239 -0
- package/src/domain/scriptdev/direMaul.ts +859 -0
- package/src/domain/scriptdev/durotar.ts +532 -0
- package/src/domain/scriptdev/dustwallowMarsh.ts +758 -0
- package/src/domain/scriptdev/dustwallowMarshHelpers.ts +994 -0
- package/src/domain/scriptdev/easternPlaguelands.ts +21 -0
- package/src/domain/scriptdev/felwood.ts +1055 -0
- package/src/domain/scriptdev/feralas.ts +571 -0
- package/src/domain/scriptdev/feralasHelpers.ts +838 -0
- package/src/domain/scriptdev/gnomeregan.ts +466 -0
- package/src/domain/scriptdev/guards.ts +616 -0
- package/src/domain/scriptdev/hinterlands.ts +693 -0
- package/src/domain/scriptdev/host.ts +1134 -0
- package/src/domain/scriptdev/index.ts +123 -0
- package/src/domain/scriptdev/instanceRaids.ts +136 -0
- package/src/domain/scriptdev/itemScripts.ts +37 -0
- package/src/domain/scriptdev/lochModan.ts +283 -0
- package/src/domain/scriptdev/maraudon.ts +232 -0
- package/src/domain/scriptdev/moltenCore.ts +831 -0
- package/src/domain/scriptdev/moonglade.ts +659 -0
- package/src/domain/scriptdev/moongladeHelpers.ts +933 -0
- package/src/domain/scriptdev/naxxramas.ts +577 -0
- package/src/domain/scriptdev/naxxramasConstruct.ts +364 -0
- package/src/domain/scriptdev/naxxramasFrostwyrm.ts +378 -0
- package/src/domain/scriptdev/naxxramasMilitary.ts +276 -0
- package/src/domain/scriptdev/npcProfessions.ts +1025 -0
- package/src/domain/scriptdev/npcsSpecial.ts +1379 -0
- package/src/domain/scriptdev/onyxiasLair.ts +313 -0
- package/src/domain/scriptdev/orgrimmar.ts +147 -0
- package/src/domain/scriptdev/professionSpecConsumer.ts +481 -0
- package/src/domain/scriptdev/razorfenDowns.ts +468 -0
- package/src/domain/scriptdev/razorfenKraul.ts +410 -0
- package/src/domain/scriptdev/redridgeMountains.ts +163 -0
- package/src/domain/scriptdev/registry.ts +945 -0
- package/src/domain/scriptdev/ruinsOfAhnqiraj.ts +356 -0
- package/src/domain/scriptdev/scarletMonastery.ts +713 -0
- package/src/domain/scriptdev/scholomance.ts +682 -0
- package/src/domain/scriptdev/searingGorge.ts +295 -0
- package/src/domain/scriptdev/shadowfangKeep.ts +181 -0
- package/src/domain/scriptdev/silithus.ts +71 -0
- package/src/domain/scriptdev/silverpineForest.ts +662 -0
- package/src/domain/scriptdev/stonetalonMountains.ts +493 -0
- package/src/domain/scriptdev/stormwindCity.ts +1192 -0
- package/src/domain/scriptdev/stranglethornVale.ts +402 -0
- package/src/domain/scriptdev/stratholme.ts +650 -0
- package/src/domain/scriptdev/stratholmeScripts.ts +505 -0
- package/src/domain/scriptdev/sunkenTemple.ts +736 -0
- package/src/domain/scriptdev/swampOfSorrows.ts +22 -0
- package/src/domain/scriptdev/tanaris.ts +391 -0
- package/src/domain/scriptdev/tanarisHelpers.ts +661 -0
- package/src/domain/scriptdev/teldrassil.ts +143 -0
- package/src/domain/scriptdev/templeOfAhnqiraj.ts +1013 -0
- package/src/domain/scriptdev/theBarrens.ts +709 -0
- package/src/domain/scriptdev/theBarrensHelpers.ts +1026 -0
- package/src/domain/scriptdev/thousandNeedles.ts +426 -0
- package/src/domain/scriptdev/thousandNeedlesHelpers.ts +566 -0
- package/src/domain/scriptdev/tirisfalGlades.ts +533 -0
- package/src/domain/scriptdev/types.ts +1026 -0
- package/src/domain/scriptdev/uldaman.ts +437 -0
- package/src/domain/scriptdev/ungoroCrater.ts +549 -0
- package/src/domain/scriptdev/ungoroCraterHelpers.ts +1006 -0
- package/src/domain/scriptdev/wailingCaverns.ts +700 -0
- package/src/domain/scriptdev/warEffort.ts +371 -0
- package/src/domain/scriptdev/westernPlaguelands.ts +952 -0
- package/src/domain/scriptdev/westfall.ts +767 -0
- package/src/domain/scriptdev/wetlands.ts +660 -0
- package/src/domain/scriptdev/winterspring.ts +482 -0
- package/src/domain/scriptdev/winterspringHelpers.ts +801 -0
- package/src/domain/scriptdev/worldBosses.ts +605 -0
- package/src/domain/scriptdev/zulfarrak.ts +478 -0
- package/src/domain/scriptdev/zulgurub.ts +1152 -0
- package/src/domain/sim/dummySim.ts +408 -0
- package/src/domain/sim/targetDummy.ts +29 -0
- package/src/domain/social/guild.ts +148 -0
- package/src/domain/spells/areaAuraMembership.ts +93 -0
- package/src/domain/spells/breakableStunCc.ts +166 -0
- package/src/domain/spells/cancelAura.ts +80 -0
- package/src/domain/spells/diminishing.ts +167 -0
- package/src/domain/spells/dispel.ts +149 -0
- package/src/domain/spells/dispelMechanic.ts +105 -0
- package/src/domain/spells/equipSpells.ts +247 -0
- package/src/domain/spells/learnOnTargets.ts +260 -0
- package/src/domain/spells/mergeSchoolImmunityFromRaw.ts +66 -0
- package/src/domain/spells/resurrectSpell.ts +124 -0
- package/src/domain/spells/spellPushback.ts +39 -0
- package/src/domain/spells/spellReflect.ts +43 -0
- package/src/domain/spells/stealth.ts +183 -0
- package/src/domain/spells/totem.ts +394 -0
- package/src/domain/spells/worldCast.ts +187 -0
- package/src/domain/world/areaTriggerLocks.ts +177 -0
- package/src/domain/world/dungeonMusic.ts +108 -0
- package/src/domain/world/gameEvents.ts +119 -0
- package/src/domain/world/gameObjects.ts +1030 -0
- package/src/domain/world/goEventFire.ts +234 -0
- package/src/domain/world/goMoTransport.ts +74 -0
- package/src/domain/world/goTransport.ts +81 -0
- package/src/domain/world/hazards.ts +459 -0
- package/src/domain/world/openLock.ts +398 -0
- package/src/domain/world/pools.ts +470 -0
- package/src/domain/world/restArea.ts +37 -0
- package/src/domain/world/weather.ts +201 -0
- package/src/domain/zoneAmbience.mjs +87 -0
- package/src/domain/zoneMusic.mjs +62 -0
- package/src/domain/zoneMusicQueue.mjs +7 -0
- package/src/engine/combat/activeSeal.ts +39 -0
- package/src/engine/combat/aiTargetResolve.ts +64 -0
- package/src/engine/combat/aoeCast.ts +289 -0
- package/src/engine/combat/applyConsumeAura.ts +74 -0
- package/src/engine/combat/applyDemonicSacrifice.ts +70 -0
- package/src/engine/combat/applyDispelMechanic.ts +30 -0
- package/src/engine/combat/applyHealEffect.ts +301 -0
- package/src/engine/combat/applySchoolAbsorb.ts +114 -0
- package/src/engine/combat/applySpell.ts +3169 -0
- package/src/engine/combat/applySpellLate.ts +2026 -0
- package/src/engine/combat/applySpellLateBag.ts +144 -0
- package/src/engine/combat/areaAura.ts +283 -0
- package/src/engine/combat/attackTable.ts +508 -0
- package/src/engine/combat/battleRez.ts +87 -0
- package/src/engine/combat/castAction.ts +36 -0
- package/src/engine/combat/castTargetGates.ts +75 -0
- package/src/engine/combat/castableKit.ts +68 -0
- package/src/engine/combat/ccMirrorUpgrade.ts +39 -0
- package/src/engine/combat/chainFan.ts +210 -0
- package/src/engine/combat/channel.ts +423 -0
- package/src/engine/combat/chaseCloseness.ts +95 -0
- package/src/engine/combat/cmangosCastGates.ts +129 -0
- package/src/engine/combat/combatFlag.ts +77 -0
- package/src/engine/combat/combatGeometry.ts +1329 -0
- package/src/engine/combat/combustion.ts +68 -0
- package/src/engine/combat/context.ts +730 -0
- package/src/engine/combat/createSession.ts +1165 -0
- package/src/engine/combat/creatureLeash.ts +38 -0
- package/src/engine/combat/crowdControl.ts +1259 -0
- package/src/engine/combat/dispelDuty.ts +54 -0
- package/src/engine/combat/dispelReflect.ts +609 -0
- package/src/engine/combat/displaceFloor.ts +113 -0
- package/src/engine/combat/duelBounds.ts +47 -0
- package/src/engine/combat/equippedItemGate.ts +297 -0
- package/src/engine/combat/evadeLifecycle.ts +63 -0
- package/src/engine/combat/eventAi.ts +2220 -0
- package/src/engine/combat/facing.ts +74 -0
- package/src/engine/combat/fearFlee.ts +137 -0
- package/src/engine/combat/fighterFactory.ts +1144 -0
- package/src/engine/combat/forceDespawnAction.ts +51 -0
- package/src/engine/combat/formulas.ts +807 -0
- package/src/engine/combat/friendlyFan.ts +55 -0
- package/src/engine/combat/groundPoolTick.ts +210 -0
- package/src/engine/combat/hazards.ts +114 -0
- package/src/engine/combat/healTargets.ts +226 -0
- package/src/engine/combat/heartbeatResist.ts +49 -0
- package/src/engine/combat/hostileItemUse.ts +75 -0
- package/src/engine/combat/interruptAuras.ts +66 -0
- package/src/engine/combat/itemRescue.ts +49 -0
- package/src/engine/combat/itemUse.ts +260 -0
- package/src/engine/combat/kitById.ts +23 -0
- package/src/engine/combat/knockBack.ts +201 -0
- package/src/engine/combat/leap.ts +89 -0
- package/src/engine/combat/mitigation.ts +587 -0
- package/src/engine/combat/modTaunt.ts +43 -0
- package/src/engine/combat/motionClaim.ts +268 -0
- package/src/engine/combat/motionGate.ts +150 -0
- package/src/engine/combat/motionGenerator.ts +27 -0
- package/src/engine/combat/navSight.ts +138 -0
- package/src/engine/combat/perfSink.ts +28 -0
- package/src/engine/combat/periodicDue.ts +55 -0
- package/src/engine/combat/petAuras.ts +86 -0
- package/src/engine/combat/pickAction.ts +3449 -0
- package/src/engine/combat/powerForm.ts +735 -0
- package/src/engine/combat/powerPools.ts +49 -0
- package/src/engine/combat/predicates.ts +538 -0
- package/src/engine/combat/procsDamage.ts +1823 -0
- package/src/engine/combat/reactiveGates.ts +135 -0
- package/src/engine/combat/roles.ts +389 -0
- package/src/engine/combat/rotationSubject.ts +78 -0
- package/src/engine/combat/ruleAfter.ts +106 -0
- package/src/engine/combat/seedDisplayAuras.ts +203 -0
- package/src/engine/combat/seedOocEffects.ts +317 -0
- package/src/engine/combat/seedSchoolImmunities.ts +45 -0
- package/src/engine/combat/session-types.ts +458 -0
- package/src/engine/combat/sessionApi.ts +1785 -0
- package/src/engine/combat/sessionCharge.ts +124 -0
- package/src/engine/combat/sessionChase.ts +869 -0
- package/src/engine/combat/sessionDeaths.ts +469 -0
- package/src/engine/combat/sessionFocus.ts +506 -0
- package/src/engine/combat/sessionFollow.ts +167 -0
- package/src/engine/combat/sessionGeometry.ts +188 -0
- package/src/engine/combat/sessionHome.ts +75 -0
- package/src/engine/combat/sessionInject.ts +719 -0
- package/src/engine/combat/sessionMechanicMove.ts +555 -0
- package/src/engine/combat/sessionMotion.ts +1553 -0
- package/src/engine/combat/sessionMotionKit.ts +247 -0
- package/src/engine/combat/sessionMoveIntent.ts +49 -0
- package/src/engine/combat/sessionMoveOrder.ts +200 -0
- package/src/engine/combat/sessionPathTo.ts +657 -0
- package/src/engine/combat/sessionPools.ts +688 -0
- package/src/engine/combat/sessionSteer.ts +144 -0
- package/src/engine/combat/sessionStepBack.ts +151 -0
- package/src/engine/combat/sessionStepWrap.ts +116 -0
- package/src/engine/combat/sessionView.ts +442 -0
- package/src/engine/combat/simEffects.ts +32 -0
- package/src/engine/combat/simFacade.ts +620 -0
- package/src/engine/combat/simPump.ts +264 -0
- package/src/engine/combat/simSnapshot.ts +165 -0
- package/src/engine/combat/simThreadPool.ts +35 -0
- package/src/engine/combat/skeramImages.ts +57 -0
- package/src/engine/combat/slowUpdate.ts +178 -0
- package/src/engine/combat/snapToFloor.ts +42 -0
- package/src/engine/combat/speedAura.ts +29 -0
- package/src/engine/combat/spellReagents.ts +71 -0
- package/src/engine/combat/stealthDetect.ts +93 -0
- package/src/engine/combat/stepBackMotion.ts +171 -0
- package/src/engine/combat/stepSession.ts +3102 -0
- package/src/engine/combat/summonCast.ts +243 -0
- package/src/engine/combat/thaddiusPolarity.ts +97 -0
- package/src/engine/combat/threat.ts +388 -0
- package/src/engine/combat/threatOps.ts +272 -0
- package/src/engine/combat/tickWorkerBody.ts +211 -0
- package/src/engine/combat/totemAuras.ts +122 -0
- package/src/engine/combat/triggerPayload.ts +42 -0
- package/src/engine/combat/triggeredCompanion.ts +63 -0
- package/src/engine/combat/types.ts +1897 -0
- package/src/engine/combat/useObjectTrap.ts +140 -0
- package/src/engine/combat/walkSurface.ts +54 -0
- package/src/engine/combat/weapon.ts +1248 -0
- package/src/engine/combat/worldFrame.ts +43 -0
- package/src/engine/combat.solo-golden.json +2242 -0
- package/src/engine/combat.ts +84 -0
- package/src/engine/core/detMath.ts +289 -0
- package/src/engine/core/events.ts +357 -0
- package/src/engine/core/rng.ts +36 -0
- package/src/engine/game/actionBars.ts +124 -0
- package/src/engine/game/activateObjectFromSpell.ts +320 -0
- package/src/engine/game/applyActiveObjectFromDbScript.ts +98 -0
- package/src/engine/game/applyDeathAndSpellScripts.ts +268 -0
- package/src/engine/game/applyDoorFromDbScript.ts +388 -0
- package/src/engine/game/applyEscortFromDbScript.ts +290 -0
- package/src/engine/game/applyGoSpellcasterCast.ts +40 -0
- package/src/engine/game/applyHoverFromDbScript.ts +110 -0
- package/src/engine/game/applyOocResurrect.ts +71 -0
- package/src/engine/game/applySpellDurability.ts +115 -0
- package/src/engine/game/applyTailFromDbScript.ts +478 -0
- package/src/engine/game/applyTerminateFromDbScript.ts +95 -0
- package/src/engine/game/auctionMail.ts +269 -0
- package/src/engine/game/bagOverflow.ts +130 -0
- package/src/engine/game/bank.ts +110 -0
- package/src/engine/game/barAutofill.ts +26 -0
- package/src/engine/game/battleground.ts +369 -0
- package/src/engine/game/combatEnemySpec.ts +216 -0
- package/src/engine/game/combatObjectTraps.ts +167 -0
- package/src/engine/game/combatSeed.ts +320 -0
- package/src/engine/game/combatSession.ts +3241 -0
- package/src/engine/game/combatSessionClock.ts +62 -0
- package/src/engine/game/companionQuestActions.ts +242 -0
- package/src/engine/game/companionTraining.ts +40 -0
- package/src/engine/game/consumables.ts +624 -0
- package/src/engine/game/context.ts +53 -0
- package/src/engine/game/createHealthstone.ts +45 -0
- package/src/engine/game/deathFlow.ts +127 -0
- package/src/engine/game/devGrant.ts +117 -0
- package/src/engine/game/dynamicObject.ts +365 -0
- package/src/engine/game/enchantItem.ts +177 -0
- package/src/engine/game/equipmentHelper.ts +806 -0
- package/src/engine/game/factionState.ts +108 -0
- package/src/engine/game/garmentsOoc.ts +69 -0
- package/src/engine/game/globalActionBar.ts +179 -0
- package/src/engine/game/goInteractQuestCredit.ts +51 -0
- package/src/engine/game/goMoTransportUse.ts +61 -0
- package/src/engine/game/goTransportUse.ts +60 -0
- package/src/engine/game/healthstoneUpkeep.ts +59 -0
- package/src/engine/game/killSkin.ts +124 -0
- package/src/engine/game/learnFromItem.ts +118 -0
- package/src/engine/game/loadReconcile.ts +551 -0
- package/src/engine/game/loot.ts +294 -0
- package/src/engine/game/macros.ts +125 -0
- package/src/engine/game/mail.ts +293 -0
- package/src/engine/game/meetingStoneQueue.ts +75 -0
- package/src/engine/game/meetingStoneUse.ts +54 -0
- package/src/engine/game/newGameState.ts +363 -0
- package/src/engine/game/oocAuraInterrupt.ts +92 -0
- package/src/engine/game/oocCast.ts +241 -0
- package/src/engine/game/oocCastEffects.ts +803 -0
- package/src/engine/game/oocCastMethods.ts +1077 -0
- package/src/engine/game/openLockFromSpell.ts +174 -0
- package/src/engine/game/outdoorPvp.ts +187 -0
- package/src/engine/game/outfitter.ts +644 -0
- package/src/engine/game/paladinBoard.ts +393 -0
- package/src/engine/game/partyAuras.ts +121 -0
- package/src/engine/game/persistCombatAuras.ts +91 -0
- package/src/engine/game/pets.ts +459 -0
- package/src/engine/game/prePullHealthstones.ts +154 -0
- package/src/engine/game/professions.ts +2227 -0
- package/src/engine/game/pruneStaleRuntime.ts +39 -0
- package/src/engine/game/questFightCredit.ts +36 -0
- package/src/engine/game/questItemOnCorpse.ts +166 -0
- package/src/engine/game/questItemOnCreature.ts +103 -0
- package/src/engine/game/questLog.ts +879 -0
- package/src/engine/game/questSpellComplete.ts +21 -0
- package/src/engine/game/questTurnIn.ts +598 -0
- package/src/engine/game/raidParty.ts +32 -0
- package/src/engine/game/reagentBuyIn.ts +39 -0
- package/src/engine/game/regenRest.ts +186 -0
- package/src/engine/game/rotation.ts +425 -0
- package/src/engine/game/runFullDungeon.ts +611 -0
- package/src/engine/game/runSupply.ts +80 -0
- package/src/engine/game/sanitizeDriftedItems.ts +135 -0
- package/src/engine/game/scriptDevGoUse.ts +397 -0
- package/src/engine/game/scriptDevInnkeeper.ts +150 -0
- package/src/engine/game/scriptDevProfessionSpec.ts +99 -0
- package/src/engine/game/sendEventFromSpell.ts +87 -0
- package/src/engine/game/sortContainers.ts +85 -0
- package/src/engine/game/spellReputation.ts +69 -0
- package/src/engine/game/squad/squadDungeonSeating.ts +197 -0
- package/src/engine/game/squadDungeon.ts +4222 -0
- package/src/engine/game/squadQuestShare.ts +260 -0
- package/src/engine/game/summonObjectSlot.ts +69 -0
- package/src/engine/game/summonObjectWild.ts +73 -0
- package/src/engine/game/summoningRitualUse.ts +141 -0
- package/src/engine/game/talents.ts +356 -0
- package/src/engine/game/tamingRodUse.ts +66 -0
- package/src/engine/game/tempSpawnCreature.ts +136 -0
- package/src/engine/game/tickDungeonPatrolScripts.ts +205 -0
- package/src/engine/game/tickTimedQuests.ts +28 -0
- package/src/engine/game/trade.ts +586 -0
- package/src/engine/game/travel.ts +1155 -0
- package/src/engine/game/types.ts +1855 -0
- package/src/engine/game/vendorOffers.ts +244 -0
- package/src/engine/game/weaponImbueItem.ts +281 -0
- package/src/engine/game/worldSession.ts +183 -0
- package/src/engine/game/worldState.ts +166 -0
- package/src/engine/game.ts +968 -0
- package/src/engine/spells/GAP.md +67 -0
- package/src/engine/spells/absorbs.ts +376 -0
- package/src/engine/spells/addTargetTrigger.ts +53 -0
- package/src/engine/spells/auraPerSecondCost.ts +50 -0
- package/src/engine/spells/auraStacking.ts +497 -0
- package/src/engine/spells/cmangos-special-cases-combustion.ts +13 -0
- package/src/engine/spells/cmangos-special-cases-s18.ts +445 -0
- package/src/engine/spells/cmangos-special-cases-s19.ts +324 -0
- package/src/engine/spells/cmangos-special-cases-s20.ts +385 -0
- package/src/engine/spells/cmangos-special-cases-s21.ts +116 -0
- package/src/engine/spells/cmangos-special-cases-s22.ts +24 -0
- package/src/engine/spells/cmangos-special-cases-s23.ts +52 -0
- package/src/engine/spells/cmangos-special-cases.ts +1570 -0
- package/src/engine/spells/conditions.ts +718 -0
- package/src/engine/spells/effects.ts +203 -0
- package/src/engine/spells/fixtures/oracle-effects-lock9a.json +1 -0
- package/src/engine/spells/immunity.ts +942 -0
- package/src/engine/spells/intentEffects.ts +351 -0
- package/src/engine/spells/interpreter/GAPS.md +21 -0
- package/src/engine/spells/interpreter/amount.ts +109 -0
- package/src/engine/spells/interpreter/class-scripts-kit.ts +478 -0
- package/src/engine/spells/interpreter/diff.ts +246 -0
- package/src/engine/spells/interpreter/duration.ts +90 -0
- package/src/engine/spells/interpreter/form-attrs.json +1 -0
- package/src/engine/spells/interpreter/form-attrs.ts +95 -0
- package/src/engine/spells/interpreter/handlers/aura-remove.ts +113 -0
- package/src/engine/spells/interpreter/handlers/auras-control.ts +1547 -0
- package/src/engine/spells/interpreter/handlers/auras-core.ts +1583 -0
- package/src/engine/spells/interpreter/handlers/auras-misc.ts +1774 -0
- package/src/engine/spells/interpreter/handlers/effect-disenchant.ts +17 -0
- package/src/engine/spells/interpreter/handlers/effect-skinning.ts +20 -0
- package/src/engine/spells/interpreter/handlers/effects-action-utility.ts +1455 -0
- package/src/engine/spells/interpreter/handlers/effects-core.ts +432 -0
- package/src/engine/spells/interpreter/handlers/effects-remainder.ts +105 -0
- package/src/engine/spells/interpreter/handlers/effects-summon-item.ts +687 -0
- package/src/engine/spells/interpreter/handlers/mod-increase-energy-percent.ts +36 -0
- package/src/engine/spells/interpreter/handlers/mod-increase-energy.ts +36 -0
- package/src/engine/spells/interpreter/handlers/periodic-trigger-zero.ts +327 -0
- package/src/engine/spells/interpreter/handlers/procs.ts +131 -0
- package/src/engine/spells/interpreter/handlers/registry.ts +975 -0
- package/src/engine/spells/interpreter/handlers/shared.ts +667 -0
- package/src/engine/spells/interpreter/handlers.ts +9 -0
- package/src/engine/spells/interpreter/hunter-kit.ts +202 -0
- package/src/engine/spells/interpreter/index.ts +525 -0
- package/src/engine/spells/interpreter/item-triggers.ts +1121 -0
- package/src/engine/spells/interpreter/lift-consume-energy.ts +37 -0
- package/src/engine/spells/interpreter/mage-portals.ts +37 -0
- package/src/engine/spells/interpreter/materialize-charm.ts +88 -0
- package/src/engine/spells/interpreter/materialize-dispel-mechanic.ts +25 -0
- package/src/engine/spells/interpreter/materialize-rating-buffs.ts +81 -0
- package/src/engine/spells/interpreter/materialize-speed.ts +109 -0
- package/src/engine/spells/interpreter/materialize-spell-magnet.ts +22 -0
- package/src/engine/spells/interpreter/opcodes.ts +789 -0
- package/src/engine/spells/interpreter/paladin-kit.ts +95 -0
- package/src/engine/spells/interpreter/paladin-seals.ts +499 -0
- package/src/engine/spells/interpreter/priest-warlock-kit.ts +135 -0
- package/src/engine/spells/interpreter/proc.ts +443 -0
- package/src/engine/spells/interpreter/registry/helpers.ts +16 -0
- package/src/engine/spells/interpreter/registry/index.ts +216 -0
- package/src/engine/spells/interpreter/registry/packs/class-abilities.ts +304 -0
- package/src/engine/spells/interpreter/registry/packs/dire-maul.ts +44 -0
- package/src/engine/spells/interpreter/registry/packs/dummy-script-tail.ts +1065 -0
- package/src/engine/spells/interpreter/registry/packs/ex-wave-m.ts +70 -0
- package/src/engine/spells/interpreter/registry/packs/life-tap.ts +107 -0
- package/src/engine/spells/interpreter/registry/packs/paladin.ts +246 -0
- package/src/engine/spells/interpreter/registry/packs/raid-script-summons.ts +26 -0
- package/src/engine/spells/interpreter/registry/packs/raid-threat-wipe.ts +22 -0
- package/src/engine/spells/interpreter/registry/packs/shaman.ts +214 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-a.ts +269 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-b.ts +370 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-c.ts +172 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-hunter.ts +60 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-warrior.ts +31 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-a.ts +114 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-b.ts +249 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-c.ts +233 -0
- package/src/engine/spells/interpreter/registry/types.ts +48 -0
- package/src/engine/spells/interpreter/talent-scripted.ts +329 -0
- package/src/engine/spells/interpreter/targeting.ts +90 -0
- package/src/engine/spells/interpreter/types-intent.ts +1501 -0
- package/src/engine/spells/interpreter/types.ts +419 -0
- package/src/engine/spells/interpreter/warrior-druid-kit.ts +314 -0
- package/src/engine/spells/interpreter/weapon-imbue-kit.ts +384 -0
- package/src/engine/spells/interrupt.ts +217 -0
- package/src/engine/spells/meleeAttackReset.ts +60 -0
- package/src/engine/spells/oocTriggerSpell.ts +49 -0
- package/src/engine/spells/overrideClassScripts.ts +319 -0
- package/src/engine/spells/parryHaste.ts +20 -0
- package/src/engine/spells/procs.ts +300 -0
- package/src/engine/spells/recovery.ts +203 -0
- package/src/engine/spells/scriptTargetDest.ts +76 -0
- package/src/engine/spells/scriptTargets.ts +85 -0
- package/src/engine/spells/singleTargetAura.ts +82 -0
- package/src/engine/spells/specialCasePayloads.ts +57 -0
- package/src/engine/spells/spellAttributes.ts +249 -0
- package/src/engine/spells/spellBonus.ts +143 -0
- package/src/engine/spells/spellModIntents.ts +419 -0
- package/src/engine/spells/spellMods.ts +283 -0
- package/src/engine/spells/spellRadius.ts +15 -0
- package/src/engine/spells/trapObjects.ts +26 -0
- package/src/json.d.ts +7 -0
- package/src/persistence/memoryRepository.ts +23 -0
- package/src/persistence/saveBackups.ts +162 -0
- package/src/persistence/saveFile.ts +68 -0
- package/src/persistence/saveRepository.ts +11 -0
- package/src/persistence/saveShape.ts +668 -0
- package/src/progression/leveling.ts +89 -0
- package/src/progression/rest.ts +1434 -0
- package/src/progression/training.ts +226 -0
- package/src/progression/xpGain.ts +369 -0
- package/src/quests/quests.ts +1580 -0
- package/src/world/aoePull.ts +198 -0
- package/src/world/areaTableDbc.ts +39 -0
- package/src/world/areaTableMapIdPairs.ts +141 -0
- package/src/world/bakeSlug.ts +43 -0
- package/src/world/continuousDungeon.ts +1250 -0
- package/src/world/dungeonBakeProjection.ts +190 -0
- package/src/world/dungeonMapDensity.ts +342 -0
- package/src/world/dungeonMapProjection.ts +135 -0
- package/src/world/dungeonNav.ts +904 -0
- package/src/world/dungeonNpcSim.ts +147 -0
- package/src/world/dungeonObjectives.ts +234 -0
- package/src/world/dungeonPreset.ts +35 -0
- package/src/world/dungeonRoute.ts +311 -0
- package/src/world/dungeonRouteGeometry.ts +732 -0
- package/src/world/dungeonRouteOrder.ts +1037 -0
- package/src/world/dungeonWingRooms.ts +426 -0
- package/src/world/escort.ts +296 -0
- package/src/world/goPoolRoll.ts +75 -0
- package/src/world/instanceObjects.ts +785 -0
- package/src/world/instances.ts +1572 -0
- package/src/world/meetingStones.ts +56 -0
- package/src/world/navmeshFloors.ts +145 -0
- package/src/world/nearGrid.ts +167 -0
- package/src/world/packBudget.ts +613 -0
- package/src/world/rareRoll.ts +335 -0
- package/src/world/routeAuthoring.ts +132 -0
- package/src/world/routeEdit.ts +209 -0
- package/src/world/world.ts +812 -0
- package/src/world/zoneHazards.ts +47 -0
|
@@ -0,0 +1,1715 @@
|
|
|
1
|
+
// Detour navmesh router — real 3-D polygons, so a floor 60 yd below another in the same column is
|
|
2
|
+
// a different poly sharing no edge. Source: the cmangos mmap output. Pure and deterministic.
|
|
3
|
+
|
|
4
|
+
import { detHypot } from '../../engine/core/detMath.ts';
|
|
5
|
+
import { STOREY_DZ_YD } from './storey.ts';
|
|
6
|
+
import {
|
|
7
|
+
fillAllowMask,
|
|
8
|
+
roomAndRing,
|
|
9
|
+
roomGraphForMesh,
|
|
10
|
+
clearRoomGraphCache,
|
|
11
|
+
type RoomGraph,
|
|
12
|
+
} from './navRoomGraph.ts';
|
|
13
|
+
|
|
14
|
+
/** Poly area bits, MoveMapSharedDefines.h:55 (NavTerrainFlag). */
|
|
15
|
+
export const NAV_GROUND = 1;
|
|
16
|
+
export const NAV_GROUND_STEEP = 2;
|
|
17
|
+
export const NAV_WATER = 4;
|
|
18
|
+
export const NAV_MAGMA_SLIME = 8;
|
|
19
|
+
/** Runtime-only. Closed GO doors; not a mmap terrain bit. */
|
|
20
|
+
export const NAV_BLOCKED = 16;
|
|
21
|
+
|
|
22
|
+
/** Lava/slime is not a floor a party walks across; cost it out of every path. */
|
|
23
|
+
const MAGMA_COST_MUL = 40;
|
|
24
|
+
/** Swim is slower than run — same ratio nav.ts SWIM_STEP_COST uses. */
|
|
25
|
+
const WATER_COST_MUL = 7.0 / 4.722222;
|
|
26
|
+
|
|
27
|
+
/** Emitted path steps are subdivided to this length so every step carries a floor z. */
|
|
28
|
+
const STEP_YD = 4;
|
|
29
|
+
|
|
30
|
+
// How far outside a polygon a unit may stand. The mesh is eroded by walkableRadius 0.533 yd and
|
|
31
|
+
// bridged gaps reach 1.33 yd, so a walker held to the edge wedges mid-doorway.
|
|
32
|
+
const OFF_POLY_TOL_YD = 1.6;
|
|
33
|
+
|
|
34
|
+
// Floor this far from a hint is still the same floor. `isWalkableNavMesh` and `snapOntoNavMesh` both
|
|
35
|
+
// default to it; a storey is STOREY_DZ_YD 10 (storey.ts).
|
|
36
|
+
const MESH_STEP_DZ_YD = 4;
|
|
37
|
+
|
|
38
|
+
export type NavMeshArrays = {
|
|
39
|
+
mapId: number;
|
|
40
|
+
/** World (x, y, z) per polygon vertex. */
|
|
41
|
+
verts: Float32Array;
|
|
42
|
+
/** Prefix into corner arrays, length polyCount + 1. */
|
|
43
|
+
polyStart: Uint32Array;
|
|
44
|
+
cornerVert: Uint32Array;
|
|
45
|
+
/** Neighbour poly per corner edge, or -1 for a wall. */
|
|
46
|
+
cornerNei: Int32Array;
|
|
47
|
+
polyFlags: Uint8Array;
|
|
48
|
+
/** Detail sub-mesh prefixes, length polyCount + 1. */
|
|
49
|
+
dVStart: Uint32Array;
|
|
50
|
+
dTStart: Uint32Array;
|
|
51
|
+
dVerts: Float32Array;
|
|
52
|
+
/** 4 bytes per triangle: a, b, c, edgeFlags (Detour convention). */
|
|
53
|
+
dTris: Uint8Array;
|
|
54
|
+
// Extra links across recast's agent-radius erosion gaps, prefix-indexed by poly. `linkVia` is the
|
|
55
|
+
// source poly's corner whose edge acts as the portal. Measured by bake-navmesh bridgeComponents.
|
|
56
|
+
linkStart: Uint32Array;
|
|
57
|
+
linkTo: Int32Array;
|
|
58
|
+
linkVia: Uint32Array;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type NavMesh = NavMeshArrays & {
|
|
62
|
+
polyCount: number;
|
|
63
|
+
// Author cost multiplier per poly, or null until something stamps one; 1 is untouched. Without
|
|
64
|
+
// this field the operator's floor carves and the Rookery hazard were inert wherever a mesh loads.
|
|
65
|
+
authorCost: Float32Array | null;
|
|
66
|
+
/** minX, minY, minZ, maxX, maxY, maxZ. */
|
|
67
|
+
bounds: Float64Array;
|
|
68
|
+
/** minX, minY, maxX, maxY per poly. */
|
|
69
|
+
polyBBox: Float32Array;
|
|
70
|
+
polyCenter: Float32Array;
|
|
71
|
+
grid: {
|
|
72
|
+
cell: number;
|
|
73
|
+
nx: number;
|
|
74
|
+
ny: number;
|
|
75
|
+
ox: number;
|
|
76
|
+
oy: number;
|
|
77
|
+
start: Uint32Array;
|
|
78
|
+
items: Uint32Array;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type NavMeshPos = { x: number; y: number; z?: number };
|
|
83
|
+
|
|
84
|
+
const GRID_CELL_YD = 12;
|
|
85
|
+
|
|
86
|
+
/** Index the raw arrays for query: bboxes, centres, and a uniform poly grid. */
|
|
87
|
+
export function buildNavMesh(a: NavMeshArrays): NavMesh {
|
|
88
|
+
const polyCount = a.polyStart.length - 1;
|
|
89
|
+
const polyBBox = new Float32Array(polyCount * 4);
|
|
90
|
+
const polyCenter = new Float32Array(polyCount * 3);
|
|
91
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
92
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
93
|
+
|
|
94
|
+
for (let p = 0; p < polyCount; p++) {
|
|
95
|
+
const s = a.polyStart[p]!;
|
|
96
|
+
const e = a.polyStart[p + 1]!;
|
|
97
|
+
let bx0 = Infinity, by0 = Infinity, bx1 = -Infinity, by1 = -Infinity;
|
|
98
|
+
let cx = 0, cy = 0, cz = 0;
|
|
99
|
+
for (let i = s; i < e; i++) {
|
|
100
|
+
const v = a.cornerVert[i]! * 3;
|
|
101
|
+
const x = a.verts[v]!, y = a.verts[v + 1]!, z = a.verts[v + 2]!;
|
|
102
|
+
if (x < bx0) bx0 = x;
|
|
103
|
+
if (y < by0) by0 = y;
|
|
104
|
+
if (x > bx1) bx1 = x;
|
|
105
|
+
if (y > by1) by1 = y;
|
|
106
|
+
cx += x; cy += y; cz += z;
|
|
107
|
+
if (z < minZ) minZ = z;
|
|
108
|
+
if (z > maxZ) maxZ = z;
|
|
109
|
+
}
|
|
110
|
+
const n = Math.max(1, e - s);
|
|
111
|
+
polyBBox[p * 4] = bx0; polyBBox[p * 4 + 1] = by0;
|
|
112
|
+
polyBBox[p * 4 + 2] = bx1; polyBBox[p * 4 + 3] = by1;
|
|
113
|
+
polyCenter[p * 3] = cx / n; polyCenter[p * 3 + 1] = cy / n; polyCenter[p * 3 + 2] = cz / n;
|
|
114
|
+
if (bx0 < minX) minX = bx0;
|
|
115
|
+
if (by0 < minY) minY = by0;
|
|
116
|
+
if (bx1 > maxX) maxX = bx1;
|
|
117
|
+
if (by1 > maxY) maxY = by1;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const cell = GRID_CELL_YD;
|
|
121
|
+
const nx = Math.max(1, Math.ceil((maxX - minX) / cell) + 1);
|
|
122
|
+
const ny = Math.max(1, Math.ceil((maxY - minY) / cell) + 1);
|
|
123
|
+
const counts = new Uint32Array(nx * ny + 1);
|
|
124
|
+
const cellRange = (p: number) => {
|
|
125
|
+
const gx0 = Math.max(0, Math.floor((polyBBox[p * 4]! - minX) / cell));
|
|
126
|
+
const gy0 = Math.max(0, Math.floor((polyBBox[p * 4 + 1]! - minY) / cell));
|
|
127
|
+
const gx1 = Math.min(nx - 1, Math.floor((polyBBox[p * 4 + 2]! - minX) / cell));
|
|
128
|
+
const gy1 = Math.min(ny - 1, Math.floor((polyBBox[p * 4 + 3]! - minY) / cell));
|
|
129
|
+
return [gx0, gy0, gx1, gy1] as const;
|
|
130
|
+
};
|
|
131
|
+
for (let p = 0; p < polyCount; p++) {
|
|
132
|
+
const [gx0, gy0, gx1, gy1] = cellRange(p);
|
|
133
|
+
for (let gy = gy0; gy <= gy1; gy++) for (let gx = gx0; gx <= gx1; gx++) counts[gy * nx + gx + 1]!++;
|
|
134
|
+
}
|
|
135
|
+
for (let i = 1; i <= nx * ny; i++) counts[i]! += counts[i - 1]!;
|
|
136
|
+
const items = new Uint32Array(counts[nx * ny]!);
|
|
137
|
+
const cursor = counts.slice(0, nx * ny);
|
|
138
|
+
for (let p = 0; p < polyCount; p++) {
|
|
139
|
+
const [gx0, gy0, gx1, gy1] = cellRange(p);
|
|
140
|
+
for (let gy = gy0; gy <= gy1; gy++) {
|
|
141
|
+
for (let gx = gx0; gx <= gx1; gx++) {
|
|
142
|
+
items[cursor[gy * nx + gx]!++] = p;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
...a,
|
|
149
|
+
polyCount,
|
|
150
|
+
bounds: new Float64Array([minX, minY, minZ, maxX, maxY, maxZ]),
|
|
151
|
+
polyBBox,
|
|
152
|
+
polyCenter,
|
|
153
|
+
authorCost: null,
|
|
154
|
+
grid: { cell, nx, ny, ox: minX, oy: minY, start: counts, items },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function polyContains2D(m: NavMesh, p: number, x: number, y: number): boolean {
|
|
159
|
+
const s = m.polyStart[p]!;
|
|
160
|
+
const e = m.polyStart[p + 1]!;
|
|
161
|
+
let inside = false;
|
|
162
|
+
for (let i = s, j = e - 1; i < e; j = i++) {
|
|
163
|
+
const vi = m.cornerVert[i]! * 3;
|
|
164
|
+
const vj = m.cornerVert[j]! * 3;
|
|
165
|
+
const xi = m.verts[vi]!, yi = m.verts[vi + 1]!;
|
|
166
|
+
const xj = m.verts[vj]!, yj = m.verts[vj + 1]!;
|
|
167
|
+
if ((yi > y) !== (yj > y) && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) inside = !inside;
|
|
168
|
+
}
|
|
169
|
+
return inside;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Detail-mesh vertex k of poly p (Detour: index < vertCount means a poly corner). `cb` is the poly's
|
|
174
|
+
* corner base, `vc` its corner count, `db` its detail-vert base — the callers hoist all three.
|
|
175
|
+
*/
|
|
176
|
+
function detailVertAt(
|
|
177
|
+
m: NavMesh, cb: number, vc: number, db: number, k: number, out: Float32Array, o: number,
|
|
178
|
+
): void {
|
|
179
|
+
if (k < vc) {
|
|
180
|
+
const v = m.cornerVert[cb + k]! * 3;
|
|
181
|
+
out[o] = m.verts[v]!; out[o + 1] = m.verts[v + 1]!; out[o + 2] = m.verts[v + 2]!;
|
|
182
|
+
} else {
|
|
183
|
+
const v = (db + (k - vc)) * 3;
|
|
184
|
+
out[o] = m.dVerts[v]!; out[o + 1] = m.dVerts[v + 1]!; out[o + 2] = m.dVerts[v + 2]!;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const triScratch = new Float32Array(9);
|
|
189
|
+
|
|
190
|
+
/** Floor height inside a poly, from its detail triangles (Detour getPolyHeight). */
|
|
191
|
+
export function polyHeightAt(m: NavMesh, p: number, x: number, y: number): number {
|
|
192
|
+
const t0 = m.dTStart[p]!;
|
|
193
|
+
const t1 = m.dTStart[p + 1]!;
|
|
194
|
+
const cb = m.polyStart[p]!;
|
|
195
|
+
const vc = m.polyStart[p + 1]! - cb;
|
|
196
|
+
const db = m.dVStart[p]!;
|
|
197
|
+
let bestZ = Number.NaN;
|
|
198
|
+
let bestD = Infinity;
|
|
199
|
+
for (let t = t0; t < t1; t++) {
|
|
200
|
+
detailVertAt(m, cb, vc, db, m.dTris[t * 4]!, triScratch, 0);
|
|
201
|
+
detailVertAt(m, cb, vc, db, m.dTris[t * 4 + 1]!, triScratch, 3);
|
|
202
|
+
detailVertAt(m, cb, vc, db, m.dTris[t * 4 + 2]!, triScratch, 6);
|
|
203
|
+
const ax = triScratch[0]!, ay = triScratch[1]!;
|
|
204
|
+
const v0x = triScratch[3]! - ax, v0y = triScratch[4]! - ay;
|
|
205
|
+
const v1x = triScratch[6]! - ax, v1y = triScratch[7]! - ay;
|
|
206
|
+
const v2x = x - ax, v2y = y - ay;
|
|
207
|
+
const den = v0x * v1y - v1x * v0y;
|
|
208
|
+
if (Math.abs(den) < 1e-9) continue;
|
|
209
|
+
let u = (v2x * v1y - v1x * v2y) / den;
|
|
210
|
+
let v = (v0x * v2y - v2x * v0y) / den;
|
|
211
|
+
const outside = Math.max(0, -u) + Math.max(0, -v) + Math.max(0, u + v - 1);
|
|
212
|
+
if (outside < bestD) {
|
|
213
|
+
u = Math.min(1, Math.max(0, u));
|
|
214
|
+
v = Math.min(1, Math.max(0, v));
|
|
215
|
+
if (u + v > 1) { const s = u + v; u /= s; v /= s; }
|
|
216
|
+
bestD = outside;
|
|
217
|
+
bestZ = triScratch[2]! + u * (triScratch[5]! - triScratch[2]!) + v * (triScratch[8]! - triScratch[2]!);
|
|
218
|
+
if (outside === 0) return bestZ;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (Number.isFinite(bestZ)) return bestZ;
|
|
222
|
+
return m.polyCenter[p * 3 + 2]!;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Closest point on a poly's detail triangles (clamped barycentric). */
|
|
226
|
+
function closestOnPoly(m: NavMesh, p: number, x: number, y: number): { x: number; y: number; z: number } {
|
|
227
|
+
const t0 = m.dTStart[p]!;
|
|
228
|
+
const t1 = m.dTStart[p + 1]!;
|
|
229
|
+
const cb = m.polyStart[p]!;
|
|
230
|
+
const vc = m.polyStart[p + 1]! - cb;
|
|
231
|
+
const db = m.dVStart[p]!;
|
|
232
|
+
let bestX = x;
|
|
233
|
+
let bestY = y;
|
|
234
|
+
let bestZ = Number.NaN;
|
|
235
|
+
let bestD = Infinity;
|
|
236
|
+
for (let t = t0; t < t1; t++) {
|
|
237
|
+
detailVertAt(m, cb, vc, db, m.dTris[t * 4]!, triScratch, 0);
|
|
238
|
+
detailVertAt(m, cb, vc, db, m.dTris[t * 4 + 1]!, triScratch, 3);
|
|
239
|
+
detailVertAt(m, cb, vc, db, m.dTris[t * 4 + 2]!, triScratch, 6);
|
|
240
|
+
const ax = triScratch[0]!, ay = triScratch[1]!;
|
|
241
|
+
const v0x = triScratch[3]! - ax, v0y = triScratch[4]! - ay;
|
|
242
|
+
const v1x = triScratch[6]! - ax, v1y = triScratch[7]! - ay;
|
|
243
|
+
const v2x = x - ax, v2y = y - ay;
|
|
244
|
+
const den = v0x * v1y - v1x * v0y;
|
|
245
|
+
if (Math.abs(den) < 1e-9) continue;
|
|
246
|
+
let u = (v2x * v1y - v1x * v2y) / den;
|
|
247
|
+
let v = (v0x * v2y - v2x * v0y) / den;
|
|
248
|
+
const outside = Math.max(0, -u) + Math.max(0, -v) + Math.max(0, u + v - 1);
|
|
249
|
+
if (outside < bestD) {
|
|
250
|
+
u = Math.min(1, Math.max(0, u));
|
|
251
|
+
v = Math.min(1, Math.max(0, v));
|
|
252
|
+
if (u + v > 1) { const s = u + v; u /= s; v /= s; }
|
|
253
|
+
bestD = outside;
|
|
254
|
+
bestX = ax + u * v0x + v * v1x;
|
|
255
|
+
bestY = ay + u * v0y + v * v1y;
|
|
256
|
+
bestZ = triScratch[2]! + u * (triScratch[5]! - triScratch[2]!) + v * (triScratch[8]! - triScratch[2]!);
|
|
257
|
+
if (outside === 0) return { x, y, z: bestZ };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (Number.isFinite(bestZ)) return { x: bestX, y: bestY, z: bestZ };
|
|
261
|
+
return { x: m.polyCenter[p * 3]!, y: m.polyCenter[p * 3 + 1]!, z: m.polyCenter[p * 3 + 2]! };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function forEachNearbyPoly(
|
|
265
|
+
m: NavMesh,
|
|
266
|
+
x: number,
|
|
267
|
+
y: number,
|
|
268
|
+
radius: number,
|
|
269
|
+
visit: (p: number) => void,
|
|
270
|
+
): void {
|
|
271
|
+
const { cell, nx, ny, ox, oy, start, items } = m.grid;
|
|
272
|
+
const gx0 = Math.max(0, Math.floor((x - radius - ox) / cell));
|
|
273
|
+
const gy0 = Math.max(0, Math.floor((y - radius - oy) / cell));
|
|
274
|
+
const gx1 = Math.min(nx - 1, Math.floor((x + radius - ox) / cell));
|
|
275
|
+
const gy1 = Math.min(ny - 1, Math.floor((y + radius - oy) / cell));
|
|
276
|
+
for (let gy = gy0; gy <= gy1; gy++) {
|
|
277
|
+
for (let gx = gx0; gx <= gx1; gx++) {
|
|
278
|
+
const c = gy * nx + gx;
|
|
279
|
+
for (let i = start[c]!; i < start[c + 1]!; i++) visit(items[i]!);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function distToPolyBBox(m: NavMesh, p: number, x: number, y: number): number {
|
|
285
|
+
const dx = Math.max(m.polyBBox[p * 4]! - x, 0, x - m.polyBBox[p * 4 + 2]!);
|
|
286
|
+
const dy = Math.max(m.polyBBox[p * 4 + 1]! - y, 0, y - m.polyBBox[p * 4 + 3]!);
|
|
287
|
+
return detHypot(dx, dy);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Exact 3-D length — `Math.hypot` is implementation-approximated (detMath.ts). */
|
|
291
|
+
function dist3(dx: number, dy: number, dz: number): number {
|
|
292
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export type PolyHit = { poly: number; z: number; dist: number };
|
|
296
|
+
|
|
297
|
+
// Nearest polygon to a point. With a z hint this is the whole reason the module exists: two floors of one
|
|
298
|
+
// column are separate polys, so the hint picks the floor instead of a height byte that averaged both.
|
|
299
|
+
export function findNearestPoly(
|
|
300
|
+
m: NavMesh,
|
|
301
|
+
x: number,
|
|
302
|
+
y: number,
|
|
303
|
+
z?: number,
|
|
304
|
+
radius = 6,
|
|
305
|
+
/** When set with `z`, ignore floors further than this (dest snap must not pick the storey below). */
|
|
306
|
+
maxDz?: number,
|
|
307
|
+
): PolyHit | null {
|
|
308
|
+
let best: PolyHit | null = null;
|
|
309
|
+
let bestScore = Infinity;
|
|
310
|
+
forEachNearbyPoly(m, x, y, radius, (p) => {
|
|
311
|
+
const flags = m.polyFlags[p]!;
|
|
312
|
+
if (flags & NAV_MAGMA_SLIME) return;
|
|
313
|
+
// polyBBox is the exact min/max of the corner verts polyContains2D walks, so containment
|
|
314
|
+
// implies d === 0; the ring walk is left for the blocked poly a unit may be standing in.
|
|
315
|
+
const d = distToPolyBBox(m, p, x, y);
|
|
316
|
+
if (d > radius) return;
|
|
317
|
+
// dz is never negative, so a candidate at d*4 >= bestScore cannot win and does not need
|
|
318
|
+
// its height: polyHeightAt walks every detail triangle in the poly (3.7% of live JS).
|
|
319
|
+
if (d * 4 >= bestScore) return;
|
|
320
|
+
if ((flags & NAV_BLOCKED) && (d > 0 || !polyContains2D(m, p, x, y))) return;
|
|
321
|
+
const h = polyHeightAt(m, p, x, y);
|
|
322
|
+
const dz = z == null ? 0 : Math.abs(h - z);
|
|
323
|
+
if (maxDz != null && z != null && Number.isFinite(z) && dz > maxDz) return;
|
|
324
|
+
// Plan distance dominates; z breaks the tie between stacked floors.
|
|
325
|
+
const score = d * 4 + dz;
|
|
326
|
+
if (score < bestScore) {
|
|
327
|
+
bestScore = score;
|
|
328
|
+
best = { poly: p, z: h, dist: d };
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
return best;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Floor z at a point, choosing the floor nearest `zHint` when columns stack. */
|
|
335
|
+
export function heightAtNavMesh(m: NavMesh, x: number, y: number, zHint?: number): number | null {
|
|
336
|
+
const hit = findNearestPoly(m, x, y, zHint, 4);
|
|
337
|
+
return hit ? hit.z : null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Floor z at a point for a mover standing at `fromZ`, refusing a hint floor the standing floor
|
|
341
|
+
// does not CONNECT to. Judge adjacency, not endpoint z. Null means no floor near fromZ here.
|
|
342
|
+
export function connectedFloorZNavMesh(
|
|
343
|
+
m: NavMesh,
|
|
344
|
+
x: number,
|
|
345
|
+
y: number,
|
|
346
|
+
fromZ: number,
|
|
347
|
+
hintZ?: number,
|
|
348
|
+
radius = 4,
|
|
349
|
+
): number | null {
|
|
350
|
+
const stand = findNearestPoly(m, x, y, fromZ, radius);
|
|
351
|
+
if (!stand) return null;
|
|
352
|
+
if (hintZ == null || Math.abs(stand.z - hintZ) <= 0.001) return stand.z;
|
|
353
|
+
const hinted = findNearestPoly(m, x, y, hintZ, radius);
|
|
354
|
+
if (!hinted || hinted.poly === stand.poly) return stand.z;
|
|
355
|
+
return polyWithinHops(m, stand.poly, hinted.poly, STEP_HOPS) ? hinted.z : stand.z;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Is there a real floor under this point (within `tol` of the hint when given)? */
|
|
359
|
+
export function isWalkableNavMesh(m: NavMesh, x: number, y: number, zHint?: number, tol = 4): boolean {
|
|
360
|
+
const hit = findNearestPoly(m, x, y, zHint, 2);
|
|
361
|
+
if (!hit || hit.dist > 0.75) return false;
|
|
362
|
+
return zHint == null || Math.abs(hit.z - zHint) <= tol;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Is the floor under this point swim water? Same gates as `isWalkableNavMesh` plus the terrain bit
|
|
366
|
+
// `polyCost` already charges `WATER_COST_MUL` for (MoveMapSharedDefines.h:55).
|
|
367
|
+
export function isSwimNavMesh(m: NavMesh, x: number, y: number, zHint?: number, tol = 4): boolean {
|
|
368
|
+
const hit = findNearestPoly(m, x, y, zHint, 2);
|
|
369
|
+
if (!hit || hit.dist > 0.75) return false;
|
|
370
|
+
if (zHint != null && Math.abs(hit.z - zHint) > tol) return false;
|
|
371
|
+
return (m.polyFlags[hit.poly]! & NAV_WATER) !== 0;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Land ON the mesh: the same XY when already on a poly, else the closest point on the nearest
|
|
375
|
+
// one. Null when nothing is in `radius` — a move dest must not keep a void XY.
|
|
376
|
+
export function snapOntoNavMesh(
|
|
377
|
+
m: NavMesh,
|
|
378
|
+
x: number,
|
|
379
|
+
y: number,
|
|
380
|
+
zHint?: number,
|
|
381
|
+
radius = 4,
|
|
382
|
+
maxDz?: number,
|
|
383
|
+
): { x: number; y: number; z: number } | null {
|
|
384
|
+
const hit = findNearestPoly(m, x, y, zHint, radius, maxDz);
|
|
385
|
+
if (!hit) return null;
|
|
386
|
+
if (zHint != null && Number.isFinite(zHint) && Math.abs(hit.z - zHint) > (maxDz ?? 4)) return null;
|
|
387
|
+
// Bbox dist is 0 inside the AABB of a convex poly, which is still void in the corners.
|
|
388
|
+
// Closest-point dist is the one that means "on the floor".
|
|
389
|
+
const on = closestOnPoly(m, hit.poly, x, y);
|
|
390
|
+
if (detHypot(on.x - x, on.y - y) <= 0.75) return { x, y, z: on.z };
|
|
391
|
+
return on;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Poly -> connected-component id, links included, cached per mesh like the room graph. */
|
|
395
|
+
const meshComponents = new WeakMap<NavMesh, Int32Array>();
|
|
396
|
+
function componentsOf(m: NavMesh): Int32Array {
|
|
397
|
+
const had = meshComponents.get(m);
|
|
398
|
+
if (had) return had;
|
|
399
|
+
const comp = new Int32Array(m.polyCount).fill(-1);
|
|
400
|
+
const stack: number[] = [];
|
|
401
|
+
let next = 0;
|
|
402
|
+
for (let seed = 0; seed < m.polyCount; seed++) {
|
|
403
|
+
if (comp[seed] !== -1) continue;
|
|
404
|
+
const id = next++;
|
|
405
|
+
comp[seed] = id;
|
|
406
|
+
stack.length = 0;
|
|
407
|
+
stack.push(seed);
|
|
408
|
+
while (stack.length) {
|
|
409
|
+
const p = stack.pop()!;
|
|
410
|
+
for (let i = m.polyStart[p]!; i < m.polyStart[p + 1]!; i++) {
|
|
411
|
+
const q = m.cornerNei[i]!;
|
|
412
|
+
if (q >= 0 && comp[q] === -1) { comp[q] = id; stack.push(q); }
|
|
413
|
+
}
|
|
414
|
+
for (let l = m.linkStart[p]!; l < m.linkStart[p + 1]!; l++) {
|
|
415
|
+
const q = m.linkTo[l]!;
|
|
416
|
+
if (q >= 0 && comp[q] === -1) { comp[q] = id; stack.push(q); }
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
meshComponents.set(m, comp);
|
|
421
|
+
return comp;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** How far under a perched boss a stand may sit: Gothik's balcony is 18.1 yd over the fight floor. */
|
|
425
|
+
const STAND_DROP_MAX_YD = 25;
|
|
426
|
+
|
|
427
|
+
/** Do these two points sit on one connected component (bake links included)? */
|
|
428
|
+
export function reachableOnMesh(m: NavMesh, from: NavMeshPos, to: NavMeshPos): boolean {
|
|
429
|
+
const a = findNearestPoly(m, from.x, from.y, from.z, 8) ?? findNearestPoly(m, from.x, from.y, from.z, 20);
|
|
430
|
+
const b = findNearestPoly(m, to.x, to.y, to.z, 8) ?? findNearestPoly(m, to.x, to.y, to.z, 20);
|
|
431
|
+
if (!a || !b) return false;
|
|
432
|
+
const comp = componentsOf(m);
|
|
433
|
+
return comp[a.poly] === comp[b.poly];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Stand for a dest `from` cannot reach: Gothik's balcony is 14 polys, 18.1 yd over the fight floor.
|
|
437
|
+
* Null when the dest is already reachable, or nothing reachable is within `radius` (Frostwyrm Lair). */
|
|
438
|
+
export function reachableStandNavMesh(
|
|
439
|
+
m: NavMesh,
|
|
440
|
+
from: NavMeshPos,
|
|
441
|
+
to: NavMeshPos,
|
|
442
|
+
radius = 12,
|
|
443
|
+
): { x: number; y: number; z: number } | null {
|
|
444
|
+
const a = findNearestPoly(m, from.x, from.y, from.z, 8);
|
|
445
|
+
const b = findNearestPoly(m, to.x, to.y, to.z, 8) ?? findNearestPoly(m, to.x, to.y, to.z, 20);
|
|
446
|
+
if (!a || !b) return null;
|
|
447
|
+
const comp = componentsOf(m);
|
|
448
|
+
if (comp[a.poly] === comp[b.poly]) return null;
|
|
449
|
+
let best: PolyHit | null = null;
|
|
450
|
+
let bestScore = Infinity;
|
|
451
|
+
forEachNearbyPoly(m, to.x, to.y, radius, (p) => {
|
|
452
|
+
if (comp[p] !== comp[a.poly]) return;
|
|
453
|
+
const flags = m.polyFlags[p]!;
|
|
454
|
+
if (flags & (NAV_MAGMA_SLIME | NAV_BLOCKED)) return;
|
|
455
|
+
const d = distToPolyBBox(m, p, to.x, to.y);
|
|
456
|
+
if (d > radius || d * 4 >= bestScore) return;
|
|
457
|
+
const z = polyHeightAt(m, p, to.x, to.y);
|
|
458
|
+
const dz = to.z != null && Number.isFinite(to.z) ? Math.abs(z - to.z) : 0;
|
|
459
|
+
// A stand is floor the party fights the boss FROM, so it stays near him in z as well as in
|
|
460
|
+
// plan. Plan-only picked whatever lay under the spawn: stratholme bosses moved 110 yd down.
|
|
461
|
+
if (dz > STAND_DROP_MAX_YD) return;
|
|
462
|
+
const score = d * 4 + dz;
|
|
463
|
+
if (score >= bestScore) return;
|
|
464
|
+
bestScore = score;
|
|
465
|
+
best = { poly: p, z, dist: d };
|
|
466
|
+
});
|
|
467
|
+
if (!best) return null;
|
|
468
|
+
const on = closestOnPoly(m, (best as PolyHit).poly, to.x, to.y);
|
|
469
|
+
return on;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Can I walk the straight line on the mesh without leaving the standing floor? Sample the line:
|
|
473
|
+
// every sample must hit a poly locally connected to the last, so another storey is not a hop.
|
|
474
|
+
export function walkLosClearNavMesh(
|
|
475
|
+
m: NavMesh,
|
|
476
|
+
from: NavMeshPos,
|
|
477
|
+
to: NavMeshPos,
|
|
478
|
+
/** Sample must sit this close to a poly. Hop uses ~0.55 so a thin wall is a wall. */
|
|
479
|
+
onPolyYd = OFF_POLY_TOL_YD,
|
|
480
|
+
): boolean {
|
|
481
|
+
const a = findNearestPoly(m, from.x, from.y, from.z, OFF_POLY_TOL_YD + 0.5);
|
|
482
|
+
if (!a || a.dist > OFF_POLY_TOL_YD) return false;
|
|
483
|
+
const b = findNearestPoly(m, to.x, to.y, to.z ?? from.z, OFF_POLY_TOL_YD + 0.5);
|
|
484
|
+
if (!b || b.dist > OFF_POLY_TOL_YD) return false;
|
|
485
|
+
if (a.poly !== b.poly && !polyWithinHops(m, a.poly, b.poly, STEP_HOPS)) return false;
|
|
486
|
+
const dist = detHypot(to.x - from.x, to.y - from.y);
|
|
487
|
+
if (!(dist > 1e-9)) return true;
|
|
488
|
+
// 0.2 yd — a Stockade wall cell is 0.45 yd; 0.5 yd samples skipped it and the
|
|
489
|
+
// 12 yd hop crawled the lip (probe-los-pathing with mesh: 391 hops, 5.7 yd short).
|
|
490
|
+
const n = Math.max(2, Math.ceil(dist / 0.2));
|
|
491
|
+
let z = from.z ?? a.z;
|
|
492
|
+
let prev = a.poly;
|
|
493
|
+
for (let i = 1; i <= n; i++) {
|
|
494
|
+
const t = i / n;
|
|
495
|
+
const x = from.x + (to.x - from.x) * t;
|
|
496
|
+
const y = from.y + (to.y - from.y) * t;
|
|
497
|
+
// Most samples are still inside the poly the last one was on, and re-running the grid search
|
|
498
|
+
// to be told that was 81% of live findNearestPoly time. A sample that left prev falls through.
|
|
499
|
+
const hit = polyContains2D(m, prev, x, y)
|
|
500
|
+
? { poly: prev, z: polyHeightAt(m, prev, x, y), dist: 0 }
|
|
501
|
+
: findNearestPoly(m, x, y, z, OFF_POLY_TOL_YD + 0.5);
|
|
502
|
+
if (!hit || hit.dist > onPolyYd) return false;
|
|
503
|
+
if (hit.poly !== prev && !polyWithinHops(m, prev, hit.poly, STEP_HOPS)) return false;
|
|
504
|
+
z = hit.z;
|
|
505
|
+
prev = hit.poly;
|
|
506
|
+
}
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// One walkable surface rises from `from` to `to` — sight up a ramp or a stairway. It is
|
|
511
|
+
// walkLosClearNavMesh plus one term: floor within dzTol of the straight-line interpolation.
|
|
512
|
+
export function rampSightNavMesh(
|
|
513
|
+
m: NavMesh,
|
|
514
|
+
from: NavMeshPos,
|
|
515
|
+
to: NavMeshPos,
|
|
516
|
+
dzTol = MESH_STEP_DZ_YD,
|
|
517
|
+
onPolyYd = OFF_POLY_TOL_YD,
|
|
518
|
+
): boolean {
|
|
519
|
+
const a = findNearestPoly(m, from.x, from.y, from.z, OFF_POLY_TOL_YD + 0.5);
|
|
520
|
+
if (!a || a.dist > OFF_POLY_TOL_YD) return false;
|
|
521
|
+
const b = findNearestPoly(m, to.x, to.y, to.z ?? from.z, OFF_POLY_TOL_YD + 0.5);
|
|
522
|
+
if (!b || b.dist > OFF_POLY_TOL_YD) return false;
|
|
523
|
+
if (a.poly === b.poly) return true;
|
|
524
|
+
const dist = detHypot(to.x - from.x, to.y - from.y);
|
|
525
|
+
// Same plan column, two floors: there is no line to sample, so the z gap is the answer.
|
|
526
|
+
if (!(dist > 1e-9)) return Math.abs(b.z - a.z) <= dzTol;
|
|
527
|
+
const n = Math.max(2, Math.ceil(dist / 0.2));
|
|
528
|
+
let prev = a.poly;
|
|
529
|
+
for (let i = 1; i <= n; i++) {
|
|
530
|
+
const t = i / n;
|
|
531
|
+
const x = from.x + (to.x - from.x) * t;
|
|
532
|
+
const y = from.y + (to.y - from.y) * t;
|
|
533
|
+
const want = a.z + (b.z - a.z) * t;
|
|
534
|
+
const hit = polyContains2D(m, prev, x, y)
|
|
535
|
+
? { poly: prev, z: polyHeightAt(m, prev, x, y), dist: 0 }
|
|
536
|
+
: findNearestPoly(m, x, y, want, OFF_POLY_TOL_YD + 0.5);
|
|
537
|
+
if (!hit || hit.dist > onPolyYd) return false;
|
|
538
|
+
if (Math.abs(hit.z - want) > dzTol) return false;
|
|
539
|
+
if (hit.poly !== prev && !polyWithinHops(m, prev, hit.poly, STEP_HOPS)) return false;
|
|
540
|
+
prev = hit.poly;
|
|
541
|
+
}
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// A* over polygons
|
|
546
|
+
|
|
547
|
+
class MinHeap {
|
|
548
|
+
private keys: Float64Array;
|
|
549
|
+
private vals: Int32Array;
|
|
550
|
+
private n = 0;
|
|
551
|
+
constructor(cap: number) {
|
|
552
|
+
this.keys = new Float64Array(cap);
|
|
553
|
+
this.vals = new Int32Array(cap);
|
|
554
|
+
}
|
|
555
|
+
get size(): number { return this.n; }
|
|
556
|
+
clear(): void { this.n = 0; }
|
|
557
|
+
push(key: number, val: number): void {
|
|
558
|
+
if (this.n === this.keys.length) {
|
|
559
|
+
const k = new Float64Array(this.n * 2); k.set(this.keys); this.keys = k;
|
|
560
|
+
const v = new Int32Array(this.n * 2); v.set(this.vals); this.vals = v;
|
|
561
|
+
}
|
|
562
|
+
let i = this.n++;
|
|
563
|
+
this.keys[i] = key; this.vals[i] = val;
|
|
564
|
+
while (i > 0) {
|
|
565
|
+
const par = (i - 1) >> 1;
|
|
566
|
+
if (this.keys[par]! <= this.keys[i]!) break;
|
|
567
|
+
const tk = this.keys[par]!, tv = this.vals[par]!;
|
|
568
|
+
this.keys[par] = this.keys[i]!; this.vals[par] = this.vals[i]!;
|
|
569
|
+
this.keys[i] = tk; this.vals[i] = tv;
|
|
570
|
+
i = par;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
pop(): number {
|
|
574
|
+
const top = this.vals[0]!;
|
|
575
|
+
this.n--;
|
|
576
|
+
if (this.n > 0) {
|
|
577
|
+
this.keys[0] = this.keys[this.n]!; this.vals[0] = this.vals[this.n]!;
|
|
578
|
+
let i = 0;
|
|
579
|
+
for (;;) {
|
|
580
|
+
const l = 2 * i + 1, r = l + 1;
|
|
581
|
+
let s = i;
|
|
582
|
+
if (l < this.n && this.keys[l]! < this.keys[s]!) s = l;
|
|
583
|
+
if (r < this.n && this.keys[r]! < this.keys[s]!) s = r;
|
|
584
|
+
if (s === i) break;
|
|
585
|
+
const tk = this.keys[s]!, tv = this.vals[s]!;
|
|
586
|
+
this.keys[s] = this.keys[i]!; this.vals[s] = this.vals[i]!;
|
|
587
|
+
this.keys[i] = tk; this.vals[i] = tv;
|
|
588
|
+
i = s;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return top;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Reused A* working set. A per-search Float64Array(polyCount).fill(Infinity) on an 11.5k-poly mesh
|
|
596
|
+
// was the sim lagspike bill — three searches a tick. Generation stamps touch only visited nodes.
|
|
597
|
+
let polyPathCap = 0;
|
|
598
|
+
let polyPathG: Float64Array | null = null;
|
|
599
|
+
let polyPathGStamp: Int32Array | null = null;
|
|
600
|
+
let polyPathParent: Int32Array | null = null;
|
|
601
|
+
let polyPathClosed: Int32Array | null = null;
|
|
602
|
+
let polyPathHeap: MinHeap | null = null;
|
|
603
|
+
let polyPathGen = 1;
|
|
604
|
+
|
|
605
|
+
function ensurePolyPathScratch(n: number): void {
|
|
606
|
+
if (polyPathCap >= n && polyPathG) return;
|
|
607
|
+
polyPathCap = n;
|
|
608
|
+
polyPathG = new Float64Array(n);
|
|
609
|
+
polyPathGStamp = new Int32Array(n);
|
|
610
|
+
polyPathParent = new Int32Array(n);
|
|
611
|
+
polyPathClosed = new Int32Array(n);
|
|
612
|
+
polyPathHeap = new MinHeap(Math.min(4096, Math.max(1024, n >> 2)));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** Shared edge between two adjacent polys, as its two endpoint vertex indices. */
|
|
616
|
+
function sharedEdge(m: NavMesh, from: number, to: number): [number, number] | null {
|
|
617
|
+
const s = m.polyStart[from]!;
|
|
618
|
+
const e = m.polyStart[from + 1]!;
|
|
619
|
+
for (let i = s; i < e; i++) {
|
|
620
|
+
if (m.cornerNei[i] !== to) continue;
|
|
621
|
+
const a = m.cornerVert[i]!;
|
|
622
|
+
const b = m.cornerVert[i + 1 < e ? i + 1 : s]!;
|
|
623
|
+
return [a, b];
|
|
624
|
+
}
|
|
625
|
+
for (let l = m.linkStart[from]!; l < m.linkStart[from + 1]!; l++) {
|
|
626
|
+
if (m.linkTo[l] !== to) continue;
|
|
627
|
+
const i = m.linkVia[l]!;
|
|
628
|
+
return [m.cornerVert[i]!, m.cornerVert[i + 1 < e ? i + 1 : s]!];
|
|
629
|
+
}
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function polyCost(m: NavMesh, p: number): number {
|
|
634
|
+
const f = m.polyFlags[p]!;
|
|
635
|
+
if (f & NAV_BLOCKED) return 1e6;
|
|
636
|
+
// Author cost multiplies the terrain cost rather than replacing it: an operator lane drawn
|
|
637
|
+
// through water is still slower than the same lane on dry floor.
|
|
638
|
+
const author = m.authorCost ? m.authorCost[p]! : 1;
|
|
639
|
+
if (f & NAV_MAGMA_SLIME) return MAGMA_COST_MUL * author;
|
|
640
|
+
if (f & NAV_WATER) return WATER_COST_MUL * author;
|
|
641
|
+
return author;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** Poly corridor from `startPoly` to `endPoly`, or null. */
|
|
645
|
+
function findPolyPath(
|
|
646
|
+
m: NavMesh,
|
|
647
|
+
startPoly: number,
|
|
648
|
+
endPoly: number,
|
|
649
|
+
maxVisited = 200_000,
|
|
650
|
+
allow?: Uint8Array | null,
|
|
651
|
+
stats?: { visited: number; capped?: boolean },
|
|
652
|
+
): number[] | null {
|
|
653
|
+
if (startPoly === endPoly) {
|
|
654
|
+
if (stats) stats.visited = 0;
|
|
655
|
+
return [startPoly];
|
|
656
|
+
}
|
|
657
|
+
ensurePolyPathScratch(m.polyCount);
|
|
658
|
+
// Wrap gen before stamp collision; zero stamps so a fresh gen is unambiguous.
|
|
659
|
+
if (polyPathGen >= 0x7ffffffe) {
|
|
660
|
+
polyPathGStamp!.fill(0);
|
|
661
|
+
polyPathClosed!.fill(0);
|
|
662
|
+
polyPathGen = 1;
|
|
663
|
+
}
|
|
664
|
+
const gen = ++polyPathGen;
|
|
665
|
+
const g = polyPathG!;
|
|
666
|
+
const gStamp = polyPathGStamp!;
|
|
667
|
+
const parent = polyPathParent!;
|
|
668
|
+
const closed = polyPathClosed!;
|
|
669
|
+
const heap = polyPathHeap!;
|
|
670
|
+
heap.clear();
|
|
671
|
+
const ex = m.polyCenter[endPoly * 3]!, ey = m.polyCenter[endPoly * 3 + 1]!, ez = m.polyCenter[endPoly * 3 + 2]!;
|
|
672
|
+
const h = (p: number) => dist3(
|
|
673
|
+
m.polyCenter[p * 3]! - ex,
|
|
674
|
+
m.polyCenter[p * 3 + 1]! - ey,
|
|
675
|
+
m.polyCenter[p * 3 + 2]! - ez,
|
|
676
|
+
);
|
|
677
|
+
const gOf = (p: number): number => (gStamp[p] === gen ? g[p]! : Infinity);
|
|
678
|
+
g[startPoly] = 0;
|
|
679
|
+
gStamp[startPoly] = gen;
|
|
680
|
+
parent[startPoly] = -1;
|
|
681
|
+
heap.push(h(startPoly), startPoly);
|
|
682
|
+
let visited = 0;
|
|
683
|
+
while (heap.size > 0) {
|
|
684
|
+
const cur = heap.pop();
|
|
685
|
+
if (closed[cur] === gen) continue;
|
|
686
|
+
closed[cur] = gen;
|
|
687
|
+
if (cur === endPoly) break;
|
|
688
|
+
if (++visited > maxVisited) {
|
|
689
|
+
// Out of budget, NOT out of mesh. The caller has to be able to tell them apart.
|
|
690
|
+
if (stats) {
|
|
691
|
+
stats.visited = visited;
|
|
692
|
+
stats.capped = true;
|
|
693
|
+
}
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
const gCur = gOf(cur);
|
|
697
|
+
const relax = (nb: number) => {
|
|
698
|
+
if (nb < 0 || closed[nb] === gen) return;
|
|
699
|
+
if (m.polyFlags[nb]! & NAV_BLOCKED) return;
|
|
700
|
+
if (allow && allow[nb] !== 1) return;
|
|
701
|
+
const step = dist3(
|
|
702
|
+
m.polyCenter[nb * 3]! - m.polyCenter[cur * 3]!,
|
|
703
|
+
m.polyCenter[nb * 3 + 1]! - m.polyCenter[cur * 3 + 1]!,
|
|
704
|
+
m.polyCenter[nb * 3 + 2]! - m.polyCenter[cur * 3 + 2]!,
|
|
705
|
+
) * polyCost(m, nb);
|
|
706
|
+
const ng = gCur + step;
|
|
707
|
+
if (ng + 1e-6 >= gOf(nb)) return;
|
|
708
|
+
g[nb] = ng;
|
|
709
|
+
gStamp[nb] = gen;
|
|
710
|
+
parent[nb] = cur;
|
|
711
|
+
heap.push(ng + h(nb), nb);
|
|
712
|
+
};
|
|
713
|
+
for (let i = m.polyStart[cur]!; i < m.polyStart[cur + 1]!; i++) relax(m.cornerNei[i]!);
|
|
714
|
+
for (let l = m.linkStart[cur]!; l < m.linkStart[cur + 1]!; l++) relax(m.linkTo[l]!);
|
|
715
|
+
}
|
|
716
|
+
if (stats) stats.visited = visited;
|
|
717
|
+
if (closed[endPoly] !== gen) return null;
|
|
718
|
+
const out: number[] = [];
|
|
719
|
+
for (let p = endPoly; p !== -1; p = parent[p]!) {
|
|
720
|
+
out.push(p);
|
|
721
|
+
if (p === startPoly) break;
|
|
722
|
+
}
|
|
723
|
+
out.reverse();
|
|
724
|
+
return out[0] === startPoly ? out : null;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function sharesEdge(m: NavMesh, a: number, b: number): boolean {
|
|
728
|
+
if (a === b) return true;
|
|
729
|
+
for (let c = m.polyStart[a]!; c < m.polyStart[a + 1]!; c++) if (m.cornerNei[c] === b) return true;
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** A bake link out of `from` landing on `to` or on a poly sharing an edge with it. */
|
|
734
|
+
function linksOnto(m: NavMesh, from: number, to: number): boolean {
|
|
735
|
+
for (let l = m.linkStart[from]!; l < m.linkStart[from + 1]!; l++) {
|
|
736
|
+
if (sharesEdge(m, m.linkTo[l]!, to)) return true;
|
|
737
|
+
}
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** Plan distance from (x, y) to the nearest boundary edge of poly `p`; 0 inside. */
|
|
742
|
+
function distToPolyEdges2D(m: NavMesh, p: number, x: number, y: number): number {
|
|
743
|
+
if (polyContains2D(m, p, x, y)) return 0;
|
|
744
|
+
const s = m.polyStart[p]!;
|
|
745
|
+
const e = m.polyStart[p + 1]!;
|
|
746
|
+
let best = Infinity;
|
|
747
|
+
for (let i = s; i < e; i++) {
|
|
748
|
+
const a = m.cornerVert[i]! * 3;
|
|
749
|
+
const b = m.cornerVert[i + 1 < e ? i + 1 : s]! * 3;
|
|
750
|
+
const ax = m.verts[a]!, ay = m.verts[a + 1]!;
|
|
751
|
+
const dx = m.verts[b]! - ax, dy = m.verts[b + 1]! - ay;
|
|
752
|
+
const len2 = dx * dx + dy * dy;
|
|
753
|
+
const t = len2 > 0 ? Math.max(0, Math.min(1, ((x - ax) * dx + (y - ay) * dy) / len2)) : 0;
|
|
754
|
+
const d = detHypot(x - (ax + dx * t), y - (ay + dy * t));
|
|
755
|
+
if (d < best) best = d;
|
|
756
|
+
}
|
|
757
|
+
return best;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/** A bake link out of `from` or its edge neighbours whose landing reaches `to` within STEP_HOPS. */
|
|
761
|
+
function linksToward(m: NavMesh, from: number, to: number): boolean {
|
|
762
|
+
const lands = (p: number): boolean => {
|
|
763
|
+
for (let l = m.linkStart[p]!; l < m.linkStart[p + 1]!; l++) {
|
|
764
|
+
if (polyWithinHops(m, m.linkTo[l]!, to, STEP_HOPS)) return true;
|
|
765
|
+
}
|
|
766
|
+
return false;
|
|
767
|
+
};
|
|
768
|
+
if (lands(from)) return true;
|
|
769
|
+
for (let c = m.polyStart[from]!; c < m.polyStart[from + 1]!; c++) {
|
|
770
|
+
const nb = m.cornerNei[c]!;
|
|
771
|
+
if (nb >= 0 && lands(nb)) return true;
|
|
772
|
+
}
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// A bake link from `from` (or its edge neighbour) onto `to` (or its edge neighbour). A link, never
|
|
777
|
+
// a hop chain: six hops through a ramp reach the hall floor under a bridge, a fall through the deck.
|
|
778
|
+
function dropLinked(m: NavMesh, from: number, to: number): boolean {
|
|
779
|
+
if (from === to || linksOnto(m, from, to)) return true;
|
|
780
|
+
for (let c = m.polyStart[from]!; c < m.polyStart[from + 1]!; c++) {
|
|
781
|
+
const nb = m.cornerNei[c]!;
|
|
782
|
+
if (nb >= 0 && linksOnto(m, nb, to)) return true;
|
|
783
|
+
}
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function corridorConnected(m: NavMesh, corr: readonly number[]): boolean {
|
|
788
|
+
for (let i = 0; i < corr.length - 1; i++) {
|
|
789
|
+
if (!sharedEdge(m, corr[i]!, corr[i + 1]!)) return false;
|
|
790
|
+
}
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function corridorLen(m: NavMesh, corr: readonly number[]): number {
|
|
795
|
+
let s = 0;
|
|
796
|
+
for (let i = 1; i < corr.length; i++) {
|
|
797
|
+
const a = corr[i - 1]!, b = corr[i]!;
|
|
798
|
+
s += dist3(
|
|
799
|
+
m.polyCenter[b * 3]! - m.polyCenter[a * 3]!,
|
|
800
|
+
m.polyCenter[b * 3 + 1]! - m.polyCenter[a * 3 + 1]!,
|
|
801
|
+
m.polyCenter[b * 3 + 2]! - m.polyCenter[a * 3 + 2]!,
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
return s;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
type PortalRep = {
|
|
808
|
+
a: number;
|
|
809
|
+
b: number;
|
|
810
|
+
polyA: number;
|
|
811
|
+
polyB: number;
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
type PortalNet = {
|
|
815
|
+
reps: PortalRep[];
|
|
816
|
+
/** i < j pairs that share a room, mesh cost through that room. */
|
|
817
|
+
cost: Float64Array;
|
|
818
|
+
n: number;
|
|
819
|
+
byRoom: number[][];
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
const portalNetByMap = new Map<number, PortalNet>();
|
|
823
|
+
|
|
824
|
+
function polyInRoom(rep: PortalRep, room: number): number {
|
|
825
|
+
return rep.a === room ? rep.polyA : rep.polyB;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function meshHop(
|
|
829
|
+
m: NavMesh,
|
|
830
|
+
graph: RoomGraph,
|
|
831
|
+
from: number,
|
|
832
|
+
to: number,
|
|
833
|
+
room: number,
|
|
834
|
+
maxVisited: number,
|
|
835
|
+
allow: Uint8Array,
|
|
836
|
+
stats?: { visited: number },
|
|
837
|
+
ring = false,
|
|
838
|
+
): number[] | null {
|
|
839
|
+
const band = ring ? roomAndRing(graph, room) : new Uint8Array(graph.names.length);
|
|
840
|
+
if (!ring) band[room] = 1;
|
|
841
|
+
fillAllowMask(graph, band, allow);
|
|
842
|
+
return findPolyPath(m, from, to, maxVisited, allow, stats);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function buildPortalNet(m: NavMesh, graph: RoomGraph): PortalNet {
|
|
846
|
+
const nRooms = graph.names.length;
|
|
847
|
+
const best = new Map<number, PortalRep & { score: number }>();
|
|
848
|
+
const pairKey = (a: number, b: number) => (a < b ? a * nRooms + b : b * nRooms + a);
|
|
849
|
+
for (let a = 0; a < nRooms; a++) {
|
|
850
|
+
for (const p of graph.portals[a]!) {
|
|
851
|
+
if (a > p.to) continue;
|
|
852
|
+
const score = dist3(
|
|
853
|
+
graph.cx[a]! - m.polyCenter[p.polyA * 3]!,
|
|
854
|
+
graph.cy[a]! - m.polyCenter[p.polyA * 3 + 1]!,
|
|
855
|
+
graph.cz[a]! - m.polyCenter[p.polyA * 3 + 2]!,
|
|
856
|
+
) + dist3(
|
|
857
|
+
graph.cx[p.to]! - m.polyCenter[p.polyB * 3]!,
|
|
858
|
+
graph.cy[p.to]! - m.polyCenter[p.polyB * 3 + 1]!,
|
|
859
|
+
graph.cz[p.to]! - m.polyCenter[p.polyB * 3 + 2]!,
|
|
860
|
+
);
|
|
861
|
+
const k = pairKey(a, p.to);
|
|
862
|
+
const prev = best.get(k);
|
|
863
|
+
if (!prev || score < prev.score) {
|
|
864
|
+
best.set(k, { a, b: p.to, polyA: p.polyA, polyB: p.polyB, score });
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
const reps = [...best.values()].map(({ a, b, polyA, polyB }) => ({ a, b, polyA, polyB }));
|
|
869
|
+
const n = reps.length;
|
|
870
|
+
const byRoom: number[][] = Array.from({ length: nRooms }, () => []);
|
|
871
|
+
for (let i = 0; i < n; i++) {
|
|
872
|
+
const r = reps[i]!;
|
|
873
|
+
byRoom[r.a]!.push(i);
|
|
874
|
+
byRoom[r.b]!.push(i);
|
|
875
|
+
}
|
|
876
|
+
const cost = new Float64Array(n * n).fill(Infinity);
|
|
877
|
+
for (let room = 0; room < nRooms; room++) {
|
|
878
|
+
const ps = byRoom[room]!;
|
|
879
|
+
for (let i = 0; i < ps.length; i++) {
|
|
880
|
+
for (let j = i + 1; j < ps.length; j++) {
|
|
881
|
+
const ia = ps[i]!, ib = ps[j]!;
|
|
882
|
+
const pa = polyInRoom(reps[ia]!, room);
|
|
883
|
+
const pb = polyInRoom(reps[ib]!, room);
|
|
884
|
+
// Same-room doorways often connect only via a neighbour room (BRD West Garrison between
|
|
885
|
+
// two Shadowforge doors). Honest mesh distance, or the portal net splits.
|
|
886
|
+
const corr = findPolyPath(m, pa, pb, 20_000);
|
|
887
|
+
if (!corr) continue;
|
|
888
|
+
const d = corridorLen(m, corr);
|
|
889
|
+
const use = d > 0 ? d : 1;
|
|
890
|
+
if (use < cost[ia * n + ib]!) {
|
|
891
|
+
cost[ia * n + ib] = use;
|
|
892
|
+
cost[ib * n + ia] = use;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return { reps, cost, n, byRoom };
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function portalNetFor(m: NavMesh, graph: RoomGraph): PortalNet {
|
|
901
|
+
const id = m.mapId | 0;
|
|
902
|
+
let net = portalNetByMap.get(id);
|
|
903
|
+
if (!net) {
|
|
904
|
+
net = buildPortalNet(m, graph);
|
|
905
|
+
portalNetByMap.set(id, net);
|
|
906
|
+
}
|
|
907
|
+
return net;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function findPolyPathViaRooms(
|
|
911
|
+
m: NavMesh,
|
|
912
|
+
graph: RoomGraph,
|
|
913
|
+
startPoly: number,
|
|
914
|
+
endPoly: number,
|
|
915
|
+
maxVisited: number,
|
|
916
|
+
stats: { visited: number; hops: number; rooms: string[] },
|
|
917
|
+
): number[] | null {
|
|
918
|
+
const sr = graph.polyRoom[startPoly]!;
|
|
919
|
+
const er = graph.polyRoom[endPoly]!;
|
|
920
|
+
if (sr < 0 || er < 0) return null;
|
|
921
|
+
|
|
922
|
+
const allow = new Uint8Array(m.polyCount);
|
|
923
|
+
const hopStats = { visited: 0 };
|
|
924
|
+
let remaining = maxVisited;
|
|
925
|
+
|
|
926
|
+
const runHop = (from: number, to: number, room: number): number[] | null => {
|
|
927
|
+
if (from === to) return [from];
|
|
928
|
+
if (remaining <= 0) return null;
|
|
929
|
+
hopStats.visited = 0;
|
|
930
|
+
let seg = meshHop(m, graph, from, to, room, remaining, allow, hopStats);
|
|
931
|
+
if (!seg) {
|
|
932
|
+
hopStats.visited = 0;
|
|
933
|
+
seg = meshHop(m, graph, from, to, room, remaining, allow, hopStats, true);
|
|
934
|
+
}
|
|
935
|
+
if (!seg) {
|
|
936
|
+
hopStats.visited = 0;
|
|
937
|
+
seg = findPolyPath(m, from, to, remaining, null, hopStats);
|
|
938
|
+
}
|
|
939
|
+
stats.visited += hopStats.visited;
|
|
940
|
+
remaining -= hopStats.visited;
|
|
941
|
+
return seg;
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
if (sr === er) {
|
|
945
|
+
const seg = runHop(startPoly, endPoly, sr);
|
|
946
|
+
if (!seg || !corridorConnected(m, seg)) return null;
|
|
947
|
+
stats.hops = 1;
|
|
948
|
+
stats.rooms = [graph.names[sr]!];
|
|
949
|
+
return seg;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const net = portalNetFor(m, graph);
|
|
953
|
+
const n = net.n;
|
|
954
|
+
const startN = n;
|
|
955
|
+
const endN = n + 1;
|
|
956
|
+
const nn = n + 2;
|
|
957
|
+
const dist = new Float64Array(nn).fill(Infinity);
|
|
958
|
+
const parent = new Int32Array(nn).fill(-1);
|
|
959
|
+
const viaRoom = new Int32Array(nn).fill(-1);
|
|
960
|
+
const seen = new Uint8Array(nn);
|
|
961
|
+
dist[startN] = 0;
|
|
962
|
+
|
|
963
|
+
const startCost = new Float64Array(n).fill(Infinity);
|
|
964
|
+
const endCost = new Float64Array(n).fill(Infinity);
|
|
965
|
+
for (const i of net.byRoom[sr]!) {
|
|
966
|
+
const p = polyInRoom(net.reps[i]!, sr);
|
|
967
|
+
const corr = meshHop(m, graph, startPoly, p, sr, 8_000, allow);
|
|
968
|
+
if (corr) startCost[i] = corridorLen(m, corr);
|
|
969
|
+
}
|
|
970
|
+
for (const i of net.byRoom[er]!) {
|
|
971
|
+
const p = polyInRoom(net.reps[i]!, er);
|
|
972
|
+
const corr = meshHop(m, graph, p, endPoly, er, 8_000, allow);
|
|
973
|
+
if (corr) endCost[i] = corridorLen(m, corr);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const relaxTo = (u: number, v: number, w: number, room: number) => {
|
|
977
|
+
if (!(w < Infinity)) return;
|
|
978
|
+
const nd = dist[u]! + w;
|
|
979
|
+
if (nd + 1e-6 < dist[v]!) {
|
|
980
|
+
dist[v] = nd;
|
|
981
|
+
parent[v] = u;
|
|
982
|
+
viaRoom[v] = room;
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
|
|
986
|
+
for (let k = 0; k < nn; k++) {
|
|
987
|
+
let u = -1;
|
|
988
|
+
let best = Infinity;
|
|
989
|
+
for (let i = 0; i < nn; i++) {
|
|
990
|
+
if (!seen[i] && dist[i]! < best) {
|
|
991
|
+
best = dist[i]!;
|
|
992
|
+
u = i;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
if (u < 0) break;
|
|
996
|
+
if (u === endN) break;
|
|
997
|
+
seen[u] = 1;
|
|
998
|
+
if (u === startN) {
|
|
999
|
+
for (let i = 0; i < n; i++) relaxTo(u, i, startCost[i]!, sr);
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (u < n) {
|
|
1003
|
+
relaxTo(u, endN, endCost[u]!, er);
|
|
1004
|
+
for (let v = 0; v < n; v++) {
|
|
1005
|
+
if (v === u) continue;
|
|
1006
|
+
const c = net.cost[u * n + v]!;
|
|
1007
|
+
if (!(c < Infinity)) continue;
|
|
1008
|
+
const ru = net.reps[u]!;
|
|
1009
|
+
const rv = net.reps[v]!;
|
|
1010
|
+
const room = ru.a === rv.a || ru.a === rv.b ? ru.a : ru.b;
|
|
1011
|
+
relaxTo(u, v, c, room);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (!Number.isFinite(dist[endN]!)) return null;
|
|
1016
|
+
|
|
1017
|
+
const nodes: number[] = [];
|
|
1018
|
+
for (let p = endN; p !== -1; p = parent[p]!) nodes.push(p);
|
|
1019
|
+
nodes.reverse();
|
|
1020
|
+
if (nodes[0] !== startN) return null;
|
|
1021
|
+
|
|
1022
|
+
const out: number[] = [startPoly];
|
|
1023
|
+
let cur = startPoly;
|
|
1024
|
+
const roomsUsed: number[] = [sr];
|
|
1025
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
1026
|
+
const prev = nodes[i - 1]!;
|
|
1027
|
+
const node = nodes[i]!;
|
|
1028
|
+
const room = viaRoom[node]!;
|
|
1029
|
+
let target: number;
|
|
1030
|
+
if (node === endN) target = endPoly;
|
|
1031
|
+
else target = polyInRoom(net.reps[node]!, room);
|
|
1032
|
+
if (cur !== target) {
|
|
1033
|
+
const seg = runHop(cur, target, room);
|
|
1034
|
+
if (!seg) return null;
|
|
1035
|
+
for (let k = 1; k < seg.length; k++) out.push(seg[k]!);
|
|
1036
|
+
cur = target;
|
|
1037
|
+
}
|
|
1038
|
+
if (node !== endN) {
|
|
1039
|
+
const rep = net.reps[node]!;
|
|
1040
|
+
const other = rep.a === room ? rep.polyB : rep.polyA;
|
|
1041
|
+
if (cur !== other) {
|
|
1042
|
+
out.push(other);
|
|
1043
|
+
cur = other;
|
|
1044
|
+
}
|
|
1045
|
+
const nextRoom = rep.a === room ? rep.b : rep.a;
|
|
1046
|
+
if (roomsUsed[roomsUsed.length - 1] !== nextRoom) roomsUsed.push(nextRoom);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (!corridorConnected(m, out)) return null;
|
|
1050
|
+
stats.hops = roomsUsed.length;
|
|
1051
|
+
stats.rooms = roomsUsed.map((i) => graph.names[i]!);
|
|
1052
|
+
return out;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// dtTriArea2D in our frame. Detour works in its (x, z) plane and we map world.x = detour.z, which
|
|
1056
|
+
// mirrors the plane and flips the sign. Backwards, the funnel swaps portals and leaves the corridor.
|
|
1057
|
+
const triarea2D = (ax: number, ay: number, bx: number, by: number, cx: number, cy: number): number =>
|
|
1058
|
+
(cy - ay) * (bx - ax) - (by - ay) * (cx - ax);
|
|
1059
|
+
|
|
1060
|
+
// Simple stupid funnel (Detour findStraightPath) over the corridor's portal edges. Each corner
|
|
1061
|
+
// carries its corridor index: a helix overlaps in plan, so poly identity needs corridor order.
|
|
1062
|
+
function stringPull(
|
|
1063
|
+
m: NavMesh,
|
|
1064
|
+
corridor: readonly number[],
|
|
1065
|
+
start: NavMeshPos,
|
|
1066
|
+
end: NavMeshPos,
|
|
1067
|
+
): { x: number; y: number; at: number }[] {
|
|
1068
|
+
const n = corridor.length;
|
|
1069
|
+
const leftX = new Float64Array(n + 1), leftY = new Float64Array(n + 1);
|
|
1070
|
+
const rightX = new Float64Array(n + 1), rightY = new Float64Array(n + 1);
|
|
1071
|
+
for (let i = 0; i < n - 1; i++) {
|
|
1072
|
+
const edge = sharedEdge(m, corridor[i]!, corridor[i + 1]!);
|
|
1073
|
+
if (!edge) return [{ x: start.x, y: start.y, at: 0 }, { x: end.x, y: end.y, at: n - 1 }];
|
|
1074
|
+
const a = edge[0]! * 3, b = edge[1]! * 3;
|
|
1075
|
+
leftX[i] = m.verts[a]!; leftY[i] = m.verts[a + 1]!;
|
|
1076
|
+
rightX[i] = m.verts[b]!; rightY[i] = m.verts[b + 1]!;
|
|
1077
|
+
}
|
|
1078
|
+
leftX[n - 1] = end.x; leftY[n - 1] = end.y;
|
|
1079
|
+
rightX[n - 1] = end.x; rightY[n - 1] = end.y;
|
|
1080
|
+
|
|
1081
|
+
const pts: { x: number; y: number; at: number }[] = [{ x: start.x, y: start.y, at: 0 }];
|
|
1082
|
+
let portalApexX = start.x, portalApexY = start.y;
|
|
1083
|
+
let portalLeftX = start.x, portalLeftY = start.y;
|
|
1084
|
+
let portalRightX = start.x, portalRightY = start.y;
|
|
1085
|
+
let apexIndex = 0, leftIndex = 0, rightIndex = 0;
|
|
1086
|
+
|
|
1087
|
+
for (let i = 0; i < n; i++) {
|
|
1088
|
+
const lx = i < n ? leftX[i]! : end.x, ly = i < n ? leftY[i]! : end.y;
|
|
1089
|
+
const rx = i < n ? rightX[i]! : end.x, ry = i < n ? rightY[i]! : end.y;
|
|
1090
|
+
if (triarea2D(portalApexX, portalApexY, portalRightX, portalRightY, rx, ry) <= 0) {
|
|
1091
|
+
if (
|
|
1092
|
+
(portalApexX === portalRightX && portalApexY === portalRightY)
|
|
1093
|
+
|| triarea2D(portalApexX, portalApexY, portalLeftX, portalLeftY, rx, ry) > 0
|
|
1094
|
+
) {
|
|
1095
|
+
portalRightX = rx; portalRightY = ry; rightIndex = i;
|
|
1096
|
+
} else {
|
|
1097
|
+
pts.push({ x: portalLeftX, y: portalLeftY, at: leftIndex });
|
|
1098
|
+
portalApexX = portalLeftX; portalApexY = portalLeftY;
|
|
1099
|
+
apexIndex = leftIndex;
|
|
1100
|
+
portalLeftX = portalApexX; portalLeftY = portalApexY;
|
|
1101
|
+
portalRightX = portalApexX; portalRightY = portalApexY;
|
|
1102
|
+
leftIndex = apexIndex; rightIndex = apexIndex;
|
|
1103
|
+
i = apexIndex;
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (triarea2D(portalApexX, portalApexY, portalLeftX, portalLeftY, lx, ly) >= 0) {
|
|
1108
|
+
if (
|
|
1109
|
+
(portalApexX === portalLeftX && portalApexY === portalLeftY)
|
|
1110
|
+
|| triarea2D(portalApexX, portalApexY, portalRightX, portalRightY, lx, ly) < 0
|
|
1111
|
+
) {
|
|
1112
|
+
portalLeftX = lx; portalLeftY = ly; leftIndex = i;
|
|
1113
|
+
} else {
|
|
1114
|
+
pts.push({ x: portalRightX, y: portalRightY, at: rightIndex });
|
|
1115
|
+
portalApexX = portalRightX; portalApexY = portalRightY;
|
|
1116
|
+
apexIndex = rightIndex;
|
|
1117
|
+
portalLeftX = portalApexX; portalLeftY = portalApexY;
|
|
1118
|
+
portalRightX = portalApexX; portalRightY = portalApexY;
|
|
1119
|
+
leftIndex = apexIndex; rightIndex = apexIndex;
|
|
1120
|
+
i = apexIndex;
|
|
1121
|
+
continue;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
pts.push({ x: end.x, y: end.y, at: n - 1 });
|
|
1126
|
+
return pts;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Corridor slot owning (x, y), searched only inside [lo, hi] — the two corners' own corridor
|
|
1130
|
+
// indices. Outside that window the same plan cell belongs to another floor of the same helix.
|
|
1131
|
+
function corridorPolyAt(
|
|
1132
|
+
m: NavMesh,
|
|
1133
|
+
corridor: readonly number[],
|
|
1134
|
+
lo: number,
|
|
1135
|
+
hi: number,
|
|
1136
|
+
prev: number,
|
|
1137
|
+
x: number,
|
|
1138
|
+
y: number,
|
|
1139
|
+
zHint?: number,
|
|
1140
|
+
): number {
|
|
1141
|
+
const a = Math.max(0, Math.min(lo, hi) - 1);
|
|
1142
|
+
const b = Math.min(corridor.length - 1, Math.max(lo, hi) + 1);
|
|
1143
|
+
// Stacked xy (Rotgrip 349: dest z=-196 vs standing z=-206 in Zaetar's Grave): prefer dest.z.
|
|
1144
|
+
if (zHint != null && Number.isFinite(zHint)) {
|
|
1145
|
+
let bestZ = Infinity;
|
|
1146
|
+
let bestI = -1;
|
|
1147
|
+
for (let i = a; i <= b; i++) {
|
|
1148
|
+
if (!polyContains2D(m, corridor[i]!, x, y)) continue;
|
|
1149
|
+
const dz = Math.abs(polyHeightAt(m, corridor[i]!, x, y) - zHint);
|
|
1150
|
+
if (dz < bestZ) { bestZ = dz; bestI = i; }
|
|
1151
|
+
}
|
|
1152
|
+
if (bestI >= 0) return bestI;
|
|
1153
|
+
}
|
|
1154
|
+
let best = -1;
|
|
1155
|
+
let bestD = Infinity;
|
|
1156
|
+
for (let i = a; i <= b; i++) {
|
|
1157
|
+
if (polyContains2D(m, corridor[i]!, x, y)) {
|
|
1158
|
+
const d = Math.abs(i - prev);
|
|
1159
|
+
if (d < bestD) { bestD = d; best = i; }
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
if (best >= 0) return best;
|
|
1163
|
+
for (let i = a; i <= b; i++) {
|
|
1164
|
+
const d = distToPolyBBox(m, corridor[i]!, x, y);
|
|
1165
|
+
if (d < bestD) { bestD = d; best = i; }
|
|
1166
|
+
}
|
|
1167
|
+
return best >= 0 ? best : prev;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
export type NavMeshPathOpts = {
|
|
1171
|
+
/** Snap radius for the endpoints (yards). */
|
|
1172
|
+
radius?: number;
|
|
1173
|
+
/** Emitted step length; smaller means a denser vertical profile. */
|
|
1174
|
+
stepYd?: number;
|
|
1175
|
+
// Filled on failure, and the three answers differ: `unsnappable` has no floor under an endpoint,
|
|
1176
|
+
// `disconnected` is an honest no, and `exhausted` hit maxVisited and may still have a path.
|
|
1177
|
+
diag?: {
|
|
1178
|
+
reason?: 'unsnappable' | 'disconnected' | 'exhausted';
|
|
1179
|
+
visited?: number;
|
|
1180
|
+
roomHops?: number;
|
|
1181
|
+
roomNames?: string[];
|
|
1182
|
+
roomFallback?: boolean;
|
|
1183
|
+
};
|
|
1184
|
+
/** Poly visits before giving up. Live walks pass 8k so a miss cannot flood the mesh. */
|
|
1185
|
+
maxVisited?: number;
|
|
1186
|
+
/** Room-graph refine (C5). Default on when the map has a room bake. Probes pass false to A/B. */
|
|
1187
|
+
roomGraph?: boolean;
|
|
1188
|
+
};
|
|
1189
|
+
|
|
1190
|
+
// Path between two world points, every step carrying its floor z. Returns null when the polys are
|
|
1191
|
+
// genuinely disconnected — an honest null, rather than a beeline through rock.
|
|
1192
|
+
export function findPathNavMesh(
|
|
1193
|
+
m: NavMesh,
|
|
1194
|
+
from: NavMeshPos,
|
|
1195
|
+
to: NavMeshPos,
|
|
1196
|
+
opts: NavMeshPathOpts = {},
|
|
1197
|
+
): { x: number; y: number; z: number }[] | null {
|
|
1198
|
+
const radius = opts.radius ?? 8;
|
|
1199
|
+
const step = opts.stepYd ?? STEP_YD;
|
|
1200
|
+
const a = findNearestPoly(m, from.x, from.y, from.z, radius);
|
|
1201
|
+
// Dest must stay on dest.z. Plan-first scoring otherwise picks dest.xy on
|
|
1202
|
+
// THIS floor (stacked column) and the walk stands on top of the boss.
|
|
1203
|
+
const storey = to.z != null && Number.isFinite(to.z) ? 8 : undefined;
|
|
1204
|
+
let b = findNearestPoly(m, to.x, to.y, to.z, radius, storey);
|
|
1205
|
+
if (!b && storey != null) b = findNearestPoly(m, to.x, to.y, to.z, Math.max(radius, 20), storey);
|
|
1206
|
+
if (!a || !b) {
|
|
1207
|
+
if (opts.diag) opts.diag.reason = 'unsnappable';
|
|
1208
|
+
return null;
|
|
1209
|
+
}
|
|
1210
|
+
const maxVisited = opts.maxVisited ?? 200_000;
|
|
1211
|
+
const hopStats = { visited: 0, capped: false };
|
|
1212
|
+
let corridor: number[] | null = null;
|
|
1213
|
+
const useRooms = opts.roomGraph !== false;
|
|
1214
|
+
const graph = useRooms ? roomGraphForMesh(m) : null;
|
|
1215
|
+
if (graph) {
|
|
1216
|
+
const roomStats = { visited: 0, hops: 0, rooms: [] as string[] };
|
|
1217
|
+
corridor = findPolyPathViaRooms(m, graph, a.poly, b.poly, maxVisited, roomStats);
|
|
1218
|
+
if (corridor) {
|
|
1219
|
+
if (opts.diag) {
|
|
1220
|
+
opts.diag.visited = roomStats.visited;
|
|
1221
|
+
opts.diag.roomHops = roomStats.hops;
|
|
1222
|
+
opts.diag.roomNames = roomStats.rooms;
|
|
1223
|
+
opts.diag.roomFallback = false;
|
|
1224
|
+
}
|
|
1225
|
+
} else if (opts.diag) {
|
|
1226
|
+
opts.diag.roomFallback = true;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if (!corridor) {
|
|
1230
|
+
hopStats.visited = 0;
|
|
1231
|
+
corridor = findPolyPath(m, a.poly, b.poly, maxVisited, null, hopStats);
|
|
1232
|
+
if (opts.diag && opts.diag.visited == null) opts.diag.visited = hopStats.visited;
|
|
1233
|
+
}
|
|
1234
|
+
if (!corridor) {
|
|
1235
|
+
if (opts.diag) opts.diag.reason = hopStats.capped ? 'exhausted' : 'disconnected';
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
const corners = stringPull(m, corridor, from, to);
|
|
1240
|
+
const out: { x: number; y: number; z: number }[] = [];
|
|
1241
|
+
let hint = 0;
|
|
1242
|
+
const emit = (
|
|
1243
|
+
x: number, y: number, lo: number, hi: number, interior = false, zHint?: number,
|
|
1244
|
+
) => {
|
|
1245
|
+
hint = corridorPolyAt(m, corridor, lo, hi, hint, x, y, zHint);
|
|
1246
|
+
// Open air between a lip and its linked landing (bake-navmesh.mjs jumpLinks) gets no waypoint:
|
|
1247
|
+
// the stepper flies lip -> landing on the link. Corners and endpoints always emit.
|
|
1248
|
+
if (interior && distToPolyEdges2D(m, corridor[hint]!, x, y) > OFF_POLY_TOL_YD) return;
|
|
1249
|
+
const z = polyHeightAt(m, corridor[hint]!, x, y);
|
|
1250
|
+
const last = out[out.length - 1];
|
|
1251
|
+
if (last && Math.abs(last.x - x) < 1e-4 && Math.abs(last.y - y) < 1e-4) {
|
|
1252
|
+
last.z = z;
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
out.push({ x, y, z });
|
|
1256
|
+
};
|
|
1257
|
+
emit(corners[0]!.x, corners[0]!.y, 0, 0, false, from.z);
|
|
1258
|
+
for (let i = 1; i < corners.length; i++) {
|
|
1259
|
+
const p0 = corners[i - 1]!;
|
|
1260
|
+
const p1 = corners[i]!;
|
|
1261
|
+
const z0 = polyHeightAt(m, corridor[p0.at]!, p0.x, p0.y);
|
|
1262
|
+
const z1 = polyHeightAt(m, corridor[p1.at]!, p1.x, p1.y);
|
|
1263
|
+
const d = dist3(p1.x - p0.x, p1.y - p0.y, z1 - z0);
|
|
1264
|
+
const n = Math.max(1, Math.ceil(d / step));
|
|
1265
|
+
const destHint = i === corners.length - 1 ? to.z : undefined;
|
|
1266
|
+
for (let k = 1; k <= n; k++) {
|
|
1267
|
+
emit(
|
|
1268
|
+
p0.x + ((p1.x - p0.x) * k) / n, p0.y + ((p1.y - p0.y) * k) / n,
|
|
1269
|
+
p0.at, p1.at, k < n, k === n ? destHint : undefined,
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
// Zero-length leg (rare spawns share one origin): a path to where you stand is a
|
|
1274
|
+
// path, not a failure — returning null here made 9 legs read as beelines.
|
|
1275
|
+
if (out.length === 1) out.push({ ...out[0]! });
|
|
1276
|
+
return out;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Walking — the other half. A router the stepper cannot follow is worth nothing.
|
|
1280
|
+
|
|
1281
|
+
/** Hops explored from the standing poly; a `stepYd` step crosses very few. */
|
|
1282
|
+
const STEP_HOPS = 6;
|
|
1283
|
+
/** 64 filled in 2–3 hops on dense Detour clusters (4–6 nei + bridges) and dropped the rest. */
|
|
1284
|
+
let hopScratch = new Int32Array(256);
|
|
1285
|
+
|
|
1286
|
+
function hopPush(nb: number, n: number): number {
|
|
1287
|
+
if (hopScratch.lastIndexOf(nb, n - 1) >= 0) return n;
|
|
1288
|
+
if (n >= hopScratch.length) {
|
|
1289
|
+
const grown = new Int32Array(hopScratch.length * 2);
|
|
1290
|
+
grown.set(hopScratch);
|
|
1291
|
+
hopScratch = grown;
|
|
1292
|
+
}
|
|
1293
|
+
hopScratch[n] = nb;
|
|
1294
|
+
return n + 1;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/** Is `to` reachable from `from` across shared edges / bridged links within `hops`? */
|
|
1298
|
+
function polyWithinHops(m: NavMesh, from: number, to: number, hops: number): boolean {
|
|
1299
|
+
if (from === to) return true;
|
|
1300
|
+
let n = 0;
|
|
1301
|
+
hopScratch[n++] = from;
|
|
1302
|
+
let readStart = 0;
|
|
1303
|
+
for (let h = 0; h < hops; h++) {
|
|
1304
|
+
const readEnd = n;
|
|
1305
|
+
for (let i = readStart; i < readEnd; i++) {
|
|
1306
|
+
const p = hopScratch[i]!;
|
|
1307
|
+
for (let c = m.polyStart[p]!; c < m.polyStart[p + 1]!; c++) {
|
|
1308
|
+
const nb = m.cornerNei[c]!;
|
|
1309
|
+
if (nb < 0) continue;
|
|
1310
|
+
if (nb === to) return true;
|
|
1311
|
+
n = hopPush(nb, n);
|
|
1312
|
+
}
|
|
1313
|
+
for (let l = m.linkStart[p]!; l < m.linkStart[p + 1]!; l++) {
|
|
1314
|
+
const nb = m.linkTo[l]!;
|
|
1315
|
+
if (nb === to) return true;
|
|
1316
|
+
n = hopPush(nb, n);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (n === readEnd) break;
|
|
1320
|
+
readStart = readEnd;
|
|
1321
|
+
}
|
|
1322
|
+
return false;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// Step toward `to` by at most `stepYd`, clamped to the mesh, sliding on a blocked axis. A steep
|
|
1326
|
+
// step is legal when the landing poly CONNECTS to the standing one — another storey does not.
|
|
1327
|
+
export function stepWalkableNavMesh(
|
|
1328
|
+
m: NavMesh,
|
|
1329
|
+
from: NavMeshPos,
|
|
1330
|
+
to: NavMeshPos,
|
|
1331
|
+
stepYd: number,
|
|
1332
|
+
zTol = 4,
|
|
1333
|
+
// followPathNavMesh sets this: greedy slides must not Euclidean-fall at a lower dest.
|
|
1334
|
+
allowJump = false,
|
|
1335
|
+
): { x: number; y: number; z?: number } {
|
|
1336
|
+
const dx = to.x - from.x;
|
|
1337
|
+
const dy = to.y - from.y;
|
|
1338
|
+
const dist = detHypot(dx, dy);
|
|
1339
|
+
if (!(dist > 1e-9)) {
|
|
1340
|
+
if (to.z != null && from.z != null && Math.abs(to.z - from.z) > 0.2) {
|
|
1341
|
+
const here = findNearestPoly(m, from.x, from.y, from.z, OFF_POLY_TOL_YD + 0.5);
|
|
1342
|
+
const hit = findNearestPoly(m, from.x, from.y, to.z, OFF_POLY_TOL_YD + 0.5);
|
|
1343
|
+
if (hit && hit.dist <= 1 && here
|
|
1344
|
+
&& polyWithinHops(m, here.poly, hit.poly, STEP_HOPS)) {
|
|
1345
|
+
const climb = Math.min(Math.abs(hit.z - from.z), stepYd);
|
|
1346
|
+
return { x: from.x, y: from.y, z: from.z + Math.sign(hit.z - from.z) * climb };
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return { x: from.x, y: from.y, z: from.z };
|
|
1350
|
+
}
|
|
1351
|
+
const travel = Math.min(stepYd, dist);
|
|
1352
|
+
const ux = dx / dist;
|
|
1353
|
+
const uy = dy / dist;
|
|
1354
|
+
const stand = findNearestPoly(m, from.x, from.y, from.z, OFF_POLY_TOL_YD + 0.5);
|
|
1355
|
+
// Stepping DOWN off a linked lip (dropLinks): the landing is the floor at the waypoint's height.
|
|
1356
|
+
// The nearest-by-own-z search below hands back the lip's slope past its edge; that wedged 1 in 6 drops.
|
|
1357
|
+
const dropping = to.z != null && from.z != null && from.z - to.z > WALKABLE_CLIMB_YD && stand != null;
|
|
1358
|
+
const at = (x: number, y: number) => {
|
|
1359
|
+
if (dropping) {
|
|
1360
|
+
const low = findNearestPoly(m, x, y, to.z, OFF_POLY_TOL_YD + 0.5);
|
|
1361
|
+
if (low && low.dist <= OFF_POLY_TOL_YD && low.z < from.z! - WALKABLE_CLIMB_YD
|
|
1362
|
+
&& dropLinked(m, stand!.poly, low.poly)) {
|
|
1363
|
+
return { x, y, z: low.z };
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
// Snap on the SEGMENT's height, not the walker's: hinting from.z holds a step on the floor it is
|
|
1367
|
+
// already on, which is how a walk drifts a storey off its corridor (zul-gurub 8502 over 8515).
|
|
1368
|
+
const segZ = to.z != null && from.z != null && Number.isFinite(to.z) && Number.isFinite(from.z)
|
|
1369
|
+
? from.z + (to.z - from.z) * Math.min(1, travel / dist)
|
|
1370
|
+
: from.z;
|
|
1371
|
+
const hit = findNearestPoly(m, x, y, segZ, OFF_POLY_TOL_YD + 0.5);
|
|
1372
|
+
if (!hit || hit.dist > OFF_POLY_TOL_YD) return null;
|
|
1373
|
+
// Storey climb only. Same-floor dest.z is a mesh height; a 0.5 yd dip then
|
|
1374
|
+
// read as "away from dest" and the step was refused — walk froze on every bump.
|
|
1375
|
+
if (to.z != null && from.z != null && Math.abs(from.z - to.z) > 3
|
|
1376
|
+
&& Math.abs(hit.z - to.z) > Math.abs(from.z - to.z) + 0.5) {
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1379
|
+
if (from.z == null) return { x, y, z: hit.z };
|
|
1380
|
+
const rise = hit.z - from.z;
|
|
1381
|
+
// Unconnected floor: UP is walkableClimb (MapBuilder.cpp:358) — the symmetric zTol mounted 4 yd
|
|
1382
|
+
// walls; DOWN is a fall (MotionMaster.cpp:575 MoveFall) and keeps zTol.
|
|
1383
|
+
if (rise <= WALKABLE_CLIMB_YD && -rise <= zTol) return { x, y, z: hit.z };
|
|
1384
|
+
if (stand && polyWithinHops(m, stand.poly, hit.poly, STEP_HOPS)) return { x, y, z: hit.z };
|
|
1385
|
+
// A long ramp step crosses more than STEP_HOPS tiny polys: walk the segment on connected floor.
|
|
1386
|
+
if (rise <= zTol && stand && walkLosClearNavMesh(m, from, { x, y, z: hit.z })) return { x, y, z: hit.z };
|
|
1387
|
+
return null;
|
|
1388
|
+
};
|
|
1389
|
+
// Off a lip whose bake link lands toward the waypoint, or already airborne (edge distance, not bbox):
|
|
1390
|
+
// step out over nothing with z falling toward the landing (MotionMaster.cpp:575 MoveFall).
|
|
1391
|
+
// Only the corridor follower opts in — a greedy slide onto a lower dest is not a jump.
|
|
1392
|
+
if (allowJump && to.z != null && from.z != null && dist <= JUMP_REACH_MAX_YD + OFF_POLY_TOL_YD) {
|
|
1393
|
+
const land = findNearestPoly(m, to.x, to.y, to.z, OFF_POLY_TOL_YD + 0.5);
|
|
1394
|
+
if (land && land.dist <= OFF_POLY_TOL_YD && land.z < from.z - WALKABLE_CLIMB_YD) {
|
|
1395
|
+
// Mid-flight over a gap that has floor UNDER it: plan geometry alone calls that standing, so the
|
|
1396
|
+
// jump ended on naxxramas' lower floor 16.8 yd below Thaddius' platform. A unit further above its
|
|
1397
|
+
// floor than one climb step (MapBuilder.cpp:1219 walkableClimb) is not standing on it.
|
|
1398
|
+
const airborne = !stand || distToPolyEdges2D(m, stand.poly, from.x, from.y) > OFF_POLY_TOL_YD
|
|
1399
|
+
|| from.z - stand.z > WALKABLE_CLIMB_YD;
|
|
1400
|
+
const nx = from.x + ux * travel;
|
|
1401
|
+
const ny = from.y + uy * travel;
|
|
1402
|
+
const offLip = !airborne && stand!.poly !== land.poly
|
|
1403
|
+
&& !polyContains2D(m, stand!.poly, nx, ny) && linksToward(m, stand!.poly, land.poly);
|
|
1404
|
+
if (airborne || offLip) {
|
|
1405
|
+
return { x: nx, y: ny, z: from.z + (land.z - from.z) * (travel / dist) };
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
const direct = at(from.x + ux * travel, from.y + uy * travel);
|
|
1410
|
+
if (direct) return direct;
|
|
1411
|
+
const tryX = at(from.x + ux * travel, from.y);
|
|
1412
|
+
const tryY = at(from.x, from.y + uy * travel);
|
|
1413
|
+
if (tryX && tryY) return Math.abs(ux) >= Math.abs(uy) ? tryX : tryY;
|
|
1414
|
+
if (tryX) return tryX;
|
|
1415
|
+
if (tryY) return tryY;
|
|
1416
|
+
return { x: from.x, y: from.y, z: from.z };
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/** Left/right mesh hop off a blocked heading. Prefers the side that closes on `to`. */
|
|
1420
|
+
export function sidestepNavMesh(
|
|
1421
|
+
m: NavMesh,
|
|
1422
|
+
from: NavMeshPos,
|
|
1423
|
+
to: NavMeshPos,
|
|
1424
|
+
hopYd: number,
|
|
1425
|
+
): { x: number; y: number; z?: number } | null {
|
|
1426
|
+
const dx = to.x - from.x;
|
|
1427
|
+
const dy = to.y - from.y;
|
|
1428
|
+
const len = detHypot(dx, dy);
|
|
1429
|
+
if (!(len > 1e-6) || !(hopYd > 0)) return null;
|
|
1430
|
+
const px = -dy / len;
|
|
1431
|
+
const py = dx / len;
|
|
1432
|
+
let best: { x: number; y: number; z?: number } | null = null;
|
|
1433
|
+
let bestClose = Infinity;
|
|
1434
|
+
for (const sign of [1, -1] as const) {
|
|
1435
|
+
const aim = {
|
|
1436
|
+
x: from.x + px * hopYd * sign,
|
|
1437
|
+
y: from.y + py * hopYd * sign,
|
|
1438
|
+
...(from.z != null ? { z: from.z } : {}),
|
|
1439
|
+
};
|
|
1440
|
+
const s = stepWalkableNavMesh(m, from, aim, hopYd);
|
|
1441
|
+
if (detHypot(s.x - from.x, s.y - from.y) < 0.4) continue;
|
|
1442
|
+
const close = detHypot(to.x - s.x, to.y - s.y);
|
|
1443
|
+
const here = detHypot(to.x - from.x, to.y - from.y);
|
|
1444
|
+
if (close >= here - 0.2) continue;
|
|
1445
|
+
if (close < bestClose) { bestClose = close; best = s; }
|
|
1446
|
+
}
|
|
1447
|
+
return best;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// Server climb-arrival: walkableClimb 4 cells × BASE_UNIT_DIM 0.2666666f = 1.0667 yd. Cite: MapBuilder.h:50
|
|
1451
|
+
// BASE_UNIT_DIM; MapBuilder.cpp:1219 walkableClimb=4.
|
|
1452
|
+
export const WALKABLE_CLIMB_YD = 4 * 0.2666666;
|
|
1453
|
+
|
|
1454
|
+
// A running jump: run 7.0 yd/s (Unit.cpp:66 baseMoveSpeed MOVE_RUN), 7.958 yd/s up at take-off
|
|
1455
|
+
// (Anticheat/module/Movement/movement.cpp:609 MSG_MOVE_JUMP), gravity 19.291 (Movement/util.cpp:24).
|
|
1456
|
+
export const RUN_SPEED_YD_S = 7.0;
|
|
1457
|
+
export const JUMP_SPEED_YD_S = 7.95797334;
|
|
1458
|
+
export const GRAVITY_YD_S2 = 19.29110527038574;
|
|
1459
|
+
/** Plan distance a running jump covers by the time it has fallen `dz` yd below the lip. */
|
|
1460
|
+
export function jumpReachYd(dz: number): number {
|
|
1461
|
+
const up = JUMP_SPEED_YD_S;
|
|
1462
|
+
return RUN_SPEED_YD_S * (up + Math.sqrt(up * up + 2 * GRAVITY_YD_S2 * Math.max(0, dz))) / GRAVITY_YD_S2;
|
|
1463
|
+
}
|
|
1464
|
+
/** jumpReachYd at the deepest fall HandleFall charges nothing for (Player.cpp:19530, 14.57 yd). */
|
|
1465
|
+
export const JUMP_REACH_MAX_YD = 12;
|
|
1466
|
+
|
|
1467
|
+
/** `followPath` over the mesh — drops reached waypoints, carries the floor z. */
|
|
1468
|
+
export function followPathNavMesh(
|
|
1469
|
+
m: NavMesh,
|
|
1470
|
+
from: NavMeshPos,
|
|
1471
|
+
path: NavMeshPos[],
|
|
1472
|
+
stepYd: number,
|
|
1473
|
+
arriveYd = 0.35,
|
|
1474
|
+
): { x: number; y: number; z?: number } {
|
|
1475
|
+
while (path.length > 0) {
|
|
1476
|
+
const wp = path[0]!;
|
|
1477
|
+
const planClose = detHypot(wp.x - from.x, wp.y - from.y) <= arriveYd;
|
|
1478
|
+
// A waypoint still more than walkableClimb ABOVE the unit is an unboarded ramp, not arrival:
|
|
1479
|
+
// popping bfd's 2.3 yd risers stranded the walker between storeys. Descents keep 4.
|
|
1480
|
+
const zBelow = wp.z != null && from.z != null ? wp.z - from.z : 0;
|
|
1481
|
+
const zClose = wp.z == null || from.z == null
|
|
1482
|
+
|| (zBelow <= WALKABLE_CLIMB_YD && -zBelow <= 4);
|
|
1483
|
+
// Keep the ramp wp until we climb. Plan-only skip left the unit on the
|
|
1484
|
+
// start floor walking dest-XY; dist≈0 below climbs in place.
|
|
1485
|
+
if (planClose && zClose) {
|
|
1486
|
+
path.shift();
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
// Connectivity carries the ramp now, so the tolerance stays tight: widening it to the
|
|
1490
|
+
// whole remaining rise let a step land on any floor the leg happened to end on.
|
|
1491
|
+
const next = stepWalkableNavMesh(m, from, wp, stepYd, 4, true);
|
|
1492
|
+
// Climb-pending wp the mesh refuses to climb in place (no poly at wp.z within
|
|
1493
|
+
// reach): the old tolerance-4 arrival was benign there — pop, don't wedge.
|
|
1494
|
+
if (planClose && !zClose && zBelow > 0 && zBelow <= 4
|
|
1495
|
+
&& Math.abs(next.x - from.x) < 1e-9 && Math.abs(next.y - from.y) < 1e-9
|
|
1496
|
+
&& (next.z == null || from.z == null || Math.abs(next.z - from.z) < 1e-9)) {
|
|
1497
|
+
path.shift();
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1500
|
+
return next;
|
|
1501
|
+
}
|
|
1502
|
+
return { x: from.x, y: from.y, z: from.z };
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// Binary artifact (<slug>.navmesh.bin) — written by src/dev/etl/bake-navmesh.mjs
|
|
1506
|
+
|
|
1507
|
+
const MAGIC = 0x4d4e4350; // 'PCNM' little-endian
|
|
1508
|
+
const VERSION = 1;
|
|
1509
|
+
|
|
1510
|
+
export function encodeNavMesh(a: NavMeshArrays): Uint8Array {
|
|
1511
|
+
const polyCount = a.polyStart.length - 1;
|
|
1512
|
+
const head = 40;
|
|
1513
|
+
const sizes = [
|
|
1514
|
+
a.verts.byteLength, a.polyStart.byteLength, a.cornerVert.byteLength,
|
|
1515
|
+
a.cornerNei.byteLength, (polyCount + 3) & ~3,
|
|
1516
|
+
a.dVStart.byteLength, a.dTStart.byteLength, a.dVerts.byteLength,
|
|
1517
|
+
(a.dTris.byteLength + 3) & ~3,
|
|
1518
|
+
a.linkStart.byteLength, a.linkTo.byteLength, a.linkVia.byteLength,
|
|
1519
|
+
];
|
|
1520
|
+
const total = head + sizes.reduce((s, n) => s + n, 0);
|
|
1521
|
+
const buf = new ArrayBuffer(total);
|
|
1522
|
+
const dv = new DataView(buf);
|
|
1523
|
+
dv.setUint32(0, MAGIC, true);
|
|
1524
|
+
dv.setUint32(4, VERSION, true);
|
|
1525
|
+
dv.setInt32(8, a.mapId, true);
|
|
1526
|
+
dv.setUint32(12, polyCount, true);
|
|
1527
|
+
dv.setUint32(16, a.verts.length / 3, true);
|
|
1528
|
+
dv.setUint32(20, a.cornerVert.length, true);
|
|
1529
|
+
dv.setUint32(24, a.dVerts.length / 3, true);
|
|
1530
|
+
dv.setUint32(28, a.dTris.length / 4, true);
|
|
1531
|
+
dv.setUint32(32, a.linkTo.length, true);
|
|
1532
|
+
dv.setUint32(36, 0, true);
|
|
1533
|
+
const u8 = new Uint8Array(buf);
|
|
1534
|
+
let o = head;
|
|
1535
|
+
const put = (src: ArrayBufferView) => {
|
|
1536
|
+
u8.set(new Uint8Array(src.buffer, src.byteOffset, src.byteLength), o);
|
|
1537
|
+
o += (src.byteLength + 3) & ~3;
|
|
1538
|
+
};
|
|
1539
|
+
put(a.verts); put(a.polyStart); put(a.cornerVert); put(a.cornerNei);
|
|
1540
|
+
put(a.polyFlags); put(a.dVStart); put(a.dTStart); put(a.dVerts); put(a.dTris);
|
|
1541
|
+
put(a.linkStart); put(a.linkTo); put(a.linkVia);
|
|
1542
|
+
return u8;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
export function decodeNavMesh(bytes: Uint8Array): NavMesh {
|
|
1546
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1547
|
+
if (dv.getUint32(0, true) !== MAGIC) throw new Error('navmesh: bad magic');
|
|
1548
|
+
if (dv.getUint32(4, true) !== VERSION) throw new Error('navmesh: bad version');
|
|
1549
|
+
const mapId = dv.getInt32(8, true);
|
|
1550
|
+
const polyCount = dv.getUint32(12, true);
|
|
1551
|
+
const vertCount = dv.getUint32(16, true);
|
|
1552
|
+
const cornerCount = dv.getUint32(20, true);
|
|
1553
|
+
const dVertCount = dv.getUint32(24, true);
|
|
1554
|
+
const dTriCount = dv.getUint32(28, true);
|
|
1555
|
+
const linkCount = dv.getUint32(32, true);
|
|
1556
|
+
let o = 40;
|
|
1557
|
+
// Copy, never view: typed arrays need natural alignment and `bytes.byteOffset`
|
|
1558
|
+
// (a fetch/readFile slice) is not guaranteed to be 4-aligned.
|
|
1559
|
+
const grab = (byteLen: number): ArrayBuffer => {
|
|
1560
|
+
const out = bytes.slice(o, o + byteLen).buffer;
|
|
1561
|
+
o += (byteLen + 3) & ~3;
|
|
1562
|
+
return out;
|
|
1563
|
+
};
|
|
1564
|
+
const verts = new Float32Array(grab(vertCount * 12));
|
|
1565
|
+
const polyStart = new Uint32Array(grab((polyCount + 1) * 4));
|
|
1566
|
+
const cornerVert = new Uint32Array(grab(cornerCount * 4));
|
|
1567
|
+
const cornerNei = new Int32Array(grab(cornerCount * 4));
|
|
1568
|
+
const polyFlags = new Uint8Array(grab(polyCount));
|
|
1569
|
+
const dVStart = new Uint32Array(grab((polyCount + 1) * 4));
|
|
1570
|
+
const dTStart = new Uint32Array(grab((polyCount + 1) * 4));
|
|
1571
|
+
const dVerts = new Float32Array(grab(dVertCount * 12));
|
|
1572
|
+
const dTris = new Uint8Array(grab(dTriCount * 4));
|
|
1573
|
+
const linkStart = new Uint32Array(grab((polyCount + 1) * 4));
|
|
1574
|
+
const linkTo = new Int32Array(grab(linkCount * 4));
|
|
1575
|
+
const linkVia = new Uint32Array(grab(linkCount * 4));
|
|
1576
|
+
return buildNavMesh({
|
|
1577
|
+
mapId, verts, polyStart, cornerVert, cornerNei, polyFlags,
|
|
1578
|
+
dVStart, dTStart, dVerts, dTris, linkStart, linkTo, linkVia,
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/**
|
|
1583
|
+
* A CLOSED DOOR IS NOT A WALL TO THE ROUTER. (Operator direction, 2026-09-05.)
|
|
1584
|
+
*
|
|
1585
|
+
* This used to stamp NAV_BLOCKED across the doorway of every shut progress door, so the walker
|
|
1586
|
+
* could not cross a shut Emberseer Out. Making that correct needs all three halves in sync — the
|
|
1587
|
+
* door baked into the plate, a physical stopper in the mesh that lifts when it opens, and a route
|
|
1588
|
+
* step that clicks the door — and only the middle one existed. What shipped was a clickable
|
|
1589
|
+
* picture with no wall behind it and a router that refused to path past it anyway, so the party
|
|
1590
|
+
* could not follow its own route until someone dragged them through by hand.
|
|
1591
|
+
*
|
|
1592
|
+
* So the block is gone and the mesh is what the client's own mmaps say: doorways are walkable.
|
|
1593
|
+
* Progression is unchanged — it never lived here. `requiresFlag` / `objectGate` on the encounter
|
|
1594
|
+
* graph is what still holds a boss shut until his door is opened.
|
|
1595
|
+
*
|
|
1596
|
+
* Kept as a named no-op returning 0 so the UBRS floor state keeps ONE call site to delete when a
|
|
1597
|
+
* real dynamic-obstacle pass lands, rather than the caller quietly forgetting doors exist.
|
|
1598
|
+
*/
|
|
1599
|
+
export function stampNavMeshDoors(
|
|
1600
|
+
_m: NavMesh,
|
|
1601
|
+
_closed: readonly { x: number; y: number; z?: number; o: number }[],
|
|
1602
|
+
): number {
|
|
1603
|
+
return 0;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
/** Lazily allocate the author cost field. 1 everywhere until something stamps. */
|
|
1607
|
+
function authorCostOf(m: NavMesh): Float32Array {
|
|
1608
|
+
if (!m.authorCost) m.authorCost = new Float32Array(m.polyCount).fill(1);
|
|
1609
|
+
return m.authorCost;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// Mark world points as HAZARDS the router should walk around — the mesh's stampNavAvoid. Cost,
|
|
1613
|
+
// never a block: UBRS has 271 Rookery Eggs at 1.77 yd spacing, so blocking them seals the room.
|
|
1614
|
+
export function stampNavMeshAvoid(
|
|
1615
|
+
m: NavMesh,
|
|
1616
|
+
points: readonly { x: number; y: number; z?: number }[],
|
|
1617
|
+
radiusYd: number,
|
|
1618
|
+
mul: number,
|
|
1619
|
+
): number {
|
|
1620
|
+
if (!points.length || !(mul > 1)) return 0;
|
|
1621
|
+
const cost = authorCostOf(m);
|
|
1622
|
+
const seen = new Set<number>();
|
|
1623
|
+
for (const pt of points) {
|
|
1624
|
+
forEachNearbyPoly(m, pt.x, pt.y, radiusYd, (p) => {
|
|
1625
|
+
if (seen.has(p)) return;
|
|
1626
|
+
const dx = m.polyCenter[p * 3]! - pt.x;
|
|
1627
|
+
const dy = m.polyCenter[p * 3 + 1]! - pt.y;
|
|
1628
|
+
if (detHypot(dx, dy) > radiusYd) return;
|
|
1629
|
+
// A hazard on the floor below is not this floor's hazard.
|
|
1630
|
+
if (pt.z != null && Number.isFinite(pt.z)
|
|
1631
|
+
&& Math.abs(m.polyCenter[p * 3 + 2]! - pt.z) > STOREY_DZ_YD) return;
|
|
1632
|
+
seen.add(p);
|
|
1633
|
+
cost[p] = mul;
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
clearRoomGraphCache(m.mapId);
|
|
1637
|
+
return seen.size;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// Operator-authored lane: make the drawn corridor CHEAPER than the floor around it. Takes the
|
|
1641
|
+
// MINIMUM, so a lane through a hazard wins. Where the mesh has no floor, re-bake — never mint.
|
|
1642
|
+
export function stampNavMeshLane(
|
|
1643
|
+
m: NavMesh,
|
|
1644
|
+
segments: readonly { x1: number; y1: number; x2: number; y2: number; z?: number }[],
|
|
1645
|
+
widthYd = 2.5,
|
|
1646
|
+
preferMul = 0.55,
|
|
1647
|
+
): number {
|
|
1648
|
+
if (!segments.length || !(preferMul > 0) || preferMul >= 1) return 0;
|
|
1649
|
+
const cost = authorCostOf(m);
|
|
1650
|
+
const half = widthYd / 2;
|
|
1651
|
+
const seen = new Set<number>();
|
|
1652
|
+
for (const sg of segments) {
|
|
1653
|
+
const vx = sg.x2 - sg.x1;
|
|
1654
|
+
const vy = sg.y2 - sg.y1;
|
|
1655
|
+
const len = detHypot(vx, vy);
|
|
1656
|
+
if (!(len > 1e-6)) continue;
|
|
1657
|
+
const steps = Math.max(1, Math.ceil(len / STEP_YD));
|
|
1658
|
+
for (let i = 0; i <= steps; i++) {
|
|
1659
|
+
const t = i / steps;
|
|
1660
|
+
const px = sg.x1 + vx * t;
|
|
1661
|
+
const py = sg.y1 + vy * t;
|
|
1662
|
+
forEachNearbyPoly(m, px, py, half, (p) => {
|
|
1663
|
+
if (seen.has(p)) return;
|
|
1664
|
+
const dx = m.polyCenter[p * 3]! - px;
|
|
1665
|
+
const dy = m.polyCenter[p * 3 + 1]! - py;
|
|
1666
|
+
if (detHypot(dx, dy) > half) return;
|
|
1667
|
+
if (sg.z != null && Number.isFinite(sg.z)
|
|
1668
|
+
&& Math.abs(m.polyCenter[p * 3 + 2]! - sg.z) > STOREY_DZ_YD) return;
|
|
1669
|
+
seen.add(p);
|
|
1670
|
+
if (preferMul < cost[p]!) cost[p] = preferMul;
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
clearRoomGraphCache(m.mapId);
|
|
1675
|
+
return seen.size;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
/** Drop every author stamp on a mesh (mask reload, editor clear). */
|
|
1679
|
+
export function clearNavMeshAuthorCost(m: NavMesh): void {
|
|
1680
|
+
m.authorCost = null;
|
|
1681
|
+
clearRoomGraphCache(m.mapId);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// Registry — same shape as nav.ts's, so findPathForMap can prefer the mesh
|
|
1685
|
+
|
|
1686
|
+
const meshByMapId = new Map<number, NavMesh>();
|
|
1687
|
+
/** Where each registered mesh was fetched from, so another thread can load the same bytes. */
|
|
1688
|
+
const meshUrlByMapId = new Map<number, string>();
|
|
1689
|
+
|
|
1690
|
+
export function registerNavMeshForMap(mapId: number, mesh: NavMesh, sourceUrl?: string): void {
|
|
1691
|
+
meshByMapId.set(mapId | 0, mesh);
|
|
1692
|
+
if (sourceUrl) meshUrlByMapId.set(mapId | 0, sourceUrl);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/** The URL the map's mesh came from, or null for a mesh registered from memory. */
|
|
1696
|
+
export function navMeshSourceUrl(mapId: number): string | null {
|
|
1697
|
+
return meshUrlByMapId.get(mapId | 0) ?? null;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
export function navMeshForMap(mapId: number): NavMesh | null {
|
|
1701
|
+
return meshByMapId.get(mapId | 0) ?? null;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
export function clearNavMeshRegistry(mapId?: number): void {
|
|
1705
|
+
if (mapId == null) {
|
|
1706
|
+
meshByMapId.clear();
|
|
1707
|
+
meshUrlByMapId.clear();
|
|
1708
|
+
portalNetByMap.clear();
|
|
1709
|
+
} else {
|
|
1710
|
+
meshByMapId.delete(mapId | 0);
|
|
1711
|
+
meshUrlByMapId.delete(mapId | 0);
|
|
1712
|
+
portalNetByMap.delete(mapId | 0);
|
|
1713
|
+
}
|
|
1714
|
+
clearRoomGraphCache(mapId);
|
|
1715
|
+
}
|