azeroth-shell 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -2
- package/bin/azsh.mjs +19 -0
- package/docs/aifid/eventai-texts.json +4414 -0
- package/docs/dungeons/atlas/atlas-rows-dungeons.json +8989 -0
- package/docs/dungeons/survey/pack-id-migration.json +2418 -0
- package/package.json +35 -4
- package/src/autopilot/bossSkip.ts +26 -0
- package/src/autopilot/combatRotation.ts +824 -0
- package/src/autopilot/config.ts +533 -0
- package/src/autopilot/consumables.ts +58 -0
- package/src/autopilot/encounterStand.ts +121 -0
- package/src/autopilot/objectGatePlan.ts +53 -0
- package/src/autopilot/pathEngage.ts +229 -0
- package/src/autopilot/plan.ts +1643 -0
- package/src/autopilot/questPolicy.ts +274 -0
- package/src/autopilot/routeStep.ts +69 -0
- package/src/autopilot/sessionReport.ts +88 -0
- package/src/autopilot/topOffPower.ts +26 -0
- package/src/autopilot/view.ts +476 -0
- package/src/cli/bootCheck.mjs +34 -0
- package/src/cli/bootData.mjs +57 -0
- package/src/cli/combatLogFilter.ts +153 -0
- package/src/cli/combatLogFormat.ts +613 -0
- package/src/cli/fileRepository.mjs +105 -0
- package/src/cli/gzLoader.mjs +31 -0
- package/src/cli/liveGauntletEscort.ts +978 -0
- package/src/cli/liveStratUndead.ts +305 -0
- package/src/cli/macroHostCombat.ts +545 -0
- package/src/cli/macroHostWorld.ts +330 -0
- package/src/cli/prefs.mjs +31 -0
- package/src/cli/register.mjs +4 -0
- package/src/cli/repl.mjs +693 -0
- package/src/cli/rotationText.mjs +226 -0
- package/src/cli/squad.mjs +105 -0
- package/src/cli/term.mjs +144 -0
- package/src/cli/text.mjs +122 -0
- package/src/cli/tui.mjs +626 -0
- package/src/cli/tuiArena.mjs +109 -0
- package/src/cli/tuiAutopilotSettings.mjs +93 -0
- package/src/cli/tuiBags.mjs +180 -0
- package/src/cli/tuiBars.mjs +133 -0
- package/src/cli/tuiBg.mjs +243 -0
- package/src/cli/tuiBulk.mjs +162 -0
- package/src/cli/tuiCheck.mjs +1882 -0
- package/src/cli/tuiCity.mjs +396 -0
- package/src/cli/tuiCompanionKit.mjs +300 -0
- package/src/cli/tuiCompanions.mjs +471 -0
- package/src/cli/tuiDev.mjs +62 -0
- package/src/cli/tuiDungeon.mjs +606 -0
- package/src/cli/tuiEnchantTarget.mjs +120 -0
- package/src/cli/tuiFight.mjs +577 -0
- package/src/cli/tuiFightPanes.mjs +228 -0
- package/src/cli/tuiFloor.mjs +506 -0
- package/src/cli/tuiGear.mjs +158 -0
- package/src/cli/tuiHonor.mjs +74 -0
- package/src/cli/tuiLoot.mjs +97 -0
- package/src/cli/tuiMacros.mjs +144 -0
- package/src/cli/tuiOutfitter.mjs +115 -0
- package/src/cli/tuiPet.mjs +116 -0
- package/src/cli/tuiProfessions.mjs +800 -0
- package/src/cli/tuiRaid.mjs +127 -0
- package/src/cli/tuiRep.mjs +65 -0
- package/src/cli/tuiSaves.mjs +69 -0
- package/src/cli/tuiSearch.mjs +363 -0
- package/src/cli/tuiSettings.mjs +151 -0
- package/src/cli/tuiSimLab.mjs +80 -0
- package/src/cli/tuiTalents.mjs +60 -0
- package/src/cli/tuiViews.mjs +1178 -0
- package/src/cli/tuiWiki.mjs +181 -0
- package/src/cli/tuiWorld.mjs +361 -0
- package/src/cli/wikiIndex.ts +259 -0
- package/src/cli/wikiSources.ts +355 -0
- package/src/data/alteracValley.ts +176 -0
- package/src/data/appearanceOptions.ts +534 -0
- package/src/data/applyLootChanceSanity.ts +21 -0
- package/src/data/createFlavor.ts +96 -0
- package/src/data/customItems.ts +33 -0
- package/src/data/dataStore.ts +1904 -0
- package/src/data/dungeonGameObjects.ts +84 -0
- package/src/data/dungeonPlateCover.ts +66 -0
- package/src/data/dungeonSpawns.ts +294 -0
- package/src/data/dungeons/alterac-valley.ts +135 -0
- package/src/data/dungeons/apply-dungeon-rooms.ts +29 -0
- package/src/data/dungeons/apply-encounter-outdoors.ts +72 -0
- package/src/data/dungeons/apply-instance-geometry.ts +65 -0
- package/src/data/dungeons/apply-spawn-placement.ts +171 -0
- package/src/data/dungeons/blackfathom-deeps.ts +539 -0
- package/src/data/dungeons/blackrock-depths.ts +1410 -0
- package/src/data/dungeons/blackwing-lair.ts +129 -0
- package/src/data/dungeons/boss-eventai.ts +8 -0
- package/src/data/dungeons/deadmines.ts +410 -0
- package/src/data/dungeons/dire-maul-east.ts +980 -0
- package/src/data/dungeons/dire-maul-north.ts +661 -0
- package/src/data/dungeons/dire-maul-west.ts +785 -0
- package/src/data/dungeons/dungeon-rooms.json +5479 -0
- package/src/data/dungeons/encounter-outdoors.json +359 -0
- package/src/data/dungeons/eventai/blackfathom-deeps.ts +308 -0
- package/src/data/dungeons/eventai/blackrock-depths.ts +1257 -0
- package/src/data/dungeons/eventai/deadmines.ts +264 -0
- package/src/data/dungeons/eventai/dire-maul-east.ts +320 -0
- package/src/data/dungeons/eventai/gnomeregan.ts +213 -0
- package/src/data/dungeons/eventai/helpers.ts +79 -0
- package/src/data/dungeons/eventai/index.ts +133 -0
- package/src/data/dungeons/eventai/lbrs.ts +372 -0
- package/src/data/dungeons/eventai/maraudon.ts +323 -0
- package/src/data/dungeons/eventai/ragefire-chasm.ts +108 -0
- package/src/data/dungeons/eventai/razorfen-downs.ts +207 -0
- package/src/data/dungeons/eventai/razorfen-kraul.ts +326 -0
- package/src/data/dungeons/eventai/scholomance.ts +680 -0
- package/src/data/dungeons/eventai/shadowfang-keep.ts +230 -0
- package/src/data/dungeons/eventai/sm-cathedral.ts +75 -0
- package/src/data/dungeons/eventai/sm-graveyard.ts +150 -0
- package/src/data/dungeons/eventai/sm-library.ts +55 -0
- package/src/data/dungeons/eventai/stratholme-undead.ts +127 -0
- package/src/data/dungeons/eventai/sunken-temple.ts +582 -0
- package/src/data/dungeons/eventai/the-stockade.ts +228 -0
- package/src/data/dungeons/eventai/ubrs.ts +225 -0
- package/src/data/dungeons/eventai/uldaman.ts +355 -0
- package/src/data/dungeons/eventai/wailing-caverns.ts +396 -0
- package/src/data/dungeons/eventai/zul-farrak.ts +591 -0
- package/src/data/dungeons/gnomeregan.ts +909 -0
- package/src/data/dungeons/index.ts +126 -0
- package/src/data/dungeons/instance-geometry.json +5233 -0
- package/src/data/dungeons/key-sources.json +169 -0
- package/src/data/dungeons/lbrs.ts +996 -0
- package/src/data/dungeons/maraudon.ts +786 -0
- package/src/data/dungeons/molten-core.ts +194 -0
- package/src/data/dungeons/naxxramas.ts +173 -0
- package/src/data/dungeons/onyxias-lair.ts +71 -0
- package/src/data/dungeons/pack-from-clusters.ts +1256 -0
- package/src/data/dungeons/ragefire-chasm.ts +238 -0
- package/src/data/dungeons/razorfen-downs.ts +698 -0
- package/src/data/dungeons/razorfen-kraul.ts +766 -0
- package/src/data/dungeons/ruins-of-ahnqiraj.ts +121 -0
- package/src/data/dungeons/scholomance.ts +1076 -0
- package/src/data/dungeons/shadowfang-keep.ts +740 -0
- package/src/data/dungeons/sm-armory.ts +266 -0
- package/src/data/dungeons/sm-cathedral.ts +430 -0
- package/src/data/dungeons/sm-graveyard.ts +482 -0
- package/src/data/dungeons/sm-library.ts +337 -0
- package/src/data/dungeons/stratholme-live.ts +808 -0
- package/src/data/dungeons/stratholme-undead.ts +1164 -0
- package/src/data/dungeons/sunken-temple.ts +931 -0
- package/src/data/dungeons/temple-of-ahnqiraj.ts +119 -0
- package/src/data/dungeons/the-stockade.ts +282 -0
- package/src/data/dungeons/ubrs.ts +1286 -0
- package/src/data/dungeons/uldaman.ts +685 -0
- package/src/data/dungeons/wailing-caverns.ts +356 -0
- package/src/data/dungeons/zul-farrak.ts +686 -0
- package/src/data/dungeons/zul-gurub.ts +142 -0
- package/src/data/dungeons.ts +42 -0
- package/src/data/fixtures.ts +446 -0
- package/src/data/gameobject-data.json.gz +0 -0
- package/src/data/item-data.json.gz +0 -0
- package/src/data/loot-data.json.gz +0 -0
- package/src/data/maps/atlas-approach-maps.json +1425 -0
- package/src/data/maps/atlas-landmarks.json +1711 -0
- package/src/data/maps/dungeon-viewports.json +392 -0
- package/src/data/materializeModDamagePercentTaken.ts +70 -0
- package/src/data/materializeModHealthRegen.ts +89 -0
- package/src/data/materializeSpellReputation.ts +26 -0
- package/src/data/pageTexts.ts +19 -0
- package/src/data/profession-data.json.gz +0 -0
- package/src/data/raid-plans/blackfathom-deeps.json +86 -0
- package/src/data/raid-plans/blackrock-depths.json +419 -0
- package/src/data/raid-plans/blackwing-lair.json +328 -0
- package/src/data/raid-plans/deadmines.json +55 -0
- package/src/data/raid-plans/dire-maul-east.json +94 -0
- package/src/data/raid-plans/dire-maul-north.json +70 -0
- package/src/data/raid-plans/dire-maul-west.json +75 -0
- package/src/data/raid-plans/gnomeregan.json +131 -0
- package/src/data/raid-plans/lbrs.json +176 -0
- package/src/data/raid-plans/maraudon.json +102 -0
- package/src/data/raid-plans/molten-core.json +169 -0
- package/src/data/raid-plans/naxxramas.json +354 -0
- package/src/data/raid-plans/onyxias-lair.json +18 -0
- package/src/data/raid-plans/ragefire-chasm.json +30 -0
- package/src/data/raid-plans/razorfen-downs.json +75 -0
- package/src/data/raid-plans/razorfen-kraul.json +53 -0
- package/src/data/raid-plans/ruins-of-ahnqiraj.json +110 -0
- package/src/data/raid-plans/scholomance.json +170 -0
- package/src/data/raid-plans/shadowfang-keep.json +87 -0
- package/src/data/raid-plans/sm-armory.json +53 -0
- package/src/data/raid-plans/sm-cathedral.json +72 -0
- package/src/data/raid-plans/sm-graveyard.json +63 -0
- package/src/data/raid-plans/sm-library.json +23 -0
- package/src/data/raid-plans/stratholme-live.json +129 -0
- package/src/data/raid-plans/stratholme-undead.json +73 -0
- package/src/data/raid-plans/sunken-temple.json +159 -0
- package/src/data/raid-plans/temple-of-ahnqiraj.json +163 -0
- package/src/data/raid-plans/the-stockade.json +42 -0
- package/src/data/raid-plans/ubrs.json +53 -0
- package/src/data/raid-plans/uldaman.json +131 -0
- package/src/data/raid-plans/wailing-caverns.json +90 -0
- package/src/data/raid-plans/zul-farrak.json +156 -0
- package/src/data/raid-plans/zul-gurub.json +307 -0
- package/src/data/raidInstanceCreatures.mjs +473 -0
- package/src/data/rotations/index.ts +152 -0
- package/src/data/rotations/layout.mjs +72 -0
- package/src/data/rotations/pve/druid/balance.json +754 -0
- package/src/data/rotations/pve/druid/feral-tank.json +2448 -0
- package/src/data/rotations/pve/druid/feral.json +2660 -0
- package/src/data/rotations/pve/druid/restoration.json +424 -0
- package/src/data/rotations/pve/hunter/beast-mastery.json +1288 -0
- package/src/data/rotations/pve/hunter/marksmanship.json +1260 -0
- package/src/data/rotations/pve/hunter/survival.json +1373 -0
- package/src/data/rotations/pve/mage/arcane-leveling.json +1798 -0
- package/src/data/rotations/pve/mage/fire-leveling.json +2003 -0
- package/src/data/rotations/pve/mage/frost-leveling.json +1912 -0
- package/src/data/rotations/pve/paladin/holy.json +755 -0
- package/src/data/rotations/pve/paladin/protection.json +962 -0
- package/src/data/rotations/pve/paladin/retribution.json +924 -0
- package/src/data/rotations/pve/priest/discipline.json +772 -0
- package/src/data/rotations/pve/priest/holy.json +707 -0
- package/src/data/rotations/pve/priest/shadow.json +787 -0
- package/src/data/rotations/pve/rogue/assassination.json +2477 -0
- package/src/data/rotations/pve/rogue/combat.json +2483 -0
- package/src/data/rotations/pve/rogue/subtlety.json +2523 -0
- package/src/data/rotations/pve/shaman/elemental.json +1157 -0
- package/src/data/rotations/pve/shaman/enhancement.json +1085 -0
- package/src/data/rotations/pve/shaman/restoration.json +592 -0
- package/src/data/rotations/pve/warlock/affliction.json +1185 -0
- package/src/data/rotations/pve/warlock/demonology.json +1165 -0
- package/src/data/rotations/pve/warlock/destruction.json +1177 -0
- package/src/data/rotations/pve/warrior/arms.json +3020 -0
- package/src/data/rotations/pve/warrior/fury.json +2996 -0
- package/src/data/rotations/pve/warrior/protection.json +2509 -0
- package/src/data/rotations/pvp/druid/balance.json +593 -0
- package/src/data/rotations/pvp/druid/feral.json +3276 -0
- package/src/data/rotations/pvp/druid/restoration.json +662 -0
- package/src/data/rotations/pvp/hunter/beast-mastery.json +665 -0
- package/src/data/rotations/pvp/hunter/marksmanship.json +2406 -0
- package/src/data/rotations/pvp/hunter/survival.json +635 -0
- package/src/data/rotations/pvp/mage/arcane.json +574 -0
- package/src/data/rotations/pvp/mage/fire.json +570 -0
- package/src/data/rotations/pvp/mage/frost.json +4048 -0
- package/src/data/rotations/pvp/paladin/holy.json +1498 -0
- package/src/data/rotations/pvp/paladin/protection.json +567 -0
- package/src/data/rotations/pvp/paladin/retribution.json +1110 -0
- package/src/data/rotations/pvp/priest/discipline.json +1848 -0
- package/src/data/rotations/pvp/priest/holy.json +6340 -0
- package/src/data/rotations/pvp/priest/shadow.json +2329 -0
- package/src/data/rotations/pvp/rogue/assassination.json +1322 -0
- package/src/data/rotations/pvp/rogue/combat.json +5258 -0
- package/src/data/rotations/pvp/rogue/subtlety.json +586 -0
- package/src/data/rotations/pvp/shaman/elemental.json +1404 -0
- package/src/data/rotations/pvp/shaman/enhancement.json +1277 -0
- package/src/data/rotations/pvp/shaman/restoration.json +2490 -0
- package/src/data/rotations/pvp/warlock/affliction.json +1017 -0
- package/src/data/rotations/pvp/warlock/demonology.json +1503 -0
- package/src/data/rotations/pvp/warlock/destruction.json +638 -0
- package/src/data/rotations/pvp/warrior/arms.json +5665 -0
- package/src/data/rotations/pvp/warrior/fury.json +850 -0
- package/src/data/rotations/pvp/warrior/protection.json +777 -0
- package/src/data/schema/bundle.ts +511 -0
- package/src/data/schema/constants.ts +49 -0
- package/src/data/schema/creature.ts +454 -0
- package/src/data/schema/encounter.ts +155 -0
- package/src/data/schema/faction.ts +48 -0
- package/src/data/schema/item.ts +297 -0
- package/src/data/schema/itemEnchant.ts +105 -0
- package/src/data/schema/npc.ts +44 -0
- package/src/data/schema/primitives.ts +295 -0
- package/src/data/schema/profession.ts +226 -0
- package/src/data/schema/quest.ts +154 -0
- package/src/data/schema/spell.ts +1149 -0
- package/src/data/schema/talent.ts +74 -0
- package/src/data/schema/world.ts +124 -0
- package/src/data/schema.ts +19 -0
- package/src/data/skinNodeGapFill.ts +68 -0
- package/src/data/spell-data.json.gz +0 -0
- package/src/data/spell-script-targets.json +1 -0
- package/src/data/tables/ahItemValues.json.gz +0 -0
- package/src/data/tables/alteracValley.json.gz +0 -0
- package/src/data/tables/areatriggerTeleports.json.gz +0 -0
- package/src/data/tables/botNames.json.gz +0 -0
- package/src/data/tables/botPopulation.json.gz +0 -0
- package/src/data/tables/botSpecs.json.gz +0 -0
- package/src/data/tables/classLevelStats.json.gz +0 -0
- package/src/data/tables/combatConditions.json.gz +0 -0
- package/src/data/tables/combatSkillMeta.json.gz +0 -0
- package/src/data/tables/conditions.json.gz +0 -0
- package/src/data/tables/createInfo.json.gz +0 -0
- package/src/data/tables/creatureAggroGroups.json.gz +0 -0
- package/src/data/tables/creatureClassLevelStats.json.gz +0 -0
- package/src/data/tables/creaturePatrolPacks.json.gz +0 -0
- package/src/data/tables/creatureWeapons.json.gz +0 -0
- package/src/data/tables/creatures.json.gz +0 -0
- package/src/data/tables/dbScripts.json.gz +0 -0
- package/src/data/tables/dungeonAuthoredRoutes.json.gz +0 -0
- package/src/data/tables/dungeonCensus.json.gz +0 -0
- package/src/data/tables/dungeonGameObjects.json.gz +0 -0
- package/src/data/tables/dungeonPacks.json.gz +0 -0
- package/src/data/tables/dungeonPlateCover.json.gz +0 -0
- package/src/data/tables/dungeonSpawns.json.gz +0 -0
- package/src/data/tables/dungeonSurvey.json.gz +0 -0
- package/src/data/tables/durabilityCosts.json.gz +0 -0
- package/src/data/tables/durabilityQuality.json.gz +0 -0
- package/src/data/tables/enchantApplicators.json.gz +0 -0
- package/src/data/tables/entranceYards.json.gz +0 -0
- package/src/data/tables/explorationBasexp.json.gz +0 -0
- package/src/data/tables/factionTemplates.json.gz +0 -0
- package/src/data/tables/factions.json.gz +0 -0
- package/src/data/tables/gameEvents.json.gz +0 -0
- package/src/data/tables/gameObjects.json.gz +0 -0
- package/src/data/tables/gossipMenus.json.gz +0 -0
- package/src/data/tables/graveyards.json.gz +0 -0
- package/src/data/tables/instanceMaps.json.gz +0 -0
- package/src/data/tables/itemEnchantmentTemplates.json.gz +0 -0
- package/src/data/tables/itemEnchants.json.gz +0 -0
- package/src/data/tables/itemRandomProperties.json.gz +0 -0
- package/src/data/tables/itemSets.json.gz +0 -0
- package/src/data/tables/items.json.gz +0 -0
- package/src/data/tables/levelStats.json.gz +0 -0
- package/src/data/tables/locks.json.gz +0 -0
- package/src/data/tables/map-pin-overrides.json.gz +0 -0
- package/src/data/tables/nonCombatTrash.json.gz +0 -0
- package/src/data/tables/npcTexts.json.gz +0 -0
- package/src/data/tables/pageTexts.json.gz +0 -0
- package/src/data/tables/petAbilityTP.json.gz +0 -0
- package/src/data/tables/petGenericSpells.json.gz +0 -0
- package/src/data/tables/petLevelStats.json.gz +0 -0
- package/src/data/tables/playerbot-weightscales.json.gz +0 -0
- package/src/data/tables/pools.json.gz +0 -0
- package/src/data/tables/quests.json.gz +0 -0
- package/src/data/tables/races.json.gz +0 -0
- package/src/data/tables/reputationRanks.json.gz +0 -0
- package/src/data/tables/reputationRates.json.gz +0 -0
- package/src/data/tables/reputationSpillover.json.gz +0 -0
- package/src/data/tables/ridingSpells.json.gz +0 -0
- package/src/data/tables/scriptTexts.json.gz +0 -0
- package/src/data/tables/soundClipVolume.json.gz +0 -0
- package/src/data/tables/spellChain.json.gz +0 -0
- package/src/data/tables/spellTeleports.json.gz +0 -0
- package/src/data/tables/spells.json.gz +0 -0
- package/src/data/tables/talents.json.gz +0 -0
- package/src/data/tables/tavernTriggers.json.gz +0 -0
- package/src/data/tables/taxiNodes.json.gz +0 -0
- package/src/data/tables/taxiRoutes.json.gz +0 -0
- package/src/data/tables/trainers.json.gz +0 -0
- package/src/data/tables/transports.json.gz +0 -0
- package/src/data/tables/travelAnchors.json.gz +0 -0
- package/src/data/tables/travelEdges.json.gz +0 -0
- package/src/data/tables/unitConditions.json.gz +0 -0
- package/src/data/tables/vendors.json.gz +0 -0
- package/src/data/tables/weaponEnchantProcs.json.gz +0 -0
- package/src/data/tables/xp.json.gz +0 -0
- package/src/data/tables/zoneWeather.json.gz +0 -0
- package/src/data/tables/zones.json.gz +0 -0
- package/src/data/world-data.ts +56 -0
- package/src/data/zone-music-sets.json +2751 -0
- package/src/dev/etl/lib/loot-chance-sanity.mjs +180 -0
- package/src/domain/audioQuality.mjs +145 -0
- package/src/domain/bg/ab.ts +433 -0
- package/src/domain/bg/arenaComps.ts +644 -0
- package/src/domain/bg/arenaLab.ts +695 -0
- package/src/domain/bg/arenaPace.ts +31 -0
- package/src/domain/bg/arenaRating.ts +110 -0
- package/src/domain/bg/av.ts +758 -0
- package/src/domain/bg/avEnemyPush.ts +219 -0
- package/src/domain/bg/avField.ts +283 -0
- package/src/domain/bg/avInstanceNodes.ts +197 -0
- package/src/domain/bg/avInstanceScore.ts +123 -0
- package/src/domain/bg/avQuestItems.ts +55 -0
- package/src/domain/bg/avQuests.ts +210 -0
- package/src/domain/bg/avSession.ts +196 -0
- package/src/domain/bg/bgConsumes.ts +269 -0
- package/src/domain/bg/bgPrebuff.ts +153 -0
- package/src/domain/bg/bgRest.ts +91 -0
- package/src/domain/bg/bgRoster.ts +305 -0
- package/src/domain/bg/bgTier.ts +61 -0
- package/src/domain/bg/bgWorld.ts +862 -0
- package/src/domain/bg/match.ts +215 -0
- package/src/domain/bg/strategies.ts +552 -0
- package/src/domain/bg/types.ts +291 -0
- package/src/domain/bg/wsg.ts +264 -0
- package/src/domain/bots/actionProviderSpec.ts +38 -0
- package/src/domain/bots/autoConsumables.ts +285 -0
- package/src/domain/bots/betweenPullRest.ts +239 -0
- package/src/domain/bots/botGear.ts +248 -0
- package/src/domain/bots/botInventory.ts +486 -0
- package/src/domain/bots/botPet.ts +535 -0
- package/src/domain/bots/botPool.ts +291 -0
- package/src/domain/bots/botProfessions.ts +334 -0
- package/src/domain/bots/companionActionBar.ts +209 -0
- package/src/domain/bots/companionHireCost.ts +236 -0
- package/src/domain/bots/companionMissions.ts +582 -0
- package/src/domain/bots/companionPrep.ts +254 -0
- package/src/domain/bots/companionQuests.ts +270 -0
- package/src/domain/bots/companionRep.ts +75 -0
- package/src/domain/bots/companionRotation.ts +708 -0
- package/src/domain/bots/companionXp.ts +66 -0
- package/src/domain/bots/consumableShare.ts +811 -0
- package/src/domain/bots/dpsMate.ts +1072 -0
- package/src/domain/bots/groupCompReport.ts +132 -0
- package/src/domain/bots/healthstoneSupply.ts +198 -0
- package/src/domain/bots/outfitter.ts +1056 -0
- package/src/domain/bots/paladinPrePull.ts +231 -0
- package/src/domain/bots/partyRole.ts +40 -0
- package/src/domain/bots/playerbotEquipCache.ts +412 -0
- package/src/domain/bots/playerbotItemEligibility.ts +60 -0
- package/src/domain/bots/playerbotStatWeight.ts +853 -0
- package/src/domain/bots/playerbotUnavailableItems.ts +147 -0
- package/src/domain/bots/prePullBuffs.ts +219 -0
- package/src/domain/bots/prePullStealth.ts +82 -0
- package/src/domain/bots/runSupply.ts +251 -0
- package/src/domain/bots/runSupplyPrefs.ts +28 -0
- package/src/domain/bots/sendTradeGoods.ts +394 -0
- package/src/domain/bots/soulShardCap.ts +35 -0
- package/src/domain/bots/squad.ts +842 -0
- package/src/domain/bots/squadRun.ts +81 -0
- package/src/domain/character/actionbar.ts +346 -0
- package/src/domain/character/appearance.ts +165 -0
- package/src/domain/character/buildCombatant.ts +1726 -0
- package/src/domain/character/character.ts +356 -0
- package/src/domain/character/characterName.ts +43 -0
- package/src/domain/character/characterRatings.ts +424 -0
- package/src/domain/character/characterSpells.ts +282 -0
- package/src/domain/character/characterStats.ts +1559 -0
- package/src/domain/character/characterTalents.ts +398 -0
- package/src/domain/character/combatSkills.ts +994 -0
- package/src/domain/character/displayForm.ts +72 -0
- package/src/domain/character/formTalentAuras.ts +171 -0
- package/src/domain/character/forms.ts +499 -0
- package/src/domain/character/races.ts +40 -0
- package/src/domain/character/resurrectionSickness.ts +49 -0
- package/src/domain/character/spellbook.ts +169 -0
- package/src/domain/character/stanceBar.ts +354 -0
- package/src/domain/character/stancePrereq.ts +70 -0
- package/src/domain/character/talentBuilds.ts +63 -0
- package/src/domain/character/talentImport.ts +130 -0
- package/src/domain/character/talentLink.ts +123 -0
- package/src/domain/character/talents.ts +201 -0
- package/src/domain/core/conditionSnapshot.ts +92 -0
- package/src/domain/core/hover.ts +14 -0
- package/src/domain/core/ids.ts +58 -0
- package/src/domain/core/internalNameTags.ts +5 -0
- package/src/domain/core/runtimeDataPatches.ts +357 -0
- package/src/domain/creature/aiTargeting.ts +588 -0
- package/src/domain/creature/combatCondition.ts +157 -0
- package/src/domain/creature/creatureAggro.ts +804 -0
- package/src/domain/creature/creatureAi.ts +206 -0
- package/src/domain/creature/creatureAiConstants.ts +661 -0
- package/src/domain/creature/creatureCastList.ts +1007 -0
- package/src/domain/creature/creatureEventAi.ts +1409 -0
- package/src/domain/creature/creatureGroups.ts +144 -0
- package/src/domain/creature/creatureLinking.ts +333 -0
- package/src/domain/creature/creatureScaling.ts +320 -0
- package/src/domain/creature/creatureTrigger.ts +53 -0
- package/src/domain/creature/creatureWander.ts +74 -0
- package/src/domain/creature/eventAiTexts.ts +145 -0
- package/src/domain/creature/faction.ts +287 -0
- package/src/domain/creature/forcedReaction.ts +48 -0
- package/src/domain/creature/outdoorHuntable.ts +74 -0
- package/src/domain/creature/patrolPacks.ts +210 -0
- package/src/domain/creature/reputation.ts +967 -0
- package/src/domain/creature/unitFlags.ts +74 -0
- package/src/domain/creature/worldBosses.ts +393 -0
- package/src/domain/dungeonMusic.mjs +48 -0
- package/src/domain/economy/auction.ts +282 -0
- package/src/domain/economy/auctionBot.ts +1060 -0
- package/src/domain/economy/honorPrice.ts +100 -0
- package/src/domain/economy/mail.ts +958 -0
- package/src/domain/economy/money.ts +42 -0
- package/src/domain/economy/syntheticClassTrainers.ts +62 -0
- package/src/domain/economy/trading.ts +296 -0
- package/src/domain/economy/vendorSellAll.ts +252 -0
- package/src/domain/economy/vendorStock.ts +146 -0
- package/src/domain/items/ammo.ts +817 -0
- package/src/domain/items/bank.ts +162 -0
- package/src/domain/items/consumableFamily.ts +205 -0
- package/src/domain/items/craftUpgrades.ts +83 -0
- package/src/domain/items/createItem.ts +93 -0
- package/src/domain/items/durability.ts +581 -0
- package/src/domain/items/equipment.ts +441 -0
- package/src/domain/items/equipmentHelper.ts +767 -0
- package/src/domain/items/inventory.ts +561 -0
- package/src/domain/items/itemBonding.ts +93 -0
- package/src/domain/items/itemContainer.ts +245 -0
- package/src/domain/items/itemDisplayName.ts +33 -0
- package/src/domain/items/itemInstance.ts +181 -0
- package/src/domain/items/itemRandomEnchantOutcomes.ts +315 -0
- package/src/domain/items/itemRandomProps.ts +77 -0
- package/src/domain/items/itemRestrictions.ts +329 -0
- package/src/domain/items/itemTeaches.ts +82 -0
- package/src/domain/items/itemUseCooldown.ts +52 -0
- package/src/domain/items/itemUseTarget.ts +90 -0
- package/src/domain/items/junkCategories.ts +174 -0
- package/src/domain/items/junkFilter.ts +368 -0
- package/src/domain/items/keyring.ts +131 -0
- package/src/domain/items/loot.ts +221 -0
- package/src/domain/items/mounts.ts +190 -0
- package/src/domain/items/onUsePayload.ts +102 -0
- package/src/domain/items/pendingLoot.ts +107 -0
- package/src/domain/items/relic.ts +105 -0
- package/src/domain/items/rocketBoots.ts +36 -0
- package/src/domain/items/sortContainer.ts +139 -0
- package/src/domain/items/spareContainers.ts +71 -0
- package/src/domain/items/spellReagentIndex.ts +131 -0
- package/src/domain/items/staleConsumables.ts +66 -0
- package/src/domain/items/weaponSpender.ts +110 -0
- package/src/domain/listDungeonIds.mjs +28 -0
- package/src/domain/macro/compose.ts +239 -0
- package/src/domain/macro/conditions.ts +260 -0
- package/src/domain/macro/creatureTypes.ts +40 -0
- package/src/domain/macro/describe.ts +169 -0
- package/src/domain/macro/icon.ts +60 -0
- package/src/domain/macro/index.ts +20 -0
- package/src/domain/macro/normalize.ts +112 -0
- package/src/domain/macro/parse.ts +316 -0
- package/src/domain/macro/plan.ts +288 -0
- package/src/domain/macro/rotation.ts +590 -0
- package/src/domain/macro/rotationEnv.ts +173 -0
- package/src/domain/macro/slots.ts +101 -0
- package/src/domain/macro/snapshot.ts +114 -0
- package/src/domain/macro/templates.ts +446 -0
- package/src/domain/macro/types.ts +70 -0
- package/src/domain/macro/units.ts +93 -0
- package/src/domain/macro/weaponTypes.ts +83 -0
- package/src/domain/nav/cityTeleports.ts +10 -0
- package/src/domain/nav/mounting.ts +113 -0
- package/src/domain/nav/nav.ts +43 -0
- package/src/domain/nav/navCollision.ts +606 -0
- package/src/domain/nav/navOutdoors.ts +91 -0
- package/src/domain/nav/navRegistry.ts +179 -0
- package/src/domain/nav/navRoomGraph.ts +302 -0
- package/src/domain/nav/navTypes.ts +43 -0
- package/src/domain/nav/navmesh.ts +1715 -0
- package/src/domain/nav/speed.ts +200 -0
- package/src/domain/nav/storey.ts +10 -0
- package/src/domain/nav/travel.ts +725 -0
- package/src/domain/nav/travelTypes.ts +75 -0
- package/src/domain/pet/pet.ts +1150 -0
- package/src/domain/pet/petActionBar.ts +180 -0
- package/src/domain/pet/petSpellbook.ts +39 -0
- package/src/domain/pet/petStable.ts +81 -0
- package/src/domain/professions/autoDisenchant.ts +236 -0
- package/src/domain/professions/characterEnchants.ts +176 -0
- package/src/domain/professions/craftCooldown.ts +52 -0
- package/src/domain/professions/enchantApply.ts +62 -0
- package/src/domain/professions/enchantTargets.ts +114 -0
- package/src/domain/professions/enchanting.ts +236 -0
- package/src/domain/professions/gatherFilter.ts +149 -0
- package/src/domain/professions/gearEnchant.ts +137 -0
- package/src/domain/professions/heldTools.ts +29 -0
- package/src/domain/professions/learnAllRecipes.ts +146 -0
- package/src/domain/professions/miningHarvest.ts +69 -0
- package/src/domain/professions/partyDisenchant.ts +294 -0
- package/src/domain/professions/partyWork.ts +83 -0
- package/src/domain/professions/pickpocket.ts +172 -0
- package/src/domain/professions/poisons.ts +240 -0
- package/src/domain/professions/professions.ts +677 -0
- package/src/domain/professions/rareNodes.ts +45 -0
- package/src/domain/professions/skinLoot.ts +27 -0
- package/src/domain/professions/vendorReagents.ts +152 -0
- package/src/domain/professions/weaponImbueHand.ts +171 -0
- package/src/domain/questFarmFilter.mjs +42 -0
- package/src/domain/quests/doQuest.ts +1204 -0
- package/src/domain/quests/gossip.ts +308 -0
- package/src/domain/quests/gossipTalk.ts +298 -0
- package/src/domain/quests/interpreter/canComplete.ts +150 -0
- package/src/domain/quests/interpreter/claim.ts +137 -0
- package/src/domain/quests/interpreter/dbscript/handlers.ts +1371 -0
- package/src/domain/quests/interpreter/dbscript/handlersBatch.ts +1251 -0
- package/src/domain/quests/interpreter/dbscript/hooks.ts +331 -0
- package/src/domain/quests/interpreter/dbscript/index.ts +212 -0
- package/src/domain/quests/interpreter/dbscript/opcodes.ts +257 -0
- package/src/domain/quests/interpreter/dbscript/registry.ts +1194 -0
- package/src/domain/quests/interpreter/dbscript/registryAreatriggerScripts.ts +539 -0
- package/src/domain/quests/interpreter/dbscript/registryAshenvale.ts +553 -0
- package/src/domain/quests/interpreter/dbscript/registryAzshara.ts +272 -0
- package/src/domain/quests/interpreter/dbscript/registryDarkshore.ts +637 -0
- package/src/domain/quests/interpreter/dbscript/registryDesolace.ts +501 -0
- package/src/domain/quests/interpreter/dbscript/registryDustwallowMarsh.ts +735 -0
- package/src/domain/quests/interpreter/dbscript/registryFelwood.ts +493 -0
- package/src/domain/quests/interpreter/dbscript/registryFeralas.ts +485 -0
- package/src/domain/quests/interpreter/dbscript/registryGoScripts.ts +203 -0
- package/src/domain/quests/interpreter/dbscript/registryMoonglade.ts +288 -0
- package/src/domain/quests/interpreter/dbscript/registryNpcProfessions.ts +230 -0
- package/src/domain/quests/interpreter/dbscript/registryNpcsSpecial.ts +545 -0
- package/src/domain/quests/interpreter/dbscript/registryRazorfenDowns.ts +293 -0
- package/src/domain/quests/interpreter/dbscript/registryRazorfenKraul.ts +221 -0
- package/src/domain/quests/interpreter/dbscript/registryRedridge.ts +112 -0
- package/src/domain/quests/interpreter/dbscript/registrySearingGorge.ts +125 -0
- package/src/domain/quests/interpreter/dbscript/registrySiblings.ts +51 -0
- package/src/domain/quests/interpreter/dbscript/registrySilverpineForest.ts +172 -0
- package/src/domain/quests/interpreter/dbscript/registryStonetalonMountains.ts +180 -0
- package/src/domain/quests/interpreter/dbscript/registryStormwindCity.ts +369 -0
- package/src/domain/quests/interpreter/dbscript/registryStranglethornVale.ts +115 -0
- package/src/domain/quests/interpreter/dbscript/registryStratholme.ts +146 -0
- package/src/domain/quests/interpreter/dbscript/registrySunkenTemple.ts +149 -0
- package/src/domain/quests/interpreter/dbscript/registrySwampOfSorrows.ts +163 -0
- package/src/domain/quests/interpreter/dbscript/registryTanaris.ts +318 -0
- package/src/domain/quests/interpreter/dbscript/registryTeldrassil.ts +120 -0
- package/src/domain/quests/interpreter/dbscript/registryTheBarrens.ts +603 -0
- package/src/domain/quests/interpreter/dbscript/registryThousandNeedles.ts +499 -0
- package/src/domain/quests/interpreter/dbscript/registryTirisfalGlades.ts +222 -0
- package/src/domain/quests/interpreter/dbscript/registryUngoroCrater.ts +291 -0
- package/src/domain/quests/interpreter/dbscript/registryWarEffort.ts +773 -0
- package/src/domain/quests/interpreter/dbscript/registryWesternPlaguelands.ts +206 -0
- package/src/domain/quests/interpreter/dbscript/registryWestfall.ts +190 -0
- package/src/domain/quests/interpreter/dbscript/registryWetlands.ts +186 -0
- package/src/domain/quests/interpreter/dbscript/registryWinterspring.ts +235 -0
- package/src/domain/quests/interpreter/dbscript/types.ts +817 -0
- package/src/domain/quests/interpreter/dbscript/vm.ts +171 -0
- package/src/domain/quests/interpreter/diff.ts +349 -0
- package/src/domain/quests/interpreter/fixtures/oracle-questlog-qi2.json +2417 -0
- package/src/domain/quests/interpreter/flags.ts +101 -0
- package/src/domain/quests/interpreter/index.ts +113 -0
- package/src/domain/quests/interpreter/live.ts +178 -0
- package/src/domain/quests/interpreter/types.ts +81 -0
- package/src/domain/quests/interpreter/updaters.ts +412 -0
- package/src/domain/quests/questCorpseUse.ts +155 -0
- package/src/domain/quests/questDialog.ts +43 -0
- package/src/domain/quests/questFight.ts +34 -0
- package/src/domain/quests/questObjectiveLabel.ts +39 -0
- package/src/domain/quests/questRewardChoice.ts +85 -0
- package/src/domain/quests/questScriptCredit.ts +26 -0
- package/src/domain/quests/questScriptItemUse.ts +74 -0
- package/src/domain/quests/questTameRods.ts +79 -0
- package/src/domain/quests/questlog.ts +1545 -0
- package/src/domain/quests/talkDeliver.ts +170 -0
- package/src/domain/raid/globalActionBar.ts +195 -0
- package/src/domain/raid/instanceRoster.ts +194 -0
- package/src/domain/raid/partySummon.ts +198 -0
- package/src/domain/raid/partyVault.ts +320 -0
- package/src/domain/raid/raidBossAbilities.ts +268 -0
- package/src/domain/raid/raidComp.ts +335 -0
- package/src/domain/raid/raidCustomRows.ts +453 -0
- package/src/domain/raid/raidEncounterGear.ts +298 -0
- package/src/domain/raid/raidEncounterTactics.ts +412 -0
- package/src/domain/raid/raidPlan.ts +509 -0
- package/src/domain/raid/raidPlanEdits.ts +98 -0
- package/src/domain/raid/raidPlanPacks.ts +480 -0
- package/src/domain/raid/raidRecovery.ts +183 -0
- package/src/domain/raid/raidRosterTemplate.ts +138 -0
- package/src/domain/rotation/auraSide.ts +68 -0
- package/src/domain/rotation/build.ts +3031 -0
- package/src/domain/rotation/classes/druid.ts +58 -0
- package/src/domain/rotation/classes/hooks.ts +59 -0
- package/src/domain/rotation/classes/hunter.ts +156 -0
- package/src/domain/rotation/classes/index.ts +104 -0
- package/src/domain/rotation/classes/mage.ts +99 -0
- package/src/domain/rotation/classes/paladin.ts +296 -0
- package/src/domain/rotation/classes/priest.ts +94 -0
- package/src/domain/rotation/classes/pullOpener.ts +92 -0
- package/src/domain/rotation/classes/rogue.ts +69 -0
- package/src/domain/rotation/classes/shaman.ts +29 -0
- package/src/domain/rotation/classes/supportSpec.ts +94 -0
- package/src/domain/rotation/classes/warlock.ts +81 -0
- package/src/domain/rotation/classes/warrior.ts +402 -0
- package/src/domain/rotation/classify.ts +1381 -0
- package/src/domain/rotation/condMetricPicker.ts +379 -0
- package/src/domain/rotation/consumables.ts +242 -0
- package/src/domain/rotation/evaluate.ts +1115 -0
- package/src/domain/rotation/finisherLadder.ts +145 -0
- package/src/domain/rotation/invent.ts +938 -0
- package/src/domain/rotation/killOrder.ts +30 -0
- package/src/domain/rotation/openerForm.ts +22 -0
- package/src/domain/rotation/paladinBuffs.ts +568 -0
- package/src/domain/rotation/prebuff.ts +283 -0
- package/src/domain/rotation/raidRowTuning.ts +34 -0
- package/src/domain/rotation/rotation.ts +11 -0
- package/src/domain/rotation/rotationProfiles.ts +180 -0
- package/src/domain/rotation/rotationSuggestions.ts +851 -0
- package/src/domain/rotation/ruleAfterChain.ts +49 -0
- package/src/domain/rotation/spec.ts +70 -0
- package/src/domain/rotation/spellPicker.ts +140 -0
- package/src/domain/rotation/stand.ts +143 -0
- package/src/domain/rotation/types.ts +718 -0
- package/src/domain/run/battlegroundRun.ts +111 -0
- package/src/domain/run/canEnterContext.ts +32 -0
- package/src/domain/run/death.ts +561 -0
- package/src/domain/run/deathRecovery.ts +289 -0
- package/src/domain/run/duel.ts +141 -0
- package/src/domain/run/dungeonRun.ts +344 -0
- package/src/domain/run/floorRest.ts +159 -0
- package/src/domain/run/honor.ts +557 -0
- package/src/domain/run/honorWeek.ts +180 -0
- package/src/domain/run/whenMap.ts +977 -0
- package/src/domain/scriptdev/arathiHighlands.ts +445 -0
- package/src/domain/scriptdev/ashenvale.ts +635 -0
- package/src/domain/scriptdev/ashenvaleHelpers.ts +1215 -0
- package/src/domain/scriptdev/azshara.ts +892 -0
- package/src/domain/scriptdev/blackfathomDeeps.ts +212 -0
- package/src/domain/scriptdev/blackrockDepths.ts +1434 -0
- package/src/domain/scriptdev/blackrockSpire.ts +730 -0
- package/src/domain/scriptdev/blackwingLair.ts +521 -0
- package/src/domain/scriptdev/burningSteppes.ts +62 -0
- package/src/domain/scriptdev/classFilter.ts +43 -0
- package/src/domain/scriptdev/darkshore.ts +817 -0
- package/src/domain/scriptdev/darkshoreHelpers.ts +853 -0
- package/src/domain/scriptdev/deadmines.ts +331 -0
- package/src/domain/scriptdev/desolace.ts +1239 -0
- package/src/domain/scriptdev/direMaul.ts +859 -0
- package/src/domain/scriptdev/durotar.ts +532 -0
- package/src/domain/scriptdev/dustwallowMarsh.ts +758 -0
- package/src/domain/scriptdev/dustwallowMarshHelpers.ts +994 -0
- package/src/domain/scriptdev/easternPlaguelands.ts +21 -0
- package/src/domain/scriptdev/felwood.ts +1055 -0
- package/src/domain/scriptdev/feralas.ts +571 -0
- package/src/domain/scriptdev/feralasHelpers.ts +838 -0
- package/src/domain/scriptdev/gnomeregan.ts +466 -0
- package/src/domain/scriptdev/guards.ts +616 -0
- package/src/domain/scriptdev/hinterlands.ts +693 -0
- package/src/domain/scriptdev/host.ts +1134 -0
- package/src/domain/scriptdev/index.ts +123 -0
- package/src/domain/scriptdev/instanceRaids.ts +136 -0
- package/src/domain/scriptdev/itemScripts.ts +37 -0
- package/src/domain/scriptdev/lochModan.ts +283 -0
- package/src/domain/scriptdev/maraudon.ts +232 -0
- package/src/domain/scriptdev/moltenCore.ts +831 -0
- package/src/domain/scriptdev/moonglade.ts +659 -0
- package/src/domain/scriptdev/moongladeHelpers.ts +933 -0
- package/src/domain/scriptdev/naxxramas.ts +577 -0
- package/src/domain/scriptdev/naxxramasConstruct.ts +364 -0
- package/src/domain/scriptdev/naxxramasFrostwyrm.ts +378 -0
- package/src/domain/scriptdev/naxxramasMilitary.ts +276 -0
- package/src/domain/scriptdev/npcProfessions.ts +1025 -0
- package/src/domain/scriptdev/npcsSpecial.ts +1379 -0
- package/src/domain/scriptdev/onyxiasLair.ts +313 -0
- package/src/domain/scriptdev/orgrimmar.ts +147 -0
- package/src/domain/scriptdev/professionSpecConsumer.ts +481 -0
- package/src/domain/scriptdev/razorfenDowns.ts +468 -0
- package/src/domain/scriptdev/razorfenKraul.ts +410 -0
- package/src/domain/scriptdev/redridgeMountains.ts +163 -0
- package/src/domain/scriptdev/registry.ts +945 -0
- package/src/domain/scriptdev/ruinsOfAhnqiraj.ts +356 -0
- package/src/domain/scriptdev/scarletMonastery.ts +713 -0
- package/src/domain/scriptdev/scholomance.ts +682 -0
- package/src/domain/scriptdev/searingGorge.ts +295 -0
- package/src/domain/scriptdev/shadowfangKeep.ts +181 -0
- package/src/domain/scriptdev/silithus.ts +71 -0
- package/src/domain/scriptdev/silverpineForest.ts +662 -0
- package/src/domain/scriptdev/stonetalonMountains.ts +493 -0
- package/src/domain/scriptdev/stormwindCity.ts +1192 -0
- package/src/domain/scriptdev/stranglethornVale.ts +402 -0
- package/src/domain/scriptdev/stratholme.ts +650 -0
- package/src/domain/scriptdev/stratholmeScripts.ts +505 -0
- package/src/domain/scriptdev/sunkenTemple.ts +736 -0
- package/src/domain/scriptdev/swampOfSorrows.ts +22 -0
- package/src/domain/scriptdev/tanaris.ts +391 -0
- package/src/domain/scriptdev/tanarisHelpers.ts +661 -0
- package/src/domain/scriptdev/teldrassil.ts +143 -0
- package/src/domain/scriptdev/templeOfAhnqiraj.ts +1013 -0
- package/src/domain/scriptdev/theBarrens.ts +709 -0
- package/src/domain/scriptdev/theBarrensHelpers.ts +1026 -0
- package/src/domain/scriptdev/thousandNeedles.ts +426 -0
- package/src/domain/scriptdev/thousandNeedlesHelpers.ts +566 -0
- package/src/domain/scriptdev/tirisfalGlades.ts +533 -0
- package/src/domain/scriptdev/types.ts +1026 -0
- package/src/domain/scriptdev/uldaman.ts +437 -0
- package/src/domain/scriptdev/ungoroCrater.ts +549 -0
- package/src/domain/scriptdev/ungoroCraterHelpers.ts +1006 -0
- package/src/domain/scriptdev/wailingCaverns.ts +700 -0
- package/src/domain/scriptdev/warEffort.ts +371 -0
- package/src/domain/scriptdev/westernPlaguelands.ts +952 -0
- package/src/domain/scriptdev/westfall.ts +767 -0
- package/src/domain/scriptdev/wetlands.ts +660 -0
- package/src/domain/scriptdev/winterspring.ts +482 -0
- package/src/domain/scriptdev/winterspringHelpers.ts +801 -0
- package/src/domain/scriptdev/worldBosses.ts +605 -0
- package/src/domain/scriptdev/zulfarrak.ts +478 -0
- package/src/domain/scriptdev/zulgurub.ts +1152 -0
- package/src/domain/sim/dummySim.ts +408 -0
- package/src/domain/sim/targetDummy.ts +29 -0
- package/src/domain/social/guild.ts +148 -0
- package/src/domain/spells/areaAuraMembership.ts +93 -0
- package/src/domain/spells/breakableStunCc.ts +166 -0
- package/src/domain/spells/cancelAura.ts +80 -0
- package/src/domain/spells/diminishing.ts +167 -0
- package/src/domain/spells/dispel.ts +149 -0
- package/src/domain/spells/dispelMechanic.ts +105 -0
- package/src/domain/spells/equipSpells.ts +247 -0
- package/src/domain/spells/learnOnTargets.ts +260 -0
- package/src/domain/spells/mergeSchoolImmunityFromRaw.ts +66 -0
- package/src/domain/spells/resurrectSpell.ts +124 -0
- package/src/domain/spells/spellPushback.ts +39 -0
- package/src/domain/spells/spellReflect.ts +43 -0
- package/src/domain/spells/stealth.ts +183 -0
- package/src/domain/spells/totem.ts +394 -0
- package/src/domain/spells/worldCast.ts +187 -0
- package/src/domain/world/areaTriggerLocks.ts +177 -0
- package/src/domain/world/dungeonMusic.ts +108 -0
- package/src/domain/world/gameEvents.ts +119 -0
- package/src/domain/world/gameObjects.ts +1030 -0
- package/src/domain/world/goEventFire.ts +234 -0
- package/src/domain/world/goMoTransport.ts +74 -0
- package/src/domain/world/goTransport.ts +81 -0
- package/src/domain/world/hazards.ts +459 -0
- package/src/domain/world/openLock.ts +398 -0
- package/src/domain/world/pools.ts +470 -0
- package/src/domain/world/restArea.ts +37 -0
- package/src/domain/world/weather.ts +201 -0
- package/src/domain/zoneAmbience.mjs +87 -0
- package/src/domain/zoneMusic.mjs +62 -0
- package/src/domain/zoneMusicQueue.mjs +7 -0
- package/src/engine/combat/activeSeal.ts +39 -0
- package/src/engine/combat/aiTargetResolve.ts +64 -0
- package/src/engine/combat/aoeCast.ts +289 -0
- package/src/engine/combat/applyConsumeAura.ts +74 -0
- package/src/engine/combat/applyDemonicSacrifice.ts +70 -0
- package/src/engine/combat/applyDispelMechanic.ts +30 -0
- package/src/engine/combat/applyHealEffect.ts +301 -0
- package/src/engine/combat/applySchoolAbsorb.ts +114 -0
- package/src/engine/combat/applySpell.ts +3169 -0
- package/src/engine/combat/applySpellLate.ts +2026 -0
- package/src/engine/combat/applySpellLateBag.ts +144 -0
- package/src/engine/combat/areaAura.ts +283 -0
- package/src/engine/combat/attackTable.ts +508 -0
- package/src/engine/combat/battleRez.ts +87 -0
- package/src/engine/combat/castAction.ts +36 -0
- package/src/engine/combat/castTargetGates.ts +75 -0
- package/src/engine/combat/castableKit.ts +68 -0
- package/src/engine/combat/ccMirrorUpgrade.ts +39 -0
- package/src/engine/combat/chainFan.ts +210 -0
- package/src/engine/combat/channel.ts +423 -0
- package/src/engine/combat/chaseCloseness.ts +95 -0
- package/src/engine/combat/cmangosCastGates.ts +129 -0
- package/src/engine/combat/combatFlag.ts +77 -0
- package/src/engine/combat/combatGeometry.ts +1329 -0
- package/src/engine/combat/combustion.ts +68 -0
- package/src/engine/combat/context.ts +730 -0
- package/src/engine/combat/createSession.ts +1165 -0
- package/src/engine/combat/creatureLeash.ts +38 -0
- package/src/engine/combat/crowdControl.ts +1259 -0
- package/src/engine/combat/dispelDuty.ts +54 -0
- package/src/engine/combat/dispelReflect.ts +609 -0
- package/src/engine/combat/displaceFloor.ts +113 -0
- package/src/engine/combat/duelBounds.ts +47 -0
- package/src/engine/combat/equippedItemGate.ts +297 -0
- package/src/engine/combat/evadeLifecycle.ts +63 -0
- package/src/engine/combat/eventAi.ts +2220 -0
- package/src/engine/combat/facing.ts +74 -0
- package/src/engine/combat/fearFlee.ts +137 -0
- package/src/engine/combat/fighterFactory.ts +1144 -0
- package/src/engine/combat/forceDespawnAction.ts +51 -0
- package/src/engine/combat/formulas.ts +807 -0
- package/src/engine/combat/friendlyFan.ts +55 -0
- package/src/engine/combat/groundPoolTick.ts +210 -0
- package/src/engine/combat/hazards.ts +114 -0
- package/src/engine/combat/healTargets.ts +226 -0
- package/src/engine/combat/heartbeatResist.ts +49 -0
- package/src/engine/combat/hostileItemUse.ts +75 -0
- package/src/engine/combat/interruptAuras.ts +66 -0
- package/src/engine/combat/itemRescue.ts +49 -0
- package/src/engine/combat/itemUse.ts +260 -0
- package/src/engine/combat/kitById.ts +23 -0
- package/src/engine/combat/knockBack.ts +201 -0
- package/src/engine/combat/leap.ts +89 -0
- package/src/engine/combat/mitigation.ts +587 -0
- package/src/engine/combat/modTaunt.ts +43 -0
- package/src/engine/combat/motionClaim.ts +268 -0
- package/src/engine/combat/motionGate.ts +150 -0
- package/src/engine/combat/motionGenerator.ts +27 -0
- package/src/engine/combat/navSight.ts +138 -0
- package/src/engine/combat/perfSink.ts +28 -0
- package/src/engine/combat/periodicDue.ts +55 -0
- package/src/engine/combat/petAuras.ts +86 -0
- package/src/engine/combat/pickAction.ts +3449 -0
- package/src/engine/combat/powerForm.ts +735 -0
- package/src/engine/combat/powerPools.ts +49 -0
- package/src/engine/combat/predicates.ts +538 -0
- package/src/engine/combat/procsDamage.ts +1823 -0
- package/src/engine/combat/reactiveGates.ts +135 -0
- package/src/engine/combat/roles.ts +389 -0
- package/src/engine/combat/rotationSubject.ts +78 -0
- package/src/engine/combat/ruleAfter.ts +106 -0
- package/src/engine/combat/seedDisplayAuras.ts +203 -0
- package/src/engine/combat/seedOocEffects.ts +317 -0
- package/src/engine/combat/seedSchoolImmunities.ts +45 -0
- package/src/engine/combat/session-types.ts +458 -0
- package/src/engine/combat/sessionApi.ts +1785 -0
- package/src/engine/combat/sessionCharge.ts +124 -0
- package/src/engine/combat/sessionChase.ts +869 -0
- package/src/engine/combat/sessionDeaths.ts +469 -0
- package/src/engine/combat/sessionFocus.ts +506 -0
- package/src/engine/combat/sessionFollow.ts +167 -0
- package/src/engine/combat/sessionGeometry.ts +188 -0
- package/src/engine/combat/sessionHome.ts +75 -0
- package/src/engine/combat/sessionInject.ts +719 -0
- package/src/engine/combat/sessionMechanicMove.ts +555 -0
- package/src/engine/combat/sessionMotion.ts +1553 -0
- package/src/engine/combat/sessionMotionKit.ts +247 -0
- package/src/engine/combat/sessionMoveIntent.ts +49 -0
- package/src/engine/combat/sessionMoveOrder.ts +200 -0
- package/src/engine/combat/sessionPathTo.ts +657 -0
- package/src/engine/combat/sessionPools.ts +688 -0
- package/src/engine/combat/sessionSteer.ts +144 -0
- package/src/engine/combat/sessionStepBack.ts +151 -0
- package/src/engine/combat/sessionStepWrap.ts +116 -0
- package/src/engine/combat/sessionView.ts +442 -0
- package/src/engine/combat/simEffects.ts +32 -0
- package/src/engine/combat/simFacade.ts +620 -0
- package/src/engine/combat/simPump.ts +264 -0
- package/src/engine/combat/simSnapshot.ts +165 -0
- package/src/engine/combat/simThreadPool.ts +35 -0
- package/src/engine/combat/skeramImages.ts +57 -0
- package/src/engine/combat/slowUpdate.ts +178 -0
- package/src/engine/combat/snapToFloor.ts +42 -0
- package/src/engine/combat/speedAura.ts +29 -0
- package/src/engine/combat/spellReagents.ts +71 -0
- package/src/engine/combat/stealthDetect.ts +93 -0
- package/src/engine/combat/stepBackMotion.ts +171 -0
- package/src/engine/combat/stepSession.ts +3102 -0
- package/src/engine/combat/summonCast.ts +243 -0
- package/src/engine/combat/thaddiusPolarity.ts +97 -0
- package/src/engine/combat/threat.ts +388 -0
- package/src/engine/combat/threatOps.ts +272 -0
- package/src/engine/combat/tickWorkerBody.ts +211 -0
- package/src/engine/combat/totemAuras.ts +122 -0
- package/src/engine/combat/triggerPayload.ts +42 -0
- package/src/engine/combat/triggeredCompanion.ts +63 -0
- package/src/engine/combat/types.ts +1897 -0
- package/src/engine/combat/useObjectTrap.ts +140 -0
- package/src/engine/combat/walkSurface.ts +54 -0
- package/src/engine/combat/weapon.ts +1248 -0
- package/src/engine/combat/worldFrame.ts +43 -0
- package/src/engine/combat.solo-golden.json +2242 -0
- package/src/engine/combat.ts +84 -0
- package/src/engine/core/detMath.ts +289 -0
- package/src/engine/core/events.ts +357 -0
- package/src/engine/core/rng.ts +36 -0
- package/src/engine/game/actionBars.ts +124 -0
- package/src/engine/game/activateObjectFromSpell.ts +320 -0
- package/src/engine/game/applyActiveObjectFromDbScript.ts +98 -0
- package/src/engine/game/applyDeathAndSpellScripts.ts +268 -0
- package/src/engine/game/applyDoorFromDbScript.ts +388 -0
- package/src/engine/game/applyEscortFromDbScript.ts +290 -0
- package/src/engine/game/applyGoSpellcasterCast.ts +40 -0
- package/src/engine/game/applyHoverFromDbScript.ts +110 -0
- package/src/engine/game/applyOocResurrect.ts +71 -0
- package/src/engine/game/applySpellDurability.ts +115 -0
- package/src/engine/game/applyTailFromDbScript.ts +478 -0
- package/src/engine/game/applyTerminateFromDbScript.ts +95 -0
- package/src/engine/game/auctionMail.ts +269 -0
- package/src/engine/game/bagOverflow.ts +130 -0
- package/src/engine/game/bank.ts +110 -0
- package/src/engine/game/barAutofill.ts +26 -0
- package/src/engine/game/battleground.ts +369 -0
- package/src/engine/game/combatEnemySpec.ts +216 -0
- package/src/engine/game/combatObjectTraps.ts +167 -0
- package/src/engine/game/combatSeed.ts +320 -0
- package/src/engine/game/combatSession.ts +3241 -0
- package/src/engine/game/combatSessionClock.ts +62 -0
- package/src/engine/game/companionQuestActions.ts +242 -0
- package/src/engine/game/companionTraining.ts +40 -0
- package/src/engine/game/consumables.ts +624 -0
- package/src/engine/game/context.ts +53 -0
- package/src/engine/game/createHealthstone.ts +45 -0
- package/src/engine/game/deathFlow.ts +127 -0
- package/src/engine/game/devGrant.ts +117 -0
- package/src/engine/game/dynamicObject.ts +365 -0
- package/src/engine/game/enchantItem.ts +177 -0
- package/src/engine/game/equipmentHelper.ts +806 -0
- package/src/engine/game/factionState.ts +108 -0
- package/src/engine/game/garmentsOoc.ts +69 -0
- package/src/engine/game/globalActionBar.ts +179 -0
- package/src/engine/game/goInteractQuestCredit.ts +51 -0
- package/src/engine/game/goMoTransportUse.ts +61 -0
- package/src/engine/game/goTransportUse.ts +60 -0
- package/src/engine/game/healthstoneUpkeep.ts +59 -0
- package/src/engine/game/killSkin.ts +124 -0
- package/src/engine/game/learnFromItem.ts +118 -0
- package/src/engine/game/loadReconcile.ts +551 -0
- package/src/engine/game/loot.ts +294 -0
- package/src/engine/game/macros.ts +125 -0
- package/src/engine/game/mail.ts +293 -0
- package/src/engine/game/meetingStoneQueue.ts +75 -0
- package/src/engine/game/meetingStoneUse.ts +54 -0
- package/src/engine/game/newGameState.ts +363 -0
- package/src/engine/game/oocAuraInterrupt.ts +92 -0
- package/src/engine/game/oocCast.ts +241 -0
- package/src/engine/game/oocCastEffects.ts +803 -0
- package/src/engine/game/oocCastMethods.ts +1077 -0
- package/src/engine/game/openLockFromSpell.ts +174 -0
- package/src/engine/game/outdoorPvp.ts +187 -0
- package/src/engine/game/outfitter.ts +644 -0
- package/src/engine/game/paladinBoard.ts +393 -0
- package/src/engine/game/partyAuras.ts +121 -0
- package/src/engine/game/persistCombatAuras.ts +91 -0
- package/src/engine/game/pets.ts +459 -0
- package/src/engine/game/prePullHealthstones.ts +154 -0
- package/src/engine/game/professions.ts +2227 -0
- package/src/engine/game/pruneStaleRuntime.ts +39 -0
- package/src/engine/game/questFightCredit.ts +36 -0
- package/src/engine/game/questItemOnCorpse.ts +166 -0
- package/src/engine/game/questItemOnCreature.ts +103 -0
- package/src/engine/game/questLog.ts +879 -0
- package/src/engine/game/questSpellComplete.ts +21 -0
- package/src/engine/game/questTurnIn.ts +598 -0
- package/src/engine/game/raidParty.ts +32 -0
- package/src/engine/game/reagentBuyIn.ts +39 -0
- package/src/engine/game/regenRest.ts +186 -0
- package/src/engine/game/rotation.ts +425 -0
- package/src/engine/game/runFullDungeon.ts +611 -0
- package/src/engine/game/runSupply.ts +80 -0
- package/src/engine/game/sanitizeDriftedItems.ts +135 -0
- package/src/engine/game/scriptDevGoUse.ts +397 -0
- package/src/engine/game/scriptDevInnkeeper.ts +150 -0
- package/src/engine/game/scriptDevProfessionSpec.ts +99 -0
- package/src/engine/game/sendEventFromSpell.ts +87 -0
- package/src/engine/game/sortContainers.ts +85 -0
- package/src/engine/game/spellReputation.ts +69 -0
- package/src/engine/game/squad/squadDungeonSeating.ts +197 -0
- package/src/engine/game/squadDungeon.ts +4222 -0
- package/src/engine/game/squadQuestShare.ts +260 -0
- package/src/engine/game/summonObjectSlot.ts +69 -0
- package/src/engine/game/summonObjectWild.ts +73 -0
- package/src/engine/game/summoningRitualUse.ts +141 -0
- package/src/engine/game/talents.ts +356 -0
- package/src/engine/game/tamingRodUse.ts +66 -0
- package/src/engine/game/tempSpawnCreature.ts +136 -0
- package/src/engine/game/tickDungeonPatrolScripts.ts +205 -0
- package/src/engine/game/tickTimedQuests.ts +28 -0
- package/src/engine/game/trade.ts +586 -0
- package/src/engine/game/travel.ts +1155 -0
- package/src/engine/game/types.ts +1855 -0
- package/src/engine/game/vendorOffers.ts +244 -0
- package/src/engine/game/weaponImbueItem.ts +281 -0
- package/src/engine/game/worldSession.ts +183 -0
- package/src/engine/game/worldState.ts +166 -0
- package/src/engine/game.ts +968 -0
- package/src/engine/spells/GAP.md +67 -0
- package/src/engine/spells/absorbs.ts +376 -0
- package/src/engine/spells/addTargetTrigger.ts +53 -0
- package/src/engine/spells/auraPerSecondCost.ts +50 -0
- package/src/engine/spells/auraStacking.ts +497 -0
- package/src/engine/spells/cmangos-special-cases-combustion.ts +13 -0
- package/src/engine/spells/cmangos-special-cases-s18.ts +445 -0
- package/src/engine/spells/cmangos-special-cases-s19.ts +324 -0
- package/src/engine/spells/cmangos-special-cases-s20.ts +385 -0
- package/src/engine/spells/cmangos-special-cases-s21.ts +116 -0
- package/src/engine/spells/cmangos-special-cases-s22.ts +24 -0
- package/src/engine/spells/cmangos-special-cases-s23.ts +52 -0
- package/src/engine/spells/cmangos-special-cases.ts +1570 -0
- package/src/engine/spells/conditions.ts +718 -0
- package/src/engine/spells/effects.ts +203 -0
- package/src/engine/spells/fixtures/oracle-effects-lock9a.json +1 -0
- package/src/engine/spells/immunity.ts +942 -0
- package/src/engine/spells/intentEffects.ts +351 -0
- package/src/engine/spells/interpreter/GAPS.md +21 -0
- package/src/engine/spells/interpreter/amount.ts +109 -0
- package/src/engine/spells/interpreter/class-scripts-kit.ts +478 -0
- package/src/engine/spells/interpreter/diff.ts +246 -0
- package/src/engine/spells/interpreter/duration.ts +90 -0
- package/src/engine/spells/interpreter/form-attrs.json +1 -0
- package/src/engine/spells/interpreter/form-attrs.ts +95 -0
- package/src/engine/spells/interpreter/handlers/aura-remove.ts +113 -0
- package/src/engine/spells/interpreter/handlers/auras-control.ts +1547 -0
- package/src/engine/spells/interpreter/handlers/auras-core.ts +1583 -0
- package/src/engine/spells/interpreter/handlers/auras-misc.ts +1774 -0
- package/src/engine/spells/interpreter/handlers/effect-disenchant.ts +17 -0
- package/src/engine/spells/interpreter/handlers/effect-skinning.ts +20 -0
- package/src/engine/spells/interpreter/handlers/effects-action-utility.ts +1455 -0
- package/src/engine/spells/interpreter/handlers/effects-core.ts +432 -0
- package/src/engine/spells/interpreter/handlers/effects-remainder.ts +105 -0
- package/src/engine/spells/interpreter/handlers/effects-summon-item.ts +687 -0
- package/src/engine/spells/interpreter/handlers/mod-increase-energy-percent.ts +36 -0
- package/src/engine/spells/interpreter/handlers/mod-increase-energy.ts +36 -0
- package/src/engine/spells/interpreter/handlers/periodic-trigger-zero.ts +327 -0
- package/src/engine/spells/interpreter/handlers/procs.ts +131 -0
- package/src/engine/spells/interpreter/handlers/registry.ts +975 -0
- package/src/engine/spells/interpreter/handlers/shared.ts +667 -0
- package/src/engine/spells/interpreter/handlers.ts +9 -0
- package/src/engine/spells/interpreter/hunter-kit.ts +202 -0
- package/src/engine/spells/interpreter/index.ts +525 -0
- package/src/engine/spells/interpreter/item-triggers.ts +1121 -0
- package/src/engine/spells/interpreter/lift-consume-energy.ts +37 -0
- package/src/engine/spells/interpreter/mage-portals.ts +37 -0
- package/src/engine/spells/interpreter/materialize-charm.ts +88 -0
- package/src/engine/spells/interpreter/materialize-dispel-mechanic.ts +25 -0
- package/src/engine/spells/interpreter/materialize-rating-buffs.ts +81 -0
- package/src/engine/spells/interpreter/materialize-speed.ts +109 -0
- package/src/engine/spells/interpreter/materialize-spell-magnet.ts +22 -0
- package/src/engine/spells/interpreter/opcodes.ts +789 -0
- package/src/engine/spells/interpreter/paladin-kit.ts +95 -0
- package/src/engine/spells/interpreter/paladin-seals.ts +499 -0
- package/src/engine/spells/interpreter/priest-warlock-kit.ts +135 -0
- package/src/engine/spells/interpreter/proc.ts +443 -0
- package/src/engine/spells/interpreter/registry/helpers.ts +16 -0
- package/src/engine/spells/interpreter/registry/index.ts +216 -0
- package/src/engine/spells/interpreter/registry/packs/class-abilities.ts +304 -0
- package/src/engine/spells/interpreter/registry/packs/dire-maul.ts +44 -0
- package/src/engine/spells/interpreter/registry/packs/dummy-script-tail.ts +1065 -0
- package/src/engine/spells/interpreter/registry/packs/ex-wave-m.ts +70 -0
- package/src/engine/spells/interpreter/registry/packs/life-tap.ts +107 -0
- package/src/engine/spells/interpreter/registry/packs/paladin.ts +246 -0
- package/src/engine/spells/interpreter/registry/packs/raid-script-summons.ts +26 -0
- package/src/engine/spells/interpreter/registry/packs/raid-threat-wipe.ts +22 -0
- package/src/engine/spells/interpreter/registry/packs/shaman.ts +214 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-a.ts +269 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-b.ts +370 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-c.ts +172 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-hunter.ts +60 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-warrior.ts +31 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-a.ts +114 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-b.ts +249 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-c.ts +233 -0
- package/src/engine/spells/interpreter/registry/types.ts +48 -0
- package/src/engine/spells/interpreter/talent-scripted.ts +329 -0
- package/src/engine/spells/interpreter/targeting.ts +90 -0
- package/src/engine/spells/interpreter/types-intent.ts +1501 -0
- package/src/engine/spells/interpreter/types.ts +419 -0
- package/src/engine/spells/interpreter/warrior-druid-kit.ts +314 -0
- package/src/engine/spells/interpreter/weapon-imbue-kit.ts +384 -0
- package/src/engine/spells/interrupt.ts +217 -0
- package/src/engine/spells/meleeAttackReset.ts +60 -0
- package/src/engine/spells/oocTriggerSpell.ts +49 -0
- package/src/engine/spells/overrideClassScripts.ts +319 -0
- package/src/engine/spells/parryHaste.ts +20 -0
- package/src/engine/spells/procs.ts +300 -0
- package/src/engine/spells/recovery.ts +203 -0
- package/src/engine/spells/scriptTargetDest.ts +76 -0
- package/src/engine/spells/scriptTargets.ts +85 -0
- package/src/engine/spells/singleTargetAura.ts +82 -0
- package/src/engine/spells/specialCasePayloads.ts +57 -0
- package/src/engine/spells/spellAttributes.ts +249 -0
- package/src/engine/spells/spellBonus.ts +143 -0
- package/src/engine/spells/spellModIntents.ts +419 -0
- package/src/engine/spells/spellMods.ts +283 -0
- package/src/engine/spells/spellRadius.ts +15 -0
- package/src/engine/spells/trapObjects.ts +26 -0
- package/src/json.d.ts +7 -0
- package/src/persistence/memoryRepository.ts +23 -0
- package/src/persistence/saveBackups.ts +162 -0
- package/src/persistence/saveFile.ts +68 -0
- package/src/persistence/saveRepository.ts +11 -0
- package/src/persistence/saveShape.ts +668 -0
- package/src/progression/leveling.ts +89 -0
- package/src/progression/rest.ts +1434 -0
- package/src/progression/training.ts +226 -0
- package/src/progression/xpGain.ts +369 -0
- package/src/quests/quests.ts +1580 -0
- package/src/world/aoePull.ts +198 -0
- package/src/world/areaTableDbc.ts +39 -0
- package/src/world/areaTableMapIdPairs.ts +141 -0
- package/src/world/bakeSlug.ts +43 -0
- package/src/world/continuousDungeon.ts +1250 -0
- package/src/world/dungeonBakeProjection.ts +190 -0
- package/src/world/dungeonMapDensity.ts +342 -0
- package/src/world/dungeonMapProjection.ts +135 -0
- package/src/world/dungeonNav.ts +904 -0
- package/src/world/dungeonNpcSim.ts +147 -0
- package/src/world/dungeonObjectives.ts +234 -0
- package/src/world/dungeonPreset.ts +35 -0
- package/src/world/dungeonRoute.ts +311 -0
- package/src/world/dungeonRouteGeometry.ts +732 -0
- package/src/world/dungeonRouteOrder.ts +1037 -0
- package/src/world/dungeonWingRooms.ts +426 -0
- package/src/world/escort.ts +296 -0
- package/src/world/goPoolRoll.ts +75 -0
- package/src/world/instanceObjects.ts +785 -0
- package/src/world/instances.ts +1572 -0
- package/src/world/meetingStones.ts +56 -0
- package/src/world/navmeshFloors.ts +145 -0
- package/src/world/nearGrid.ts +167 -0
- package/src/world/packBudget.ts +613 -0
- package/src/world/rareRoll.ts +335 -0
- package/src/world/routeAuthoring.ts +132 -0
- package/src/world/routeEdit.ts +209 -0
- package/src/world/world.ts +812 -0
- package/src/world/zoneHazards.ts +47 -0
|
@@ -0,0 +1,1381 @@
|
|
|
1
|
+
// PURE: no DOM/RNG/Date. Pure spell classifiers and keep-up / gate helpers for rotations.
|
|
2
|
+
|
|
3
|
+
import { CREATURE_TYPE, SHAPESHIFT_FORM, type SpellDef } from '../../data/schema.ts';
|
|
4
|
+
import {
|
|
5
|
+
isHunterAspectSpell,
|
|
6
|
+
spellExclusionGroup,
|
|
7
|
+
} from '../character/character.ts';
|
|
8
|
+
import { inferRole } from '../../engine/combat.ts';
|
|
9
|
+
import { auraHolderIsCastTarget, auraRadiatesToParty } from '../../engine/combat/healTargets.ts';
|
|
10
|
+
import { normalizeSpellName } from './rotationSuggestions.ts';
|
|
11
|
+
import {
|
|
12
|
+
formIdFromLabel,
|
|
13
|
+
isWeaponImbueSpellLabel,
|
|
14
|
+
} from '../run/whenMap.ts';
|
|
15
|
+
import { formIdFromSpell, isFormSpell } from '../character/forms.ts';
|
|
16
|
+
import { IN_MELEE_YD } from './stand.ts';
|
|
17
|
+
import {
|
|
18
|
+
auraStackIdentity,
|
|
19
|
+
stackIdentitiesExclude,
|
|
20
|
+
type AuraStackIdentity,
|
|
21
|
+
} from '../../engine/spells/auraStacking.ts';
|
|
22
|
+
import type {
|
|
23
|
+
CondMetric,
|
|
24
|
+
CondOp,
|
|
25
|
+
CondSubject,
|
|
26
|
+
Condition,
|
|
27
|
+
Rotation,
|
|
28
|
+
RotationRule,
|
|
29
|
+
} from './types.ts';
|
|
30
|
+
import { DEFAULT_THRESHOLDS, allyFilterIsAny } from './types.ts';
|
|
31
|
+
|
|
32
|
+
/** Prefer effect-sourced form id; fall back to spell name (Wave 1A labels). */
|
|
33
|
+
export function formIdForRotationSpell(spell: SpellDef): number {
|
|
34
|
+
return (formIdFromSpell(spell) | 0) || (formIdFromLabel(spell.name) ?? 0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const cond = (subject: CondSubject, metric: CondMetric, op: CondOp, value: number, ref?: number): Condition =>
|
|
38
|
+
({ subject, metric, op, value, ...(ref != null ? { ref } : {}) });
|
|
39
|
+
|
|
40
|
+
// Pure HoT (Renew, Rejuvenation): restore_health only, with no direct heal rider. It must always
|
|
41
|
+
// gate on the buff missing or defaults thrash every GCD into OOM. Regrowth is a main heal.
|
|
42
|
+
export function isPureHotSpell(spell: {
|
|
43
|
+
effects?: ReadonlyArray<{ kind: string }> | null;
|
|
44
|
+
}): boolean {
|
|
45
|
+
const effects = spell.effects ?? [];
|
|
46
|
+
const hasHot = effects.some((e) => e.kind === 'restore_health');
|
|
47
|
+
if (!hasHot) return false;
|
|
48
|
+
const hasDirect = effects.some(
|
|
49
|
+
(e) => e.kind === 'heal' || e.kind === 'holy_shock' || e.kind === 'heal_max_health',
|
|
50
|
+
);
|
|
51
|
+
return !hasDirect;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Default conditions for a pure HoT: low health + not already ticking on self (solo / self-heal). */
|
|
55
|
+
export function pureHotSelfConditions(spellId: number): Condition[] {
|
|
56
|
+
return [
|
|
57
|
+
cond('self', 'healthPct', '<', DEFAULT_THRESHOLDS.healLowPct),
|
|
58
|
+
cond('self', 'buffPresent', '==', 0, spellId),
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Seal kind priority for the active tree. Retribution: Command (talent seal) > Righteousness > Light >
|
|
63
|
+
// Wisdom. Holy / Protection: never prefer Ret's Seal of Command as the default — SoR/Light/Wisdom first.
|
|
64
|
+
export function sealKindPriority(spec: string | null | undefined): Record<string, number> {
|
|
65
|
+
if (spec === 'Holy' || spec === 'Protection') {
|
|
66
|
+
return { righteousness: 3, light: 2, wisdom: 1, command: 0 };
|
|
67
|
+
}
|
|
68
|
+
// Retribution (and unknown/default): talent seal first.
|
|
69
|
+
return { command: 3, righteousness: 2, light: 1, wisdom: 0 };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** CMaNGOS spell_group Id 5 — all paladin seals (UNIQUE_PER_CASTER). */
|
|
73
|
+
export const PALADIN_SEAL_EXCLUSION_GROUP = 5;
|
|
74
|
+
|
|
75
|
+
export function sealKindOf(sp: SpellDef): string {
|
|
76
|
+
const e = sp.effects.find((x) => x.kind === 'seal');
|
|
77
|
+
return e && e.kind === 'seal' ? e.seal : '';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isSealSpell(sp: SpellDef): boolean {
|
|
81
|
+
if ((sp.role ?? inferRole(sp)) === 'seal' || sp.effects.some((e) => e.kind === 'seal')) return true;
|
|
82
|
+
// Bake residual: many seals ship as empty/proc_trigger without role:'seal' (KI-001).
|
|
83
|
+
if (spellExclusionGroup(sp) === PALADIN_SEAL_EXCLUSION_GROUP) return true;
|
|
84
|
+
return /^Seal of /i.test(sp.name ?? '');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isJudgementSpell(sp: SpellDef): boolean {
|
|
88
|
+
return (sp.role ?? inferRole(sp)) === 'judgement' || sp.effects.some((e) => e.kind === 'judgement');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// True weapon-imbue APPLY spells. A rogue poison CONJURE makes a bag item and must not use
|
|
92
|
+
// weaponImbuePresent — a conjure never fills the temp slot (KI-369 / KI-388).
|
|
93
|
+
export function isTrueWeaponImbueSpell(sp: SpellDef): boolean {
|
|
94
|
+
if (sp.effects.some((e) => e.kind === 'conjure')) return false;
|
|
95
|
+
if (sp.effects.some((e) => e.kind === 'weapon_imbue')) return true;
|
|
96
|
+
return isWeaponImbueSpellLabel(sp.name);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Rogue create-poison spells (Instant/Deadly/… vials) — exclusive preferred-family keep-up. */
|
|
100
|
+
export function isPoisonConjureSpell(sp: SpellDef): boolean {
|
|
101
|
+
if (!sp.effects.some((e) => e.kind === 'conjure')) return false;
|
|
102
|
+
return /\b(instant|deadly|crippling|mind-numbing|wound)\s+poison\b/i.test(sp.name ?? '');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Classic mage armors are mutually exclusive (spell_group Id 13). Prefer baked exclusionGroup /
|
|
106
|
+
// spellGroupId; name set is last-resort for fixtures without flags. Wave 2-MAGE / Wave J.
|
|
107
|
+
const MAGE_ARMOR_NAMES = new Set(['frost armor', 'ice armor', 'mage armor']);
|
|
108
|
+
const MAGE_ARMOR_EXCLUSION_GROUP = 13;
|
|
109
|
+
|
|
110
|
+
export function isMageArmorSpell(spell: {
|
|
111
|
+
name?: string;
|
|
112
|
+
exclusionGroup?: number;
|
|
113
|
+
spellGroupId?: number;
|
|
114
|
+
} | null | undefined): boolean {
|
|
115
|
+
if (!spell) return false;
|
|
116
|
+
if (spell.exclusionGroup === MAGE_ARMOR_EXCLUSION_GROUP || spell.spellGroupId === MAGE_ARMOR_EXCLUSION_GROUP) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return MAGE_ARMOR_NAMES.has(normalizeSpellName(spell.name ?? ''));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function isCreateHealthstoneSpell(sp: { name?: string } | null | undefined): boolean {
|
|
123
|
+
return /^Create Healthstone\b/i.test(sp?.name ?? '');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Mutual-exclusion family for keep-up thrash collapse, keyed semantically so ranks missing
|
|
127
|
+
// exclusionGroup still collapse with siblings that have it. First rule in list order wins.
|
|
128
|
+
/** The Blessings a paladin keeps up, as opposed to the group-7 panic cooldowns. */
|
|
129
|
+
const UPKEEP_BLESSING_NAME =
|
|
130
|
+
/^(Greater )?Blessing of (Might|Wisdom|Kings|Sanctuary|Salvation|Light)$/i;
|
|
131
|
+
|
|
132
|
+
export function exclusiveKeepupFamily(sp: SpellDef): string | null {
|
|
133
|
+
const g = spellExclusionGroup(sp);
|
|
134
|
+
// Classic exclusive families — name + group so residual bake gaps share one key.
|
|
135
|
+
if (isHunterAspectSpell(sp) || g === 10) return 'hunter-aspect';
|
|
136
|
+
if (isMageArmorSpell(sp) || g === 13) return 'mage-armor';
|
|
137
|
+
if (isSealSpell(sp) || g === PALADIN_SEAL_EXCLUSION_GROUP) return 'paladin-seal';
|
|
138
|
+
// Paladin UPKEEP blessings — spell_group 7, one on a body per paladin, so a second self row only
|
|
139
|
+
// replaces the first. Protection / Freedom / Sacrifice share the group but are panic cooldowns,
|
|
140
|
+
// not keep-ups, and collapsing them would delete the tool. Cite: Unit.cpp:4973 personal;
|
|
141
|
+
// PlayerbotPaladinAI.cpp:643-716 the four families it picks between.
|
|
142
|
+
if (UPKEEP_BLESSING_NAME.test(sp.name ?? '')) return 'paladin-blessing';
|
|
143
|
+
// Paladin auras (Devotion / Retribution / Concentration / …) — spell_group 8.
|
|
144
|
+
if (g === 8 || (/\bAura\b/.test(sp.name ?? '') && !/Prayer of/i.test(sp.name ?? ''))) {
|
|
145
|
+
// Narrow aura name match to paladin-style "* Aura" only when group missing.
|
|
146
|
+
if (g === 8 || /^(Devotion|Retribution|Concentration|Shadow Resistance|Frost Resistance|Fire Resistance|Sanctity)\s+Aura$/i.test(sp.name ?? '')) {
|
|
147
|
+
return 'paladin-aura';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Hunter stings (spell_group 9); Wyvern is a CC, not the one-sting family.
|
|
151
|
+
if ((g === 9 || (/\bSting\b/i.test(sp.name ?? '') && !/Wyvern/i.test(sp.name ?? '')))) {
|
|
152
|
+
return 'hunter-sting';
|
|
153
|
+
}
|
|
154
|
+
// Warlock curses (spell_group 12) — only one curse per target.
|
|
155
|
+
if (g === 12 || /^Curse of /i.test(sp.name ?? '')) return 'warlock-curse';
|
|
156
|
+
// Hunter traps: Freezing/Immolation/Frost/Explosive — one laid trap at a time.
|
|
157
|
+
if (/\bTrap\b/i.test(sp.name ?? '') && !/Detector|Disarm/i.test(sp.name ?? '')) {
|
|
158
|
+
return 'hunter-trap';
|
|
159
|
+
}
|
|
160
|
+
// One demon pet — packs list Imp+VW+Felhunter+Succubus all alive==0 otherwise thrash.
|
|
161
|
+
if ((sp.role ?? '') === 'pet_summon' || /^Summon (Imp|Voidwalker|Succubus|Felhunter|Felguard)\b/i.test(sp.name ?? '')) {
|
|
162
|
+
return 'warlock-pet';
|
|
163
|
+
}
|
|
164
|
+
// One Create *stone rank — packs used to list Minor+Lesser+… all itemCount thrash.
|
|
165
|
+
if (isCreateHealthstoneSpell(sp)) return 'warlock-healthstone';
|
|
166
|
+
if (/^Create Soulstone\b/i.test(sp.name ?? '')) return 'warlock-soulstone';
|
|
167
|
+
if (/^Create Firestone\b/i.test(sp.name ?? '')) return 'warlock-firestone';
|
|
168
|
+
if (/^Create Spellstone\b/i.test(sp.name ?? '')) return 'warlock-spellstone';
|
|
169
|
+
// One Conjure Food / Water rank — leftover ranks each gate a different item.
|
|
170
|
+
if (/^Conjure Food\b/i.test(sp.name ?? '')) return 'mage-conjure-food';
|
|
171
|
+
if (/^Conjure Water\b/i.test(sp.name ?? '')) return 'mage-conjure-water';
|
|
172
|
+
if (isTrueWeaponImbueSpell(sp)) return 'weapon-imbue';
|
|
173
|
+
if (isPoisonConjureSpell(sp)) return 'poison-conjure';
|
|
174
|
+
// Any other baked exclusion group still collapses among itself.
|
|
175
|
+
if (g != null) return `excl:${g}`;
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// The set of numbers one condition admits, as an interval or a single excluded point.
|
|
180
|
+
type CondSet =
|
|
181
|
+
| { kind: 'range'; lo: number; loOpen: boolean; hi: number; hiOpen: boolean }
|
|
182
|
+
| { kind: 'not'; value: number };
|
|
183
|
+
|
|
184
|
+
function condSet(c: Condition): CondSet {
|
|
185
|
+
switch (c.op) {
|
|
186
|
+
case '<': return { kind: 'range', lo: -Infinity, loOpen: true, hi: c.value, hiOpen: true };
|
|
187
|
+
case '<=': return { kind: 'range', lo: -Infinity, loOpen: true, hi: c.value, hiOpen: false };
|
|
188
|
+
case '>': return { kind: 'range', lo: c.value, loOpen: true, hi: Infinity, hiOpen: true };
|
|
189
|
+
case '>=': return { kind: 'range', lo: c.value, loOpen: false, hi: Infinity, hiOpen: true };
|
|
190
|
+
case '==': return { kind: 'range', lo: c.value, loOpen: false, hi: c.value, hiOpen: false };
|
|
191
|
+
default: return { kind: 'not', value: c.value };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// `target.inMelee` and `targetDistanceYd` are the same reading — the melee band IS a distance
|
|
196
|
+
// (evaluate.ts:344 "Melee band vs the acting unit"; stand.ts IN_MELEE_YD). Without this the hunter
|
|
197
|
+
// packs' authored aspect swap (Monkey in melee, Hawk beyond 8 yd) collapsed to one row.
|
|
198
|
+
function meleeBandAsDistance(c: Condition): Condition | null {
|
|
199
|
+
if (c.metric !== 'inMelee' || c.subject !== 'target' || c.op !== '==') return null;
|
|
200
|
+
if (c.value === 1) return { subject: 'target', metric: 'targetDistanceYd', op: '<=', value: IN_MELEE_YD };
|
|
201
|
+
if (c.value === 0) return { subject: 'target', metric: 'targetDistanceYd', op: '>', value: IN_MELEE_YD };
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Two conditions on the SAME reading that no value satisfies at once. Sound, not complete. */
|
|
206
|
+
function conditionsExclude(rawA: Condition, rawB: Condition): boolean {
|
|
207
|
+
const a = meleeBandAsDistance(rawA) ?? rawA;
|
|
208
|
+
const b = meleeBandAsDistance(rawB) ?? rawB;
|
|
209
|
+
if (a.subject !== b.subject || a.metric !== b.metric) return false;
|
|
210
|
+
if ((a.ref ?? null) !== (b.ref ?? null)) return false;
|
|
211
|
+
const x = condSet(a);
|
|
212
|
+
const y = condSet(b);
|
|
213
|
+
// `!=` only excludes the one point, so it can only conflict with `== that point`.
|
|
214
|
+
if (x.kind === 'not' || y.kind === 'not') {
|
|
215
|
+
const not = x.kind === 'not' ? x : (y as { kind: 'not'; value: number });
|
|
216
|
+
const other = x.kind === 'not' ? y : x;
|
|
217
|
+
if (other.kind !== 'range') return false;
|
|
218
|
+
return other.lo === other.hi && !other.loOpen && !other.hiOpen && other.lo === not.value;
|
|
219
|
+
}
|
|
220
|
+
const lo = Math.max(x.lo, y.lo);
|
|
221
|
+
const hi = Math.min(x.hi, y.hi);
|
|
222
|
+
if (lo > hi) return true;
|
|
223
|
+
if (lo < hi) return false;
|
|
224
|
+
const loOpen = (x.lo === lo && x.loOpen) || (y.lo === lo && y.loOpen);
|
|
225
|
+
const hiOpen = (x.hi === hi && x.hiOpen) || (y.hi === hi && y.hiOpen);
|
|
226
|
+
return loOpen || hiOpen;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Two rules that can never both be live. Without this an exclusive family collapses to ONE row, so
|
|
230
|
+
// every "which one against whom" answer a band writes is deleted at build time: warlock Affliction
|
|
231
|
+
// built 1 curse of 4 and hunter Marksmanship 1 sting of 3, each gated on a metric the other's gate
|
|
232
|
+
// contradicts. Gates that partition (isCaster 1/0, classId ==/!=) keep their rows; two rows that
|
|
233
|
+
// could both want the slot still collapse, which is the thrash the family rule exists to stop.
|
|
234
|
+
export function rulesAreMutuallyExclusive(a: RotationRule, b: RotationRule): boolean {
|
|
235
|
+
for (const ca of a.conditions ?? []) {
|
|
236
|
+
for (const cb of b.conditions ?? []) if (conditionsExclude(ca, cb)) return true;
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Keep at most one LIVE rule per exclusive family — Aspects, Auras, Seals, Stings, Curses, imbues,
|
|
242
|
+
// poisons, mage armors, warlock pets, hunter traps. First match wins, so suggestion order beats it.
|
|
243
|
+
export function collapseExclusiveFamilyRules(
|
|
244
|
+
rules: RotationRule[],
|
|
245
|
+
resolve: (spellId: number) => SpellDef | undefined | null,
|
|
246
|
+
): RotationRule[] {
|
|
247
|
+
const keptByFamily = new Map<string, RotationRule[]>();
|
|
248
|
+
// A row aimed at an ALLY by filter is its own slot: the same aura on two DIFFERENT bodies is not
|
|
249
|
+
// the keep-up thrash this collapse exists for. Keying them together deleted the paladin's 2nd BoP band.
|
|
250
|
+
const aimKey = (r: RotationRule): string => {
|
|
251
|
+
for (const c of r.conditions ?? []) {
|
|
252
|
+
if (c.subject === 'ally' && !allyFilterIsAny(c.ally)) {
|
|
253
|
+
const f = c.ally!;
|
|
254
|
+
return `${f.role ?? ''}|${f.notRole ?? ''}|${f.classId ?? ''}|${f.unit ?? ''}#`;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return '';
|
|
258
|
+
};
|
|
259
|
+
// A multirank pair is a relation, not one key: Mark of the Wild carries aura 22 alone and Gift
|
|
260
|
+
// of the Wild 22/29/143, so the overlap is tested against every rule already kept.
|
|
261
|
+
const keptIdents: Array<{ ident: AuraStackIdentity; rule: RotationRule; aim: string }> = [];
|
|
262
|
+
const out: RotationRule[] = [];
|
|
263
|
+
for (const r of rules) {
|
|
264
|
+
if (r.action.kind !== 'spell') {
|
|
265
|
+
out.push(r);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const def = resolve(r.action.spellId);
|
|
269
|
+
if (!def) {
|
|
270
|
+
out.push(r);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const ident = auraStackIdentity(def);
|
|
274
|
+
const aim = aimKey(r);
|
|
275
|
+
if (ident && keptIdents.some(
|
|
276
|
+
(k) => k.aim === aim && stackIdentitiesExclude(ident, k.ident)
|
|
277
|
+
&& !rulesAreMutuallyExclusive(r, k.rule),
|
|
278
|
+
)) continue;
|
|
279
|
+
const fam = exclusiveKeepupFamily(def);
|
|
280
|
+
if (fam != null) {
|
|
281
|
+
const key = `${aim}${fam}`;
|
|
282
|
+
const kept = keptByFamily.get(key);
|
|
283
|
+
if (kept?.some((k) => !rulesAreMutuallyExclusive(r, k))) continue;
|
|
284
|
+
if (kept) kept.push(r);
|
|
285
|
+
else keptByFamily.set(key, [r]);
|
|
286
|
+
}
|
|
287
|
+
if (ident) keptIdents.push({ ident, rule: r, aim });
|
|
288
|
+
out.push(r);
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Short-duration damage shields belong in combat (Holy Shield charges); long ones stay OOC keep-ups.
|
|
294
|
+
// The 30 s threshold matches Holy Shield's 10 s duration with headroom for rank variance.
|
|
295
|
+
export function isCombatDamageShield(sp: SpellDef): boolean {
|
|
296
|
+
if (/^holy shield$/i.test(sp.name)) return true;
|
|
297
|
+
return sp.effects.some(
|
|
298
|
+
(e) => e.kind === 'damage_shield' && (e.durationMs ?? 0) > 0 && (e.durationMs ?? 0) <= 30_000,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Overpower is a post-dodge react (not a CP finisher). */
|
|
303
|
+
export function isOverpowerSpell(s: SpellDef): boolean {
|
|
304
|
+
return /^overpower$/i.test(s.name);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Classic combo-point spenders — rogue and cat finishers. Bake noise flags requiresComboPoints on
|
|
308
|
+
// rage abilities like Overpower, so rage and focus are hard exclusions.
|
|
309
|
+
export function isComboPointConsumer(s: SpellDef): boolean {
|
|
310
|
+
if (!s.requiresComboPoints) return false;
|
|
311
|
+
// Overpower and any warrior rage "finisher" flags are bake noise — warriors never spend CP.
|
|
312
|
+
if (isOverpowerSpell(s) || s.powerType === 'rage' || s.powerType === 'focus') return false;
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Stack cap for hostile keep-up auras. Bake often omits `stackAmount` on Sunder Armor even though Classic
|
|
317
|
+
// applies up to 5 stacks (description: "Can be applied up to 5 times").
|
|
318
|
+
export function hostileStackCap(s: SpellDef): number {
|
|
319
|
+
if ((s.stackAmount ?? 0) > 1) return s.stackAmount!;
|
|
320
|
+
if (/^sunder armor$/i.test(s.name)) return 5;
|
|
321
|
+
const m = s.description?.match(/up to\s+(\d+)\s+times/i);
|
|
322
|
+
if (m) {
|
|
323
|
+
const n = +m[1]!;
|
|
324
|
+
if (n > 1) return n;
|
|
325
|
+
}
|
|
326
|
+
return 0;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// True when the spell can leave a lasting aura, DoT or CC the target can "have". Any buff effect
|
|
330
|
+
// counts, since fixtures often omit hostile flags. A pure direct-damage spell does not.
|
|
331
|
+
export function spellAppliesTargetAura(s: SpellDef): boolean {
|
|
332
|
+
// 'cc' covers stun/slow/fear (control field); 'root' is a distinct legacy kind
|
|
333
|
+
// (Frost Nova); buff/dot cover the rest. A pure direct_damage spell (Scorch) has none.
|
|
334
|
+
return s.effects.some((e) =>
|
|
335
|
+
e.kind === 'buff' || e.kind === 'dot' || e.kind === 'cc' || e.kind === 'root',
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// itemCount gate for a conjure spell's buff-phase rule. Mage food and water are session provisions;
|
|
340
|
+
// everything else is single-use, and stockpiling five Soulstones is OOC thrash.
|
|
341
|
+
export function conjureItemCountThreshold(spell: SpellDef): number {
|
|
342
|
+
if (/^conjure (food|water)\b/i.test(spell.name)) return DEFAULT_THRESHOLDS.conjureBelow;
|
|
343
|
+
return DEFAULT_THRESHOLDS.conjureUtilityBelow;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Bind a conjure rule's stock gate to the spell's actual output item. Pack bands bake one rank's
|
|
347
|
+
// item id, so a level-40 band checking the higher id never stops while the mage casts the lower.
|
|
348
|
+
export function bindConjureStockGate(spell: SpellDef, conditions: Condition[]): Condition[] {
|
|
349
|
+
const conjured = spell.effects.find((e) => e.kind === 'conjure');
|
|
350
|
+
if (!conjured || conjured.kind !== 'conjure' || !(conjured.item > 0)) return conditions;
|
|
351
|
+
const item = conjured.item;
|
|
352
|
+
let changed = false;
|
|
353
|
+
const next = conditions.map((c) => {
|
|
354
|
+
if (c.metric !== 'itemCount' || c.op !== '<') return c;
|
|
355
|
+
if (c.ref === item) return c;
|
|
356
|
+
changed = true;
|
|
357
|
+
return { ...c, ref: item };
|
|
358
|
+
});
|
|
359
|
+
if (!next.some((c) => c.metric === 'itemCount' && c.op === '<')) {
|
|
360
|
+
return [cond('self', 'itemCount', '<', conjureItemCountThreshold(spell), item), ...next];
|
|
361
|
+
}
|
|
362
|
+
return changed ? next : conditions;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Rewrite conjure stock gates on an already-built rotation (live saves + companions). */
|
|
366
|
+
export function repairConjureStockGates(
|
|
367
|
+
rot: Rotation,
|
|
368
|
+
resolve: (spellId: number) => SpellDef | undefined | null,
|
|
369
|
+
): Rotation {
|
|
370
|
+
let changed = false;
|
|
371
|
+
const map = (rules: RotationRule[]): RotationRule[] => {
|
|
372
|
+
let any = false;
|
|
373
|
+
const next = rules.map((r) => {
|
|
374
|
+
if (r.action.kind !== 'spell') return r;
|
|
375
|
+
const sp = resolve(r.action.spellId);
|
|
376
|
+
if (!sp) return r;
|
|
377
|
+
const conds = bindConjureStockGate(sp, r.conditions);
|
|
378
|
+
if (conds === r.conditions) return r;
|
|
379
|
+
any = true;
|
|
380
|
+
return { ...r, conditions: conds };
|
|
381
|
+
});
|
|
382
|
+
if (any) changed = true;
|
|
383
|
+
return any ? next : rules;
|
|
384
|
+
};
|
|
385
|
+
const phases = {
|
|
386
|
+
prebuff: map(rot.phases.prebuff ?? []),
|
|
387
|
+
buffs: map(rot.phases.buffs),
|
|
388
|
+
pull: map(rot.phases.pull),
|
|
389
|
+
combat: map(rot.phases.combat),
|
|
390
|
+
recovery: map(rot.phases.recovery),
|
|
391
|
+
};
|
|
392
|
+
return changed ? { ...rot, phases } : rot;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** SpellEntry.TargetCreatureType mask: Undead (6) | Demon (3) → bit5 | bit2 = 32 | 4. */
|
|
396
|
+
export const UNDEAD_DEMON_CREATURE_MASK =
|
|
397
|
+
(1 << (CREATURE_TYPE.UNDEAD - 1)) | (1 << (CREATURE_TYPE.DEMON - 1));
|
|
398
|
+
|
|
399
|
+
/** Long-duration armor keep-ups (Demon Skin/Armor) are buffs, not emergency heals (KI-398/412). */
|
|
400
|
+
export function isLongArmorKeepUp(s: SpellDef): boolean {
|
|
401
|
+
return s.effects.some(
|
|
402
|
+
(e) => e.kind === 'buff' && e.mod === 'armor' && (e.durationMs ?? 0) >= 5 * 60_000,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// A friendly buff placed on ONE body at a time — Arcane Intellect, Fortitude, Mark of the Wild, the
|
|
407
|
+
// blessings. `applyTo: 'party'` fans itself and needs no second row. Cite: applySpell friendBuffTarget.
|
|
408
|
+
// A paladin Blessing is deliberately NOT one of these: `pb-ally-<id>` blankets one spell on
|
|
409
|
+
// everyone, and which Blessing a body wants is per-class (Wisdom on a rage warrior never even
|
|
410
|
+
// applies — SpellAuras.cpp:4838). bots/paladinPrePull.ts plans those per target instead.
|
|
411
|
+
export function isSingleTargetPartyBuff(s: SpellDef): boolean {
|
|
412
|
+
let single = false;
|
|
413
|
+
for (const e of s.effects ?? []) {
|
|
414
|
+
if (e.kind !== 'buff' || e.hostile === true) continue;
|
|
415
|
+
const applyTo = (e as { applyTo?: string }).applyTo;
|
|
416
|
+
if (applyTo === 'party') return false;
|
|
417
|
+
if (applyTo === 'friendly' && (e.durationMs ?? 0) > 0) single = true;
|
|
418
|
+
}
|
|
419
|
+
// A damage shield carries no applyTo, so Thorns read as a self keep-up and the tank never got it.
|
|
420
|
+
// The DBC target is the source: TARGET_UNIT_FRIEND / TARGET_UNIT_PARTY name a body, not the caster.
|
|
421
|
+
if (
|
|
422
|
+
!single
|
|
423
|
+
&& !auraRadiatesToParty(s)
|
|
424
|
+
&& auraHolderIsCastTarget(s)
|
|
425
|
+
&& (s.effects ?? []).some((e) => e.kind === 'damage_shield' && (e.durationMs ?? 0) > 0)
|
|
426
|
+
) {
|
|
427
|
+
single = true;
|
|
428
|
+
}
|
|
429
|
+
return single;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Bloodrage rage-generator cast (not the periodic aura child). */
|
|
433
|
+
export function isBloodrageSpell(s: SpellDef): boolean {
|
|
434
|
+
return normalizeSpellName(s.name) === 'bloodrage';
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Labels that mean "consume a Healthstone item", never "Create Healthstone" conjure. Authored recovery rows
|
|
438
|
+
// say "Use Healthstone"; fuzzy spell resolve otherwise hits Create*.
|
|
439
|
+
export function isUseHealthstoneLabel(label: string, labels: readonly string[]): boolean {
|
|
440
|
+
const all = [label, ...labels].map((l) => normalizeSpellName(l));
|
|
441
|
+
if (all.some((l) => /^create\b/.test(l))) return false;
|
|
442
|
+
return all.some(
|
|
443
|
+
(l) =>
|
|
444
|
+
l === 'use healthstone'
|
|
445
|
+
|| l === 'healthstone'
|
|
446
|
+
|| /^(minor|lesser|greater|major) healthstone$/.test(l)
|
|
447
|
+
|| /healthstone/.test(l) && /use|consume|eat/.test(l),
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/** Party utility that increases *incoming* spell damage — never solo self keep-up (KI-289). */
|
|
452
|
+
export function isHostileSelfUtilityBuff(s: SpellDef): boolean {
|
|
453
|
+
const n = normalizeSpellName(s.name);
|
|
454
|
+
return n === 'amplify magic' || n.startsWith('amplify magic ');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Party or battle resurrection. Never a self buffPresent keep-up: no aura lands and costPct is
|
|
458
|
+
// 75–90% of base mana, so the role-bucket "buff absent" path OOMs every prep cycle.
|
|
459
|
+
export function isResurrectSpell(s: {
|
|
460
|
+
name?: string;
|
|
461
|
+
effects?: ReadonlyArray<{ kind: string }> | null;
|
|
462
|
+
}): boolean {
|
|
463
|
+
if (s.effects?.some((e) => e.kind === 'resurrect' || e.kind === 'resurrect_pct')) return true;
|
|
464
|
+
const n = normalizeSpellName(s.name ?? '');
|
|
465
|
+
// Exact classic player rez names only — not "Spirit of Redemption" (priest talent) or
|
|
466
|
+
// "Soulstone Resurrection" (warlock self-rez item spell).
|
|
467
|
+
return n === 'redemption'
|
|
468
|
+
|| n === 'resurrection'
|
|
469
|
+
|| n === 'ancestral spirit'
|
|
470
|
+
|| n === 'rebirth';
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Battle rez — the resurrection cast DURING a fight. Read off the row, not the name: a resurrect
|
|
474
|
+
// with a real cooldown and a short cast. In 1.12 that is exactly druid Rebirth.
|
|
475
|
+
export function isBattleRezSpell(s: {
|
|
476
|
+
name?: string;
|
|
477
|
+
castTimeMs?: number;
|
|
478
|
+
cooldownMs?: number;
|
|
479
|
+
effects?: ReadonlyArray<{ kind: string }> | null;
|
|
480
|
+
}): boolean {
|
|
481
|
+
if (!isResurrectSpell(s)) return false;
|
|
482
|
+
return (s.cooldownMs ?? 0) > 0 && (s.castTimeMs ?? 0) <= BATTLE_REZ_MAX_CAST_MS;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** Rebirth is 2 s; the out-of-combat rezzes are all 10 s. 5 s splits them with room to spare. */
|
|
486
|
+
export const BATTLE_REZ_MAX_CAST_MS = 5_000;
|
|
487
|
+
|
|
488
|
+
/** Gate for party rez: only when a dead friendly exists (snapshot fills deadAllyCount). */
|
|
489
|
+
export function resurrectGateConditions(): Condition[] {
|
|
490
|
+
return [cond('self', 'deadAllyCount', '>=', 1)];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// One-shot mobility / CD utilities that must not sit as buffPresent keep-ups (gate never clears when
|
|
494
|
+
// effects are empty). Blink, Cold Snap, Presence of Mind, Amplify Curse, empty aspects.
|
|
495
|
+
export function isNonKeepUpUtility(s: SpellDef): boolean {
|
|
496
|
+
const n = normalizeSpellName(s.name);
|
|
497
|
+
if (
|
|
498
|
+
n === 'blink'
|
|
499
|
+
|| n === 'cold snap'
|
|
500
|
+
|| n === 'presence of mind'
|
|
501
|
+
|| n === 'amplify curse'
|
|
502
|
+
|| n === 'will of the forsaken'
|
|
503
|
+
|| n === 'perception'
|
|
504
|
+
|| n === 'escape artist'
|
|
505
|
+
|| n === 'stoneform'
|
|
506
|
+
|| n === 'every man for himself'
|
|
507
|
+
) {
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
// Empty-effect short/long CDs are not maintainable buffs.
|
|
511
|
+
if ((s.effects?.length ?? 0) === 0 && (s.cooldownMs ?? 0) >= 10_000) return true;
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ---- Empty-effect / long-CD thrash guards (KI-364 / KI-375 / KI-373–377) ----
|
|
516
|
+
|
|
517
|
+
// Spells that still do work with empty bake `effects[]` (strikes, forms, seals, resource stubs). Used so we
|
|
518
|
+
// do not drop Life Tap / Heroic Strike / Seal labels while filtering inert ALWAYS / keep-up thrash.
|
|
519
|
+
export function spellWorksWithoutBakeEffects(s: SpellDef): boolean {
|
|
520
|
+
if (s.onNextSwing || (s.strikeBonus ?? 0) > 0) return true;
|
|
521
|
+
const role = s.role;
|
|
522
|
+
if (role === 'strike' || role === 'shoot' || role === 'form' || role === 'seal' || role === 'judgement'
|
|
523
|
+
|| role === 'pet_summon' || role === 'charge' || role === 'stealth') {
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
if (s.comboPointsGenerated || s.requiresComboPoints) return true;
|
|
527
|
+
if (isFormSpell(s) || formIdFromLabel(s.name) != null) return true;
|
|
528
|
+
if (isSealSpell(s) || isJudgementSpell(s)) return true;
|
|
529
|
+
// Classic seals often bake empty; still valid keep-ups once the interpreter resolves them.
|
|
530
|
+
if (/^seal of\b/i.test(s.name) || /^judgement\b/i.test(s.name)) return true;
|
|
531
|
+
// Wave 2-LOCK utility stubs: empty bake, resource-gated in packs (not keep-up thrash).
|
|
532
|
+
if (/^life tap$/i.test(s.name) || /^dark pact$/i.test(s.name)) return true;
|
|
533
|
+
// Feign Death 5384 — bake effects[] empty; interpreter emits modify_threat_pct −100.
|
|
534
|
+
if (isFeignDeathSpell(s)) return true;
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Player Feign Death (5384). 5385 is LEARN_SPELL of 5384 — never the combat action. Cite: trainers.json
|
|
539
|
+
// Hunter Trainer 1003 teaches 5384 @30; spell_template 5385 Effect1=36.
|
|
540
|
+
export function isFeignDeathSpell(s: { name?: string; rawEffects?: ReadonlyArray<{ effect?: number }> | null }): boolean {
|
|
541
|
+
if (!/^feign death$/i.test(s.name ?? '')) return false;
|
|
542
|
+
return (s.rawEffects?.[0]?.effect ?? 0) !== 36;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Pure buffPresent/debuffPresent==0 keep-up (no other gates). */
|
|
546
|
+
export function isPresenceKeepUpOnly(conditions: readonly Condition[]): boolean {
|
|
547
|
+
return conditions.length > 0 && conditions.every(
|
|
548
|
+
(c) => (c.metric === 'buffPresent' || c.metric === 'debuffPresent')
|
|
549
|
+
&& c.op === '=='
|
|
550
|
+
&& c.value === 0,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Drop empty-bake rules that would thrash as ALWAYS combat fillers or presence keep-ups. Resource,
|
|
555
|
+
// health and form gates are kept even when the baked effects are empty (Life Tap, Execute).
|
|
556
|
+
export function shouldDropEmptyEffectRule(s: SpellDef, conditions: readonly Condition[]): boolean {
|
|
557
|
+
if ((s.effects?.length ?? 0) > 0) return false;
|
|
558
|
+
if (spellWorksWithoutBakeEffects(s)) return false;
|
|
559
|
+
if (conditions.length === 0) return true; // ALWAYS
|
|
560
|
+
if (isPresenceKeepUpOnly(conditions)) return true;
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** max_health_pct emergency (Last Stand) — recovery health gate, not pull thrash. */
|
|
565
|
+
export function isMaxHealthPctEmergency(s: SpellDef): boolean {
|
|
566
|
+
return s.effects.some((e) => e.kind === 'buff' && e.mod === 'max_health_pct');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Self CDs that must not enter pull/buffs as buffPresent keep-ups: long cooldown combat CDs, next-cast arms
|
|
570
|
+
// (Fel Domination / PoM), pure mechanic-immunity bursts (Berserker Rage).
|
|
571
|
+
export function shouldSkipBuffKeepUp(s: SpellDef): boolean {
|
|
572
|
+
if (s.effects.some((e) => e.kind === 'next_cast_mod' || e.kind === 'reset_cooldowns')) return true;
|
|
573
|
+
if (s.effects.length > 0 && s.effects.every((e) => e.kind === 'mechanic_immunity')) return true;
|
|
574
|
+
if ((s.cooldownMs ?? 0) >= DEFAULT_THRESHOLDS.combatCdKeepUpMinMs) return true;
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ---- Smart defaults for manually-added rules --------------------------------
|
|
579
|
+
// Prefill the same guard the default generator would have chosen, so a fresh rule behaves sensibly.
|
|
580
|
+
|
|
581
|
+
/** True when the spell carries an interrupt effect (Kick/Pummel/Counterspell/Earth Shock…). */
|
|
582
|
+
const interruptSpellMemo = new WeakMap<SpellDef, boolean>();
|
|
583
|
+
export function isInterruptSpell(s: SpellDef): boolean {
|
|
584
|
+
const hit = interruptSpellMemo.get(s);
|
|
585
|
+
if (hit !== undefined) return hit;
|
|
586
|
+
const v = s.effects.some((e) => e.kind === 'interrupt_cast');
|
|
587
|
+
interruptSpellMemo.set(s, v);
|
|
588
|
+
return v;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Kick, Pummel, Counterspell and Shield Bash — the interrupt is the whole spell. Earth Shock also
|
|
592
|
+
// does damage, so a cast gate on its authored always-copy would delete the instant filler.
|
|
593
|
+
export function isPureInterruptSpell(s: {
|
|
594
|
+
effects?: ReadonlyArray<{ kind?: string }>;
|
|
595
|
+
}): boolean {
|
|
596
|
+
const effects = s.effects;
|
|
597
|
+
if (!Array.isArray(effects) || !effects.some((e) => e.kind === 'interrupt_cast')) return false;
|
|
598
|
+
return !effects.some(
|
|
599
|
+
(e) =>
|
|
600
|
+
e.kind === 'direct_damage'
|
|
601
|
+
|| e.kind === 'dot'
|
|
602
|
+
|| e.kind === 'weapon_percent_damage'
|
|
603
|
+
|| e.kind === 'weapon_damage'
|
|
604
|
+
|| e.kind === 'weapon_strike',
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Damageless fear CC (Fear, Psychic Scream, Howl of Terror, Intimidating Shout, Scare Beast, Turn Undead).
|
|
609
|
+
// Death Coil is a nuke that happens to fear — not this.
|
|
610
|
+
export function isFearSpell(s: {
|
|
611
|
+
effects?: ReadonlyArray<{ kind?: string; control?: string }>;
|
|
612
|
+
} | null | undefined): boolean {
|
|
613
|
+
const effects = s?.effects;
|
|
614
|
+
if (!Array.isArray(effects) || effects.length === 0) return false;
|
|
615
|
+
if (!effects.some((e) => e.kind === 'cc' && e.control === 'fear')) return false;
|
|
616
|
+
return !effects.some(
|
|
617
|
+
(e) =>
|
|
618
|
+
e.kind === 'direct_damage'
|
|
619
|
+
|| e.kind === 'dot'
|
|
620
|
+
|| e.kind === 'weapon_percent_damage'
|
|
621
|
+
|| e.kind === 'weapon_damage'
|
|
622
|
+
|| e.kind === 'weapon_strike',
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// True when the spell is a PRIMARY dispel or purge. A spell carrying only a secondary dispel chance
|
|
627
|
+
// on top of damage, like Shield Slam, is a damage nuke for auto-battler classification.
|
|
628
|
+
const dispelSpellMemo = new WeakMap<SpellDef, boolean>();
|
|
629
|
+
export function isDispelSpell(s: SpellDef): boolean {
|
|
630
|
+
const hit = dispelSpellMemo.get(s);
|
|
631
|
+
if (hit !== undefined) return hit;
|
|
632
|
+
const v = s.effects.some((e) => e.kind === 'dispel')
|
|
633
|
+
&& !s.effects.some((e) => e.kind === 'direct_damage' || e.kind === 'dot');
|
|
634
|
+
dispelSpellMemo.set(s, v);
|
|
635
|
+
return v;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// One entry per rule. Categories on the SAME subject ride one rule OR-joined (Purify strips disease
|
|
639
|
+
// OR poison); magic stays two rules, an enemy purge and a friendly cleanse. Cite: conditionsPass.
|
|
640
|
+
export function dispelGateConditions(s: SpellDef): Condition[][] {
|
|
641
|
+
const cats = new Set<number>();
|
|
642
|
+
for (const e of s.effects) if (e.kind === 'dispel') cats.add(e.dispelType);
|
|
643
|
+
// Who the cast may be aimed at, so MAGIC's dual gate never offers Purge a friendly.
|
|
644
|
+
// Cite: SpellTargetDefines.h TARGET_UNIT_CASTER=1 / _ENEMY=6 / _FRIEND=21 / TARGET_UNIT=25.
|
|
645
|
+
const raws = s.rawEffects ?? [];
|
|
646
|
+
const hitsEnemy = raws.length === 0 || raws.some((r) => (r.targetA | 0) === 6 || (r.targetA | 0) === 25);
|
|
647
|
+
const hitsFriend = raws.length === 0 || raws.some((r) => {
|
|
648
|
+
const t = r.targetA | 0;
|
|
649
|
+
return t === 1 || t === 21 || t === 25;
|
|
650
|
+
});
|
|
651
|
+
const bySubject = new Map<CondSubject, number[]>();
|
|
652
|
+
const put = (subject: CondSubject, cat: number): void => {
|
|
653
|
+
const list = bySubject.get(subject);
|
|
654
|
+
if (list) list.push(cat);
|
|
655
|
+
else bySubject.set(subject, [cat]);
|
|
656
|
+
};
|
|
657
|
+
for (const cat of cats) {
|
|
658
|
+
const isCure = cat === 2 || cat === 3 || cat === 4; // CURSE / DISEASE / POISON
|
|
659
|
+
if (isCure) put('self', cat);
|
|
660
|
+
else if (cat === 1) { // MAGIC — dual (offensive purge + defensive cleanse)
|
|
661
|
+
if (hitsEnemy) put('target', cat);
|
|
662
|
+
if (hitsFriend) put('self', cat);
|
|
663
|
+
} else put('target', cat); // enrage / stealth / invisibility
|
|
664
|
+
}
|
|
665
|
+
const out: Condition[][] = [];
|
|
666
|
+
for (const [subject, list] of bySubject) {
|
|
667
|
+
out.push([...list].sort((a, b) => a - b).map((cat, i) => ({
|
|
668
|
+
...cond(subject, 'dispellablePresent', '==', 1, cat),
|
|
669
|
+
...(i > 0 ? { join: 'or' as const } : {}),
|
|
670
|
+
})));
|
|
671
|
+
}
|
|
672
|
+
return out;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Whether a buff effect is a hostile aura on the enemy. Prefer explicit `hostile`, and never treat
|
|
676
|
+
// hostile:false as hostile even when the amount is negative. Cite: combat.ts isHostileBuffEffect.
|
|
677
|
+
export function isHostileBuffAura(e: {
|
|
678
|
+
kind?: string;
|
|
679
|
+
hostile?: boolean;
|
|
680
|
+
amount?: number;
|
|
681
|
+
}): boolean {
|
|
682
|
+
if (e.kind !== 'buff') return false;
|
|
683
|
+
if (e.hostile === true) return true;
|
|
684
|
+
if (e.hostile === false) return false;
|
|
685
|
+
return (e.amount ?? 0) < 0;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** The hostile aura ('buff' with hostile flag / negative amount) this spell puts on the target. */
|
|
689
|
+
export function hostileAuraEffect(s: SpellDef): SpellDef['effects'][number] | undefined {
|
|
690
|
+
return s.effects.find((e) => isHostileBuffAura(e));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// ---- Offensive burst cooldowns and CC breaks ------------------------------------------
|
|
694
|
+
// A burst granting fear immunity read as a panic defensive, so five sites gated it on low health.
|
|
695
|
+
|
|
696
|
+
/** SPELL_EFFECT_DUMMY — scripted racials (Blood Fury / Berserking) carry no aura of their own. */
|
|
697
|
+
const SPELL_EFFECT_DUMMY = 3;
|
|
698
|
+
/** SPELL_EFFECT_APPLY_AURA. */
|
|
699
|
+
const SPELL_EFFECT_APPLY_AURA_RAW = 6;
|
|
700
|
+
/** SPELL_AURA_MOD_CRIT_PERCENT — Recklessness's +100% crit lives only in rawEffects. */
|
|
701
|
+
const AURA_MOD_CRIT_PERCENT = 52;
|
|
702
|
+
/** SPELL_AURA_ADD_FLAT_MODIFIER / ADD_PCT_MODIFIER — the SpellModOp carriers. */
|
|
703
|
+
const AURA_ADD_FLAT_MODIFIER = 107;
|
|
704
|
+
const AURA_ADD_PCT_MODIFIER = 108;
|
|
705
|
+
/** SPELL_AURA_MOD_POWER_REGEN_PERCENT — Adrenaline Rush (energy), Evocation (mana). */
|
|
706
|
+
const AURA_MOD_POWER_REGEN_PERCENT = 110;
|
|
707
|
+
/** SPELL_AURA_MOD_RANGED_HASTE — Rapid Fire. */
|
|
708
|
+
const AURA_MOD_RANGED_HASTE = 140;
|
|
709
|
+
/** SPELL_AURA_MECHANIC_IMMUNITY. */
|
|
710
|
+
const AURA_MECHANIC_IMMUNITY = 77;
|
|
711
|
+
|
|
712
|
+
// SpellModOps that raise the caster's own OUTPUT. Cost and casting time are deliberately absent:
|
|
713
|
+
// they are cast-enablers whose value depends on what you cast next, which a priority list cannot promise.
|
|
714
|
+
const OFFENSIVE_SPELLMOD_OPS: ReadonlySet<number> = new Set([
|
|
715
|
+
0, // DAMAGE — Arcane Power
|
|
716
|
+
7, // CRITICAL_CHANCE — Inner Focus, Elemental Mastery, Cold Blood, Divine Favor, Combustion
|
|
717
|
+
8, // ALL_EFFECTS — Amplify Curse
|
|
718
|
+
22, // DOT — Arcane Power
|
|
719
|
+
24, // SPELL_BONUS_DAMAGE
|
|
720
|
+
]);
|
|
721
|
+
|
|
722
|
+
/** BuffMods that raise the caster's own output (positive amount only). */
|
|
723
|
+
const OUTPUT_BUFF_MODS: ReadonlySet<string> = new Set([
|
|
724
|
+
'damage_done_pct', 'crit', 'haste', 'attack_power', 'ranged_attack_power', 'spell_damage',
|
|
725
|
+
]);
|
|
726
|
+
|
|
727
|
+
/** BuffMods whose NEGATIVE amount means mitigation — never an offensive burst. */
|
|
728
|
+
const MITIGATION_BUFF_MODS: ReadonlySet<string> = new Set([
|
|
729
|
+
'damage_taken_pct', 'physical_taken', 'physical_taken_pct', 'spell_taken', 'max_health_pct',
|
|
730
|
+
]);
|
|
731
|
+
|
|
732
|
+
// Racial dummies whose payload the interpreter casts for us — SPELL_EFFECT_DUMMY, so nothing in the
|
|
733
|
+
// row itself says "this raises damage". Cite: SpellEffects.cpp:760 case 20572; :1267–1285 icon 1661.
|
|
734
|
+
const BLOOD_FURY_SPELL_ID = 20572;
|
|
735
|
+
const BERSERKING_ICON_ID = 1661;
|
|
736
|
+
|
|
737
|
+
function rawApplyAuras(s: SpellDef): NonNullable<SpellDef['rawEffects']> {
|
|
738
|
+
return (s.rawEffects ?? []).filter((r) => (r.effect | 0) === SPELL_EFFECT_APPLY_AURA_RAW);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** A self buff that raises output, read off the baked effects. */
|
|
742
|
+
function hasOutputBuffEffect(s: SpellDef): boolean {
|
|
743
|
+
return s.effects.some(
|
|
744
|
+
(e) =>
|
|
745
|
+
e.kind === 'buff'
|
|
746
|
+
&& OUTPUT_BUFF_MODS.has((e as { mod?: string }).mod ?? '')
|
|
747
|
+
&& ((e as { amount?: number }).amount ?? 0) > 0
|
|
748
|
+
&& (e as { hostile?: boolean }).hostile !== true,
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// A cost the CASTER pays for more output: more damage taken, or less armor. A spell that makes you
|
|
753
|
+
// squishier is definitionally not the button you press at 20% health.
|
|
754
|
+
function hasOffensiveSelfDownside(s: SpellDef): boolean {
|
|
755
|
+
return s.effects.some((e) => {
|
|
756
|
+
if (e.kind !== 'buff') return false;
|
|
757
|
+
const mod = (e as { mod?: string }).mod ?? '';
|
|
758
|
+
const amount = (e as { amount?: number }).amount ?? 0;
|
|
759
|
+
if (mod === 'damage_taken_pct' && amount > 0) return true;
|
|
760
|
+
return mod === 'armor_pct' && amount < 0;
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/** Mitigation / immunity payloads that make a spell defensive whatever else it carries. */
|
|
765
|
+
function hasMitigationPayload(s: SpellDef): boolean {
|
|
766
|
+
return s.effects.some((e) => {
|
|
767
|
+
if (e.kind === 'school_immunity' || e.kind === 'damage_immunity') return true;
|
|
768
|
+
if (e.kind !== 'buff') return false;
|
|
769
|
+
const mod = (e as { mod?: string }).mod ?? '';
|
|
770
|
+
const amount = (e as { amount?: number }).amount ?? 0;
|
|
771
|
+
if (MITIGATION_BUFF_MODS.has(mod) && amount < 0) return true;
|
|
772
|
+
return mod === 'armor_pct' && amount > 0;
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/** Output-raising aura in a raw APPLY_AURA slot (crit%, ranged haste, offensive SpellMod). */
|
|
777
|
+
function rawSlotRaisesOutput(r: NonNullable<SpellDef['rawEffects']>[number]): boolean {
|
|
778
|
+
const aura = r.auraName | 0;
|
|
779
|
+
if (aura === AURA_MOD_CRIT_PERCENT || aura === AURA_MOD_RANGED_HASTE) return true;
|
|
780
|
+
return (aura === AURA_ADD_FLAT_MODIFIER || aura === AURA_ADD_PCT_MODIFIER)
|
|
781
|
+
&& OFFENSIVE_SPELLMOD_OPS.has(r.miscValue | 0);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// True when the spell is a burst spent for more output — a damage cooldown or racial. Most bake
|
|
785
|
+
// empty, so the read goes to rawEffects too. Mitigation, heals and cast-enablers are NOT here.
|
|
786
|
+
export function isOffensiveBurstCooldown(
|
|
787
|
+
s: SpellDef,
|
|
788
|
+
resolve?: (id: number) => SpellDef | null | undefined,
|
|
789
|
+
): boolean {
|
|
790
|
+
// Self-cast, on a real cooldown. The floor is the same one that decides a CD is not a
|
|
791
|
+
// maintainable keep-up, so nothing can be both a burst and a keep-up.
|
|
792
|
+
if ((s.rangeMax ?? 0) !== 0) return false;
|
|
793
|
+
if ((s.cooldownMs ?? 0) < DEFAULT_THRESHOLDS.combatCdKeepUpMinMs) return false;
|
|
794
|
+
if (isFormSpell(s) || formIdForRotationSpell(s) > 0) return false;
|
|
795
|
+
if (isSealSpell(s) || isJudgementSpell(s) || isResurrectSpell(s)) return false;
|
|
796
|
+
if (s.requiresComboPoints || s.comboPointsGenerated) return false;
|
|
797
|
+
// A heal is a recovery action and mitigation is a panic CD. Neither is a burst.
|
|
798
|
+
if (s.effects.some(
|
|
799
|
+
(e) => e.kind === 'heal' || e.kind === 'restore_health' || e.kind === 'heal_max_health',
|
|
800
|
+
)) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
if (hasMitigationPayload(s)) return false;
|
|
804
|
+
// A hostile aura belongs on the enemy (Demo Shout, curses) — that is a debuff keep-up.
|
|
805
|
+
if (hostileAuraEffect(s)) return false;
|
|
806
|
+
|
|
807
|
+
if (hasOutputBuffEffect(s) || hasOffensiveSelfDownside(s)) return true;
|
|
808
|
+
|
|
809
|
+
for (const r of rawApplyAuras(s)) {
|
|
810
|
+
if (rawSlotRaisesOutput(r)) return true;
|
|
811
|
+
// Power regen: energy/rage only. miscValue 0 is mana, i.e. Evocation — a mana recovery
|
|
812
|
+
// channel, not a damage burst.
|
|
813
|
+
if (
|
|
814
|
+
(r.auraName | 0) === AURA_MOD_POWER_REGEN_PERCENT
|
|
815
|
+
&& (r.basePoints | 0) > 0
|
|
816
|
+
&& (r.miscValue | 0) !== 0
|
|
817
|
+
) {
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Scripted racial dummies (Blood Fury, Berserking) — no aura of their own to read.
|
|
823
|
+
if (
|
|
824
|
+
(s.rawEffects ?? []).some((r) => (r.effect | 0) === SPELL_EFFECT_DUMMY)
|
|
825
|
+
&& (s.id === BLOOD_FURY_SPELL_ID || (s.spellIconId ?? 0) === BERSERKING_ICON_ID)
|
|
826
|
+
) {
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// One trigger hop: Combustion 11129's whole payload is trigger_spell 28682 (a crit SpellMod).
|
|
831
|
+
if (resolve) {
|
|
832
|
+
for (const e of s.effects) {
|
|
833
|
+
if (e.kind !== 'trigger_spell') continue;
|
|
834
|
+
const inner = resolve((e as { spellId?: number }).spellId ?? 0);
|
|
835
|
+
if (!inner || inner.id === s.id) continue;
|
|
836
|
+
if (hasMitigationPayload(inner)) continue;
|
|
837
|
+
if (hasOutputBuffEffect(inner)) return true;
|
|
838
|
+
if (rawApplyAuras(inner).some(rawSlotRaisesOutput)) return true;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Gate for a damage cooldown or racial, two conditions at most so the row still reads as a
|
|
845
|
+
// sentence: spend it once the fight is real, and — when it costs you defence — not while low.
|
|
846
|
+
export function burstCooldownConditions(s: SpellDef): Condition[] {
|
|
847
|
+
const long = (s.cooldownMs ?? 0) >= DEFAULT_THRESHOLDS.burstLongCdMs;
|
|
848
|
+
const out: Condition[] = [
|
|
849
|
+
cond('self', 'combatTimeMs', '>=', long
|
|
850
|
+
? DEFAULT_THRESHOLDS.burstLongFightMs
|
|
851
|
+
: DEFAULT_THRESHOLDS.burstOpenMs),
|
|
852
|
+
];
|
|
853
|
+
if (hasOffensiveSelfDownside(s)) {
|
|
854
|
+
out.push(cond('self', 'healthPct', '>', DEFAULT_THRESHOLDS.emergencyDefensivePct));
|
|
855
|
+
}
|
|
856
|
+
return out;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// The control state each immunity mechanic describes, for the mechanics the rotation vocabulary can
|
|
860
|
+
// see. Cite: SharedDefines.h MECHANIC_FEAR 5 / ROOT 7 / SNARE 11 / STUN 12.
|
|
861
|
+
const CC_BREAK_METRIC_BY_MECHANIC: Readonly<Record<number, CondMetric>> = {
|
|
862
|
+
5: 'feared',
|
|
863
|
+
7: 'rooted',
|
|
864
|
+
11: 'snared',
|
|
865
|
+
12: 'stunned',
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
// Order the break rows are emitted in — worst control first, so a caller that can take only one
|
|
869
|
+
// takes the one worth breaking. Without a fixed order this followed effect slot order.
|
|
870
|
+
const CC_BREAK_MECHANIC_PRIORITY: readonly number[] = [12, 5, 7, 11];
|
|
871
|
+
|
|
872
|
+
// Gate conditions for a self-cast whose WHOLE payload is mechanic immunity. The honest trigger is
|
|
873
|
+
// "I am feared", never "I am low". One rule per mechanic, since a rule's conditions AND.
|
|
874
|
+
export function ccBreakGateConditions(s: SpellDef): Condition[][] {
|
|
875
|
+
if ((s.rangeMax ?? 0) !== 0) return [];
|
|
876
|
+
if (isFormSpell(s) || formIdForRotationSpell(s) > 0) return [];
|
|
877
|
+
const baked = s.effects;
|
|
878
|
+
const raws = rawApplyAuras(s);
|
|
879
|
+
const pureBaked = baked.length > 0 && baked.every((e) => e.kind === 'mechanic_immunity');
|
|
880
|
+
const pureRaw = baked.length === 0
|
|
881
|
+
&& raws.length > 0
|
|
882
|
+
&& raws.every((r) => (r.auraName | 0) === AURA_MECHANIC_IMMUNITY);
|
|
883
|
+
if (!pureBaked && !pureRaw) return [];
|
|
884
|
+
|
|
885
|
+
const mechanics = new Set<number>();
|
|
886
|
+
for (const e of baked) {
|
|
887
|
+
if (e.kind === 'mechanic_immunity') mechanics.add((e as { mechanic?: number }).mechanic ?? 0);
|
|
888
|
+
}
|
|
889
|
+
for (const r of raws) {
|
|
890
|
+
if ((r.auraName | 0) === AURA_MECHANIC_IMMUNITY) mechanics.add(r.miscValue | 0);
|
|
891
|
+
}
|
|
892
|
+
const out: Condition[][] = [];
|
|
893
|
+
for (const m of CC_BREAK_MECHANIC_PRIORITY) {
|
|
894
|
+
if (!mechanics.has(m)) continue;
|
|
895
|
+
const metric = CC_BREAK_METRIC_BY_MECHANIC[m];
|
|
896
|
+
if (metric) out.push([cond('self', metric, '==', 1)]);
|
|
897
|
+
}
|
|
898
|
+
return out;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Self immunities and long-cooldown mitigation, gated on self health and never as enemy debuff
|
|
902
|
+
// keep-ups. Shapeshift forms carry mechanic_immunity too — treating those as panic CDs killed them.
|
|
903
|
+
export function isSelfDefensiveSpell(s: SpellDef): boolean {
|
|
904
|
+
// Forms/stances: enter rules use formId gates, never emergency HP panic inject.
|
|
905
|
+
if (isFormSpell(s) || formIdForRotationSpell(s) > 0) return false;
|
|
906
|
+
// A burst you spend for more output is not a panic button, even though Death Wish and
|
|
907
|
+
// Recklessness both carry fear immunity and so matched the immunity branch below.
|
|
908
|
+
if (isOffensiveBurstCooldown(s)) return false;
|
|
909
|
+
const selfRange = (s.rangeMax ?? 0) === 0;
|
|
910
|
+
if (
|
|
911
|
+
selfRange
|
|
912
|
+
&& s.effects.some(
|
|
913
|
+
(e) =>
|
|
914
|
+
e.kind === 'school_immunity'
|
|
915
|
+
|| e.kind === 'damage_immunity'
|
|
916
|
+
|| e.kind === 'mechanic_immunity',
|
|
917
|
+
)
|
|
918
|
+
) {
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
// Mitigation self buffs with long CD (Shield Wall physical_taken −75 hostile:false).
|
|
922
|
+
if (
|
|
923
|
+
(s.cooldownMs ?? 0) >= DEFAULT_THRESHOLDS.combatCdKeepUpMinMs
|
|
924
|
+
&& s.effects.some(
|
|
925
|
+
(e) =>
|
|
926
|
+
e.kind === 'buff'
|
|
927
|
+
&& e.hostile === false
|
|
928
|
+
&& (e.mod === 'physical_taken'
|
|
929
|
+
|| e.mod === 'physical_taken_pct'
|
|
930
|
+
|| e.mod === 'damage_taken_pct'
|
|
931
|
+
|| e.mod === 'spell_taken'
|
|
932
|
+
|| e.mod === 'haste')
|
|
933
|
+
&& (e.amount ?? 0) < 0,
|
|
934
|
+
)
|
|
935
|
+
) {
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// A threat DUMP: the spell's only threat term is negative. isThreatOrTauntUtility is the superset,
|
|
942
|
+
// also matching taunts and positive pulses, whose default gate is the exact inverse.
|
|
943
|
+
export function isThreatDumpUtility(s: SpellDef): boolean {
|
|
944
|
+
let negative = false;
|
|
945
|
+
for (const e of s.effects) {
|
|
946
|
+
if (e.kind === 'threat') {
|
|
947
|
+
if ((e.amount ?? 0) >= 0) return false;
|
|
948
|
+
negative = true;
|
|
949
|
+
} else if (e.kind === 'modify_threat_pct') {
|
|
950
|
+
if (e.pct >= 0) return false;
|
|
951
|
+
negative = true;
|
|
952
|
+
} else if (e.kind === 'taunt' || e.kind === 'mod_taunt') {
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return negative;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Spells that must not be scheduled as permanent target-debuff keep-ups in solo defaults: threat
|
|
960
|
+
// dumps and gens, taunts, self cooldowns, pure mana drains. Situational CC is omitted entirely.
|
|
961
|
+
const threatTauntMemo = new WeakMap<SpellDef, boolean>();
|
|
962
|
+
export function isThreatOrTauntUtility(s: SpellDef): boolean {
|
|
963
|
+
const cached = threatTauntMemo.get(s);
|
|
964
|
+
if (cached !== undefined) return cached;
|
|
965
|
+
const v = computeThreatOrTauntUtility(s);
|
|
966
|
+
threatTauntMemo.set(s, v);
|
|
967
|
+
return v;
|
|
968
|
+
}
|
|
969
|
+
function computeThreatOrTauntUtility(s: SpellDef): boolean {
|
|
970
|
+
// Negative threat pulses (Feint, Disengage, Cower).
|
|
971
|
+
if (s.effects.some((e) => e.kind === 'threat' && (e.amount ?? 0) < 0)) return true;
|
|
972
|
+
// modify_threat_pct uses `pct` (signed), not `amount`. Cite: SpellEffects.cpp:5262–5268.
|
|
973
|
+
if (s.effects.some((e) => e.kind === 'modify_threat_pct' && e.pct < 0)) return true;
|
|
974
|
+
// Pure positive threat (Distracting Shot) — not a reapply-debuff.
|
|
975
|
+
if (
|
|
976
|
+
s.effects.some((e) => e.kind === 'threat' || e.kind === 'modify_threat_pct')
|
|
977
|
+
&& !s.effects.some(
|
|
978
|
+
(e) =>
|
|
979
|
+
e.kind === 'direct_damage'
|
|
980
|
+
|| e.kind === 'dot'
|
|
981
|
+
|| e.kind === 'weapon_percent_damage'
|
|
982
|
+
|| isHostileBuffAura(e),
|
|
983
|
+
)
|
|
984
|
+
) {
|
|
985
|
+
return true;
|
|
986
|
+
}
|
|
987
|
+
// AoE/ST taunts (Challenging Shout/Roar, Mocking Blow).
|
|
988
|
+
if (s.effects.some((e) => e.kind === 'taunt')) return true;
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Pure mana-leech channels (Drain Mana) — situational vs casters, not trash DoT keep-ups. Viper Sting has
|
|
993
|
+
// the same shape; solo defaults omit both (authored packs may re-add).
|
|
994
|
+
export function isManaDrainKeepUp(s: SpellDef): boolean {
|
|
995
|
+
const hasManaLeech = s.effects.some(
|
|
996
|
+
(e) => e.kind === 'restore_power' && (e as { amountPerTick?: number }).amountPerTick! < 0,
|
|
997
|
+
);
|
|
998
|
+
if (!hasManaLeech) return false;
|
|
999
|
+
// Real damage DoTs (Corruption) stay; pure leech-only does not.
|
|
1000
|
+
return !s.effects.some((e) => e.kind === 'dot' || e.kind === 'direct_damage');
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Whether defaults should schedule a target-debuff keep-up. Hostile auras yes; self, friendly,
|
|
1004
|
+
// threat, taunt, CC and reacts no — a self-stun or a disarm must never become missing-debuff thrash.
|
|
1005
|
+
export function shouldScheduleTargetDebuffKeepUp(s: SpellDef): boolean {
|
|
1006
|
+
if (isSelfDefensiveSpell(s)) return false;
|
|
1007
|
+
if (isDefenseReactSpell(s)) return false;
|
|
1008
|
+
if (isThreatOrTauntUtility(s)) return false;
|
|
1009
|
+
if (isManaDrainKeepUp(s)) return false;
|
|
1010
|
+
// Real hostile aura on the enemy (explicit hostile or legacy negative amount).
|
|
1011
|
+
if (hostileAuraEffect(s)) return true;
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Multi-target tools that must not steal GCDs on a pure 1v1 pull. Prefer the baked `aoe`, with name
|
|
1016
|
+
// fallbacks for Multi-Shot, Cleave, Swipe, Blade Flurry and Hellfire when the bake omits it.
|
|
1017
|
+
const MULTI_TARGET_NAME_FALLBACKS = new Set([
|
|
1018
|
+
'blade flurry',
|
|
1019
|
+
'multi-shot',
|
|
1020
|
+
'multishot',
|
|
1021
|
+
'cleave',
|
|
1022
|
+
'swipe',
|
|
1023
|
+
'whirlwind',
|
|
1024
|
+
'arcane explosion',
|
|
1025
|
+
'blast wave',
|
|
1026
|
+
'flamestrike',
|
|
1027
|
+
'cone of cold',
|
|
1028
|
+
'blizzard',
|
|
1029
|
+
'rain of fire',
|
|
1030
|
+
'holy nova',
|
|
1031
|
+
'hellfire',
|
|
1032
|
+
]);
|
|
1033
|
+
|
|
1034
|
+
const multiTargetMemo = new WeakMap<object, boolean>();
|
|
1035
|
+
export function isMultiTargetCombatSpell(
|
|
1036
|
+
spell: {
|
|
1037
|
+
name?: string;
|
|
1038
|
+
aoe?: boolean;
|
|
1039
|
+
effects?: ReadonlyArray<{ kind?: string; control?: string }>;
|
|
1040
|
+
} | null | undefined,
|
|
1041
|
+
): boolean {
|
|
1042
|
+
if (!spell) return false;
|
|
1043
|
+
const cached = multiTargetMemo.get(spell);
|
|
1044
|
+
if (cached !== undefined) return cached;
|
|
1045
|
+
// AoE MODE reorders damage dumps to the front, and every hard fear is aoe:true, so flipping to
|
|
1046
|
+
// AoE opened every pack with a scatter. A damageless AoE CC keeps the player's own place.
|
|
1047
|
+
const v = !isDamagelessHardCc(spell)
|
|
1048
|
+
&& (spell.aoe === true || MULTI_TARGET_NAME_FALLBACKS.has(normalizeSpellName(spell.name ?? '')));
|
|
1049
|
+
multiTargetMemo.set(spell, v);
|
|
1050
|
+
return v;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Hard CC carrying NO damage rider (War Stomp, Psychic Scream, Sap): full value on one target, so
|
|
1054
|
+
// the pack-density gate must not withhold it. Mixed tools keep pack density via their damage.
|
|
1055
|
+
|
|
1056
|
+
/** Frost Nova ranks (1.12). Auto-kite: root the pile, then step back. */
|
|
1057
|
+
export const FROST_NOVA_RANK_IDS: readonly number[] = [122, 865, 6131, 10230];
|
|
1058
|
+
|
|
1059
|
+
/** spells.json 12323 — Fury talent, dest-at-caster 10 yd snare. */
|
|
1060
|
+
export const PIERCING_HOWL_SPELL_ID = 12323;
|
|
1061
|
+
|
|
1062
|
+
export function isPiercingHowlSpell(s: { id?: number; name?: string } | null | undefined): boolean {
|
|
1063
|
+
if (!s) return false;
|
|
1064
|
+
if (((s.id ?? 0) | 0) === PIERCING_HOWL_SPELL_ID) return true;
|
|
1065
|
+
return /^piercing howl$/i.test(s.name ?? '');
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/** Out of melee, inside the howl disc, target not already snared (Howl or Hamstring). */
|
|
1069
|
+
export function piercingHowlGapConditions(spell?: { radiusYd?: number }): Condition[] {
|
|
1070
|
+
const yards = spell?.radiusYd != null && spell.radiusYd > 0 ? Math.round(spell.radiusYd) : 10;
|
|
1071
|
+
return [
|
|
1072
|
+
cond('target', 'inMelee', '==', 0),
|
|
1073
|
+
cond('target', 'targetDistanceYd', '<=', yards),
|
|
1074
|
+
cond('target', 'snared', '==', 0),
|
|
1075
|
+
];
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/** True when Howl must not win the GCD — melee, already snared, snare-immune, or past the disc. */
|
|
1079
|
+
export function piercingHowlRefused(opts: {
|
|
1080
|
+
inMelee: boolean;
|
|
1081
|
+
snared: boolean;
|
|
1082
|
+
snareImmune?: boolean;
|
|
1083
|
+
distanceYd: number | null;
|
|
1084
|
+
radiusYd?: number;
|
|
1085
|
+
}): boolean {
|
|
1086
|
+
if (opts.inMelee || opts.snared || opts.snareImmune) return true;
|
|
1087
|
+
const cap = opts.radiusYd != null && opts.radiusYd > 0 ? opts.radiusYd : 10;
|
|
1088
|
+
return opts.distanceYd != null && opts.distanceYd > cap;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/** Yards to walk after a self-root AoE. Gets a melee caster out of white reach. */
|
|
1092
|
+
export const NOVA_KITE_STEP_BACK_YD = 10;
|
|
1093
|
+
|
|
1094
|
+
// Furthest nearest-hostile the post-Nova walk will retreat from. Past the disc the "nearest" body is the
|
|
1095
|
+
// Frostbolt focus — walking away from that is leaving the fight.
|
|
1096
|
+
export const NOVA_KITE_NEAR_YD = 12;
|
|
1097
|
+
|
|
1098
|
+
/** Self-centered root AoE (Frost Nova). Not Polymorph / Fear / stun stomps. */
|
|
1099
|
+
export function isNovaKiteSpell(s: {
|
|
1100
|
+
id?: number;
|
|
1101
|
+
name?: string;
|
|
1102
|
+
aoe?: boolean;
|
|
1103
|
+
rangeMax?: number | null;
|
|
1104
|
+
effects?: ReadonlyArray<{ kind?: string }>;
|
|
1105
|
+
}): boolean {
|
|
1106
|
+
if (s.id != null && FROST_NOVA_RANK_IDS.includes(s.id | 0)) return true;
|
|
1107
|
+
if (/^frost nova$/i.test(s.name ?? '')) return true;
|
|
1108
|
+
if (!s.effects?.some((e) => e.kind === 'root')) return false;
|
|
1109
|
+
if (s.aoe !== true) return false;
|
|
1110
|
+
return !((s.rangeMax ?? 0) > 0);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
export function novaKiteConditions(spell?: { radiusYd?: number }): Condition[] {
|
|
1114
|
+
return [cond('self', 'enemiesWithinYd', '>=', 1, multiTargetGateYards(spell ?? {}))];
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/** True when any listed hostile sits inside this spell's disc. Missing list → unknown. */
|
|
1118
|
+
export function novaDiscOccupied(
|
|
1119
|
+
dists: readonly number[] | undefined,
|
|
1120
|
+
spell?: { radiusYd?: number; rangeMax?: number },
|
|
1121
|
+
): boolean {
|
|
1122
|
+
if (!dists) return false;
|
|
1123
|
+
const yards = multiTargetGateYards(spell ?? {});
|
|
1124
|
+
for (const d of dists) if (d <= yards) return true;
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// `ensureNovaKiteRotation` lived here and is deliberately gone: it rewrote a rotation the player
|
|
1129
|
+
// owns, invisibly. build.ts and classes/mage.ts author the same rule INTO the pack instead.
|
|
1130
|
+
|
|
1131
|
+
export function isDamagelessHardCc(
|
|
1132
|
+
spell: {
|
|
1133
|
+
effects?: ReadonlyArray<{ kind?: string; control?: string }>;
|
|
1134
|
+
} | null | undefined,
|
|
1135
|
+
): boolean {
|
|
1136
|
+
const effects = spell?.effects;
|
|
1137
|
+
if (!Array.isArray(effects) || effects.length === 0) return false;
|
|
1138
|
+
const hasHardCc = effects.some(
|
|
1139
|
+
(e) => e.kind === 'cc' || e.kind === 'root' || (e.control != null && e.control !== ''),
|
|
1140
|
+
);
|
|
1141
|
+
if (!hasHardCc) return false;
|
|
1142
|
+
return !effects.some(
|
|
1143
|
+
(e) =>
|
|
1144
|
+
e.kind === 'direct_damage'
|
|
1145
|
+
|| e.kind === 'dot'
|
|
1146
|
+
|| e.kind === 'weapon_percent_damage'
|
|
1147
|
+
|| e.kind === 'weapon_damage'
|
|
1148
|
+
|| e.kind === 'weapon_strike',
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// Whether a multi-target spell must wait for pack density. Damage dumps yes; hostile-aura PBAoEs,
|
|
1153
|
+
// damageless hard CC and Whirlwind no. A thin row with no effects falls through to "needs pack".
|
|
1154
|
+
export function multiTargetNeedsPackDensity(
|
|
1155
|
+
spell: {
|
|
1156
|
+
name?: string;
|
|
1157
|
+
aoe?: boolean;
|
|
1158
|
+
effects?: ReadonlyArray<{ kind?: string; hostile?: boolean; amount?: number; control?: string }>;
|
|
1159
|
+
role?: string;
|
|
1160
|
+
} | null | undefined,
|
|
1161
|
+
): boolean {
|
|
1162
|
+
if (!isMultiTargetCombatSpell(spell)) return false;
|
|
1163
|
+
if (/^whirlwind$/i.test(spell!.name ?? '')) return false;
|
|
1164
|
+
// Prot paladin 1v1 threat tool — playerbot casts it on the focus with no pack check.
|
|
1165
|
+
// Cite: PlayerbotPaladinAI.cpp:256.
|
|
1166
|
+
if (/^consecration$/i.test(spell!.name ?? '')) return false;
|
|
1167
|
+
const effects = spell!.effects;
|
|
1168
|
+
if (Array.isArray(effects) && effects.length > 0) {
|
|
1169
|
+
// Full shape: hostile-aura AoEs that are not channeled multi damage stay 1v1-ok.
|
|
1170
|
+
const asDef = spell as SpellDef;
|
|
1171
|
+
if (hostileAuraEffect(asDef) && !isChanneledAoeDamageSpell(asDef)) return false;
|
|
1172
|
+
// Damageless hard CC — 1v1-ok (War Stomp 2m stun; Psychic Scream; Polymorph).
|
|
1173
|
+
if (isDamagelessHardCc(asDef)) return false;
|
|
1174
|
+
// Control-primary mixed tools (Frost Nova DD+root) are kiting CC, not pack dumps.
|
|
1175
|
+
if ((asDef.role ?? inferRole(asDef)) === 'control') return false;
|
|
1176
|
+
// Also heals friendlies (Holy Nova): the heal fires whatever the damage half hit, so the press
|
|
1177
|
+
// does not scale with enemy count. Cite: PlayerbotPriestAI.cpp:291 gates it on meleeReach only.
|
|
1178
|
+
if (effects.some((e) => e.kind === 'heal')) return false;
|
|
1179
|
+
}
|
|
1180
|
+
return true;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Single-target fillers that must not dump resource on packs when a multi-target counterpart is
|
|
1184
|
+
// known — Heroic Strike against Cleave, Maul against Swipe. Skipped when aoeMode is single-target.
|
|
1185
|
+
export function isPackSuppressedStFiller(
|
|
1186
|
+
spell: { name?: string } | null | undefined,
|
|
1187
|
+
opts: {
|
|
1188
|
+
enemyCount: number;
|
|
1189
|
+
hasCleave: boolean;
|
|
1190
|
+
hasSwipe: boolean;
|
|
1191
|
+
/** When true (aoeMode single), do not suppress — player asked for ST dumps. */
|
|
1192
|
+
singleTargetMode?: boolean;
|
|
1193
|
+
},
|
|
1194
|
+
): boolean {
|
|
1195
|
+
if (!spell || opts.singleTargetMode || opts.enemyCount < 2) return false;
|
|
1196
|
+
const name = normalizeSpellName(spell.name ?? '');
|
|
1197
|
+
if (name === 'heroic strike' && opts.hasCleave) return true;
|
|
1198
|
+
if (name === 'maul' && opts.hasSwipe) return true;
|
|
1199
|
+
if (name === 'slam' && opts.hasCleave) return true;
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Resolve a single formId for `spell.stances` when the mask is one form or the Bear family. A
|
|
1204
|
+
// multi-stance warrior mask returns null — session usability already enforces those.
|
|
1205
|
+
export function requiredFormIdFromStances(stances: number): number | null {
|
|
1206
|
+
if (!stances) return null;
|
|
1207
|
+
const forms: number[] = [];
|
|
1208
|
+
for (let formId = 1; formId <= 31; formId++) {
|
|
1209
|
+
if (stances & (1 << (formId - 1))) forms.push(formId);
|
|
1210
|
+
}
|
|
1211
|
+
if (!forms.length) return null;
|
|
1212
|
+
// Bear family only → formId == BEAR (rotation formIdEquals treats DIREBEAR as match).
|
|
1213
|
+
if (forms.every((f) => f === SHAPESHIFT_FORM.BEAR || f === SHAPESHIFT_FORM.DIREBEAR)) {
|
|
1214
|
+
return SHAPESHIFT_FORM.BEAR;
|
|
1215
|
+
}
|
|
1216
|
+
if (forms.length === 1) return forms[0]!;
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// Channeled or persistent multi-target damage (Blizzard, Rain of Fire, Hurricane, Consecration):
|
|
1221
|
+
// never a single-target DoT keep-up, gated on enemyCount alone. A secondary rider must not reclassify.
|
|
1222
|
+
export function isChanneledAoeDamageSpell(spell: SpellDef): boolean {
|
|
1223
|
+
if (!isMultiTargetCombatSpell(spell)) return false;
|
|
1224
|
+
const role = spell.role ?? inferRole(spell);
|
|
1225
|
+
// Multi + scheduled as DoT (periodic damage primary) — never ST keep-up.
|
|
1226
|
+
if (role === 'dot') return true;
|
|
1227
|
+
// Ground ticks with no direct_damage and not a buff/control primary role
|
|
1228
|
+
// (covers pure aoe dots even if role is stamped oddly).
|
|
1229
|
+
if (role === 'buff' || role === 'control') return false;
|
|
1230
|
+
const hasDot = spell.effects.some((e) => e.kind === 'dot');
|
|
1231
|
+
if (!hasDot) return false;
|
|
1232
|
+
return !spell.effects.some((e) => e.kind === 'direct_damage');
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/** Classic small PBAoE / default `enemiesWithinYd` ref when SpellRadius is missing. */
|
|
1236
|
+
export const DEFAULT_MULTI_RADIUS_YD = 10;
|
|
1237
|
+
|
|
1238
|
+
/** Yards for radius-aware multi gates: baked radius, else short melee rangeMax, else 10. */
|
|
1239
|
+
export function multiTargetGateYards(spell: {
|
|
1240
|
+
radiusYd?: number;
|
|
1241
|
+
rangeMax?: number;
|
|
1242
|
+
}): number {
|
|
1243
|
+
if (spell.radiusYd != null && spell.radiusYd > 0) return Math.round(spell.radiusYd);
|
|
1244
|
+
if (spell.rangeMax != null && spell.rangeMax > 0 && spell.rangeMax <= 8) {
|
|
1245
|
+
return Math.round(spell.rangeMax);
|
|
1246
|
+
}
|
|
1247
|
+
return DEFAULT_MULTI_RADIUS_YD;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// Ground-targeted multi, measuring density at the cast point. Prefer the baked aoeShape, with a name
|
|
1251
|
+
// fallback for thin rows. A self-range dest-at-caster spell is treated as a PBAoE for density.
|
|
1252
|
+
export function isGroundTargetedMulti(spell: {
|
|
1253
|
+
name?: string;
|
|
1254
|
+
aoeShape?: string;
|
|
1255
|
+
rangeMax?: number;
|
|
1256
|
+
} | null | undefined): boolean {
|
|
1257
|
+
if (!spell) return false;
|
|
1258
|
+
if (spell.aoeShape === 'ground') {
|
|
1259
|
+
if ((spell.rangeMax ?? 0) === 0) return false;
|
|
1260
|
+
return true;
|
|
1261
|
+
}
|
|
1262
|
+
const n = normalizeSpellName(spell.name ?? '');
|
|
1263
|
+
return n === 'blizzard' || n === 'rain of fire' || n === 'flamestrike'
|
|
1264
|
+
|| n === 'hurricane' || n === 'volley';
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/** Conditions for channeled multi-target damage: multi only, never ST debuff keep-up. */
|
|
1268
|
+
export function channeledAoeDamageConditions(spell?: {
|
|
1269
|
+
radiusYd?: number;
|
|
1270
|
+
rangeMax?: number;
|
|
1271
|
+
aoeShape?: string;
|
|
1272
|
+
}): Condition[] {
|
|
1273
|
+
// Ground rain: density at the focus (cast point), not at the caster's feet.
|
|
1274
|
+
if (spell && isGroundTargetedMulti(spell)) {
|
|
1275
|
+
return [cond('target', 'enemiesWithinYd', '>=', 2, multiTargetGateYards(spell))];
|
|
1276
|
+
}
|
|
1277
|
+
// PBAoE / cone / self-centered channel: density at the caster.
|
|
1278
|
+
return [cond('self', 'enemiesWithinYd', '>=', 2, multiTargetGateYards(spell ?? {}))];
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/** Append a condition if an equivalent metric/op/value/ref is not already present. */
|
|
1282
|
+
export function ensureCondition(conditions: Condition[], extra: Condition): Condition[] {
|
|
1283
|
+
if (
|
|
1284
|
+
conditions.some(
|
|
1285
|
+
(c) =>
|
|
1286
|
+
c.subject === extra.subject &&
|
|
1287
|
+
c.metric === extra.metric &&
|
|
1288
|
+
c.op === extra.op &&
|
|
1289
|
+
c.value === extra.value &&
|
|
1290
|
+
(c.ref ?? null) === (extra.ref ?? null),
|
|
1291
|
+
)
|
|
1292
|
+
) {
|
|
1293
|
+
return conditions;
|
|
1294
|
+
}
|
|
1295
|
+
return [...conditions, extra];
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
// Gate multi-target combat spells on pack density the blast can actually hit: a PBAoE measures
|
|
1299
|
+
// around the caster, a ground rain around the focus, both at the spell's own radius.
|
|
1300
|
+
export function applyMultiTargetCombatGates(spell: SpellDef, conditions: Condition[]): Condition[] {
|
|
1301
|
+
if (!multiTargetNeedsPackDensity(spell)) return conditions;
|
|
1302
|
+
let c = conditions;
|
|
1303
|
+
if (isChanneledAoeDamageSpell(spell)) {
|
|
1304
|
+
c = c.filter(
|
|
1305
|
+
(x) =>
|
|
1306
|
+
!(
|
|
1307
|
+
x.subject === 'target' &&
|
|
1308
|
+
(x.metric === 'debuffPresent' || x.metric === 'debuffStacks')
|
|
1309
|
+
),
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
// Drop fight-wide pack gates so rebuilds upgrade to radius-aware (saved rotations).
|
|
1313
|
+
c = c.filter(
|
|
1314
|
+
(x) =>
|
|
1315
|
+
!(
|
|
1316
|
+
x.subject === 'self' &&
|
|
1317
|
+
x.metric === 'enemyCount' &&
|
|
1318
|
+
x.op === '>=' &&
|
|
1319
|
+
x.value === 2
|
|
1320
|
+
),
|
|
1321
|
+
);
|
|
1322
|
+
if (c.some((x) => x.metric === 'enemiesWithinYd' || (x.metric === 'enemyCount' && x.op === '>='))) {
|
|
1323
|
+
return c;
|
|
1324
|
+
}
|
|
1325
|
+
const yards = multiTargetGateYards(spell);
|
|
1326
|
+
// Ground: density at focus. PBAoE: density at caster.
|
|
1327
|
+
if (isGroundTargetedMulti(spell)) {
|
|
1328
|
+
return ensureCondition(c, cond('target', 'enemiesWithinYd', '>=', 2, yards));
|
|
1329
|
+
}
|
|
1330
|
+
return ensureCondition(c, cond('self', 'enemiesWithinYd', '>=', 2, yards));
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// A form or stance enter must never fire while already in that form, since a recast toggles off.
|
|
1334
|
+
// The leave-to-heal recovery rules are the exception and intentionally require formId == form.
|
|
1335
|
+
export function applyFormEnterGate(spell: SpellDef, conditions: Condition[]): Condition[] {
|
|
1336
|
+
const formId = formIdForRotationSpell(spell);
|
|
1337
|
+
if (!formId && !isFormSpell(spell)) return conditions;
|
|
1338
|
+
if (!formId) return conditions;
|
|
1339
|
+
// Leave-form toggle (health emergency): formId == this form already present.
|
|
1340
|
+
if (conditions.some((c) => c.metric === 'formId' && c.op === '==' && c.value === formId)) {
|
|
1341
|
+
return conditions;
|
|
1342
|
+
}
|
|
1343
|
+
// Drop wrong buffPresent-of-form-spell gates (form is modal state, not a buff id).
|
|
1344
|
+
const cleaned = conditions.filter(
|
|
1345
|
+
(c) => !(c.metric === 'buffPresent' && c.ref === spell.id),
|
|
1346
|
+
);
|
|
1347
|
+
return ensureCondition(cleaned, cond('self', 'formId', '!=', formId));
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Post-defense-react strikes (Revenge, Riposte) are only usable after a dodge, parry or block.
|
|
1351
|
+
// Overpower is a separate target-dodge gate and is not included here.
|
|
1352
|
+
export function isDefenseReactSpell(spell: SpellDef): boolean {
|
|
1353
|
+
if (isOverpowerSpell(spell)) return false;
|
|
1354
|
+
// CasterAuraState is the gate itself, so the baked value decides both ways: DEFENSE on 17 rows,
|
|
1355
|
+
// HUNTER_PARRY on 7. The spell text cannot separate them — both say "after parrying".
|
|
1356
|
+
const state = (spell.casterAuraState ?? 0) | 0;
|
|
1357
|
+
if (state) return state === 1;
|
|
1358
|
+
// A def built without the baked column (fixtures, hand-authored kits) still needs the gate,
|
|
1359
|
+
// or Revenge ships as bare ALWAYS filler again (KI-251).
|
|
1360
|
+
const text = `${spell.name ?? ''} ${spell.description ?? ''}`;
|
|
1361
|
+
return (
|
|
1362
|
+
/\brevenge\b/i.test(spell.name ?? '')
|
|
1363
|
+
|| /must follow a |becomes active after parry|after parrying|only usable after|only useable after/i.test(text)
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// Counterattack: CasterAuraState 7 HUNTER_PARRY. Only a hunter's own parry arms it, since a dodge
|
|
1368
|
+
// sets DEFENSE instead, so the two windows never overlap. Cite: UnitAuraProcHandler.cpp:310-316.
|
|
1369
|
+
export function isHunterParryReactSpell(spell: SpellDef): boolean {
|
|
1370
|
+
return (((spell.casterAuraState ?? 0) | 0) === 7);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// Revenge and the other defense-react strikes must never ship as bare ALWAYS filler: in 1.12
|
|
1374
|
+
// Revenge is usable only for 5 s after the warrior dodges, parries or blocks.
|
|
1375
|
+
export function applyDefenseReactGate(spell: SpellDef, conditions: Condition[]): Condition[] {
|
|
1376
|
+
// defenseReactRecently is AURA_STATE_DEFENSE, which skips a rogue's dodge — so for Riposte
|
|
1377
|
+
// this window is already parry-only. Cite: UnitAuraProcHandler.cpp:302 "skip Rogue Riposte".
|
|
1378
|
+
if (!isDefenseReactSpell(spell)) return conditions;
|
|
1379
|
+
if (conditions.some((c) => /Recently$/.test(c.metric))) return conditions;
|
|
1380
|
+
return ensureCondition(conditions, cond('self', 'defenseReactRecently', '==', 1));
|
|
1381
|
+
}
|