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,1329 @@
|
|
|
1
|
+
// Combat geometry — deterministic per-unit yards relative to a pull origin. Low-dimensional, sourced
|
|
2
|
+
// and RNG-free, with no continuous pathing. Pairwise distance is planar, as outdoor aggro is.
|
|
3
|
+
|
|
4
|
+
import type { SpellDef } from '../../data/schema.ts';
|
|
5
|
+
import { triggeredIdsOf, type CompanionCandidate } from './triggeredCompanion.ts';
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_DETECTION_RANGE,
|
|
8
|
+
MIN_AGGRO_DISTANCE,
|
|
9
|
+
planarDistance,
|
|
10
|
+
} from '../../domain/creature/creatureAi.ts';
|
|
11
|
+
import { BASE_CHARGE_SPEED, BASE_RUN_SPEED } from '../../domain/nav/speed.ts';
|
|
12
|
+
import { roleOf } from './roles.ts';
|
|
13
|
+
import { allySideAt, enemySideAt, type Side } from '../core/events.ts';
|
|
14
|
+
import { detHypot, detCos, orbitCos, orbitSin, arcCos, arcSin } from '../core/detMath.ts';
|
|
15
|
+
import { STOREY_DZ_YD } from '../../domain/nav/storey.ts';
|
|
16
|
+
|
|
17
|
+
export { BASE_CHARGE_SPEED, BASE_RUN_SPEED, MIN_AGGRO_DISTANCE };
|
|
18
|
+
|
|
19
|
+
// The melee white-swing gate AND the gap-close STOP band — the reach, never a seed open distance.
|
|
20
|
+
// The same number as the aggro minimum today, but named apart so a seeder cannot open inside it.
|
|
21
|
+
export const MELEE_REACH_YD = MIN_AGGRO_DISTANCE;
|
|
22
|
+
|
|
23
|
+
// Charge / Intercept max yards. Cite: PlayerbotWarriorAI.cpp:175-176 (`fTargetDist > 25.0f`).
|
|
24
|
+
export const CHARGE_INTERCEPT_MAX_YD = 25;
|
|
25
|
+
|
|
26
|
+
// Leeway for in-melee checks against a unit PARKED ON the reach ring: the aim lands movers at exactly
|
|
27
|
+
// the reach, so a zero-slack gate read a unit in visual contact as out of reach.
|
|
28
|
+
export const MELEE_PARK_SLACK_YD = 0.5;
|
|
29
|
+
|
|
30
|
+
/** Float ULP guard on the REACH+SLACK park ring (measured 5.500000000000003 vs 5.5). */
|
|
31
|
+
export const MELEE_CONTACT_EPS_YD = 0.05;
|
|
32
|
+
|
|
33
|
+
/** White-swing / melee-spell contact reach = park stop + float guard. */
|
|
34
|
+
export const MELEE_CONTACT_YD = MELEE_REACH_YD + MELEE_PARK_SLACK_YD + MELEE_CONTACT_EPS_YD;
|
|
35
|
+
|
|
36
|
+
// Extra reach a swing gets while BOTH units are running. Cite: ObjectDefines.h:27 MELEE_LEEWAY;
|
|
37
|
+
// Unit.cpp:787 CanReachWithMeleeAttack; Spell.cpp:6197-6199 for a melee-range spell.
|
|
38
|
+
export const MELEE_LEEWAY_YD = 8 / 3;
|
|
39
|
+
|
|
40
|
+
// `BASE_MELEERANGE_OFFSET` - the flat term every melee reach carries. Cite: Unit.h:65; Object.cpp:1746-1756
|
|
41
|
+
// GetCombinedCombatReach.
|
|
42
|
+
export const BASE_MELEERANGE_OFFSET_YD = 1.33;
|
|
43
|
+
|
|
44
|
+
// A player-sized body's own reach. `creature_model_info.combat_reach` is 1.5 for every player display id
|
|
45
|
+
// (49-58 measured), and a creature carries its own on `spec.combatReachYd`.
|
|
46
|
+
export const PLAYER_COMBAT_REACH_YD = 1.5;
|
|
47
|
+
|
|
48
|
+
// GetCombinedCombatReach for melee — own reach plus the victim's plus 1.33, floored at the attack
|
|
49
|
+
// distance. Two player-sized bodies floor at 5; Onyxia comes out at 26.2.
|
|
50
|
+
export function combinedCombatReachYd(aReachYd?: number, bReachYd?: number): number {
|
|
51
|
+
const a = aReachYd != null && aReachYd > 0 ? aReachYd : PLAYER_COMBAT_REACH_YD;
|
|
52
|
+
const b = bReachYd != null && bReachYd > 0 ? bReachYd : PLAYER_COMBAT_REACH_YD;
|
|
53
|
+
return Math.max(MELEE_REACH_YD, a + b + BASE_MELEERANGE_OFFSET_YD);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// The same call with the melee flag FALSE — the two reaches with no offset and no floor. That is the
|
|
57
|
+
// form the chase generator's backpedal and fan use: 3.0 where the melee form is 5.
|
|
58
|
+
export function combatReachSumYd(aReachYd?: number, bReachYd?: number): number {
|
|
59
|
+
const a = aReachYd != null && aReachYd > 0 ? aReachYd : PLAYER_COMBAT_REACH_YD;
|
|
60
|
+
const b = bReachYd != null && bReachYd > 0 ? bReachYd : PLAYER_COMBAT_REACH_YD;
|
|
61
|
+
return a + b;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Where a melee chase PARKS, which is NOT the ring it swings from: half the combined reach, while
|
|
65
|
+
// only the range CHECK uses the whole thing. It also retires the knife edge that made two bosses die.
|
|
66
|
+
export const CHASE_RANGE_FACTOR = 0.5;
|
|
67
|
+
|
|
68
|
+
export function meleeParkYd(aReachYd?: number, bReachYd?: number): number {
|
|
69
|
+
return CHASE_RANGE_FACTOR * combinedCombatReachYd(aReachYd, bReachYd);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Contact reach for one pair, which is what the C++ has and a bare constant cannot be: a mob chasing a
|
|
73
|
+
// running target swings with 2.67 yd more reach, which is why a same-speed chase never breaks off.
|
|
74
|
+
export function meleeContactYd(
|
|
75
|
+
bothMoving: boolean,
|
|
76
|
+
aReachYd?: number,
|
|
77
|
+
bReachYd?: number,
|
|
78
|
+
): number {
|
|
79
|
+
// The park slack and the float eps stay: `sessionChase.ts:245-258` is two measured unkillable
|
|
80
|
+
// bosses decided by one ULP on this ring, and a pair-sized reach does not make that go away.
|
|
81
|
+
const reach = combinedCombatReachYd(aReachYd, bReachYd)
|
|
82
|
+
+ MELEE_PARK_SLACK_YD + MELEE_CONTACT_EPS_YD;
|
|
83
|
+
return bothMoving ? reach + MELEE_LEEWAY_YD : reach;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The synthetic seed OPEN band — where bodies stand at t=0 of a pull with no real spawn coords. Never
|
|
87
|
+
// the stop band: a melee pull must have yards to walk. Product seeds prefer the per-creature value.
|
|
88
|
+
export const DEFAULT_OPEN_YD = DEFAULT_DETECTION_RANGE;
|
|
89
|
+
|
|
90
|
+
// A combat position in yards from the pull origin. `z` is the unit's floor height in WORLD yards, not
|
|
91
|
+
// an offset, and is absent without a nav map — consumers fall back to planar, never assume 0.
|
|
92
|
+
export interface CombatPosYd {
|
|
93
|
+
x: number;
|
|
94
|
+
y: number;
|
|
95
|
+
z?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** One owner, in domain/nav/storey.ts, which also says why the reach grade is a different 8. */
|
|
99
|
+
export { STOREY_DZ_YD } from '../../domain/nav/storey.ts';
|
|
100
|
+
|
|
101
|
+
// Are two units on the same floor? An unknown z is true, so a session without a nav map behaves as it
|
|
102
|
+
// did when positions were planar. Blackrock Spire has 60.6% of hostile pairs on different storeys.
|
|
103
|
+
export function sameStoreyYd(
|
|
104
|
+
a: CombatPosYd | undefined,
|
|
105
|
+
b: CombatPosYd | undefined,
|
|
106
|
+
): boolean {
|
|
107
|
+
if (a?.z == null || b?.z == null) return true;
|
|
108
|
+
if (!Number.isFinite(a.z) || !Number.isFinite(b.z)) return true;
|
|
109
|
+
return Math.abs(a.z - b.z) <= STOREY_DZ_YD;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Is planar range honest here? A destination 7–20 yd below is still "in 30 yd" in plan, so casters
|
|
113
|
+
// stand and refuse. The tolerance is one mesh step, not a whole storey.
|
|
114
|
+
export function planarRangeHonestYd(
|
|
115
|
+
a: CombatPosYd | undefined,
|
|
116
|
+
b: CombatPosYd | undefined,
|
|
117
|
+
): boolean {
|
|
118
|
+
if (a?.z == null || b?.z == null) return true;
|
|
119
|
+
if (!Number.isFinite(a.z) || !Number.isFinite(b.z)) return true;
|
|
120
|
+
return Math.abs(a.z - b.z) <= 4;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type UnitPositionsYd = Readonly<Record<string, CombatPosYd>>;
|
|
124
|
+
|
|
125
|
+
// Which frame a session's offsets live in: world-translated offsets are a pure translation of real
|
|
126
|
+
// spawn coordinates and may be drawn on the floorplan; pull-local bearings are not world-true.
|
|
127
|
+
export type PullFrame = 'world-translated' | 'pull-local';
|
|
128
|
+
|
|
129
|
+
export interface PullGeometryAnchor {
|
|
130
|
+
frame: PullFrame;
|
|
131
|
+
/** World anchor of the session's (0,0). Present only on `world-translated`. */
|
|
132
|
+
origin?: { x: number; y: number; z?: number };
|
|
133
|
+
/** Instance map id, when the pull resolved against a known cluster map. */
|
|
134
|
+
mapId?: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// §7 party back line / "behind tank" — medium band 10–20 / long 20–30. Use 20 yd so radiusYd 8 melee-pile
|
|
138
|
+
// AoE cannot reach the healer. Cite: PACK-COMBAT-VISION.md §7.2 party line.
|
|
139
|
+
export const PARTY_BACK_LINE_YD = 20;
|
|
140
|
+
|
|
141
|
+
// §7 caster ring outer bound (long band 20–30). Inner = DEFAULT_DETECTION_RANGE (18). Cite:
|
|
142
|
+
// PACK-COMBAT-VISION.md §7.2 "Caster ring ~18–30 yd"; Detection 18.
|
|
143
|
+
export const CASTER_RING_OUTER_YD = 30;
|
|
144
|
+
|
|
145
|
+
// Lateral yard step so co-banded units are not identical points. Stays inside the short band (5–10)
|
|
146
|
+
// relative to pile; not a freeform arena offset. Cite: §7 short band; Unit melee cluster.
|
|
147
|
+
const LATERAL_STEP_YD = 1;
|
|
148
|
+
|
|
149
|
+
/** detMath ORBIT_* length — ring-0 slots 0..13 stay on that table (5-man bit-identical). */
|
|
150
|
+
const ORBIT_SLOT_COUNT = 14;
|
|
151
|
+
|
|
152
|
+
/** Back-line units per row (party/subgroup size); row 0 = old single-row lateralY. */
|
|
153
|
+
const BACK_LINE_ROW_WIDTH = 5;
|
|
154
|
+
|
|
155
|
+
/** Depth between ranged-camp rows (2 × lateral step). */
|
|
156
|
+
const BACK_LINE_ROW_DEPTH_YD = 2 * LATERAL_STEP_YD;
|
|
157
|
+
|
|
158
|
+
/** Ranged-arc angular step in π/30 slots (2 → 12°, ≈4 yd apart on the 20 yd band). */
|
|
159
|
+
const BACK_LINE_ARC_STEP = 2;
|
|
160
|
+
|
|
161
|
+
/** Arc positions per band: ±2 … ±14 slots, i.e. the back semicircle to ±84°. */
|
|
162
|
+
const BACK_LINE_ARC_SLOTS = 14;
|
|
163
|
+
|
|
164
|
+
// First raid-camp bearing clear of a default cone. `CONE_HALF_ANGLE_RAD` is π/6 = 30° = 5 π/30 slots, so
|
|
165
|
+
// slot 6 (36°) is the first bearing a cone cannot reach.
|
|
166
|
+
const RAID_CAMP_MIN_K = 6;
|
|
167
|
+
|
|
168
|
+
/** Widest raid-camp bearing: 15 π/30 slots = 90°, abreast of the boss. */
|
|
169
|
+
const RAID_CAMP_MAX_K = 15;
|
|
170
|
+
|
|
171
|
+
/** Bearings per camp row: k = 15…6, mirrored left/right. */
|
|
172
|
+
const RAID_CAMP_ROW_SLOTS = (RAID_CAMP_MAX_K - RAID_CAMP_MIN_K + 1) * 2;
|
|
173
|
+
|
|
174
|
+
// Pairwise yards. The same storey, or a missing z, stays planar so a ramp cannot lose melee; another
|
|
175
|
+
// floor pays the extra height. Cite: WorldObject::GetDistance is3D; STOREY_DZ_YD.
|
|
176
|
+
export function combatDistanceYd(
|
|
177
|
+
a: CombatPosYd | undefined,
|
|
178
|
+
b: CombatPosYd | undefined,
|
|
179
|
+
): number | undefined {
|
|
180
|
+
if (a == null || b == null) return undefined;
|
|
181
|
+
if (!Number.isFinite(a.x) || !Number.isFinite(a.y) || !Number.isFinite(b.x) || !Number.isFinite(b.y)) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const planar = planarDistance(a.x, a.y, b.x, b.y);
|
|
185
|
+
if (a.z == null || b.z == null || !Number.isFinite(a.z) || !Number.isFinite(b.z)) return planar;
|
|
186
|
+
const dz = Math.abs(a.z - b.z);
|
|
187
|
+
if (dz <= STOREY_DZ_YD) return planar;
|
|
188
|
+
return detHypot(planar, dz - STOREY_DZ_YD);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Radius filter for AoE and ground pools. A missing radius or position is always true, since neither
|
|
192
|
+
// invents a default; another storey is false; the same floor is planar, because a radius is a disc.
|
|
193
|
+
export function isWithinRadiusYd(
|
|
194
|
+
a: CombatPosYd | undefined,
|
|
195
|
+
b: CombatPosYd | undefined,
|
|
196
|
+
radiusYd: number | undefined | null,
|
|
197
|
+
): boolean {
|
|
198
|
+
if (radiusYd == null || !(radiusYd > 0)) return true;
|
|
199
|
+
if (a == null || b == null) return true;
|
|
200
|
+
if (!Number.isFinite(a.x) || !Number.isFinite(a.y) || !Number.isFinite(b.x) || !Number.isFinite(b.y)) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
if (!sameStoreyYd(a, b)) return false;
|
|
204
|
+
return planarDistance(a.x, a.y, b.x, b.y) <= radiusYd;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The spell cast range gate. An unknown distance or a non-positive rangeMax allows. The melee band
|
|
208
|
+
// adds contact leeway, the running leeway, and the pair's own combat reach when the caller has it.
|
|
209
|
+
export function isInSpellRange(
|
|
210
|
+
distanceYd: number | undefined,
|
|
211
|
+
rangeMax: number | undefined | null,
|
|
212
|
+
rangeMin?: number | undefined | null,
|
|
213
|
+
bothMoving = false,
|
|
214
|
+
aReachYd?: number,
|
|
215
|
+
bReachYd?: number,
|
|
216
|
+
): boolean {
|
|
217
|
+
if (distanceYd == null) return true;
|
|
218
|
+
const max = rangeMax ?? 0;
|
|
219
|
+
if (!(max > 0)) return true;
|
|
220
|
+
// Melee-band spells share MELEE_CONTACT_YD with whites (park stop + float eps).
|
|
221
|
+
const reach = max <= MELEE_REACH_YD
|
|
222
|
+
? meleeContactYd(bothMoving, aReachYd, bReachYd)
|
|
223
|
+
: max;
|
|
224
|
+
if (distanceYd > reach) return false;
|
|
225
|
+
const min = rangeMin ?? 0;
|
|
226
|
+
if (min > 0 && distanceYd < min) return false;
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// A straight-line step toward `to` by at most `stepYd`. With `stopAtDist` set it does not overshoot
|
|
231
|
+
// that distance from the destination. No pathfinding, no LOS, deterministic.
|
|
232
|
+
export function stepToward(
|
|
233
|
+
from: CombatPosYd,
|
|
234
|
+
to: CombatPosYd,
|
|
235
|
+
stepYd: number,
|
|
236
|
+
stopAtDist?: number,
|
|
237
|
+
): CombatPosYd {
|
|
238
|
+
if (!(stepYd > 0)) return { x: from.x, y: from.y };
|
|
239
|
+
const dx = to.x - from.x;
|
|
240
|
+
const dy = to.y - from.y;
|
|
241
|
+
const dist = detHypot(dx, dy);
|
|
242
|
+
if (!(dist > 0)) return { x: from.x, y: from.y };
|
|
243
|
+
const stop = stopAtDist != null && stopAtDist > 0 ? stopAtDist : 0;
|
|
244
|
+
if (dist <= stop) return { x: from.x, y: from.y };
|
|
245
|
+
const travel = Math.min(stepYd, dist - stop);
|
|
246
|
+
const ux = dx / dist;
|
|
247
|
+
const uy = dy / dist;
|
|
248
|
+
return { x: from.x + ux * travel, y: from.y + uy * travel };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Charge and Intercept landing: melee reach short of the target on the ray between them. The distance
|
|
252
|
+
// comes from the live pair at cast, not rangeMax and not a walk destination.
|
|
253
|
+
export function chargeLandingPos(from: CombatPosYd, to: CombatPosYd): CombatPosYd {
|
|
254
|
+
const land = stepToward(from, to, 1e9, MELEE_REACH_YD);
|
|
255
|
+
const z = to.z ?? from.z;
|
|
256
|
+
return z != null ? { x: land.x, y: land.y, z } : land;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Yards moved in one fixed combat tick at `speedYdPerSec`. Cite: TUNING.tickMs fixed timestep; Unit.cpp
|
|
260
|
+
// base run / BASE_CHARGE_SPEED.
|
|
261
|
+
export function moveStepYd(speedYdPerSec: number, tickMs: number): number {
|
|
262
|
+
return speedYdPerSec * (tickMs / 1000);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Missile flight time in ms from planar yards and the spell's own speed. A non-positive speed or
|
|
266
|
+
// distance is an instant impact. Cite: spell_template.Speed; Spell.cpp missile delay after cast.
|
|
267
|
+
export function missileTravelMs(distanceYd: number, speedYdPerSec: number): number {
|
|
268
|
+
if (!(speedYdPerSec > 0) || !Number.isFinite(speedYdPerSec)) return 0;
|
|
269
|
+
if (!(distanceYd > 0) || !Number.isFinite(distanceYd)) return 0;
|
|
270
|
+
return Math.round((distanceYd / speedYdPerSec) * 1000);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// The AoE fan origin for the secondary filter: a pbaoe, a cone or an absent shape fans from the
|
|
274
|
+
// caster, while a ground shape fans from the primary target's position.
|
|
275
|
+
export function aoeFanFromCaster(aoeShape: 'pbaoe' | 'ground' | 'cone' | undefined): boolean {
|
|
276
|
+
return aoeShape !== 'ground';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// EffectImplicitTargetA values whose location IS the caster's own position. Everything else names a
|
|
280
|
+
// dest somewhere other than the caster. Cite: SpellTargetDefines.h:22-64.
|
|
281
|
+
const CASTER_LOCATION_TARGET_A: ReadonlySet<number> = new Set([1, 7, 15, 18, 22, 24, 30, 33]);
|
|
282
|
+
|
|
283
|
+
// Fan origin for a live spell: is the area centred on the CASTER or on its target? aoeShape alone
|
|
284
|
+
// cannot answer, since 'ground' only means "an area at a dest" and targetA says where that dest is.
|
|
285
|
+
export function spellAoeFromCaster(spell: {
|
|
286
|
+
aoeShape?: 'pbaoe' | 'ground' | 'cone';
|
|
287
|
+
rangeMax?: number | null;
|
|
288
|
+
rawEffects?: readonly { targetA?: number }[];
|
|
289
|
+
}): boolean {
|
|
290
|
+
if (!((spell.rangeMax ?? 0) > 0)) return true;
|
|
291
|
+
// A pbaoe or a cone is already an area around the caster, and its targetA often names the UNIT the
|
|
292
|
+
// effect picks rather than the centre. Reading targetA there flipped 37 spells the wrong way.
|
|
293
|
+
if (aoeFanFromCaster(spell.aoeShape)) return true;
|
|
294
|
+
for (const e of spell.rawEffects ?? []) {
|
|
295
|
+
const a = e.targetA;
|
|
296
|
+
if (a != null && a > 0) return CASTER_LOCATION_TARGET_A.has(a);
|
|
297
|
+
}
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Classic's default cone half-angle: GetCone returns 60° when the spell has no SpellCones row, so a
|
|
302
|
+
// cone reaches 30° either side of the caster's facing. Cite: Spell.cpp:7585-7591.
|
|
303
|
+
export const CONE_HALF_ANGLE_RAD = Math.PI / 6;
|
|
304
|
+
|
|
305
|
+
// Is `at` inside the caster's cone? The caster faces its current victim, so the arc is measured from
|
|
306
|
+
// that bearing. Fails OPEN on a missing coordinate. Modelled as a circle, one breath hit 49 of 49.
|
|
307
|
+
export function isWithinConeYd(
|
|
308
|
+
origin: CombatPosYd | undefined,
|
|
309
|
+
facingAt: CombatPosYd | undefined,
|
|
310
|
+
at: CombatPosYd | undefined,
|
|
311
|
+
halfAngleRad: number = CONE_HALF_ANGLE_RAD,
|
|
312
|
+
rear = false,
|
|
313
|
+
): boolean {
|
|
314
|
+
if (!origin || !facingAt || !at) return true;
|
|
315
|
+
const fx = facingAt.x - origin.x;
|
|
316
|
+
const fy = facingAt.y - origin.y;
|
|
317
|
+
const tx = at.x - origin.x;
|
|
318
|
+
const ty = at.y - origin.y;
|
|
319
|
+
const fLen = detHypot(fx, fy);
|
|
320
|
+
const tLen = detHypot(tx, ty);
|
|
321
|
+
// Caster standing on its victim has no bearing; a unit on the caster is always hit.
|
|
322
|
+
if (fLen < 1e-6 || tLen < 1e-6) return true;
|
|
323
|
+
const cos = (fx * tx + fy * ty) / (fLen * tLen);
|
|
324
|
+
// angle ≤ half ⇔ cos ≥ cos(half) for half ∈ [0, π]. Default π/6 → ARC k=5 (√3/2).
|
|
325
|
+
const cosHalf = halfAngleRad === CONE_HALF_ANGLE_RAD ? arcCos(5) : detCos(halfAngleRad);
|
|
326
|
+
// A rear cone is the negation of the front test, so the border is STRICT where the front one is
|
|
327
|
+
// inclusive. Cite: Object.cpp:1589-1592; Spell.h:1151-1155.
|
|
328
|
+
if (rear) return cos < -cosHalf;
|
|
329
|
+
return cos >= cosHalf;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// spell_cone.ConeDegrees gives the arc one cone test uses. A NEGATIVE angle is not a narrow cone but
|
|
333
|
+
// a REAR one, so Tail Sweep sweeps behind the dragon and misses everything in front.
|
|
334
|
+
export function coneArcOf(coneDeg: number | null | undefined): {
|
|
335
|
+
halfAngleRad: number;
|
|
336
|
+
rear: boolean;
|
|
337
|
+
} {
|
|
338
|
+
if (coneDeg == null || coneDeg === 60) return { halfAngleRad: CONE_HALF_ANGLE_RAD, rear: false };
|
|
339
|
+
const rear = coneDeg < 0;
|
|
340
|
+
const full = (rear ? -coneDeg : coneDeg) * (Math.PI / 180);
|
|
341
|
+
return { halfAngleRad: full / 2, rear };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// One cone spell as this module needs to see it: a shape and a signed angle. The kit's DISTINCT arcs
|
|
345
|
+
// are what "am I in its breath" asks, since one caster can hold a front cone and a rear one at once.
|
|
346
|
+
interface ConeKitSpell {
|
|
347
|
+
/** Needed by the `castable` filter below; absent on a hand-built fixture spell. */
|
|
348
|
+
id?: number;
|
|
349
|
+
aoeShape?: 'pbaoe' | 'ground' | 'cone';
|
|
350
|
+
coneDeg?: number;
|
|
351
|
+
radiusYd?: number;
|
|
352
|
+
}
|
|
353
|
+
// One wedge a kit can sweep: the arc AND how far it reaches. Both halves are needed — 41 of 129 cone
|
|
354
|
+
// spells stop at 10 yd, so an angle alone puts a healer at 30 yd inside a Cleave that cannot reach.
|
|
355
|
+
export type ConeArc = { halfAngleRad: number; rear: boolean; radiusYd: number };
|
|
356
|
+
const CONE_ARCS_BY_KIT = new WeakMap<readonly ConeKitSpell[], ConeArc[]>();
|
|
357
|
+
|
|
358
|
+
export function coneArcsOfKit(
|
|
359
|
+
spells: readonly ConeKitSpell[],
|
|
360
|
+
// Ids the creature can actually reach — scheduled, or a triggered payload of one. Absent means
|
|
361
|
+
// every kit spell counts, which is what a fixture wants. 36 creatures published a phantom cone.
|
|
362
|
+
castable?: ReadonlySet<number> | null,
|
|
363
|
+
): ConeArc[] {
|
|
364
|
+
// Keyed on the kit ARRAY, like the sibling caches, so a shapeshift misses naturally. Keyed on the
|
|
365
|
+
// array ALONE, which is safe only because the filter is a pure function of that spec.
|
|
366
|
+
const hit = CONE_ARCS_BY_KIT.get(spells);
|
|
367
|
+
if (hit) return hit;
|
|
368
|
+
const seen = new Set<string>();
|
|
369
|
+
const out: ConeArc[] = [];
|
|
370
|
+
for (const s of spells) {
|
|
371
|
+
if (s.aoeShape !== 'cone') continue;
|
|
372
|
+
if (castable && !castable.has((s.id ?? 0) | 0)) continue;
|
|
373
|
+
const deg = s.coneDeg ?? 60;
|
|
374
|
+
// Absent radius is UNBOUNDED, never zero: every baked cone carries one, so a missing value
|
|
375
|
+
// is a fixture, and a fixture must not silently stop being a cone.
|
|
376
|
+
const radiusYd = s.radiusYd ?? Number.POSITIVE_INFINITY;
|
|
377
|
+
const key = `${deg}:${radiusYd}`;
|
|
378
|
+
if (seen.has(key)) continue;
|
|
379
|
+
seen.add(key);
|
|
380
|
+
out.push({ ...coneArcOf(s.coneDeg), radiusYd });
|
|
381
|
+
}
|
|
382
|
+
CONE_ARCS_BY_KIT.set(spells, out);
|
|
383
|
+
return out;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Does this wedge reach `at` — angle AND radius, the pair `isWithinConeYd` deliberately splits. */
|
|
387
|
+
export function coneCovers(
|
|
388
|
+
origin: CombatPosYd | undefined,
|
|
389
|
+
facingAt: CombatPosYd | undefined,
|
|
390
|
+
at: CombatPosYd | undefined,
|
|
391
|
+
arc: ConeArc,
|
|
392
|
+
): boolean {
|
|
393
|
+
if (!origin || !facingAt || !at) return true;
|
|
394
|
+
const d = combatDistanceYd(origin, at);
|
|
395
|
+
if (d != null && d > arc.radiusYd) return false;
|
|
396
|
+
return isWithinConeYd(origin, facingAt, at, arc.halfAngleRad, arc.rear);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Clearance kept off every arc border, in radians (~11.5 deg). The focus turns with its victim, so a slot
|
|
400
|
+
// solved exactly on a cone edge flips in and out as the tank shuffles.
|
|
401
|
+
const MELEE_SLOT_MARGIN_RAD = 0.2;
|
|
402
|
+
|
|
403
|
+
// Where a melee DPS should stand, as a bearing OFFSET from the focus's facing. Two things make the
|
|
404
|
+
// front bad: parry and block need the front arc, and a cone sweeps its own wedge.
|
|
405
|
+
export function meleeSlotOffsetRad(arcs: readonly ConeArc[]): number {
|
|
406
|
+
const band = meleeSlotBandRad(arcs);
|
|
407
|
+
return band ? band.hi : Math.PI;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// The same two bounds as a BAND, so N melee on one boss get N slots. One shared point is not a
|
|
411
|
+
// parking place: body separation pushes each arrival off it, and they walk for the whole fight.
|
|
412
|
+
export function meleeSlotBandRad(arcs: readonly ConeArc[]): { lo: number; hi: number } | null {
|
|
413
|
+
let frontHalf = 0;
|
|
414
|
+
let rearHalf = 0;
|
|
415
|
+
for (const a of arcs) {
|
|
416
|
+
if (a.rear) rearHalf = Math.max(rearHalf, a.halfAngleRad);
|
|
417
|
+
else frontHalf = Math.max(frontHalf, a.halfAngleRad);
|
|
418
|
+
}
|
|
419
|
+
const lo = Math.max(Math.PI / 2, frontHalf) + MELEE_SLOT_MARGIN_RAD;
|
|
420
|
+
const hi = Math.PI - (rearHalf > 0 ? rearHalf + MELEE_SLOT_MARGIN_RAD : 0);
|
|
421
|
+
return hi >= lo ? { lo, hi } : null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// The band of bearing offsets clearing every wedge that reaches this distance. A front wedge raises
|
|
425
|
+
// the floor, a rear one drops the ceiling. Null when they cross — nowhere at that radius is clear.
|
|
426
|
+
export function coneClearBandRad(
|
|
427
|
+
arcs: readonly ConeArc[],
|
|
428
|
+
distanceYd: number,
|
|
429
|
+
): { lo: number; hi: number } | null {
|
|
430
|
+
let lo = 0;
|
|
431
|
+
let hi = Math.PI;
|
|
432
|
+
for (const a of arcs) {
|
|
433
|
+
if (a.radiusYd < distanceYd) continue; // this wedge cannot reach us where we stand
|
|
434
|
+
if (a.rear) hi = Math.min(hi, Math.PI - a.halfAngleRad - MELEE_SLOT_MARGIN_RAD);
|
|
435
|
+
else lo = Math.max(lo, a.halfAngleRad + MELEE_SLOT_MARGIN_RAD);
|
|
436
|
+
}
|
|
437
|
+
return lo > hi ? null : { lo, hi };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Is this spot one a melee DPS should leave — inside the focus's front half, or inside a wedge its
|
|
441
|
+
// kit can sweep? The margin makes it an ENTRY test: without one the moving boundary walks you forever.
|
|
442
|
+
export function meleeSlotIsExposed(
|
|
443
|
+
origin: CombatPosYd | undefined,
|
|
444
|
+
facingAt: CombatPosYd | undefined,
|
|
445
|
+
at: CombatPosYd | undefined,
|
|
446
|
+
arcs: readonly ConeArc[],
|
|
447
|
+
marginRad = 0,
|
|
448
|
+
): boolean {
|
|
449
|
+
if (!origin || !facingAt || !at) return false;
|
|
450
|
+
// Front HALF, so the same pi arc HasInArc uses — not a cone.
|
|
451
|
+
const half = Math.PI / 2 - marginRad;
|
|
452
|
+
if (half > 0 && isWithinConeYd(origin, facingAt, at, half, false)) return true;
|
|
453
|
+
for (const a of arcs) {
|
|
454
|
+
const ha = a.halfAngleRad - marginRad;
|
|
455
|
+
if (ha <= 0) continue;
|
|
456
|
+
const d = combatDistanceYd(origin, at);
|
|
457
|
+
if (d != null && d > a.radiusYd) continue;
|
|
458
|
+
if (isWithinConeYd(origin, facingAt, at, ha, a.rear)) return true;
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Outdoor CallForHelp pack layout: the player at the origin, the primary at honest contact yards
|
|
464
|
+
// rather than stacked on them, and each helper its reported assist distance further out.
|
|
465
|
+
export function seedOutdoorPackPositions(opts: {
|
|
466
|
+
/** Assist distances (yards) in enemy_1.. order — same order as packAssistForPull. */
|
|
467
|
+
helperDistancesYd: readonly number[];
|
|
468
|
+
/**
|
|
469
|
+
* Player-chosen AoE pull mobs (aoePullExtras), seated after the assist helpers. Without these
|
|
470
|
+
* a dialled pull put N mobs in the fight and gave N-1 of them no position, so the stage drew
|
|
471
|
+
* one mob and the player fought a pack it could not see.
|
|
472
|
+
*/
|
|
473
|
+
extraDistancesYd?: readonly number[];
|
|
474
|
+
allyCount?: number;
|
|
475
|
+
includePet?: boolean;
|
|
476
|
+
// Contact yards for the primary. Sourced per-creature at the product seam (`getCreatureAttackDistance`);
|
|
477
|
+
// defaults to the Classic detection band so a caller that omits it still never opens on the melee arc.
|
|
478
|
+
primaryOpenYd?: number;
|
|
479
|
+
/** Honoured for the player only inside a raid camp — see `playerTakesBackLine`. */
|
|
480
|
+
playerBackLine?: boolean;
|
|
481
|
+
allyBackLine?: readonly boolean[];
|
|
482
|
+
/** Per-ally group role in ally_0.. order — raid camps give healers the widest bearings. */
|
|
483
|
+
allyRole?: readonly (string | undefined)[];
|
|
484
|
+
playerRole?: string;
|
|
485
|
+
}): Record<string, CombatPosYd> {
|
|
486
|
+
const origin: CombatPosYd = { x: 0, y: 0 };
|
|
487
|
+
const openYd = opts.primaryOpenYd != null && opts.primaryOpenYd > 0
|
|
488
|
+
? opts.primaryOpenYd
|
|
489
|
+
: DEFAULT_OPEN_YD;
|
|
490
|
+
const out: Record<string, CombatPosYd> = {};
|
|
491
|
+
|
|
492
|
+
const backSides: string[] = [];
|
|
493
|
+
const backHealer: boolean[] = [];
|
|
494
|
+
let meleePileSlot = 0;
|
|
495
|
+
const placeParty = (side: string, back: boolean, role?: string): void => {
|
|
496
|
+
if (back) {
|
|
497
|
+
backSides.push(side);
|
|
498
|
+
backHealer.push(role === 'healer');
|
|
499
|
+
out[side] = { x: 0, y: 0 }; // key order; overwritten below
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
// Melee pile: lateral y so pet/allies are not stacked on the player origin.
|
|
503
|
+
// hypot(open, 1 yd) ≈ open — walk-in and whites stay intact.
|
|
504
|
+
out[side] = { x: origin.x, y: origin.y + lateralY(meleePileSlot++) };
|
|
505
|
+
};
|
|
506
|
+
const nAllies = opts.allyCount ?? 0;
|
|
507
|
+
const raidCamp = isRaidBackLine(
|
|
508
|
+
backLineCount(opts.playerBackLine, opts.allyBackLine, nAllies),
|
|
509
|
+
);
|
|
510
|
+
placeParty('player', playerTakesBackLine(opts.playerBackLine, raidCamp), opts.playerRole);
|
|
511
|
+
if (opts.includePet) placeParty('pet', false);
|
|
512
|
+
for (let i = 0; i < nAllies; i++) {
|
|
513
|
+
placeParty(allySideAt(i), opts.allyBackLine?.[i] === true, opts.allyRole?.[i]);
|
|
514
|
+
}
|
|
515
|
+
const backSlots = backLineSlots(backHealer, raidCamp);
|
|
516
|
+
for (let i = 0; i < backSides.length; i++) {
|
|
517
|
+
const side = backSides[i]!;
|
|
518
|
+
if (raidCamp) {
|
|
519
|
+
out[side] = raidBackLineCamp(backSlots[i]!, openYd);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
const camp = backLineCampOffset(backSlots[i]!);
|
|
523
|
+
out[side] = { x: -PARTY_BACK_LINE_YD + camp.x, y: camp.y };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
out.enemy = { x: openYd, y: 0 };
|
|
527
|
+
for (let i = 0; i < opts.helperDistancesYd.length; i++) {
|
|
528
|
+
const d = opts.helperDistancesYd[i]!;
|
|
529
|
+
// enemy_1 is first helper (primary is index 0 / side 'enemy'); assist distance
|
|
530
|
+
// is measured from the caller, so stack it beyond the primary's station.
|
|
531
|
+
out[enemySideAt(i + 1)] = { x: openYd + d, y: 0 };
|
|
532
|
+
}
|
|
533
|
+
const extras = opts.extraDistancesYd ?? [];
|
|
534
|
+
const afterHelpers = opts.helperDistancesYd.length;
|
|
535
|
+
for (let i = 0; i < extras.length; i++) {
|
|
536
|
+
// Lateral, because a camp of eight can hold two mobs at the same distance and a shared
|
|
537
|
+
// point is one body on the stage. Same step the party melee pile uses.
|
|
538
|
+
out[enemySideAt(afterHelpers + i + 1)] = { x: openYd + extras[i]!, y: lateralY(i + 1) };
|
|
539
|
+
}
|
|
540
|
+
return out;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Pure caster class ids — they stay at cast range even when the staff has real weapon damage. Every
|
|
544
|
+
// hybrid, and every creature with a real white swing, closes into melee.
|
|
545
|
+
const PURE_CASTER_CLASS_IDS: ReadonlySet<number> = new Set([
|
|
546
|
+
5, // PRIEST
|
|
547
|
+
8, // MAGE
|
|
548
|
+
9, // WARLOCK
|
|
549
|
+
]);
|
|
550
|
+
|
|
551
|
+
// SpellDef.role is the BAKE column and is null for most spells, so live motion resolves it instead.
|
|
552
|
+
// Reading the raw field seeded a 39-companion raid's casters into the melee pile.
|
|
553
|
+
function kitSpellRole(s: { role?: string | null }): string | null {
|
|
554
|
+
if (s.role != null) return s.role;
|
|
555
|
+
// Structural callers (probes, fixtures) pass {rangeMax, role} without effects.
|
|
556
|
+
return Array.isArray((s as SpellDef).effects) ? roleOf(s as SpellDef) : null;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Whether a kit's MOTION PLAN is close-and-swing or hold at its longest offense range. Shared by
|
|
560
|
+
// gap-close and the party seed, so a hybrid cannot seed on the back line and then refuse to walk.
|
|
561
|
+
export function kitWantsMeleeBand(opts: {
|
|
562
|
+
/** Hunter bow / wand primary auto — never walk into melee for whites. */
|
|
563
|
+
rangedAuto?: boolean;
|
|
564
|
+
groupRole?: 'tank' | 'healer' | 'damage';
|
|
565
|
+
/** schema CLASS id when known (player / companion). */
|
|
566
|
+
classId?: number;
|
|
567
|
+
/** Main-hand weapon dmgMax — creatures bake melee into this (attackPower often 0). */
|
|
568
|
+
weaponDmgMax?: number;
|
|
569
|
+
// Spells (rangeMax + role). Utility `shoot` does not count as ranged offense when the unit also has a
|
|
570
|
+
// real melee weapon (Warrior Throw must not exile a melee kit).
|
|
571
|
+
spells?: readonly { id?: number; rangeMax?: number | null; role?: string | null }[];
|
|
572
|
+
/** Charge / intercept kit → always wants melee after the gap-close lands. */
|
|
573
|
+
hasCharge?: boolean;
|
|
574
|
+
// Live only: true when a ranged nuke or dot is castable RIGHT NOW. False walks a hybrid with a
|
|
575
|
+
// white swing into melee; undefined is the static seed classification with no live cooldowns.
|
|
576
|
+
hasCastableRangedOffense?: boolean;
|
|
577
|
+
/** Stamped stand yards — when set, this is the motion plan (5 = melee, 20+ = hold). */
|
|
578
|
+
standYards?: number;
|
|
579
|
+
/** Kit ids this unit can reach - scheduled or a payload of one (castableKit.ts). */
|
|
580
|
+
castable?: ReadonlySet<number> | null;
|
|
581
|
+
}): boolean {
|
|
582
|
+
if (opts.standYards != null) return opts.standYards <= MIN_AGGRO_DISTANCE + 1;
|
|
583
|
+
if (opts.rangedAuto) return false;
|
|
584
|
+
if (opts.groupRole === 'tank') return true;
|
|
585
|
+
|
|
586
|
+
// Pure caster classes hold at cast range (staff white-swings are not the plan).
|
|
587
|
+
if (opts.classId != null && PURE_CASTER_CLASS_IDS.has(opts.classId)) return false;
|
|
588
|
+
|
|
589
|
+
const hasRealMelee = (opts.weaponDmgMax ?? 0) > 0;
|
|
590
|
+
// A kit spell the unit cannot REACH is no reason to hold at range. Tested inside the
|
|
591
|
+
// loop below, so nothing is allocated per unit per tick. Cite: castableKit.ts.
|
|
592
|
+
const castable = opts.castable;
|
|
593
|
+
const whiteSwing = (opts.weaponDmgMax ?? 0) > 1;
|
|
594
|
+
let longOffense = 0;
|
|
595
|
+
for (const s of opts.spells ?? []) {
|
|
596
|
+
if (castable && !castable.has((s.id ?? 0) | 0)) continue;
|
|
597
|
+
// Bake `role` is often null — resolve via roleOf when effects exist (kitSpellRole).
|
|
598
|
+
const role = kitSpellRole(s);
|
|
599
|
+
const rangedOffense =
|
|
600
|
+
role === 'nuke' || role === 'dot'
|
|
601
|
+
|| (role === 'shoot' && !hasRealMelee);
|
|
602
|
+
if (!rangedOffense) continue;
|
|
603
|
+
const r = s.rangeMax ?? 0;
|
|
604
|
+
if (r > longOffense) longOffense = r;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (opts.hasCharge) return true;
|
|
608
|
+
|
|
609
|
+
// No long-range offense: close only when there is a real white swing, so paper pins stay put. A
|
|
610
|
+
// healer kit without a bolt lands here too — an unconditional false froze them out of range.
|
|
611
|
+
if (longOffense <= MIN_AGGRO_DISTANCE) return whiteSwing;
|
|
612
|
+
|
|
613
|
+
// Healer with a real ranged offense band holds back (resto LB / Wrath / Smite).
|
|
614
|
+
if (opts.groupRole === 'healer') return false;
|
|
615
|
+
|
|
616
|
+
// Hybrid player class (shaman / paladin / warrior / …) with a real weapon: always
|
|
617
|
+
// close. Lightning Bolt alone must not exile them to 30 yd.
|
|
618
|
+
if (whiteSwing && opts.classId != null && !PURE_CASTER_CLASS_IDS.has(opts.classId)) {
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
// A melee-role companion with a white swing stays front even knowing a bolt. The class id is
|
|
622
|
+
// required: a pet stamps the same role, and an Imp's Firebolt must not walk it into the pile.
|
|
623
|
+
if (whiteSwing && opts.groupRole === 'damage' && opts.classId != null) return true;
|
|
624
|
+
|
|
625
|
+
// A creature or unclassed hybrid walks into melee when the ranged plan is unavailable. While a
|
|
626
|
+
// ranged cast is still available it holds, so a pure caster keeps casting.
|
|
627
|
+
if (whiteSwing && opts.hasCastableRangedOffense === false) return true;
|
|
628
|
+
|
|
629
|
+
// Ranged-nuke kit with cast still available (or static seed with no live CD) → hold.
|
|
630
|
+
return false;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Party unit to back line or melee pile: a healer is always back and a tank always melee, and damage
|
|
634
|
+
// is the inverse of the melee-band test. Pets are placed by the caller, not here.
|
|
635
|
+
export function isPartyBackLineUnit(opts: {
|
|
636
|
+
groupRole?: 'tank' | 'healer' | 'damage';
|
|
637
|
+
/** Player usually has no groupRole — kit heuristics apply. */
|
|
638
|
+
isPlayer?: boolean;
|
|
639
|
+
rangedAuto?: boolean;
|
|
640
|
+
/** schema CLASS id when known — pure casters (mage/warlock/priest) stay back. */
|
|
641
|
+
classId?: number;
|
|
642
|
+
/** Stamped stand yards — 20+ parks on the back line. */
|
|
643
|
+
standYards?: number;
|
|
644
|
+
/** Main-hand weapon dmgMax — hybrids with a real white-swing stay front. */
|
|
645
|
+
weaponDmgMax?: number;
|
|
646
|
+
// The unit's spells. Back-line classification keys on a PRIMARY ranged offense, not on merely
|
|
647
|
+
// owning a bow: a warrior who knows Throw is melee and must not be exiled to the back line.
|
|
648
|
+
spells?: readonly { rangeMax?: number | null; role?: string | null }[];
|
|
649
|
+
}): boolean {
|
|
650
|
+
if (opts.groupRole === 'healer') return true;
|
|
651
|
+
if (opts.groupRole === 'tank') return false;
|
|
652
|
+
if (opts.standYards != null) return opts.standYards > MIN_AGGRO_DISTANCE + 1;
|
|
653
|
+
// Primary ranged auto-attack (hunter in ranged mode / wand caster) → back line.
|
|
654
|
+
if (opts.rangedAuto) return true;
|
|
655
|
+
if (opts.groupRole === 'damage' || opts.isPlayer) {
|
|
656
|
+
// Hybrid / tanky white-swing kits seed front (same plan as live gap-close).
|
|
657
|
+
if (kitWantsMeleeBand({
|
|
658
|
+
rangedAuto: opts.rangedAuto,
|
|
659
|
+
groupRole: opts.groupRole,
|
|
660
|
+
classId: opts.classId,
|
|
661
|
+
weaponDmgMax: opts.weaponDmgMax,
|
|
662
|
+
spells: opts.spells,
|
|
663
|
+
})) {
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
// Pure ranged offense (nuke/dot only — not Throw/Shoot utility) → back line. Shoot-only without a
|
|
667
|
+
// weapon stays front so a stranded Throw warrior still seeds in the melee pile (#187 sibling).
|
|
668
|
+
return (opts.spells ?? []).some(
|
|
669
|
+
(s) => {
|
|
670
|
+
const role = kitSpellRole(s);
|
|
671
|
+
return (role === 'nuke' || role === 'dot') && (s.rangeMax ?? 0) > MIN_AGGRO_DISTANCE;
|
|
672
|
+
},
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
return false;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Two maps, not one keyed on the array alone: the same kit is asked WITH a reachability filter from
|
|
679
|
+
// an enemy seat and WITHOUT one elsewhere, and a single key would let the first answer stand for both.
|
|
680
|
+
const CASTER_KIT_FILTERED = new WeakMap<object, boolean>();
|
|
681
|
+
const CASTER_KIT_RAW = new WeakMap<object, boolean>();
|
|
682
|
+
|
|
683
|
+
export function isEnemyCasterKit(
|
|
684
|
+
spells: readonly (CompanionCandidate & { rangeMax?: number | null; role?: string | null })[],
|
|
685
|
+
opts?: {
|
|
686
|
+
maxPower?: number;
|
|
687
|
+
// Kit ids the unit can REACH. The same argument as the triggered skip below, one population
|
|
688
|
+
// over: the kit also carries the charm bar, and 22 creatures read as casters off one row.
|
|
689
|
+
castable?: ReadonlySet<number> | null;
|
|
690
|
+
},
|
|
691
|
+
): boolean {
|
|
692
|
+
// No power pool → cannot sustain a ranged kit; melee station.
|
|
693
|
+
// (Fixture kobolds list Firebolt but maxPower 0 — stay in melee pile.)
|
|
694
|
+
if (opts != null && !(opts.maxPower! > 0)) return false;
|
|
695
|
+
const castable = opts?.castable;
|
|
696
|
+
// MEMOIZED on the kit array, like its sibling caches. Adding the reachability test to an unmemoized
|
|
697
|
+
// walk cost 4.8 points of combat.sim, and an empty kit is answered before the memo, never keyed.
|
|
698
|
+
if (spells.length === 0) return false;
|
|
699
|
+
const cache = castable ? CASTER_KIT_FILTERED : CASTER_KIT_RAW;
|
|
700
|
+
const memo = cache.get(spells);
|
|
701
|
+
if (memo !== undefined) return memo;
|
|
702
|
+
|
|
703
|
+
// A payload is in the kit so the engine can RESOLVE it, not so the unit can cast it. Frost Armor's
|
|
704
|
+
// Chilled carries a 30 yd range, and 12 melee creatures opened at the caster ring off it.
|
|
705
|
+
const triggered = triggeredIdsOf(spells);
|
|
706
|
+
for (const s of spells) {
|
|
707
|
+
if (s.id != null && triggered.has(s.id)) continue;
|
|
708
|
+
if (castable && !castable.has((s.id ?? 0) | 0)) continue;
|
|
709
|
+
const r = s.rangeMax ?? 0;
|
|
710
|
+
if (r >= DEFAULT_DETECTION_RANGE) {
|
|
711
|
+
cache.set(spells, true);
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
// Explicit long-range nuke/dot below Detection still implies ranged kit
|
|
715
|
+
// (medium band 10–20). Melee specials with rangeMax 5–10 stay melee.
|
|
716
|
+
if (r > 10 && (s.role === 'nuke' || s.role === 'dot')) {
|
|
717
|
+
cache.set(spells, true);
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
cache.set(spells, false);
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/** Symmetric lateral y for the i-th unit in a band (0, +1, −1, +2, −2, …). */
|
|
726
|
+
function lateralY(index: number): number {
|
|
727
|
+
if (index <= 0) return 0;
|
|
728
|
+
const step = Math.ceil(index / 2) * LATERAL_STEP_YD;
|
|
729
|
+
return index % 2 === 1 ? step : -step;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// The ranged camp. Row 0 matches the old lateral spread, so every 5-man is unchanged. Past it the camp
|
|
733
|
+
// fans onto an arc behind the melee pile: a column put all 40 raiders on the boss's facing ray.
|
|
734
|
+
function backLineCampOffset(index: number): CombatPosYd {
|
|
735
|
+
const i = index <= 0 ? 0 : index | 0;
|
|
736
|
+
if (i < BACK_LINE_ROW_WIDTH) return { x: 0, y: lateralY(i) };
|
|
737
|
+
const j = i - BACK_LINE_ROW_WIDTH;
|
|
738
|
+
const band = Math.floor(j / BACK_LINE_ARC_SLOTS);
|
|
739
|
+
const p = j - band * BACK_LINE_ARC_SLOTS;
|
|
740
|
+
// ±12° steps from the back axis, alternating sides: +2, −2, +4, … ±14 (±84°).
|
|
741
|
+
// Starts at ±2 because row 0 already owns the ±6° wedge (y ≤ 2 yd at 20 yd).
|
|
742
|
+
const k = (p % 2 === 0 ? 1 : -1) * (BACK_LINE_ARC_STEP + BACK_LINE_ARC_STEP * Math.floor(p / 2));
|
|
743
|
+
const radius = PARTY_BACK_LINE_YD + band * BACK_LINE_ROW_DEPTH_YD;
|
|
744
|
+
return {
|
|
745
|
+
x: PARTY_BACK_LINE_YD - radius * arcCos(k),
|
|
746
|
+
y: radius * arcSin(k),
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// The raid ranged camp — an arc around the BOSS, measured off its facing. Slot 0 is the WIDEST
|
|
751
|
+
// bearing, since re-seeding healers at 50–82° doubled raid healing and took her to 38.6%.
|
|
752
|
+
function raidBackLineCamp(slot: number, bossOpenYd: number): CombatPosYd {
|
|
753
|
+
const s = slot <= 0 ? 0 : slot | 0;
|
|
754
|
+
const row = Math.floor(s / RAID_CAMP_ROW_SLOTS);
|
|
755
|
+
const p = s - row * RAID_CAMP_ROW_SLOTS;
|
|
756
|
+
const k = Math.max(RAID_CAMP_MIN_K, RAID_CAMP_MAX_K - Math.floor(p / 2));
|
|
757
|
+
const sign = p % 2 === 0 ? 1 : -1;
|
|
758
|
+
const radius = Math.max(
|
|
759
|
+
DEFAULT_DETECTION_RANGE,
|
|
760
|
+
CASTER_RING_OUTER_YD - row * BACK_LINE_ROW_DEPTH_YD,
|
|
761
|
+
);
|
|
762
|
+
return {
|
|
763
|
+
x: bossOpenYd - radius * arcCos(k),
|
|
764
|
+
y: sign * radius * arcSin(k),
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Back-line slot per unit, healers on the widest bearings. Slots used to go in ally-index order, which
|
|
769
|
+
// handed the healers the narrow column. Returns a slot per input position, so the caller keeps order.
|
|
770
|
+
function backLineSlots(healer: readonly boolean[], raid: boolean): number[] {
|
|
771
|
+
const out = new Array<number>(healer.length);
|
|
772
|
+
if (!raid) {
|
|
773
|
+
for (let i = 0; i < healer.length; i++) out[i] = i;
|
|
774
|
+
return out;
|
|
775
|
+
}
|
|
776
|
+
let next = 0;
|
|
777
|
+
for (let i = 0; i < healer.length; i++) if (healer[i]) out[i] = next++;
|
|
778
|
+
for (let i = 0; i < healer.length; i++) if (!healer[i]) out[i] = next++;
|
|
779
|
+
return out;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/** Back line big enough that the fan has left row 0 — the raid camp owns it from here. */
|
|
783
|
+
function isRaidBackLine(count: number): boolean {
|
|
784
|
+
return count > BACK_LINE_ROW_WIDTH;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Does the PLAYER take a back-line stand at the seed? Not outside a raid camp: the player is the
|
|
788
|
+
// puller and the pull-open distance is measured from them, so parking them behind it was a lie.
|
|
789
|
+
function playerTakesBackLine(playerBackLine: boolean | undefined, raidCamp: boolean): boolean {
|
|
790
|
+
return playerBackLine === true && raidCamp;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/** Back-line head count as the raid-camp test sees it — the player included. */
|
|
794
|
+
function backLineCount(
|
|
795
|
+
playerBackLine: boolean | undefined,
|
|
796
|
+
allyBackLine: readonly boolean[] | undefined,
|
|
797
|
+
allyCount: number,
|
|
798
|
+
): number {
|
|
799
|
+
let n = playerBackLine === true ? 1 : 0;
|
|
800
|
+
for (let i = 0; i < allyCount; i++) if (allyBackLine?.[i] === true) n++;
|
|
801
|
+
return n;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Five-man party bubble. Measured on a 75 s Deadmines floor run: without it the healer and mage moved 0.0
|
|
805
|
+
// yd in 75 s while the melee half chased to a 29.5 yd spread.
|
|
806
|
+
export const PARTY_FOLLOW_YD = 14;
|
|
807
|
+
|
|
808
|
+
// Where the party STANDS on the live floor: one stacked group, tanks in front, melee a yard back,
|
|
809
|
+
// ranged and healers three. Nothing here is the boss-relative pull camp the endless floor never runs.
|
|
810
|
+
export const PARTY_BAND_DEPTH_YD: Readonly<Record<'tank' | 'melee' | 'back', number>> = {
|
|
811
|
+
tank: 0,
|
|
812
|
+
melee: 1,
|
|
813
|
+
back: 3,
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
/** Units that fit on a ring: floor(2πr / spacing), capped at the ARC table (60). */
|
|
817
|
+
export function meleeRingCapacity(radiusYd: number, spacingYd: number = LATERAL_STEP_YD): number {
|
|
818
|
+
if (!(radiusYd > 0) || !(spacingYd > 0)) return 1;
|
|
819
|
+
const fit = Math.floor((2 * Math.PI * radiusYd) / spacingYd);
|
|
820
|
+
return Math.max(1, Math.min(60, fit));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Stable melee-orbit slot for a combat side. Used so gap-close aims at different points on the MELEE_REACH
|
|
824
|
+
// circle instead of stacking everyone on the same ray (player + pet + adds all ending on one pixel).
|
|
825
|
+
function meleeOrbitSlot(side: string): number {
|
|
826
|
+
if (side === 'player') return 0;
|
|
827
|
+
if (side === 'pet') return 1;
|
|
828
|
+
if (side.startsWith('ally_')) {
|
|
829
|
+
const n = Number(side.slice('ally_'.length));
|
|
830
|
+
return 2 + (Number.isFinite(n) ? n : 0);
|
|
831
|
+
}
|
|
832
|
+
if (side === 'enemy') return 0;
|
|
833
|
+
if (side.startsWith('enemy_')) {
|
|
834
|
+
const n = Number(side.slice('enemy_'.length));
|
|
835
|
+
return 1 + (Number.isFinite(n) ? n : 0);
|
|
836
|
+
}
|
|
837
|
+
return 0;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// The polar offset for a melee orbit slot. Ring 0 uses the orbit table at melee reach, so a 5-man is
|
|
841
|
+
// bit-identical; further slots densify on that ring, then overflow outward but stay inside the slack.
|
|
842
|
+
function meleeOrbitOffset(slot: number): { radius: number; co: number; so: number } {
|
|
843
|
+
let s = slot | 0;
|
|
844
|
+
// Legacy negative wrap stays on ring 0 of the orbit table (probe + old callers).
|
|
845
|
+
if (s < 0) {
|
|
846
|
+
return { radius: MELEE_REACH_YD, co: orbitCos(s), so: orbitSin(s) };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
let ring = 0;
|
|
850
|
+
for (;;) {
|
|
851
|
+
// Ring 0 at exact MELEE_REACH; further rings step by LATERAL_STEP but clamp to
|
|
852
|
+
// MELEE_REACH + MELEE_PARK_SLACK so gap-close parks still white-swing.
|
|
853
|
+
const rawR = MELEE_REACH_YD + ring * LATERAL_STEP_YD;
|
|
854
|
+
const radius = ring === 0
|
|
855
|
+
? MELEE_REACH_YD
|
|
856
|
+
: Math.min(rawR, MELEE_REACH_YD + MELEE_PARK_SLACK_YD);
|
|
857
|
+
|
|
858
|
+
// Ring 0: orbit table (14) + up to 30 odd ARC gaps (π/30·(2i+1) never equals k·π/7).
|
|
859
|
+
// Outer rings: full circumference/spacing capacity, ARC-even fan.
|
|
860
|
+
const spacingCap = meleeRingCapacity(radius);
|
|
861
|
+
const cap = ring === 0
|
|
862
|
+
? Math.min(ORBIT_SLOT_COUNT + 30, Math.max(ORBIT_SLOT_COUNT, spacingCap))
|
|
863
|
+
: spacingCap;
|
|
864
|
+
|
|
865
|
+
if (s < cap) {
|
|
866
|
+
if (ring === 0 && s < ORBIT_SLOT_COUNT) {
|
|
867
|
+
return { radius: MELEE_REACH_YD, co: orbitCos(s), so: orbitSin(s) };
|
|
868
|
+
}
|
|
869
|
+
if (ring === 0) {
|
|
870
|
+
// Odd ARC indices only — disjoint from the two orbit-coincident ARC angles (0, π).
|
|
871
|
+
const densified = s - ORBIT_SLOT_COUNT;
|
|
872
|
+
const k = (2 * densified + 1) % 60;
|
|
873
|
+
return { radius: MELEE_REACH_YD, co: arcCos(k), so: arcSin(k) };
|
|
874
|
+
}
|
|
875
|
+
// Even fan; cap ≤ 60 so floor(s·60/cap) is injective for s ∈ [0, cap).
|
|
876
|
+
const k = Math.floor((s * 60) / cap) % 60;
|
|
877
|
+
return { radius, co: arcCos(k), so: arcSin(k) };
|
|
878
|
+
}
|
|
879
|
+
s -= cap;
|
|
880
|
+
ring++;
|
|
881
|
+
if (ring > 16) {
|
|
882
|
+
const k = s % 60;
|
|
883
|
+
return {
|
|
884
|
+
radius: MELEE_REACH_YD + MELEE_PARK_SLACK_YD,
|
|
885
|
+
co: arcCos(k),
|
|
886
|
+
so: arcSin(k),
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// The aim point on the melee-reach ring around the target, fanned by orbit slot from the approach
|
|
893
|
+
// bearing. Everyone still lands in white-swing range, and a pet no longer shares the player's point.
|
|
894
|
+
export function meleeOrbitAim(
|
|
895
|
+
from: CombatPosYd,
|
|
896
|
+
to: CombatPosYd,
|
|
897
|
+
slot: number,
|
|
898
|
+
): CombatPosYd {
|
|
899
|
+
const dx = from.x - to.x;
|
|
900
|
+
const dy = from.y - to.y;
|
|
901
|
+
// Angle addition instead of atan2→cos/sin: cos(base)=dx/d, sin(base)=dy/d, and ring-0
|
|
902
|
+
// offsets are integer multiples of π/7 (orbit table) so the 5-man path is exact (W1b).
|
|
903
|
+
const d = detHypot(dx, dy);
|
|
904
|
+
const cb = d > 0 ? dx / d : 1;
|
|
905
|
+
const sb = d > 0 ? dy / d : 0;
|
|
906
|
+
const { radius, co, so } = meleeOrbitOffset(slot);
|
|
907
|
+
return {
|
|
908
|
+
x: to.x + radius * (cb * co - sb * so),
|
|
909
|
+
y: to.y + radius * (sb * co + cb * so),
|
|
910
|
+
...(to.z != null && Number.isFinite(to.z) ? { z: to.z } : {}),
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Max yards from the party stand to any enemy at seed. Beyond it the pull is one the party cannot
|
|
915
|
+
// close without a cross-map march; the tighter ring sum was too small for real 8-mob rooms.
|
|
916
|
+
export const MAX_SEED_PARTY_ENEMY_YD = 60;
|
|
917
|
+
|
|
918
|
+
export function injectEnemySlotPosition(opts: {
|
|
919
|
+
/** Index of the new enemy among all enemies after inject (0 = primary 'enemy'). */
|
|
920
|
+
enemyIndex: number;
|
|
921
|
+
/** When true, place on caster ring instead of melee open arc. */
|
|
922
|
+
isCaster?: boolean;
|
|
923
|
+
/** Melee open band (sourced attack distance); defaults to DEFAULT_OPEN_YD. */
|
|
924
|
+
meleeOpenYd?: number;
|
|
925
|
+
}): CombatPosYd {
|
|
926
|
+
const i = Math.max(0, opts.enemyIndex | 0);
|
|
927
|
+
if (opts.isCaster) {
|
|
928
|
+
// A bounded ring: wrap the slot around the circle so a high index still lands on the outer arc.
|
|
929
|
+
// The old lateral formula grew without bound and put enemy 104 at 125 yd.
|
|
930
|
+
const ringSpan = CASTER_RING_OUTER_YD - DEFAULT_DETECTION_RANGE;
|
|
931
|
+
const ring = 12;
|
|
932
|
+
const depth = Math.min(ringSpan, Math.floor(i / ring) * (ringSpan / 3));
|
|
933
|
+
const r = DEFAULT_DETECTION_RANGE + depth;
|
|
934
|
+
const slot = i % ring;
|
|
935
|
+
return {
|
|
936
|
+
x: r * orbitCos(slot),
|
|
937
|
+
y: r * orbitSin(slot),
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
// Melee arc on the OPEN circle (same ~12° step as seedMeleePackPositions).
|
|
941
|
+
// Wrap every 30 slots so a dense floor inject never lands beyond open yards.
|
|
942
|
+
const open = opts.meleeOpenYd != null && opts.meleeOpenYd > 0 ? opts.meleeOpenYd : DEFAULT_OPEN_YD;
|
|
943
|
+
const slot = i % 30;
|
|
944
|
+
return {
|
|
945
|
+
x: open * arcCos(slot),
|
|
946
|
+
y: open * arcSin(slot),
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/** ObjectDefines.h `CONTACT_DISTANCE 1.f` — how far a spawner's summon lands from its feet. */
|
|
951
|
+
export const CONTACT_DISTANCE_YD = 1;
|
|
952
|
+
|
|
953
|
+
// A map-absolute spawn point handed to a summon, plus what the placement may do with it. Script rows
|
|
954
|
+
// arrive bare; a near-caster dest arrives map-verified, with a walkable centre to clamp out from.
|
|
955
|
+
export type SummonWorldPoint = {
|
|
956
|
+
x: number;
|
|
957
|
+
y: number;
|
|
958
|
+
/** Walkable world centre the point was drawn around — the floor clamp's `from`. */
|
|
959
|
+
from?: { x: number; y: number };
|
|
960
|
+
/** Read off this map's spawn table, so the 100 yd wrong-map cap does not apply. */
|
|
961
|
+
mapVerified?: boolean;
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
// Where a summoned add appears: at its SUMMONER, one contact distance out. These used to fall through
|
|
965
|
+
// to a ring around the PULL ORIGIN, which is how frost spectres arrived across the map.
|
|
966
|
+
export function summonSpawnPosition(summoner: CombatPosYd, slot: number): CombatPosYd {
|
|
967
|
+
return {
|
|
968
|
+
x: summoner.x + CONTACT_DISTANCE_YD * orbitCos(slot),
|
|
969
|
+
y: summoner.y + CONTACT_DISTANCE_YD * orbitSin(slot),
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Multi-enemy dungeon pack layout — slots and range bands along the pull axis. Melee enemies open on
|
|
974
|
+
// the OPEN arc, not the reach arc: a pull already in white range has no walk-in, so Charge cannot fire.
|
|
975
|
+
export function seedMeleePackPositions(opts: {
|
|
976
|
+
enemyCount: number;
|
|
977
|
+
allyCount?: number;
|
|
978
|
+
includePet?: boolean;
|
|
979
|
+
/** Player on back line (ranged/caster kit). Default false → melee pile. */
|
|
980
|
+
playerBackLine?: boolean;
|
|
981
|
+
/** Per-ally back-line flags in ally_0.. order. Missing entry → melee. */
|
|
982
|
+
allyBackLine?: readonly boolean[];
|
|
983
|
+
/** Per-ally group role in ally_0.. order — raid camps give healers the widest bearings. */
|
|
984
|
+
allyRole?: readonly (string | undefined)[];
|
|
985
|
+
/** Player group role, when the host knows it (raid healer player). */
|
|
986
|
+
playerRole?: string;
|
|
987
|
+
/** Per-enemy caster-ring flags in enemy / enemy_1.. order. Missing → melee. */
|
|
988
|
+
enemyCaster?: readonly boolean[];
|
|
989
|
+
/**
|
|
990
|
+
* The other side is a TEAM, not a mob pack: an arena or duel-lab team match. Its bodies then
|
|
991
|
+
* take the party's OWN formation mirrored across the contact line, so each team's back line
|
|
992
|
+
* stands behind its own melee and both are the same distance from the enemy. `ARENA_START` is
|
|
993
|
+
* symmetric (`battlegroundRun.ts:32`, mid-12 / mid+12) and `seedDuelGeometry` kept only its
|
|
994
|
+
* distance, so side B was seeded as an approach arc plus a caster ring while side A got the
|
|
995
|
+
* protected -20 yd party line. Measured 2026-09-10: side A won 20/20 mirrors.
|
|
996
|
+
*/
|
|
997
|
+
enemyTeamBackLine?: readonly boolean[];
|
|
998
|
+
/** Enemy group roles in the same order, for the healer bearings the party line already uses. */
|
|
999
|
+
enemyTeamRole?: readonly (string | undefined)[];
|
|
1000
|
+
// Melee **open** band radius (sourced `getCreatureAttackDistance` at the product seam). Defaults to
|
|
1001
|
+
// DEFAULT_OPEN_YD. Never pass MELEE_REACH_YD unless the fight genuinely began in melee.
|
|
1002
|
+
meleeOpenYd?: number;
|
|
1003
|
+
}): Record<string, CombatPosYd> {
|
|
1004
|
+
const out: Record<string, CombatPosYd> = {};
|
|
1005
|
+
const meleeOpenYd = opts.meleeOpenYd != null && opts.meleeOpenYd > 0
|
|
1006
|
+
? opts.meleeOpenYd
|
|
1007
|
+
: DEFAULT_OPEN_YD;
|
|
1008
|
+
|
|
1009
|
+
// Party: the melee pile sits at the origin with a small lateral spread so bodies are not one pixel,
|
|
1010
|
+
// and the back line is placed in two passes because raid camp bearings are handed out at once.
|
|
1011
|
+
const backSides: string[] = [];
|
|
1012
|
+
const backHealer: boolean[] = [];
|
|
1013
|
+
let meleePileSlot = 0;
|
|
1014
|
+
const placeParty = (side: string, back: boolean, role?: string): void => {
|
|
1015
|
+
if (back) {
|
|
1016
|
+
backSides.push(side);
|
|
1017
|
+
backHealer.push(role === 'healer');
|
|
1018
|
+
out[side] = { x: 0, y: 0 }; // keeps party key order; overwritten in the second pass
|
|
1019
|
+
} else {
|
|
1020
|
+
out[side] = { x: 0, y: lateralY(meleePileSlot++) };
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
const nAllies = opts.allyCount ?? 0;
|
|
1025
|
+
const raidCamp = isRaidBackLine(
|
|
1026
|
+
backLineCount(opts.playerBackLine, opts.allyBackLine, nAllies),
|
|
1027
|
+
);
|
|
1028
|
+
placeParty('player', playerTakesBackLine(opts.playerBackLine, raidCamp), opts.playerRole);
|
|
1029
|
+
if (opts.includePet) {
|
|
1030
|
+
// Pets: melee pile with a lateral slot (not on the player's exact point).
|
|
1031
|
+
placeParty('pet', false);
|
|
1032
|
+
}
|
|
1033
|
+
for (let i = 0; i < nAllies; i++) {
|
|
1034
|
+
const back = opts.allyBackLine?.[i] === true;
|
|
1035
|
+
placeParty(allySideAt(i), back, opts.allyRole?.[i]);
|
|
1036
|
+
}
|
|
1037
|
+
const backSlots = backLineSlots(backHealer, raidCamp);
|
|
1038
|
+
for (let i = 0; i < backSides.length; i++) {
|
|
1039
|
+
const side = backSides[i]!;
|
|
1040
|
+
if (raidCamp) {
|
|
1041
|
+
out[side] = raidBackLineCamp(backSlots[i]!, meleeOpenYd);
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
const camp = backLineCampOffset(backSlots[i]!);
|
|
1045
|
+
out[side] = { x: -PARTY_BACK_LINE_YD + camp.x, y: camp.y };
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// --- Enemies: melee open station vs caster ring ---
|
|
1049
|
+
const n = Math.max(0, opts.enemyCount | 0);
|
|
1050
|
+
// Count melee vs caster first so arc placement stays on the open circle (hypot(x,y) === meleeOpenYd) —
|
|
1051
|
+
// lateral y at fixed x would make each slot a different distance and desynchronise the walk-in.
|
|
1052
|
+
let meleeEnemyCount = 0;
|
|
1053
|
+
let casterEnemyCount = 0;
|
|
1054
|
+
for (let i = 0; i < n; i++) {
|
|
1055
|
+
if (opts.enemyCaster?.[i] === true) casterEnemyCount++;
|
|
1056
|
+
else meleeEnemyCount++;
|
|
1057
|
+
}
|
|
1058
|
+
// A TEAM opponent is placed by the party's rule, mirrored: melee on the open arc at
|
|
1059
|
+
// `meleeOpenYd`, back line PARTY_BACK_LINE_YD beyond it. Both sides then read the same.
|
|
1060
|
+
if (opts.enemyTeamBackLine) {
|
|
1061
|
+
const backIdx: number[] = [];
|
|
1062
|
+
const backIsHealer: boolean[] = [];
|
|
1063
|
+
let mSlot = 0;
|
|
1064
|
+
for (let i = 0; i < n; i++) {
|
|
1065
|
+
const side = enemySideAt(i);
|
|
1066
|
+
if (opts.enemyTeamBackLine[i] === true) {
|
|
1067
|
+
backIdx.push(i);
|
|
1068
|
+
backIsHealer.push(opts.enemyTeamRole?.[i] === 'healer');
|
|
1069
|
+
out[side] = { x: meleeOpenYd, y: 0 }; // key order; overwritten below
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
// The party's OWN melee rule mirrored — a pile at the contact point with `lateralY`
|
|
1073
|
+
// offsets — not the mob arc. The arc spreads bodies ~5 yd apart at 24 yd while the party
|
|
1074
|
+
// pile sits 1 yd apart, so a 5v5 mirror opened with A's two melee 1.0 yd apart and B's
|
|
1075
|
+
// 5.0 (measured). Same reason the back line above mirrors rather than fanning on a ring.
|
|
1076
|
+
out[side] = { x: meleeOpenYd, y: lateralY(mSlot++) };
|
|
1077
|
+
}
|
|
1078
|
+
const slots = backLineSlots(backIsHealer, false);
|
|
1079
|
+
for (let j = 0; j < backIdx.length; j++) {
|
|
1080
|
+
const camp = backLineCampOffset(slots[j]!);
|
|
1081
|
+
// Mirror of the party's `-PARTY_BACK_LINE_YD + camp.x`: further from the party, not nearer.
|
|
1082
|
+
out[enemySideAt(backIdx[j]!)] = { x: meleeOpenYd + PARTY_BACK_LINE_YD - camp.x, y: camp.y };
|
|
1083
|
+
}
|
|
1084
|
+
return out;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
let meleeEnemySlot = 0;
|
|
1088
|
+
let casterEnemySlot = 0;
|
|
1089
|
+
for (let i = 0; i < n; i++) {
|
|
1090
|
+
const side = enemySideAt(i);
|
|
1091
|
+
if (opts.enemyCaster?.[i] === true) {
|
|
1092
|
+
// Fan casters along the ring: first at (DETECTION, 0), then alternate y / push x toward outer.
|
|
1093
|
+
// Inner ring DEFAULT_DETECTION_RANGE (18); outer CASTER_RING_OUTER_YD (30) — §7 caster ring.
|
|
1094
|
+
const t = casterEnemySlot++;
|
|
1095
|
+
const ringSpan = CASTER_RING_OUTER_YD - DEFAULT_DETECTION_RANGE;
|
|
1096
|
+
const depth =
|
|
1097
|
+
casterEnemyCount > 1
|
|
1098
|
+
? (t / Math.max(1, casterEnemyCount - 1)) * ringSpan
|
|
1099
|
+
: 0;
|
|
1100
|
+
const x = DEFAULT_DETECTION_RANGE + depth;
|
|
1101
|
+
const y = lateralY(t) * 2;
|
|
1102
|
+
out[side] = { x, y };
|
|
1103
|
+
} else {
|
|
1104
|
+
// Arc on the OPEN circle so pairwise distance to the melee pile is exactly
|
|
1105
|
+
// `meleeOpenYd` for every melee mob — they all walk in together.
|
|
1106
|
+
const t = meleeEnemySlot++;
|
|
1107
|
+
// ~12° steps — small spread, still planar, still one open band.
|
|
1108
|
+
// (t − (n−1)/2)·(π/15) = (2t − n + 1)·(π/30) — integer slot on the π/30 table (W1b).
|
|
1109
|
+
const k = 2 * t - meleeEnemyCount + 1;
|
|
1110
|
+
out[side] = {
|
|
1111
|
+
x: meleeOpenYd * arcCos(k),
|
|
1112
|
+
y: meleeOpenYd * arcSin(k),
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
return out;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// Convenience: seed from role/kit flags already derived by the host (game.ts). Same layout as
|
|
1121
|
+
// seedMeleePackPositions — alias for call sites that pass full kit inputs.
|
|
1122
|
+
function seedPackCombatPositions(opts: {
|
|
1123
|
+
enemyCount: number;
|
|
1124
|
+
allyCount?: number;
|
|
1125
|
+
includePet?: boolean;
|
|
1126
|
+
playerBackLine?: boolean;
|
|
1127
|
+
allyBackLine?: readonly boolean[];
|
|
1128
|
+
enemyCaster?: readonly boolean[];
|
|
1129
|
+
meleeOpenYd?: number;
|
|
1130
|
+
}): Record<string, CombatPosYd> {
|
|
1131
|
+
return seedMeleePackPositions(opts);
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// One cluster spawn member used for the real-coord seed. Coords are world planar yards from the pack
|
|
1135
|
+
// clusters. A z may be present but this seed is strictly planar and ignores it.
|
|
1136
|
+
export interface ClusterSeedMember {
|
|
1137
|
+
guid: number;
|
|
1138
|
+
entry: number;
|
|
1139
|
+
x: number;
|
|
1140
|
+
y: number;
|
|
1141
|
+
z?: number;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Multi-enemy pack layout from REAL cluster spawn coords: enemies sit relative to the pack centroid,
|
|
1145
|
+
// matched to members by entry. Unmatchable returns null so the caller falls back to the synthetic seed.
|
|
1146
|
+
export function seedClusterPackPositions(opts: {
|
|
1147
|
+
/** Pack origin/centroid. Optional z is ignored (planar seed). */
|
|
1148
|
+
origin: { x: number; y: number; z?: number };
|
|
1149
|
+
/** Cluster members for this pull (must cover spawnGuids). Optional z ignored. */
|
|
1150
|
+
members: readonly ClusterSeedMember[];
|
|
1151
|
+
// Enemy creature entries in combat side order (enemy, enemy_1, …) — same order as enemySpecs after
|
|
1152
|
+
// primary + pack expansion.
|
|
1153
|
+
enemyEntries: readonly number[];
|
|
1154
|
+
allyCount?: number;
|
|
1155
|
+
includePet?: boolean;
|
|
1156
|
+
playerBackLine?: boolean;
|
|
1157
|
+
allyBackLine?: readonly boolean[];
|
|
1158
|
+
/** Per-ally group role in ally_0.. order — raid camps give healers the widest bearings. */
|
|
1159
|
+
allyRole?: readonly (string | undefined)[];
|
|
1160
|
+
playerRole?: string;
|
|
1161
|
+
// Approach standoff from the pack centroid for the melee pile. Ring 1: "do not snap melee to 5 yd" — the
|
|
1162
|
+
// party opens at the honest open band and walks in. Defaults to DEFAULT_OPEN_YD.
|
|
1163
|
+
meleeOpenYd?: number;
|
|
1164
|
+
}): Record<string, CombatPosYd> | null {
|
|
1165
|
+
const n = opts.enemyEntries.length;
|
|
1166
|
+
// Solo: the enemy sits on the origin, so the mean is ~0 and the approach falls back to +x, often
|
|
1167
|
+
// into a wall. Keep the synthetic pull-local layout for 1v1; multi-spawn packs keep the real one.
|
|
1168
|
+
if (n <= 1) return null;
|
|
1169
|
+
if (!opts.members.length) return null;
|
|
1170
|
+
|
|
1171
|
+
// We need one member slot per enemy; a pack with fewer real spawns than combatants
|
|
1172
|
+
// cannot be laid out from spawns alone.
|
|
1173
|
+
if (opts.members.length < n) return null;
|
|
1174
|
+
|
|
1175
|
+
// Two passes. The first matches by entry, which is exact. The second exists because that alone
|
|
1176
|
+
// rejected most real pulls, and a leftover REAL spawn point beats an invented bearing.
|
|
1177
|
+
const unused = opts.members.map((m, i) => i);
|
|
1178
|
+
const slot: (number | null)[] = new Array(n).fill(null);
|
|
1179
|
+
for (let ei = 0; ei < n; ei++) {
|
|
1180
|
+
const entry = opts.enemyEntries[ei]!;
|
|
1181
|
+
const uidx = unused.findIndex((mi) => opts.members[mi]!.entry === entry);
|
|
1182
|
+
if (uidx < 0) continue;
|
|
1183
|
+
slot[ei] = unused[uidx]!;
|
|
1184
|
+
unused.splice(uidx, 1);
|
|
1185
|
+
}
|
|
1186
|
+
for (let ei = 0; ei < n; ei++) {
|
|
1187
|
+
if (slot[ei] != null) continue;
|
|
1188
|
+
const mi = unused.shift();
|
|
1189
|
+
if (mi == null) return null; // ran out of real spawns — refuse rather than invent one
|
|
1190
|
+
slot[ei] = mi;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
const enemyRel: CombatPosYd[] = [];
|
|
1194
|
+
for (let ei = 0; ei < n; ei++) {
|
|
1195
|
+
const m = opts.members[slot[ei]!]!;
|
|
1196
|
+
enemyRel.push({ x: m.x - opts.origin.x, y: m.y - opts.origin.y });
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Approach: from party toward pack mean (or +x when mean is ~0).
|
|
1200
|
+
let meanX = 0;
|
|
1201
|
+
let meanY = 0;
|
|
1202
|
+
for (const p of enemyRel) {
|
|
1203
|
+
meanX += p.x;
|
|
1204
|
+
meanY += p.y;
|
|
1205
|
+
}
|
|
1206
|
+
meanX /= n;
|
|
1207
|
+
meanY /= n;
|
|
1208
|
+
let dirX = meanX;
|
|
1209
|
+
let dirY = meanY;
|
|
1210
|
+
let dirLen = detHypot(dirX, dirY);
|
|
1211
|
+
if (!(dirLen > 1e-6)) {
|
|
1212
|
+
dirX = 1;
|
|
1213
|
+
dirY = 0;
|
|
1214
|
+
dirLen = 1;
|
|
1215
|
+
}
|
|
1216
|
+
const ux = dirX / dirLen;
|
|
1217
|
+
const uy = dirY / dirLen;
|
|
1218
|
+
|
|
1219
|
+
const out: Record<string, CombatPosYd> = {};
|
|
1220
|
+
for (let i = 0; i < n; i++) {
|
|
1221
|
+
out[enemySideAt(i)] = enemyRel[i]!;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// The melee pile stands on the approach side of the centroid at the honest OPEN band, not snapped to
|
|
1225
|
+
// melee reach: the party stands that many yards short of the pack and walks in.
|
|
1226
|
+
const approachYd = opts.meleeOpenYd != null && opts.meleeOpenYd > 0
|
|
1227
|
+
? opts.meleeOpenYd
|
|
1228
|
+
: DEFAULT_OPEN_YD;
|
|
1229
|
+
const meleeBase: CombatPosYd = {
|
|
1230
|
+
x: -ux * approachYd,
|
|
1231
|
+
y: -uy * approachYd,
|
|
1232
|
+
};
|
|
1233
|
+
// Back line: further opposite the approach (far side of the pack from the enemies' mean).
|
|
1234
|
+
const backBase: CombatPosYd = {
|
|
1235
|
+
x: meleeBase.x - ux * PARTY_BACK_LINE_YD,
|
|
1236
|
+
y: meleeBase.y - uy * PARTY_BACK_LINE_YD,
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
const backSides: string[] = [];
|
|
1240
|
+
const backHealer: boolean[] = [];
|
|
1241
|
+
let meleePileSlot = 0;
|
|
1242
|
+
const placeParty = (side: string, back: boolean, role?: string): void => {
|
|
1243
|
+
// Perp of approach (ux,uy) is (-uy, ux) — lateral along the party line.
|
|
1244
|
+
if (back) {
|
|
1245
|
+
backSides.push(side);
|
|
1246
|
+
backHealer.push(role === 'healer');
|
|
1247
|
+
out[side] = { x: backBase.x, y: backBase.y }; // key order; overwritten below
|
|
1248
|
+
} else {
|
|
1249
|
+
// Melee pile: fan pet/allies off the tank point along the party line.
|
|
1250
|
+
const lat = lateralY(meleePileSlot++);
|
|
1251
|
+
out[side] = {
|
|
1252
|
+
x: meleeBase.x + (-uy * lat),
|
|
1253
|
+
y: meleeBase.y + (ux * lat),
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
const nAllies = opts.allyCount ?? 0;
|
|
1259
|
+
const raidCamp = isRaidBackLine(
|
|
1260
|
+
backLineCount(opts.playerBackLine, opts.allyBackLine, nAllies),
|
|
1261
|
+
);
|
|
1262
|
+
placeParty('player', playerTakesBackLine(opts.playerBackLine, raidCamp), opts.playerRole);
|
|
1263
|
+
if (opts.includePet) placeParty('pet', false);
|
|
1264
|
+
for (let i = 0; i < nAllies; i++) {
|
|
1265
|
+
placeParty(allySideAt(i), opts.allyBackLine?.[i] === true, opts.allyRole?.[i]);
|
|
1266
|
+
}
|
|
1267
|
+
const backSlots = backLineSlots(backHealer, raidCamp);
|
|
1268
|
+
for (let i = 0; i < backSides.length; i++) {
|
|
1269
|
+
const side = backSides[i]!;
|
|
1270
|
+
if (raidCamp) {
|
|
1271
|
+
// Pull-frame yards (+x toward the pack centroid) rotated onto the approach axis.
|
|
1272
|
+
const camp = raidBackLineCamp(backSlots[i]!, approachYd);
|
|
1273
|
+
out[side] = {
|
|
1274
|
+
x: meleeBase.x + ux * camp.x + (-uy * camp.y),
|
|
1275
|
+
y: meleeBase.y + uy * camp.x + (ux * camp.y),
|
|
1276
|
+
};
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
// Further-back rows step along −approach (same as synthetic camp depth).
|
|
1280
|
+
const camp = backLineCampOffset(backSlots[i]!);
|
|
1281
|
+
const depth = -camp.x; // row * BACK_LINE_ROW_DEPTH_YD ≥ 0
|
|
1282
|
+
out[side] = {
|
|
1283
|
+
x: backBase.x - ux * depth + (-uy * camp.y),
|
|
1284
|
+
y: backBase.y - uy * depth + (ux * camp.y),
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// A wide pack room leaves a far member past the seed cap, so slide the WHOLE party toward the pack
|
|
1289
|
+
// mean until the worst enemy is in reach. Enemy coords stay real; only the stand moves.
|
|
1290
|
+
const partyKeys = Object.keys(out).filter((k) => k === 'player' || k === 'pet' || k.startsWith('ally_'));
|
|
1291
|
+
const p0 = out.player;
|
|
1292
|
+
if (p0 && partyKeys.length) {
|
|
1293
|
+
let worst = 0;
|
|
1294
|
+
let far: CombatPosYd | null = null;
|
|
1295
|
+
for (let i = 0; i < n; i++) {
|
|
1296
|
+
const e = out[enemySideAt(i)];
|
|
1297
|
+
if (!e) continue;
|
|
1298
|
+
const d = detHypot(e.x - p0.x, e.y - p0.y);
|
|
1299
|
+
if (d > worst) { worst = d; far = e; }
|
|
1300
|
+
}
|
|
1301
|
+
// ε under the cap so floating point never reports maxSeedYd=60.0000001 as OVER.
|
|
1302
|
+
const cap = MAX_SEED_PARTY_ENEMY_YD - 1e-6;
|
|
1303
|
+
if (far && worst > cap) {
|
|
1304
|
+
const excess = worst - cap;
|
|
1305
|
+
const dx = far.x - p0.x;
|
|
1306
|
+
const dy = far.y - p0.y;
|
|
1307
|
+
const len = detHypot(dx, dy);
|
|
1308
|
+
if (len > 1e-6) {
|
|
1309
|
+
const sx = (dx / len) * excess;
|
|
1310
|
+
const sy = (dy / len) * excess;
|
|
1311
|
+
for (const k of partyKeys) {
|
|
1312
|
+
const p = out[k]!;
|
|
1313
|
+
out[k] = { x: p.x + sx, y: p.y + sy };
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
return out;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/** Look up a combatant side in a positions map. */
|
|
1323
|
+
export function positionOf(
|
|
1324
|
+
positions: UnitPositionsYd | undefined | null,
|
|
1325
|
+
side: Side | string,
|
|
1326
|
+
): CombatPosYd | undefined {
|
|
1327
|
+
if (!positions) return undefined;
|
|
1328
|
+
return positions[side];
|
|
1329
|
+
}
|