azeroth-shell 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1163) hide show
  1. package/README.md +138 -2
  2. package/bin/azsh.mjs +19 -0
  3. package/docs/aifid/eventai-texts.json +4414 -0
  4. package/docs/dungeons/atlas/atlas-rows-dungeons.json +8989 -0
  5. package/docs/dungeons/survey/pack-id-migration.json +2418 -0
  6. package/package.json +26 -4
  7. package/src/autopilot/bossSkip.ts +26 -0
  8. package/src/autopilot/combatRotation.ts +824 -0
  9. package/src/autopilot/config.ts +533 -0
  10. package/src/autopilot/consumables.ts +58 -0
  11. package/src/autopilot/encounterStand.ts +121 -0
  12. package/src/autopilot/objectGatePlan.ts +53 -0
  13. package/src/autopilot/pathEngage.ts +229 -0
  14. package/src/autopilot/plan.ts +1643 -0
  15. package/src/autopilot/questPolicy.ts +274 -0
  16. package/src/autopilot/routeStep.ts +69 -0
  17. package/src/autopilot/sessionReport.ts +88 -0
  18. package/src/autopilot/topOffPower.ts +26 -0
  19. package/src/autopilot/view.ts +476 -0
  20. package/src/cli/bootCheck.mjs +34 -0
  21. package/src/cli/bootData.mjs +57 -0
  22. package/src/cli/combatLogFilter.ts +153 -0
  23. package/src/cli/combatLogFormat.ts +613 -0
  24. package/src/cli/fileRepository.mjs +105 -0
  25. package/src/cli/gzLoader.mjs +31 -0
  26. package/src/cli/liveGauntletEscort.ts +978 -0
  27. package/src/cli/liveStratUndead.ts +305 -0
  28. package/src/cli/macroHostCombat.ts +545 -0
  29. package/src/cli/macroHostWorld.ts +330 -0
  30. package/src/cli/prefs.mjs +31 -0
  31. package/src/cli/register.mjs +4 -0
  32. package/src/cli/repl.mjs +693 -0
  33. package/src/cli/rotationText.mjs +226 -0
  34. package/src/cli/squad.mjs +105 -0
  35. package/src/cli/term.mjs +126 -0
  36. package/src/cli/text.mjs +122 -0
  37. package/src/cli/tui.mjs +626 -0
  38. package/src/cli/tuiArena.mjs +109 -0
  39. package/src/cli/tuiAutopilotSettings.mjs +93 -0
  40. package/src/cli/tuiBags.mjs +180 -0
  41. package/src/cli/tuiBars.mjs +133 -0
  42. package/src/cli/tuiBg.mjs +243 -0
  43. package/src/cli/tuiBulk.mjs +162 -0
  44. package/src/cli/tuiCheck.mjs +1882 -0
  45. package/src/cli/tuiCity.mjs +396 -0
  46. package/src/cli/tuiCompanionKit.mjs +300 -0
  47. package/src/cli/tuiCompanions.mjs +471 -0
  48. package/src/cli/tuiDev.mjs +62 -0
  49. package/src/cli/tuiDungeon.mjs +606 -0
  50. package/src/cli/tuiEnchantTarget.mjs +120 -0
  51. package/src/cli/tuiFight.mjs +577 -0
  52. package/src/cli/tuiFightPanes.mjs +228 -0
  53. package/src/cli/tuiFloor.mjs +506 -0
  54. package/src/cli/tuiGear.mjs +158 -0
  55. package/src/cli/tuiHonor.mjs +74 -0
  56. package/src/cli/tuiLoot.mjs +97 -0
  57. package/src/cli/tuiMacros.mjs +144 -0
  58. package/src/cli/tuiOutfitter.mjs +115 -0
  59. package/src/cli/tuiPet.mjs +116 -0
  60. package/src/cli/tuiProfessions.mjs +800 -0
  61. package/src/cli/tuiRaid.mjs +127 -0
  62. package/src/cli/tuiRep.mjs +65 -0
  63. package/src/cli/tuiSaves.mjs +69 -0
  64. package/src/cli/tuiSearch.mjs +363 -0
  65. package/src/cli/tuiSettings.mjs +151 -0
  66. package/src/cli/tuiSimLab.mjs +80 -0
  67. package/src/cli/tuiTalents.mjs +60 -0
  68. package/src/cli/tuiViews.mjs +1178 -0
  69. package/src/cli/tuiWiki.mjs +181 -0
  70. package/src/cli/tuiWorld.mjs +361 -0
  71. package/src/cli/wikiIndex.ts +259 -0
  72. package/src/cli/wikiSources.ts +355 -0
  73. package/src/data/alteracValley.ts +176 -0
  74. package/src/data/appearanceOptions.ts +534 -0
  75. package/src/data/applyLootChanceSanity.ts +21 -0
  76. package/src/data/createFlavor.ts +96 -0
  77. package/src/data/customItems.ts +33 -0
  78. package/src/data/dataStore.ts +1904 -0
  79. package/src/data/dungeonGameObjects.ts +84 -0
  80. package/src/data/dungeonPlateCover.ts +66 -0
  81. package/src/data/dungeonSpawns.ts +294 -0
  82. package/src/data/dungeons/alterac-valley.ts +135 -0
  83. package/src/data/dungeons/apply-dungeon-rooms.ts +29 -0
  84. package/src/data/dungeons/apply-encounter-outdoors.ts +72 -0
  85. package/src/data/dungeons/apply-instance-geometry.ts +65 -0
  86. package/src/data/dungeons/apply-spawn-placement.ts +171 -0
  87. package/src/data/dungeons/blackfathom-deeps.ts +539 -0
  88. package/src/data/dungeons/blackrock-depths.ts +1410 -0
  89. package/src/data/dungeons/blackwing-lair.ts +129 -0
  90. package/src/data/dungeons/boss-eventai.ts +8 -0
  91. package/src/data/dungeons/deadmines.ts +410 -0
  92. package/src/data/dungeons/dire-maul-east.ts +980 -0
  93. package/src/data/dungeons/dire-maul-north.ts +661 -0
  94. package/src/data/dungeons/dire-maul-west.ts +785 -0
  95. package/src/data/dungeons/dungeon-rooms.json +5479 -0
  96. package/src/data/dungeons/encounter-outdoors.json +359 -0
  97. package/src/data/dungeons/eventai/blackfathom-deeps.ts +308 -0
  98. package/src/data/dungeons/eventai/blackrock-depths.ts +1257 -0
  99. package/src/data/dungeons/eventai/deadmines.ts +264 -0
  100. package/src/data/dungeons/eventai/dire-maul-east.ts +320 -0
  101. package/src/data/dungeons/eventai/gnomeregan.ts +213 -0
  102. package/src/data/dungeons/eventai/helpers.ts +79 -0
  103. package/src/data/dungeons/eventai/index.ts +133 -0
  104. package/src/data/dungeons/eventai/lbrs.ts +372 -0
  105. package/src/data/dungeons/eventai/maraudon.ts +323 -0
  106. package/src/data/dungeons/eventai/ragefire-chasm.ts +108 -0
  107. package/src/data/dungeons/eventai/razorfen-downs.ts +207 -0
  108. package/src/data/dungeons/eventai/razorfen-kraul.ts +326 -0
  109. package/src/data/dungeons/eventai/scholomance.ts +680 -0
  110. package/src/data/dungeons/eventai/shadowfang-keep.ts +230 -0
  111. package/src/data/dungeons/eventai/sm-cathedral.ts +75 -0
  112. package/src/data/dungeons/eventai/sm-graveyard.ts +150 -0
  113. package/src/data/dungeons/eventai/sm-library.ts +55 -0
  114. package/src/data/dungeons/eventai/stratholme-undead.ts +127 -0
  115. package/src/data/dungeons/eventai/sunken-temple.ts +582 -0
  116. package/src/data/dungeons/eventai/the-stockade.ts +228 -0
  117. package/src/data/dungeons/eventai/ubrs.ts +225 -0
  118. package/src/data/dungeons/eventai/uldaman.ts +355 -0
  119. package/src/data/dungeons/eventai/wailing-caverns.ts +396 -0
  120. package/src/data/dungeons/eventai/zul-farrak.ts +591 -0
  121. package/src/data/dungeons/gnomeregan.ts +909 -0
  122. package/src/data/dungeons/index.ts +126 -0
  123. package/src/data/dungeons/instance-geometry.json +5233 -0
  124. package/src/data/dungeons/key-sources.json +169 -0
  125. package/src/data/dungeons/lbrs.ts +996 -0
  126. package/src/data/dungeons/maraudon.ts +786 -0
  127. package/src/data/dungeons/molten-core.ts +194 -0
  128. package/src/data/dungeons/naxxramas.ts +173 -0
  129. package/src/data/dungeons/onyxias-lair.ts +71 -0
  130. package/src/data/dungeons/pack-from-clusters.ts +1256 -0
  131. package/src/data/dungeons/ragefire-chasm.ts +238 -0
  132. package/src/data/dungeons/razorfen-downs.ts +698 -0
  133. package/src/data/dungeons/razorfen-kraul.ts +766 -0
  134. package/src/data/dungeons/ruins-of-ahnqiraj.ts +121 -0
  135. package/src/data/dungeons/scholomance.ts +1076 -0
  136. package/src/data/dungeons/shadowfang-keep.ts +740 -0
  137. package/src/data/dungeons/sm-armory.ts +266 -0
  138. package/src/data/dungeons/sm-cathedral.ts +430 -0
  139. package/src/data/dungeons/sm-graveyard.ts +482 -0
  140. package/src/data/dungeons/sm-library.ts +337 -0
  141. package/src/data/dungeons/stratholme-live.ts +808 -0
  142. package/src/data/dungeons/stratholme-undead.ts +1164 -0
  143. package/src/data/dungeons/sunken-temple.ts +931 -0
  144. package/src/data/dungeons/temple-of-ahnqiraj.ts +119 -0
  145. package/src/data/dungeons/the-stockade.ts +282 -0
  146. package/src/data/dungeons/ubrs.ts +1286 -0
  147. package/src/data/dungeons/uldaman.ts +685 -0
  148. package/src/data/dungeons/wailing-caverns.ts +356 -0
  149. package/src/data/dungeons/zul-farrak.ts +686 -0
  150. package/src/data/dungeons/zul-gurub.ts +142 -0
  151. package/src/data/dungeons.ts +42 -0
  152. package/src/data/fixtures.ts +446 -0
  153. package/src/data/gameobject-data.json.gz +0 -0
  154. package/src/data/item-data.json.gz +0 -0
  155. package/src/data/loot-data.json.gz +0 -0
  156. package/src/data/maps/atlas-approach-maps.json +1425 -0
  157. package/src/data/maps/atlas-landmarks.json +1711 -0
  158. package/src/data/maps/dungeon-viewports.json +392 -0
  159. package/src/data/materializeModDamagePercentTaken.ts +70 -0
  160. package/src/data/materializeModHealthRegen.ts +89 -0
  161. package/src/data/materializeSpellReputation.ts +26 -0
  162. package/src/data/pageTexts.ts +19 -0
  163. package/src/data/profession-data.json.gz +0 -0
  164. package/src/data/raid-plans/blackfathom-deeps.json +86 -0
  165. package/src/data/raid-plans/blackrock-depths.json +419 -0
  166. package/src/data/raid-plans/blackwing-lair.json +328 -0
  167. package/src/data/raid-plans/deadmines.json +55 -0
  168. package/src/data/raid-plans/dire-maul-east.json +94 -0
  169. package/src/data/raid-plans/dire-maul-north.json +70 -0
  170. package/src/data/raid-plans/dire-maul-west.json +75 -0
  171. package/src/data/raid-plans/gnomeregan.json +131 -0
  172. package/src/data/raid-plans/lbrs.json +176 -0
  173. package/src/data/raid-plans/maraudon.json +102 -0
  174. package/src/data/raid-plans/molten-core.json +169 -0
  175. package/src/data/raid-plans/naxxramas.json +354 -0
  176. package/src/data/raid-plans/onyxias-lair.json +18 -0
  177. package/src/data/raid-plans/ragefire-chasm.json +30 -0
  178. package/src/data/raid-plans/razorfen-downs.json +75 -0
  179. package/src/data/raid-plans/razorfen-kraul.json +53 -0
  180. package/src/data/raid-plans/ruins-of-ahnqiraj.json +110 -0
  181. package/src/data/raid-plans/scholomance.json +170 -0
  182. package/src/data/raid-plans/shadowfang-keep.json +87 -0
  183. package/src/data/raid-plans/sm-armory.json +53 -0
  184. package/src/data/raid-plans/sm-cathedral.json +72 -0
  185. package/src/data/raid-plans/sm-graveyard.json +63 -0
  186. package/src/data/raid-plans/sm-library.json +23 -0
  187. package/src/data/raid-plans/stratholme-live.json +129 -0
  188. package/src/data/raid-plans/stratholme-undead.json +73 -0
  189. package/src/data/raid-plans/sunken-temple.json +159 -0
  190. package/src/data/raid-plans/temple-of-ahnqiraj.json +163 -0
  191. package/src/data/raid-plans/the-stockade.json +42 -0
  192. package/src/data/raid-plans/ubrs.json +53 -0
  193. package/src/data/raid-plans/uldaman.json +131 -0
  194. package/src/data/raid-plans/wailing-caverns.json +90 -0
  195. package/src/data/raid-plans/zul-farrak.json +156 -0
  196. package/src/data/raid-plans/zul-gurub.json +307 -0
  197. package/src/data/raidInstanceCreatures.mjs +473 -0
  198. package/src/data/rotations/index.ts +152 -0
  199. package/src/data/rotations/layout.mjs +72 -0
  200. package/src/data/rotations/pve/druid/balance.json +754 -0
  201. package/src/data/rotations/pve/druid/feral-tank.json +2448 -0
  202. package/src/data/rotations/pve/druid/feral.json +2660 -0
  203. package/src/data/rotations/pve/druid/restoration.json +424 -0
  204. package/src/data/rotations/pve/hunter/beast-mastery.json +1288 -0
  205. package/src/data/rotations/pve/hunter/marksmanship.json +1260 -0
  206. package/src/data/rotations/pve/hunter/survival.json +1373 -0
  207. package/src/data/rotations/pve/mage/arcane-leveling.json +1798 -0
  208. package/src/data/rotations/pve/mage/fire-leveling.json +2003 -0
  209. package/src/data/rotations/pve/mage/frost-leveling.json +1912 -0
  210. package/src/data/rotations/pve/paladin/holy.json +755 -0
  211. package/src/data/rotations/pve/paladin/protection.json +962 -0
  212. package/src/data/rotations/pve/paladin/retribution.json +924 -0
  213. package/src/data/rotations/pve/priest/discipline.json +772 -0
  214. package/src/data/rotations/pve/priest/holy.json +707 -0
  215. package/src/data/rotations/pve/priest/shadow.json +787 -0
  216. package/src/data/rotations/pve/rogue/assassination.json +2477 -0
  217. package/src/data/rotations/pve/rogue/combat.json +2483 -0
  218. package/src/data/rotations/pve/rogue/subtlety.json +2523 -0
  219. package/src/data/rotations/pve/shaman/elemental.json +1157 -0
  220. package/src/data/rotations/pve/shaman/enhancement.json +1085 -0
  221. package/src/data/rotations/pve/shaman/restoration.json +592 -0
  222. package/src/data/rotations/pve/warlock/affliction.json +1185 -0
  223. package/src/data/rotations/pve/warlock/demonology.json +1165 -0
  224. package/src/data/rotations/pve/warlock/destruction.json +1177 -0
  225. package/src/data/rotations/pve/warrior/arms.json +3020 -0
  226. package/src/data/rotations/pve/warrior/fury.json +2996 -0
  227. package/src/data/rotations/pve/warrior/protection.json +2509 -0
  228. package/src/data/rotations/pvp/druid/balance.json +593 -0
  229. package/src/data/rotations/pvp/druid/feral.json +3276 -0
  230. package/src/data/rotations/pvp/druid/restoration.json +662 -0
  231. package/src/data/rotations/pvp/hunter/beast-mastery.json +665 -0
  232. package/src/data/rotations/pvp/hunter/marksmanship.json +2406 -0
  233. package/src/data/rotations/pvp/hunter/survival.json +635 -0
  234. package/src/data/rotations/pvp/mage/arcane.json +574 -0
  235. package/src/data/rotations/pvp/mage/fire.json +570 -0
  236. package/src/data/rotations/pvp/mage/frost.json +4048 -0
  237. package/src/data/rotations/pvp/paladin/holy.json +1498 -0
  238. package/src/data/rotations/pvp/paladin/protection.json +567 -0
  239. package/src/data/rotations/pvp/paladin/retribution.json +1110 -0
  240. package/src/data/rotations/pvp/priest/discipline.json +1848 -0
  241. package/src/data/rotations/pvp/priest/holy.json +6340 -0
  242. package/src/data/rotations/pvp/priest/shadow.json +2329 -0
  243. package/src/data/rotations/pvp/rogue/assassination.json +1322 -0
  244. package/src/data/rotations/pvp/rogue/combat.json +5258 -0
  245. package/src/data/rotations/pvp/rogue/subtlety.json +586 -0
  246. package/src/data/rotations/pvp/shaman/elemental.json +1404 -0
  247. package/src/data/rotations/pvp/shaman/enhancement.json +1277 -0
  248. package/src/data/rotations/pvp/shaman/restoration.json +2490 -0
  249. package/src/data/rotations/pvp/warlock/affliction.json +1017 -0
  250. package/src/data/rotations/pvp/warlock/demonology.json +1503 -0
  251. package/src/data/rotations/pvp/warlock/destruction.json +638 -0
  252. package/src/data/rotations/pvp/warrior/arms.json +5665 -0
  253. package/src/data/rotations/pvp/warrior/fury.json +850 -0
  254. package/src/data/rotations/pvp/warrior/protection.json +777 -0
  255. package/src/data/schema/bundle.ts +511 -0
  256. package/src/data/schema/constants.ts +49 -0
  257. package/src/data/schema/creature.ts +454 -0
  258. package/src/data/schema/encounter.ts +155 -0
  259. package/src/data/schema/faction.ts +48 -0
  260. package/src/data/schema/item.ts +297 -0
  261. package/src/data/schema/itemEnchant.ts +105 -0
  262. package/src/data/schema/npc.ts +44 -0
  263. package/src/data/schema/primitives.ts +295 -0
  264. package/src/data/schema/profession.ts +226 -0
  265. package/src/data/schema/quest.ts +154 -0
  266. package/src/data/schema/spell.ts +1149 -0
  267. package/src/data/schema/talent.ts +74 -0
  268. package/src/data/schema/world.ts +124 -0
  269. package/src/data/schema.ts +19 -0
  270. package/src/data/skinNodeGapFill.ts +68 -0
  271. package/src/data/spell-data.json.gz +0 -0
  272. package/src/data/spell-script-targets.json +1 -0
  273. package/src/data/tables/ahItemValues.json.gz +0 -0
  274. package/src/data/tables/alteracValley.json.gz +0 -0
  275. package/src/data/tables/areatriggerTeleports.json.gz +0 -0
  276. package/src/data/tables/botNames.json.gz +0 -0
  277. package/src/data/tables/botPopulation.json.gz +0 -0
  278. package/src/data/tables/botSpecs.json.gz +0 -0
  279. package/src/data/tables/classLevelStats.json.gz +0 -0
  280. package/src/data/tables/combatConditions.json.gz +0 -0
  281. package/src/data/tables/combatSkillMeta.json.gz +0 -0
  282. package/src/data/tables/conditions.json.gz +0 -0
  283. package/src/data/tables/createInfo.json.gz +0 -0
  284. package/src/data/tables/creatureAggroGroups.json.gz +0 -0
  285. package/src/data/tables/creatureClassLevelStats.json.gz +0 -0
  286. package/src/data/tables/creaturePatrolPacks.json.gz +0 -0
  287. package/src/data/tables/creatureWeapons.json.gz +0 -0
  288. package/src/data/tables/creatures.json.gz +0 -0
  289. package/src/data/tables/dbScripts.json.gz +0 -0
  290. package/src/data/tables/dungeonAuthoredRoutes.json.gz +0 -0
  291. package/src/data/tables/dungeonCensus.json.gz +0 -0
  292. package/src/data/tables/dungeonGameObjects.json.gz +0 -0
  293. package/src/data/tables/dungeonPacks.json.gz +0 -0
  294. package/src/data/tables/dungeonPlateCover.json.gz +0 -0
  295. package/src/data/tables/dungeonSpawns.json.gz +0 -0
  296. package/src/data/tables/dungeonSurvey.json.gz +0 -0
  297. package/src/data/tables/durabilityCosts.json.gz +0 -0
  298. package/src/data/tables/durabilityQuality.json.gz +0 -0
  299. package/src/data/tables/enchantApplicators.json.gz +0 -0
  300. package/src/data/tables/entranceYards.json.gz +0 -0
  301. package/src/data/tables/explorationBasexp.json.gz +0 -0
  302. package/src/data/tables/factionTemplates.json.gz +0 -0
  303. package/src/data/tables/factions.json.gz +0 -0
  304. package/src/data/tables/gameEvents.json.gz +0 -0
  305. package/src/data/tables/gameObjects.json.gz +0 -0
  306. package/src/data/tables/gossipMenus.json.gz +0 -0
  307. package/src/data/tables/graveyards.json.gz +0 -0
  308. package/src/data/tables/instanceMaps.json.gz +0 -0
  309. package/src/data/tables/itemEnchantmentTemplates.json.gz +0 -0
  310. package/src/data/tables/itemEnchants.json.gz +0 -0
  311. package/src/data/tables/itemRandomProperties.json.gz +0 -0
  312. package/src/data/tables/itemSets.json.gz +0 -0
  313. package/src/data/tables/items.json.gz +0 -0
  314. package/src/data/tables/levelStats.json.gz +0 -0
  315. package/src/data/tables/locks.json.gz +0 -0
  316. package/src/data/tables/map-pin-overrides.json.gz +0 -0
  317. package/src/data/tables/nonCombatTrash.json.gz +0 -0
  318. package/src/data/tables/npcTexts.json.gz +0 -0
  319. package/src/data/tables/pageTexts.json.gz +0 -0
  320. package/src/data/tables/petAbilityTP.json.gz +0 -0
  321. package/src/data/tables/petGenericSpells.json.gz +0 -0
  322. package/src/data/tables/petLevelStats.json.gz +0 -0
  323. package/src/data/tables/playerbot-weightscales.json.gz +0 -0
  324. package/src/data/tables/pools.json.gz +0 -0
  325. package/src/data/tables/quests.json.gz +0 -0
  326. package/src/data/tables/races.json.gz +0 -0
  327. package/src/data/tables/reputationRanks.json.gz +0 -0
  328. package/src/data/tables/reputationRates.json.gz +0 -0
  329. package/src/data/tables/reputationSpillover.json.gz +0 -0
  330. package/src/data/tables/ridingSpells.json.gz +0 -0
  331. package/src/data/tables/scriptTexts.json.gz +0 -0
  332. package/src/data/tables/soundClipVolume.json.gz +0 -0
  333. package/src/data/tables/spellChain.json.gz +0 -0
  334. package/src/data/tables/spellTeleports.json.gz +0 -0
  335. package/src/data/tables/spells.json.gz +0 -0
  336. package/src/data/tables/talents.json.gz +0 -0
  337. package/src/data/tables/tavernTriggers.json.gz +0 -0
  338. package/src/data/tables/taxiNodes.json.gz +0 -0
  339. package/src/data/tables/taxiRoutes.json.gz +0 -0
  340. package/src/data/tables/trainers.json.gz +0 -0
  341. package/src/data/tables/transports.json.gz +0 -0
  342. package/src/data/tables/travelAnchors.json.gz +0 -0
  343. package/src/data/tables/travelEdges.json.gz +0 -0
  344. package/src/data/tables/unitConditions.json.gz +0 -0
  345. package/src/data/tables/vendors.json.gz +0 -0
  346. package/src/data/tables/weaponEnchantProcs.json.gz +0 -0
  347. package/src/data/tables/xp.json.gz +0 -0
  348. package/src/data/tables/zoneWeather.json.gz +0 -0
  349. package/src/data/tables/zones.json.gz +0 -0
  350. package/src/data/world-data.ts +56 -0
  351. package/src/data/zone-music-sets.json +2751 -0
  352. package/src/dev/etl/lib/loot-chance-sanity.mjs +180 -0
  353. package/src/domain/audioQuality.mjs +145 -0
  354. package/src/domain/bg/ab.ts +433 -0
  355. package/src/domain/bg/arenaComps.ts +644 -0
  356. package/src/domain/bg/arenaLab.ts +695 -0
  357. package/src/domain/bg/arenaPace.ts +31 -0
  358. package/src/domain/bg/arenaRating.ts +110 -0
  359. package/src/domain/bg/av.ts +758 -0
  360. package/src/domain/bg/avEnemyPush.ts +219 -0
  361. package/src/domain/bg/avField.ts +283 -0
  362. package/src/domain/bg/avInstanceNodes.ts +197 -0
  363. package/src/domain/bg/avInstanceScore.ts +123 -0
  364. package/src/domain/bg/avQuestItems.ts +55 -0
  365. package/src/domain/bg/avQuests.ts +210 -0
  366. package/src/domain/bg/avSession.ts +196 -0
  367. package/src/domain/bg/bgConsumes.ts +269 -0
  368. package/src/domain/bg/bgPrebuff.ts +153 -0
  369. package/src/domain/bg/bgRest.ts +91 -0
  370. package/src/domain/bg/bgRoster.ts +305 -0
  371. package/src/domain/bg/bgTier.ts +61 -0
  372. package/src/domain/bg/bgWorld.ts +862 -0
  373. package/src/domain/bg/match.ts +215 -0
  374. package/src/domain/bg/strategies.ts +552 -0
  375. package/src/domain/bg/types.ts +291 -0
  376. package/src/domain/bg/wsg.ts +264 -0
  377. package/src/domain/bots/actionProviderSpec.ts +38 -0
  378. package/src/domain/bots/autoConsumables.ts +285 -0
  379. package/src/domain/bots/betweenPullRest.ts +239 -0
  380. package/src/domain/bots/botGear.ts +248 -0
  381. package/src/domain/bots/botInventory.ts +486 -0
  382. package/src/domain/bots/botPet.ts +535 -0
  383. package/src/domain/bots/botPool.ts +291 -0
  384. package/src/domain/bots/botProfessions.ts +334 -0
  385. package/src/domain/bots/companionActionBar.ts +209 -0
  386. package/src/domain/bots/companionHireCost.ts +236 -0
  387. package/src/domain/bots/companionMissions.ts +582 -0
  388. package/src/domain/bots/companionPrep.ts +254 -0
  389. package/src/domain/bots/companionQuests.ts +270 -0
  390. package/src/domain/bots/companionRep.ts +75 -0
  391. package/src/domain/bots/companionRotation.ts +708 -0
  392. package/src/domain/bots/companionXp.ts +66 -0
  393. package/src/domain/bots/consumableShare.ts +811 -0
  394. package/src/domain/bots/dpsMate.ts +1072 -0
  395. package/src/domain/bots/groupCompReport.ts +132 -0
  396. package/src/domain/bots/healthstoneSupply.ts +198 -0
  397. package/src/domain/bots/outfitter.ts +1056 -0
  398. package/src/domain/bots/paladinPrePull.ts +231 -0
  399. package/src/domain/bots/partyRole.ts +40 -0
  400. package/src/domain/bots/playerbotEquipCache.ts +412 -0
  401. package/src/domain/bots/playerbotItemEligibility.ts +60 -0
  402. package/src/domain/bots/playerbotStatWeight.ts +853 -0
  403. package/src/domain/bots/playerbotUnavailableItems.ts +147 -0
  404. package/src/domain/bots/prePullBuffs.ts +219 -0
  405. package/src/domain/bots/prePullStealth.ts +82 -0
  406. package/src/domain/bots/runSupply.ts +251 -0
  407. package/src/domain/bots/runSupplyPrefs.ts +28 -0
  408. package/src/domain/bots/sendTradeGoods.ts +394 -0
  409. package/src/domain/bots/soulShardCap.ts +35 -0
  410. package/src/domain/bots/squad.ts +842 -0
  411. package/src/domain/bots/squadRun.ts +81 -0
  412. package/src/domain/character/actionbar.ts +346 -0
  413. package/src/domain/character/appearance.ts +165 -0
  414. package/src/domain/character/buildCombatant.ts +1726 -0
  415. package/src/domain/character/character.ts +356 -0
  416. package/src/domain/character/characterName.ts +43 -0
  417. package/src/domain/character/characterRatings.ts +424 -0
  418. package/src/domain/character/characterSpells.ts +282 -0
  419. package/src/domain/character/characterStats.ts +1559 -0
  420. package/src/domain/character/characterTalents.ts +398 -0
  421. package/src/domain/character/combatSkills.ts +994 -0
  422. package/src/domain/character/displayForm.ts +72 -0
  423. package/src/domain/character/formTalentAuras.ts +171 -0
  424. package/src/domain/character/forms.ts +499 -0
  425. package/src/domain/character/races.ts +40 -0
  426. package/src/domain/character/resurrectionSickness.ts +49 -0
  427. package/src/domain/character/spellbook.ts +169 -0
  428. package/src/domain/character/stanceBar.ts +354 -0
  429. package/src/domain/character/stancePrereq.ts +70 -0
  430. package/src/domain/character/talentBuilds.ts +63 -0
  431. package/src/domain/character/talentImport.ts +130 -0
  432. package/src/domain/character/talentLink.ts +123 -0
  433. package/src/domain/character/talents.ts +201 -0
  434. package/src/domain/core/conditionSnapshot.ts +92 -0
  435. package/src/domain/core/hover.ts +14 -0
  436. package/src/domain/core/ids.ts +58 -0
  437. package/src/domain/core/internalNameTags.ts +5 -0
  438. package/src/domain/core/runtimeDataPatches.ts +357 -0
  439. package/src/domain/creature/aiTargeting.ts +588 -0
  440. package/src/domain/creature/combatCondition.ts +157 -0
  441. package/src/domain/creature/creatureAggro.ts +804 -0
  442. package/src/domain/creature/creatureAi.ts +206 -0
  443. package/src/domain/creature/creatureAiConstants.ts +661 -0
  444. package/src/domain/creature/creatureCastList.ts +1007 -0
  445. package/src/domain/creature/creatureEventAi.ts +1409 -0
  446. package/src/domain/creature/creatureGroups.ts +144 -0
  447. package/src/domain/creature/creatureLinking.ts +333 -0
  448. package/src/domain/creature/creatureScaling.ts +320 -0
  449. package/src/domain/creature/creatureTrigger.ts +53 -0
  450. package/src/domain/creature/creatureWander.ts +74 -0
  451. package/src/domain/creature/eventAiTexts.ts +145 -0
  452. package/src/domain/creature/faction.ts +287 -0
  453. package/src/domain/creature/forcedReaction.ts +48 -0
  454. package/src/domain/creature/outdoorHuntable.ts +74 -0
  455. package/src/domain/creature/patrolPacks.ts +210 -0
  456. package/src/domain/creature/reputation.ts +967 -0
  457. package/src/domain/creature/unitFlags.ts +74 -0
  458. package/src/domain/creature/worldBosses.ts +393 -0
  459. package/src/domain/dungeonMusic.mjs +48 -0
  460. package/src/domain/economy/auction.ts +282 -0
  461. package/src/domain/economy/auctionBot.ts +1060 -0
  462. package/src/domain/economy/honorPrice.ts +100 -0
  463. package/src/domain/economy/mail.ts +958 -0
  464. package/src/domain/economy/money.ts +42 -0
  465. package/src/domain/economy/syntheticClassTrainers.ts +62 -0
  466. package/src/domain/economy/trading.ts +296 -0
  467. package/src/domain/economy/vendorSellAll.ts +252 -0
  468. package/src/domain/economy/vendorStock.ts +146 -0
  469. package/src/domain/items/ammo.ts +817 -0
  470. package/src/domain/items/bank.ts +162 -0
  471. package/src/domain/items/consumableFamily.ts +205 -0
  472. package/src/domain/items/craftUpgrades.ts +83 -0
  473. package/src/domain/items/createItem.ts +93 -0
  474. package/src/domain/items/durability.ts +581 -0
  475. package/src/domain/items/equipment.ts +441 -0
  476. package/src/domain/items/equipmentHelper.ts +767 -0
  477. package/src/domain/items/inventory.ts +561 -0
  478. package/src/domain/items/itemBonding.ts +93 -0
  479. package/src/domain/items/itemContainer.ts +245 -0
  480. package/src/domain/items/itemDisplayName.ts +33 -0
  481. package/src/domain/items/itemInstance.ts +181 -0
  482. package/src/domain/items/itemRandomEnchantOutcomes.ts +315 -0
  483. package/src/domain/items/itemRandomProps.ts +77 -0
  484. package/src/domain/items/itemRestrictions.ts +329 -0
  485. package/src/domain/items/itemTeaches.ts +82 -0
  486. package/src/domain/items/itemUseCooldown.ts +52 -0
  487. package/src/domain/items/itemUseTarget.ts +90 -0
  488. package/src/domain/items/junkCategories.ts +174 -0
  489. package/src/domain/items/junkFilter.ts +368 -0
  490. package/src/domain/items/keyring.ts +131 -0
  491. package/src/domain/items/loot.ts +221 -0
  492. package/src/domain/items/mounts.ts +190 -0
  493. package/src/domain/items/onUsePayload.ts +102 -0
  494. package/src/domain/items/pendingLoot.ts +107 -0
  495. package/src/domain/items/relic.ts +105 -0
  496. package/src/domain/items/rocketBoots.ts +36 -0
  497. package/src/domain/items/sortContainer.ts +139 -0
  498. package/src/domain/items/spareContainers.ts +71 -0
  499. package/src/domain/items/spellReagentIndex.ts +131 -0
  500. package/src/domain/items/staleConsumables.ts +66 -0
  501. package/src/domain/items/weaponSpender.ts +110 -0
  502. package/src/domain/listDungeonIds.mjs +28 -0
  503. package/src/domain/macro/compose.ts +239 -0
  504. package/src/domain/macro/conditions.ts +260 -0
  505. package/src/domain/macro/creatureTypes.ts +40 -0
  506. package/src/domain/macro/describe.ts +169 -0
  507. package/src/domain/macro/icon.ts +60 -0
  508. package/src/domain/macro/index.ts +20 -0
  509. package/src/domain/macro/normalize.ts +112 -0
  510. package/src/domain/macro/parse.ts +316 -0
  511. package/src/domain/macro/plan.ts +288 -0
  512. package/src/domain/macro/rotation.ts +590 -0
  513. package/src/domain/macro/rotationEnv.ts +173 -0
  514. package/src/domain/macro/slots.ts +101 -0
  515. package/src/domain/macro/snapshot.ts +114 -0
  516. package/src/domain/macro/templates.ts +446 -0
  517. package/src/domain/macro/types.ts +70 -0
  518. package/src/domain/macro/units.ts +93 -0
  519. package/src/domain/macro/weaponTypes.ts +83 -0
  520. package/src/domain/nav/cityTeleports.ts +10 -0
  521. package/src/domain/nav/mounting.ts +113 -0
  522. package/src/domain/nav/nav.ts +43 -0
  523. package/src/domain/nav/navCollision.ts +606 -0
  524. package/src/domain/nav/navOutdoors.ts +91 -0
  525. package/src/domain/nav/navRegistry.ts +179 -0
  526. package/src/domain/nav/navRoomGraph.ts +302 -0
  527. package/src/domain/nav/navTypes.ts +43 -0
  528. package/src/domain/nav/navmesh.ts +1715 -0
  529. package/src/domain/nav/speed.ts +200 -0
  530. package/src/domain/nav/storey.ts +10 -0
  531. package/src/domain/nav/travel.ts +725 -0
  532. package/src/domain/nav/travelTypes.ts +75 -0
  533. package/src/domain/pet/pet.ts +1150 -0
  534. package/src/domain/pet/petActionBar.ts +180 -0
  535. package/src/domain/pet/petSpellbook.ts +39 -0
  536. package/src/domain/pet/petStable.ts +81 -0
  537. package/src/domain/professions/autoDisenchant.ts +236 -0
  538. package/src/domain/professions/characterEnchants.ts +176 -0
  539. package/src/domain/professions/craftCooldown.ts +52 -0
  540. package/src/domain/professions/enchantApply.ts +62 -0
  541. package/src/domain/professions/enchantTargets.ts +114 -0
  542. package/src/domain/professions/enchanting.ts +236 -0
  543. package/src/domain/professions/gatherFilter.ts +149 -0
  544. package/src/domain/professions/gearEnchant.ts +137 -0
  545. package/src/domain/professions/heldTools.ts +29 -0
  546. package/src/domain/professions/learnAllRecipes.ts +146 -0
  547. package/src/domain/professions/miningHarvest.ts +69 -0
  548. package/src/domain/professions/partyDisenchant.ts +294 -0
  549. package/src/domain/professions/partyWork.ts +83 -0
  550. package/src/domain/professions/pickpocket.ts +172 -0
  551. package/src/domain/professions/poisons.ts +240 -0
  552. package/src/domain/professions/professions.ts +677 -0
  553. package/src/domain/professions/rareNodes.ts +45 -0
  554. package/src/domain/professions/skinLoot.ts +27 -0
  555. package/src/domain/professions/vendorReagents.ts +152 -0
  556. package/src/domain/professions/weaponImbueHand.ts +171 -0
  557. package/src/domain/questFarmFilter.mjs +42 -0
  558. package/src/domain/quests/doQuest.ts +1204 -0
  559. package/src/domain/quests/gossip.ts +308 -0
  560. package/src/domain/quests/gossipTalk.ts +298 -0
  561. package/src/domain/quests/interpreter/canComplete.ts +150 -0
  562. package/src/domain/quests/interpreter/claim.ts +137 -0
  563. package/src/domain/quests/interpreter/dbscript/handlers.ts +1371 -0
  564. package/src/domain/quests/interpreter/dbscript/handlersBatch.ts +1251 -0
  565. package/src/domain/quests/interpreter/dbscript/hooks.ts +331 -0
  566. package/src/domain/quests/interpreter/dbscript/index.ts +212 -0
  567. package/src/domain/quests/interpreter/dbscript/opcodes.ts +257 -0
  568. package/src/domain/quests/interpreter/dbscript/registry.ts +1194 -0
  569. package/src/domain/quests/interpreter/dbscript/registryAreatriggerScripts.ts +539 -0
  570. package/src/domain/quests/interpreter/dbscript/registryAshenvale.ts +553 -0
  571. package/src/domain/quests/interpreter/dbscript/registryAzshara.ts +272 -0
  572. package/src/domain/quests/interpreter/dbscript/registryDarkshore.ts +637 -0
  573. package/src/domain/quests/interpreter/dbscript/registryDesolace.ts +501 -0
  574. package/src/domain/quests/interpreter/dbscript/registryDustwallowMarsh.ts +735 -0
  575. package/src/domain/quests/interpreter/dbscript/registryFelwood.ts +493 -0
  576. package/src/domain/quests/interpreter/dbscript/registryFeralas.ts +485 -0
  577. package/src/domain/quests/interpreter/dbscript/registryGoScripts.ts +203 -0
  578. package/src/domain/quests/interpreter/dbscript/registryMoonglade.ts +288 -0
  579. package/src/domain/quests/interpreter/dbscript/registryNpcProfessions.ts +230 -0
  580. package/src/domain/quests/interpreter/dbscript/registryNpcsSpecial.ts +545 -0
  581. package/src/domain/quests/interpreter/dbscript/registryRazorfenDowns.ts +293 -0
  582. package/src/domain/quests/interpreter/dbscript/registryRazorfenKraul.ts +221 -0
  583. package/src/domain/quests/interpreter/dbscript/registryRedridge.ts +112 -0
  584. package/src/domain/quests/interpreter/dbscript/registrySearingGorge.ts +125 -0
  585. package/src/domain/quests/interpreter/dbscript/registrySiblings.ts +51 -0
  586. package/src/domain/quests/interpreter/dbscript/registrySilverpineForest.ts +172 -0
  587. package/src/domain/quests/interpreter/dbscript/registryStonetalonMountains.ts +180 -0
  588. package/src/domain/quests/interpreter/dbscript/registryStormwindCity.ts +369 -0
  589. package/src/domain/quests/interpreter/dbscript/registryStranglethornVale.ts +115 -0
  590. package/src/domain/quests/interpreter/dbscript/registryStratholme.ts +146 -0
  591. package/src/domain/quests/interpreter/dbscript/registrySunkenTemple.ts +149 -0
  592. package/src/domain/quests/interpreter/dbscript/registrySwampOfSorrows.ts +163 -0
  593. package/src/domain/quests/interpreter/dbscript/registryTanaris.ts +318 -0
  594. package/src/domain/quests/interpreter/dbscript/registryTeldrassil.ts +120 -0
  595. package/src/domain/quests/interpreter/dbscript/registryTheBarrens.ts +603 -0
  596. package/src/domain/quests/interpreter/dbscript/registryThousandNeedles.ts +499 -0
  597. package/src/domain/quests/interpreter/dbscript/registryTirisfalGlades.ts +222 -0
  598. package/src/domain/quests/interpreter/dbscript/registryUngoroCrater.ts +291 -0
  599. package/src/domain/quests/interpreter/dbscript/registryWarEffort.ts +773 -0
  600. package/src/domain/quests/interpreter/dbscript/registryWesternPlaguelands.ts +206 -0
  601. package/src/domain/quests/interpreter/dbscript/registryWestfall.ts +190 -0
  602. package/src/domain/quests/interpreter/dbscript/registryWetlands.ts +186 -0
  603. package/src/domain/quests/interpreter/dbscript/registryWinterspring.ts +235 -0
  604. package/src/domain/quests/interpreter/dbscript/types.ts +817 -0
  605. package/src/domain/quests/interpreter/dbscript/vm.ts +171 -0
  606. package/src/domain/quests/interpreter/diff.ts +349 -0
  607. package/src/domain/quests/interpreter/fixtures/oracle-questlog-qi2.json +2417 -0
  608. package/src/domain/quests/interpreter/flags.ts +101 -0
  609. package/src/domain/quests/interpreter/index.ts +113 -0
  610. package/src/domain/quests/interpreter/live.ts +178 -0
  611. package/src/domain/quests/interpreter/types.ts +81 -0
  612. package/src/domain/quests/interpreter/updaters.ts +412 -0
  613. package/src/domain/quests/questCorpseUse.ts +155 -0
  614. package/src/domain/quests/questDialog.ts +43 -0
  615. package/src/domain/quests/questFight.ts +34 -0
  616. package/src/domain/quests/questObjectiveLabel.ts +39 -0
  617. package/src/domain/quests/questRewardChoice.ts +85 -0
  618. package/src/domain/quests/questScriptCredit.ts +26 -0
  619. package/src/domain/quests/questScriptItemUse.ts +74 -0
  620. package/src/domain/quests/questTameRods.ts +79 -0
  621. package/src/domain/quests/questlog.ts +1545 -0
  622. package/src/domain/quests/talkDeliver.ts +170 -0
  623. package/src/domain/raid/globalActionBar.ts +195 -0
  624. package/src/domain/raid/instanceRoster.ts +194 -0
  625. package/src/domain/raid/partySummon.ts +198 -0
  626. package/src/domain/raid/partyVault.ts +320 -0
  627. package/src/domain/raid/raidBossAbilities.ts +268 -0
  628. package/src/domain/raid/raidComp.ts +335 -0
  629. package/src/domain/raid/raidCustomRows.ts +453 -0
  630. package/src/domain/raid/raidEncounterGear.ts +298 -0
  631. package/src/domain/raid/raidEncounterTactics.ts +412 -0
  632. package/src/domain/raid/raidPlan.ts +509 -0
  633. package/src/domain/raid/raidPlanEdits.ts +98 -0
  634. package/src/domain/raid/raidPlanPacks.ts +480 -0
  635. package/src/domain/raid/raidRecovery.ts +183 -0
  636. package/src/domain/raid/raidRosterTemplate.ts +138 -0
  637. package/src/domain/rotation/auraSide.ts +68 -0
  638. package/src/domain/rotation/build.ts +3031 -0
  639. package/src/domain/rotation/classes/druid.ts +58 -0
  640. package/src/domain/rotation/classes/hooks.ts +59 -0
  641. package/src/domain/rotation/classes/hunter.ts +156 -0
  642. package/src/domain/rotation/classes/index.ts +104 -0
  643. package/src/domain/rotation/classes/mage.ts +99 -0
  644. package/src/domain/rotation/classes/paladin.ts +296 -0
  645. package/src/domain/rotation/classes/priest.ts +94 -0
  646. package/src/domain/rotation/classes/pullOpener.ts +92 -0
  647. package/src/domain/rotation/classes/rogue.ts +69 -0
  648. package/src/domain/rotation/classes/shaman.ts +29 -0
  649. package/src/domain/rotation/classes/supportSpec.ts +94 -0
  650. package/src/domain/rotation/classes/warlock.ts +81 -0
  651. package/src/domain/rotation/classes/warrior.ts +402 -0
  652. package/src/domain/rotation/classify.ts +1381 -0
  653. package/src/domain/rotation/condMetricPicker.ts +379 -0
  654. package/src/domain/rotation/consumables.ts +242 -0
  655. package/src/domain/rotation/evaluate.ts +1115 -0
  656. package/src/domain/rotation/finisherLadder.ts +145 -0
  657. package/src/domain/rotation/invent.ts +938 -0
  658. package/src/domain/rotation/killOrder.ts +30 -0
  659. package/src/domain/rotation/openerForm.ts +22 -0
  660. package/src/domain/rotation/paladinBuffs.ts +568 -0
  661. package/src/domain/rotation/prebuff.ts +283 -0
  662. package/src/domain/rotation/raidRowTuning.ts +34 -0
  663. package/src/domain/rotation/rotation.ts +11 -0
  664. package/src/domain/rotation/rotationProfiles.ts +180 -0
  665. package/src/domain/rotation/rotationSuggestions.ts +851 -0
  666. package/src/domain/rotation/ruleAfterChain.ts +49 -0
  667. package/src/domain/rotation/spec.ts +70 -0
  668. package/src/domain/rotation/spellPicker.ts +140 -0
  669. package/src/domain/rotation/stand.ts +143 -0
  670. package/src/domain/rotation/types.ts +718 -0
  671. package/src/domain/run/battlegroundRun.ts +111 -0
  672. package/src/domain/run/canEnterContext.ts +32 -0
  673. package/src/domain/run/death.ts +561 -0
  674. package/src/domain/run/deathRecovery.ts +289 -0
  675. package/src/domain/run/duel.ts +141 -0
  676. package/src/domain/run/dungeonRun.ts +344 -0
  677. package/src/domain/run/floorRest.ts +159 -0
  678. package/src/domain/run/honor.ts +557 -0
  679. package/src/domain/run/honorWeek.ts +180 -0
  680. package/src/domain/run/whenMap.ts +977 -0
  681. package/src/domain/scriptdev/arathiHighlands.ts +445 -0
  682. package/src/domain/scriptdev/ashenvale.ts +635 -0
  683. package/src/domain/scriptdev/ashenvaleHelpers.ts +1215 -0
  684. package/src/domain/scriptdev/azshara.ts +892 -0
  685. package/src/domain/scriptdev/blackfathomDeeps.ts +212 -0
  686. package/src/domain/scriptdev/blackrockDepths.ts +1434 -0
  687. package/src/domain/scriptdev/blackrockSpire.ts +730 -0
  688. package/src/domain/scriptdev/blackwingLair.ts +521 -0
  689. package/src/domain/scriptdev/burningSteppes.ts +62 -0
  690. package/src/domain/scriptdev/classFilter.ts +43 -0
  691. package/src/domain/scriptdev/darkshore.ts +817 -0
  692. package/src/domain/scriptdev/darkshoreHelpers.ts +853 -0
  693. package/src/domain/scriptdev/deadmines.ts +331 -0
  694. package/src/domain/scriptdev/desolace.ts +1239 -0
  695. package/src/domain/scriptdev/direMaul.ts +859 -0
  696. package/src/domain/scriptdev/durotar.ts +532 -0
  697. package/src/domain/scriptdev/dustwallowMarsh.ts +758 -0
  698. package/src/domain/scriptdev/dustwallowMarshHelpers.ts +994 -0
  699. package/src/domain/scriptdev/easternPlaguelands.ts +21 -0
  700. package/src/domain/scriptdev/felwood.ts +1055 -0
  701. package/src/domain/scriptdev/feralas.ts +571 -0
  702. package/src/domain/scriptdev/feralasHelpers.ts +838 -0
  703. package/src/domain/scriptdev/gnomeregan.ts +466 -0
  704. package/src/domain/scriptdev/guards.ts +616 -0
  705. package/src/domain/scriptdev/hinterlands.ts +693 -0
  706. package/src/domain/scriptdev/host.ts +1134 -0
  707. package/src/domain/scriptdev/index.ts +123 -0
  708. package/src/domain/scriptdev/instanceRaids.ts +136 -0
  709. package/src/domain/scriptdev/itemScripts.ts +37 -0
  710. package/src/domain/scriptdev/lochModan.ts +283 -0
  711. package/src/domain/scriptdev/maraudon.ts +232 -0
  712. package/src/domain/scriptdev/moltenCore.ts +831 -0
  713. package/src/domain/scriptdev/moonglade.ts +659 -0
  714. package/src/domain/scriptdev/moongladeHelpers.ts +933 -0
  715. package/src/domain/scriptdev/naxxramas.ts +577 -0
  716. package/src/domain/scriptdev/naxxramasConstruct.ts +364 -0
  717. package/src/domain/scriptdev/naxxramasFrostwyrm.ts +378 -0
  718. package/src/domain/scriptdev/naxxramasMilitary.ts +276 -0
  719. package/src/domain/scriptdev/npcProfessions.ts +1025 -0
  720. package/src/domain/scriptdev/npcsSpecial.ts +1379 -0
  721. package/src/domain/scriptdev/onyxiasLair.ts +313 -0
  722. package/src/domain/scriptdev/orgrimmar.ts +147 -0
  723. package/src/domain/scriptdev/professionSpecConsumer.ts +481 -0
  724. package/src/domain/scriptdev/razorfenDowns.ts +468 -0
  725. package/src/domain/scriptdev/razorfenKraul.ts +410 -0
  726. package/src/domain/scriptdev/redridgeMountains.ts +163 -0
  727. package/src/domain/scriptdev/registry.ts +945 -0
  728. package/src/domain/scriptdev/ruinsOfAhnqiraj.ts +356 -0
  729. package/src/domain/scriptdev/scarletMonastery.ts +713 -0
  730. package/src/domain/scriptdev/scholomance.ts +682 -0
  731. package/src/domain/scriptdev/searingGorge.ts +295 -0
  732. package/src/domain/scriptdev/shadowfangKeep.ts +181 -0
  733. package/src/domain/scriptdev/silithus.ts +71 -0
  734. package/src/domain/scriptdev/silverpineForest.ts +662 -0
  735. package/src/domain/scriptdev/stonetalonMountains.ts +493 -0
  736. package/src/domain/scriptdev/stormwindCity.ts +1192 -0
  737. package/src/domain/scriptdev/stranglethornVale.ts +402 -0
  738. package/src/domain/scriptdev/stratholme.ts +650 -0
  739. package/src/domain/scriptdev/stratholmeScripts.ts +505 -0
  740. package/src/domain/scriptdev/sunkenTemple.ts +736 -0
  741. package/src/domain/scriptdev/swampOfSorrows.ts +22 -0
  742. package/src/domain/scriptdev/tanaris.ts +391 -0
  743. package/src/domain/scriptdev/tanarisHelpers.ts +661 -0
  744. package/src/domain/scriptdev/teldrassil.ts +143 -0
  745. package/src/domain/scriptdev/templeOfAhnqiraj.ts +1013 -0
  746. package/src/domain/scriptdev/theBarrens.ts +709 -0
  747. package/src/domain/scriptdev/theBarrensHelpers.ts +1026 -0
  748. package/src/domain/scriptdev/thousandNeedles.ts +426 -0
  749. package/src/domain/scriptdev/thousandNeedlesHelpers.ts +566 -0
  750. package/src/domain/scriptdev/tirisfalGlades.ts +533 -0
  751. package/src/domain/scriptdev/types.ts +1026 -0
  752. package/src/domain/scriptdev/uldaman.ts +437 -0
  753. package/src/domain/scriptdev/ungoroCrater.ts +549 -0
  754. package/src/domain/scriptdev/ungoroCraterHelpers.ts +1006 -0
  755. package/src/domain/scriptdev/wailingCaverns.ts +700 -0
  756. package/src/domain/scriptdev/warEffort.ts +371 -0
  757. package/src/domain/scriptdev/westernPlaguelands.ts +952 -0
  758. package/src/domain/scriptdev/westfall.ts +767 -0
  759. package/src/domain/scriptdev/wetlands.ts +660 -0
  760. package/src/domain/scriptdev/winterspring.ts +482 -0
  761. package/src/domain/scriptdev/winterspringHelpers.ts +801 -0
  762. package/src/domain/scriptdev/worldBosses.ts +605 -0
  763. package/src/domain/scriptdev/zulfarrak.ts +478 -0
  764. package/src/domain/scriptdev/zulgurub.ts +1152 -0
  765. package/src/domain/sim/dummySim.ts +408 -0
  766. package/src/domain/sim/targetDummy.ts +29 -0
  767. package/src/domain/social/guild.ts +148 -0
  768. package/src/domain/spells/areaAuraMembership.ts +93 -0
  769. package/src/domain/spells/breakableStunCc.ts +166 -0
  770. package/src/domain/spells/cancelAura.ts +80 -0
  771. package/src/domain/spells/diminishing.ts +167 -0
  772. package/src/domain/spells/dispel.ts +149 -0
  773. package/src/domain/spells/dispelMechanic.ts +105 -0
  774. package/src/domain/spells/equipSpells.ts +247 -0
  775. package/src/domain/spells/learnOnTargets.ts +260 -0
  776. package/src/domain/spells/mergeSchoolImmunityFromRaw.ts +66 -0
  777. package/src/domain/spells/resurrectSpell.ts +124 -0
  778. package/src/domain/spells/spellPushback.ts +39 -0
  779. package/src/domain/spells/spellReflect.ts +43 -0
  780. package/src/domain/spells/stealth.ts +183 -0
  781. package/src/domain/spells/totem.ts +394 -0
  782. package/src/domain/spells/worldCast.ts +187 -0
  783. package/src/domain/world/areaTriggerLocks.ts +177 -0
  784. package/src/domain/world/dungeonMusic.ts +108 -0
  785. package/src/domain/world/gameEvents.ts +119 -0
  786. package/src/domain/world/gameObjects.ts +1030 -0
  787. package/src/domain/world/goEventFire.ts +234 -0
  788. package/src/domain/world/goMoTransport.ts +74 -0
  789. package/src/domain/world/goTransport.ts +81 -0
  790. package/src/domain/world/hazards.ts +459 -0
  791. package/src/domain/world/openLock.ts +398 -0
  792. package/src/domain/world/pools.ts +470 -0
  793. package/src/domain/world/restArea.ts +37 -0
  794. package/src/domain/world/weather.ts +201 -0
  795. package/src/domain/zoneAmbience.mjs +87 -0
  796. package/src/domain/zoneMusic.mjs +62 -0
  797. package/src/domain/zoneMusicQueue.mjs +7 -0
  798. package/src/engine/combat/activeSeal.ts +39 -0
  799. package/src/engine/combat/aiTargetResolve.ts +64 -0
  800. package/src/engine/combat/aoeCast.ts +289 -0
  801. package/src/engine/combat/applyConsumeAura.ts +74 -0
  802. package/src/engine/combat/applyDemonicSacrifice.ts +70 -0
  803. package/src/engine/combat/applyDispelMechanic.ts +30 -0
  804. package/src/engine/combat/applyHealEffect.ts +301 -0
  805. package/src/engine/combat/applySchoolAbsorb.ts +114 -0
  806. package/src/engine/combat/applySpell.ts +3169 -0
  807. package/src/engine/combat/applySpellLate.ts +2026 -0
  808. package/src/engine/combat/applySpellLateBag.ts +144 -0
  809. package/src/engine/combat/areaAura.ts +283 -0
  810. package/src/engine/combat/attackTable.ts +508 -0
  811. package/src/engine/combat/battleRez.ts +87 -0
  812. package/src/engine/combat/castAction.ts +36 -0
  813. package/src/engine/combat/castTargetGates.ts +75 -0
  814. package/src/engine/combat/castableKit.ts +68 -0
  815. package/src/engine/combat/ccMirrorUpgrade.ts +39 -0
  816. package/src/engine/combat/chainFan.ts +210 -0
  817. package/src/engine/combat/channel.ts +423 -0
  818. package/src/engine/combat/chaseCloseness.ts +95 -0
  819. package/src/engine/combat/cmangosCastGates.ts +129 -0
  820. package/src/engine/combat/combatFlag.ts +77 -0
  821. package/src/engine/combat/combatGeometry.ts +1329 -0
  822. package/src/engine/combat/combustion.ts +68 -0
  823. package/src/engine/combat/context.ts +730 -0
  824. package/src/engine/combat/createSession.ts +1165 -0
  825. package/src/engine/combat/creatureLeash.ts +38 -0
  826. package/src/engine/combat/crowdControl.ts +1259 -0
  827. package/src/engine/combat/dispelDuty.ts +54 -0
  828. package/src/engine/combat/dispelReflect.ts +609 -0
  829. package/src/engine/combat/displaceFloor.ts +113 -0
  830. package/src/engine/combat/duelBounds.ts +47 -0
  831. package/src/engine/combat/equippedItemGate.ts +297 -0
  832. package/src/engine/combat/evadeLifecycle.ts +63 -0
  833. package/src/engine/combat/eventAi.ts +2220 -0
  834. package/src/engine/combat/facing.ts +74 -0
  835. package/src/engine/combat/fearFlee.ts +137 -0
  836. package/src/engine/combat/fighterFactory.ts +1144 -0
  837. package/src/engine/combat/forceDespawnAction.ts +51 -0
  838. package/src/engine/combat/formulas.ts +807 -0
  839. package/src/engine/combat/friendlyFan.ts +55 -0
  840. package/src/engine/combat/groundPoolTick.ts +210 -0
  841. package/src/engine/combat/hazards.ts +114 -0
  842. package/src/engine/combat/healTargets.ts +226 -0
  843. package/src/engine/combat/heartbeatResist.ts +49 -0
  844. package/src/engine/combat/hostileItemUse.ts +75 -0
  845. package/src/engine/combat/interruptAuras.ts +66 -0
  846. package/src/engine/combat/itemRescue.ts +49 -0
  847. package/src/engine/combat/itemUse.ts +260 -0
  848. package/src/engine/combat/kitById.ts +23 -0
  849. package/src/engine/combat/knockBack.ts +201 -0
  850. package/src/engine/combat/leap.ts +89 -0
  851. package/src/engine/combat/mitigation.ts +587 -0
  852. package/src/engine/combat/modTaunt.ts +43 -0
  853. package/src/engine/combat/motionClaim.ts +268 -0
  854. package/src/engine/combat/motionGate.ts +150 -0
  855. package/src/engine/combat/motionGenerator.ts +27 -0
  856. package/src/engine/combat/navSight.ts +138 -0
  857. package/src/engine/combat/perfSink.ts +28 -0
  858. package/src/engine/combat/periodicDue.ts +55 -0
  859. package/src/engine/combat/petAuras.ts +86 -0
  860. package/src/engine/combat/pickAction.ts +3449 -0
  861. package/src/engine/combat/powerForm.ts +735 -0
  862. package/src/engine/combat/powerPools.ts +49 -0
  863. package/src/engine/combat/predicates.ts +538 -0
  864. package/src/engine/combat/procsDamage.ts +1823 -0
  865. package/src/engine/combat/reactiveGates.ts +135 -0
  866. package/src/engine/combat/roles.ts +389 -0
  867. package/src/engine/combat/rotationSubject.ts +78 -0
  868. package/src/engine/combat/ruleAfter.ts +106 -0
  869. package/src/engine/combat/seedDisplayAuras.ts +203 -0
  870. package/src/engine/combat/seedOocEffects.ts +317 -0
  871. package/src/engine/combat/seedSchoolImmunities.ts +45 -0
  872. package/src/engine/combat/session-types.ts +458 -0
  873. package/src/engine/combat/sessionApi.ts +1785 -0
  874. package/src/engine/combat/sessionCharge.ts +124 -0
  875. package/src/engine/combat/sessionChase.ts +869 -0
  876. package/src/engine/combat/sessionDeaths.ts +469 -0
  877. package/src/engine/combat/sessionFocus.ts +506 -0
  878. package/src/engine/combat/sessionFollow.ts +167 -0
  879. package/src/engine/combat/sessionGeometry.ts +188 -0
  880. package/src/engine/combat/sessionHome.ts +75 -0
  881. package/src/engine/combat/sessionInject.ts +719 -0
  882. package/src/engine/combat/sessionMechanicMove.ts +555 -0
  883. package/src/engine/combat/sessionMotion.ts +1553 -0
  884. package/src/engine/combat/sessionMotionKit.ts +247 -0
  885. package/src/engine/combat/sessionMoveIntent.ts +49 -0
  886. package/src/engine/combat/sessionMoveOrder.ts +200 -0
  887. package/src/engine/combat/sessionPathTo.ts +657 -0
  888. package/src/engine/combat/sessionPools.ts +688 -0
  889. package/src/engine/combat/sessionSteer.ts +144 -0
  890. package/src/engine/combat/sessionStepBack.ts +151 -0
  891. package/src/engine/combat/sessionStepWrap.ts +116 -0
  892. package/src/engine/combat/sessionView.ts +442 -0
  893. package/src/engine/combat/simEffects.ts +32 -0
  894. package/src/engine/combat/simFacade.ts +620 -0
  895. package/src/engine/combat/simPump.ts +264 -0
  896. package/src/engine/combat/simSnapshot.ts +165 -0
  897. package/src/engine/combat/simThreadPool.ts +35 -0
  898. package/src/engine/combat/skeramImages.ts +57 -0
  899. package/src/engine/combat/slowUpdate.ts +178 -0
  900. package/src/engine/combat/snapToFloor.ts +42 -0
  901. package/src/engine/combat/speedAura.ts +29 -0
  902. package/src/engine/combat/spellReagents.ts +71 -0
  903. package/src/engine/combat/stealthDetect.ts +93 -0
  904. package/src/engine/combat/stepBackMotion.ts +171 -0
  905. package/src/engine/combat/stepSession.ts +3102 -0
  906. package/src/engine/combat/summonCast.ts +243 -0
  907. package/src/engine/combat/thaddiusPolarity.ts +97 -0
  908. package/src/engine/combat/threat.ts +388 -0
  909. package/src/engine/combat/threatOps.ts +272 -0
  910. package/src/engine/combat/tickWorkerBody.ts +211 -0
  911. package/src/engine/combat/totemAuras.ts +122 -0
  912. package/src/engine/combat/triggerPayload.ts +42 -0
  913. package/src/engine/combat/triggeredCompanion.ts +63 -0
  914. package/src/engine/combat/types.ts +1897 -0
  915. package/src/engine/combat/useObjectTrap.ts +140 -0
  916. package/src/engine/combat/walkSurface.ts +54 -0
  917. package/src/engine/combat/weapon.ts +1248 -0
  918. package/src/engine/combat/worldFrame.ts +43 -0
  919. package/src/engine/combat.solo-golden.json +2242 -0
  920. package/src/engine/combat.ts +84 -0
  921. package/src/engine/core/detMath.ts +289 -0
  922. package/src/engine/core/events.ts +357 -0
  923. package/src/engine/core/rng.ts +36 -0
  924. package/src/engine/game/actionBars.ts +124 -0
  925. package/src/engine/game/activateObjectFromSpell.ts +320 -0
  926. package/src/engine/game/applyActiveObjectFromDbScript.ts +98 -0
  927. package/src/engine/game/applyDeathAndSpellScripts.ts +268 -0
  928. package/src/engine/game/applyDoorFromDbScript.ts +388 -0
  929. package/src/engine/game/applyEscortFromDbScript.ts +290 -0
  930. package/src/engine/game/applyGoSpellcasterCast.ts +40 -0
  931. package/src/engine/game/applyHoverFromDbScript.ts +110 -0
  932. package/src/engine/game/applyOocResurrect.ts +71 -0
  933. package/src/engine/game/applySpellDurability.ts +115 -0
  934. package/src/engine/game/applyTailFromDbScript.ts +478 -0
  935. package/src/engine/game/applyTerminateFromDbScript.ts +95 -0
  936. package/src/engine/game/auctionMail.ts +269 -0
  937. package/src/engine/game/bagOverflow.ts +130 -0
  938. package/src/engine/game/bank.ts +110 -0
  939. package/src/engine/game/barAutofill.ts +26 -0
  940. package/src/engine/game/battleground.ts +369 -0
  941. package/src/engine/game/combatEnemySpec.ts +216 -0
  942. package/src/engine/game/combatObjectTraps.ts +167 -0
  943. package/src/engine/game/combatSeed.ts +320 -0
  944. package/src/engine/game/combatSession.ts +3241 -0
  945. package/src/engine/game/combatSessionClock.ts +62 -0
  946. package/src/engine/game/companionQuestActions.ts +242 -0
  947. package/src/engine/game/companionTraining.ts +40 -0
  948. package/src/engine/game/consumables.ts +624 -0
  949. package/src/engine/game/context.ts +53 -0
  950. package/src/engine/game/createHealthstone.ts +45 -0
  951. package/src/engine/game/deathFlow.ts +127 -0
  952. package/src/engine/game/devGrant.ts +117 -0
  953. package/src/engine/game/dynamicObject.ts +365 -0
  954. package/src/engine/game/enchantItem.ts +177 -0
  955. package/src/engine/game/equipmentHelper.ts +806 -0
  956. package/src/engine/game/factionState.ts +108 -0
  957. package/src/engine/game/garmentsOoc.ts +69 -0
  958. package/src/engine/game/globalActionBar.ts +179 -0
  959. package/src/engine/game/goInteractQuestCredit.ts +51 -0
  960. package/src/engine/game/goMoTransportUse.ts +61 -0
  961. package/src/engine/game/goTransportUse.ts +60 -0
  962. package/src/engine/game/healthstoneUpkeep.ts +59 -0
  963. package/src/engine/game/killSkin.ts +124 -0
  964. package/src/engine/game/learnFromItem.ts +118 -0
  965. package/src/engine/game/loadReconcile.ts +551 -0
  966. package/src/engine/game/loot.ts +294 -0
  967. package/src/engine/game/macros.ts +125 -0
  968. package/src/engine/game/mail.ts +293 -0
  969. package/src/engine/game/meetingStoneQueue.ts +75 -0
  970. package/src/engine/game/meetingStoneUse.ts +54 -0
  971. package/src/engine/game/newGameState.ts +363 -0
  972. package/src/engine/game/oocAuraInterrupt.ts +92 -0
  973. package/src/engine/game/oocCast.ts +241 -0
  974. package/src/engine/game/oocCastEffects.ts +803 -0
  975. package/src/engine/game/oocCastMethods.ts +1077 -0
  976. package/src/engine/game/openLockFromSpell.ts +174 -0
  977. package/src/engine/game/outdoorPvp.ts +187 -0
  978. package/src/engine/game/outfitter.ts +644 -0
  979. package/src/engine/game/paladinBoard.ts +393 -0
  980. package/src/engine/game/partyAuras.ts +121 -0
  981. package/src/engine/game/persistCombatAuras.ts +91 -0
  982. package/src/engine/game/pets.ts +459 -0
  983. package/src/engine/game/prePullHealthstones.ts +154 -0
  984. package/src/engine/game/professions.ts +2227 -0
  985. package/src/engine/game/pruneStaleRuntime.ts +39 -0
  986. package/src/engine/game/questFightCredit.ts +36 -0
  987. package/src/engine/game/questItemOnCorpse.ts +166 -0
  988. package/src/engine/game/questItemOnCreature.ts +103 -0
  989. package/src/engine/game/questLog.ts +879 -0
  990. package/src/engine/game/questSpellComplete.ts +21 -0
  991. package/src/engine/game/questTurnIn.ts +598 -0
  992. package/src/engine/game/raidParty.ts +32 -0
  993. package/src/engine/game/reagentBuyIn.ts +39 -0
  994. package/src/engine/game/regenRest.ts +186 -0
  995. package/src/engine/game/rotation.ts +425 -0
  996. package/src/engine/game/runFullDungeon.ts +611 -0
  997. package/src/engine/game/runSupply.ts +80 -0
  998. package/src/engine/game/sanitizeDriftedItems.ts +135 -0
  999. package/src/engine/game/scriptDevGoUse.ts +397 -0
  1000. package/src/engine/game/scriptDevInnkeeper.ts +150 -0
  1001. package/src/engine/game/scriptDevProfessionSpec.ts +99 -0
  1002. package/src/engine/game/sendEventFromSpell.ts +87 -0
  1003. package/src/engine/game/sortContainers.ts +85 -0
  1004. package/src/engine/game/spellReputation.ts +69 -0
  1005. package/src/engine/game/squad/squadDungeonSeating.ts +197 -0
  1006. package/src/engine/game/squadDungeon.ts +4222 -0
  1007. package/src/engine/game/squadQuestShare.ts +260 -0
  1008. package/src/engine/game/summonObjectSlot.ts +69 -0
  1009. package/src/engine/game/summonObjectWild.ts +73 -0
  1010. package/src/engine/game/summoningRitualUse.ts +141 -0
  1011. package/src/engine/game/talents.ts +356 -0
  1012. package/src/engine/game/tamingRodUse.ts +66 -0
  1013. package/src/engine/game/tempSpawnCreature.ts +136 -0
  1014. package/src/engine/game/tickDungeonPatrolScripts.ts +205 -0
  1015. package/src/engine/game/tickTimedQuests.ts +28 -0
  1016. package/src/engine/game/trade.ts +586 -0
  1017. package/src/engine/game/travel.ts +1155 -0
  1018. package/src/engine/game/types.ts +1855 -0
  1019. package/src/engine/game/vendorOffers.ts +244 -0
  1020. package/src/engine/game/weaponImbueItem.ts +281 -0
  1021. package/src/engine/game/worldSession.ts +183 -0
  1022. package/src/engine/game/worldState.ts +166 -0
  1023. package/src/engine/game.ts +968 -0
  1024. package/src/engine/spells/GAP.md +67 -0
  1025. package/src/engine/spells/absorbs.ts +376 -0
  1026. package/src/engine/spells/addTargetTrigger.ts +53 -0
  1027. package/src/engine/spells/auraPerSecondCost.ts +50 -0
  1028. package/src/engine/spells/auraStacking.ts +497 -0
  1029. package/src/engine/spells/cmangos-special-cases-combustion.ts +13 -0
  1030. package/src/engine/spells/cmangos-special-cases-s18.ts +445 -0
  1031. package/src/engine/spells/cmangos-special-cases-s19.ts +324 -0
  1032. package/src/engine/spells/cmangos-special-cases-s20.ts +385 -0
  1033. package/src/engine/spells/cmangos-special-cases-s21.ts +116 -0
  1034. package/src/engine/spells/cmangos-special-cases-s22.ts +24 -0
  1035. package/src/engine/spells/cmangos-special-cases-s23.ts +52 -0
  1036. package/src/engine/spells/cmangos-special-cases.ts +1570 -0
  1037. package/src/engine/spells/conditions.ts +718 -0
  1038. package/src/engine/spells/effects.ts +203 -0
  1039. package/src/engine/spells/fixtures/oracle-effects-lock9a.json +1 -0
  1040. package/src/engine/spells/immunity.ts +942 -0
  1041. package/src/engine/spells/intentEffects.ts +351 -0
  1042. package/src/engine/spells/interpreter/GAPS.md +21 -0
  1043. package/src/engine/spells/interpreter/amount.ts +109 -0
  1044. package/src/engine/spells/interpreter/class-scripts-kit.ts +478 -0
  1045. package/src/engine/spells/interpreter/diff.ts +246 -0
  1046. package/src/engine/spells/interpreter/duration.ts +90 -0
  1047. package/src/engine/spells/interpreter/form-attrs.json +1 -0
  1048. package/src/engine/spells/interpreter/form-attrs.ts +95 -0
  1049. package/src/engine/spells/interpreter/handlers/aura-remove.ts +113 -0
  1050. package/src/engine/spells/interpreter/handlers/auras-control.ts +1547 -0
  1051. package/src/engine/spells/interpreter/handlers/auras-core.ts +1583 -0
  1052. package/src/engine/spells/interpreter/handlers/auras-misc.ts +1774 -0
  1053. package/src/engine/spells/interpreter/handlers/effect-disenchant.ts +17 -0
  1054. package/src/engine/spells/interpreter/handlers/effect-skinning.ts +20 -0
  1055. package/src/engine/spells/interpreter/handlers/effects-action-utility.ts +1455 -0
  1056. package/src/engine/spells/interpreter/handlers/effects-core.ts +432 -0
  1057. package/src/engine/spells/interpreter/handlers/effects-remainder.ts +105 -0
  1058. package/src/engine/spells/interpreter/handlers/effects-summon-item.ts +687 -0
  1059. package/src/engine/spells/interpreter/handlers/mod-increase-energy-percent.ts +36 -0
  1060. package/src/engine/spells/interpreter/handlers/mod-increase-energy.ts +36 -0
  1061. package/src/engine/spells/interpreter/handlers/periodic-trigger-zero.ts +327 -0
  1062. package/src/engine/spells/interpreter/handlers/procs.ts +131 -0
  1063. package/src/engine/spells/interpreter/handlers/registry.ts +975 -0
  1064. package/src/engine/spells/interpreter/handlers/shared.ts +667 -0
  1065. package/src/engine/spells/interpreter/handlers.ts +9 -0
  1066. package/src/engine/spells/interpreter/hunter-kit.ts +202 -0
  1067. package/src/engine/spells/interpreter/index.ts +525 -0
  1068. package/src/engine/spells/interpreter/item-triggers.ts +1121 -0
  1069. package/src/engine/spells/interpreter/lift-consume-energy.ts +37 -0
  1070. package/src/engine/spells/interpreter/mage-portals.ts +37 -0
  1071. package/src/engine/spells/interpreter/materialize-charm.ts +88 -0
  1072. package/src/engine/spells/interpreter/materialize-dispel-mechanic.ts +25 -0
  1073. package/src/engine/spells/interpreter/materialize-rating-buffs.ts +81 -0
  1074. package/src/engine/spells/interpreter/materialize-speed.ts +109 -0
  1075. package/src/engine/spells/interpreter/materialize-spell-magnet.ts +22 -0
  1076. package/src/engine/spells/interpreter/opcodes.ts +789 -0
  1077. package/src/engine/spells/interpreter/paladin-kit.ts +95 -0
  1078. package/src/engine/spells/interpreter/paladin-seals.ts +499 -0
  1079. package/src/engine/spells/interpreter/priest-warlock-kit.ts +135 -0
  1080. package/src/engine/spells/interpreter/proc.ts +443 -0
  1081. package/src/engine/spells/interpreter/registry/helpers.ts +16 -0
  1082. package/src/engine/spells/interpreter/registry/index.ts +216 -0
  1083. package/src/engine/spells/interpreter/registry/packs/class-abilities.ts +304 -0
  1084. package/src/engine/spells/interpreter/registry/packs/dire-maul.ts +44 -0
  1085. package/src/engine/spells/interpreter/registry/packs/dummy-script-tail.ts +1065 -0
  1086. package/src/engine/spells/interpreter/registry/packs/ex-wave-m.ts +70 -0
  1087. package/src/engine/spells/interpreter/registry/packs/life-tap.ts +107 -0
  1088. package/src/engine/spells/interpreter/registry/packs/paladin.ts +246 -0
  1089. package/src/engine/spells/interpreter/registry/packs/raid-script-summons.ts +26 -0
  1090. package/src/engine/spells/interpreter/registry/packs/raid-threat-wipe.ts +22 -0
  1091. package/src/engine/spells/interpreter/registry/packs/shaman.ts +214 -0
  1092. package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-a.ts +269 -0
  1093. package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-b.ts +370 -0
  1094. package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-c.ts +172 -0
  1095. package/src/engine/spells/interpreter/registry/packs/si4-dummy-hunter.ts +60 -0
  1096. package/src/engine/spells/interpreter/registry/packs/si4-dummy-warrior.ts +31 -0
  1097. package/src/engine/spells/interpreter/registry/packs/si4-script-generic-a.ts +114 -0
  1098. package/src/engine/spells/interpreter/registry/packs/si4-script-generic-b.ts +249 -0
  1099. package/src/engine/spells/interpreter/registry/packs/si4-script-generic-c.ts +233 -0
  1100. package/src/engine/spells/interpreter/registry/types.ts +48 -0
  1101. package/src/engine/spells/interpreter/talent-scripted.ts +329 -0
  1102. package/src/engine/spells/interpreter/targeting.ts +90 -0
  1103. package/src/engine/spells/interpreter/types-intent.ts +1501 -0
  1104. package/src/engine/spells/interpreter/types.ts +419 -0
  1105. package/src/engine/spells/interpreter/warrior-druid-kit.ts +314 -0
  1106. package/src/engine/spells/interpreter/weapon-imbue-kit.ts +384 -0
  1107. package/src/engine/spells/interrupt.ts +217 -0
  1108. package/src/engine/spells/meleeAttackReset.ts +60 -0
  1109. package/src/engine/spells/oocTriggerSpell.ts +49 -0
  1110. package/src/engine/spells/overrideClassScripts.ts +319 -0
  1111. package/src/engine/spells/parryHaste.ts +20 -0
  1112. package/src/engine/spells/procs.ts +300 -0
  1113. package/src/engine/spells/recovery.ts +203 -0
  1114. package/src/engine/spells/scriptTargetDest.ts +76 -0
  1115. package/src/engine/spells/scriptTargets.ts +85 -0
  1116. package/src/engine/spells/singleTargetAura.ts +82 -0
  1117. package/src/engine/spells/specialCasePayloads.ts +57 -0
  1118. package/src/engine/spells/spellAttributes.ts +249 -0
  1119. package/src/engine/spells/spellBonus.ts +143 -0
  1120. package/src/engine/spells/spellModIntents.ts +419 -0
  1121. package/src/engine/spells/spellMods.ts +283 -0
  1122. package/src/engine/spells/spellRadius.ts +15 -0
  1123. package/src/engine/spells/trapObjects.ts +26 -0
  1124. package/src/json.d.ts +7 -0
  1125. package/src/persistence/memoryRepository.ts +23 -0
  1126. package/src/persistence/saveBackups.ts +162 -0
  1127. package/src/persistence/saveFile.ts +68 -0
  1128. package/src/persistence/saveRepository.ts +11 -0
  1129. package/src/persistence/saveShape.ts +668 -0
  1130. package/src/progression/leveling.ts +89 -0
  1131. package/src/progression/rest.ts +1434 -0
  1132. package/src/progression/training.ts +226 -0
  1133. package/src/progression/xpGain.ts +369 -0
  1134. package/src/quests/quests.ts +1580 -0
  1135. package/src/world/aoePull.ts +198 -0
  1136. package/src/world/areaTableDbc.ts +39 -0
  1137. package/src/world/areaTableMapIdPairs.ts +141 -0
  1138. package/src/world/bakeSlug.ts +43 -0
  1139. package/src/world/continuousDungeon.ts +1250 -0
  1140. package/src/world/dungeonBakeProjection.ts +190 -0
  1141. package/src/world/dungeonMapDensity.ts +342 -0
  1142. package/src/world/dungeonMapProjection.ts +135 -0
  1143. package/src/world/dungeonNav.ts +904 -0
  1144. package/src/world/dungeonNpcSim.ts +147 -0
  1145. package/src/world/dungeonObjectives.ts +234 -0
  1146. package/src/world/dungeonPreset.ts +35 -0
  1147. package/src/world/dungeonRoute.ts +311 -0
  1148. package/src/world/dungeonRouteGeometry.ts +732 -0
  1149. package/src/world/dungeonRouteOrder.ts +1037 -0
  1150. package/src/world/dungeonWingRooms.ts +426 -0
  1151. package/src/world/escort.ts +296 -0
  1152. package/src/world/goPoolRoll.ts +75 -0
  1153. package/src/world/instanceObjects.ts +785 -0
  1154. package/src/world/instances.ts +1572 -0
  1155. package/src/world/meetingStones.ts +56 -0
  1156. package/src/world/navmeshFloors.ts +145 -0
  1157. package/src/world/nearGrid.ts +167 -0
  1158. package/src/world/packBudget.ts +613 -0
  1159. package/src/world/rareRoll.ts +335 -0
  1160. package/src/world/routeAuthoring.ts +132 -0
  1161. package/src/world/routeEdit.ts +209 -0
  1162. package/src/world/world.ts +812 -0
  1163. package/src/world/zoneHazards.ts +47 -0
@@ -0,0 +1,938 @@
1
+ // MATERIALIZE-ONLY invent path for the default rotation packs — product auto-combat never imports
2
+ // it. Regenerate with PH_ROTATION_INVENT=1 and src/dev/materialize-rotation-packs.mjs.
3
+
4
+ import type { DataStore } from '../../data/dataStore.ts';
5
+ import { ALLY_HEAL_THRESHOLD } from '../../engine/combat.ts';
6
+ import { formIdFromSpell } from '../character/forms.ts';
7
+ import {
8
+ isMultiTargetCombatSpell,
9
+ ROTATION_PHASES,
10
+ type Condition,
11
+ type Rotation,
12
+ type RotationRule,
13
+ } from './rotation.ts';
14
+ import { AOE_MODE_CODE } from '../bots/squad.ts';
15
+
16
+ /** Healer-companion triage gate (%): the S0 engine heuristic threshold, now an editable condition. */
17
+ export const COMPANION_TRIAGE_PCT = Math.round(ALLY_HEAL_THRESHOLD * 100);
18
+
19
+ // Off-heal emergency threshold (%): same documented triage gate as ally healers (ALLY_HEAL_THRESHOLD = 0.75
20
+ // → 75%). Hybrid DPS only fire when most-hurt ally is below this.
21
+ export const COMPANION_OFF_HEAL_PCT = COMPANION_TRIAGE_PCT;
22
+
23
+ // Healer-seat mana % under which a hybrid DPS starts healing. A dead seat reads 0, so this one gate
24
+ // is "the healer is dead or dry" — the only state where giving up DPS for a heal is right.
25
+ export const HEALER_DRY_POWER_PCT = 5;
26
+
27
+ // Healer ladder bands for party triage: panic under 25%, an emergency flash below that, an AoE heal
28
+ // only with 2+ hurt, a main heal under triage, and a HoT as a top-up that never outranks a direct.
29
+ export const HEALER_PANIC_PCT = 25;
30
+ export const HEALER_EMERGENCY_PCT = 40;
31
+ export const HEALER_HOT_PCT = 90;
32
+ /** Minimum hurt friendlies before multi-target heals are allowed to fire. */
33
+ export const HEALER_AOE_MIN_TARGETS = 2;
34
+ /** Classic Chain Heal bounce proximity (~12.5 yd); used when radiusYd is absent. */
35
+ export const CHAIN_HEAL_BOUNCE_YD = 12;
36
+
37
+ /** Minimal spell shape for heal classification (DataStore row or session ability). */
38
+ export type HealSpellLike = {
39
+ id: number;
40
+ name?: string;
41
+ castTimeMs?: number;
42
+ aoe?: boolean;
43
+ radiusYd?: number;
44
+ effects?: ReadonlyArray<{ kind: string }>;
45
+ };
46
+
47
+ export type HealTier = 'panic' | 'emergency' | 'aoe' | 'main' | 'hot';
48
+
49
+ /** True when the spell is a party-healing cast (direct, HoT, LoH, Holy Shock). */
50
+ export function spellIsHeal(spell: HealSpellLike | null | undefined): boolean {
51
+ return !!spell?.effects?.some(
52
+ (e) =>
53
+ e.kind === 'heal'
54
+ || e.kind === 'restore_health'
55
+ || e.kind === 'heal_max_health'
56
+ || e.kind === 'holy_shock',
57
+ );
58
+ }
59
+
60
+ // Multi-target / chain party heals — must not fire on a single bruised tank. Name + aoe/radius heuristics
61
+ // (Prayer of Healing often lacks a baked `aoe` flag).
62
+ export function isAoeHealSpell(spell: HealSpellLike | null | undefined): boolean {
63
+ if (!spell || !spellIsHeal(spell)) return false;
64
+ const name = (spell.name ?? '').toLowerCase();
65
+ if (
66
+ name.includes('chain heal')
67
+ || name.includes('prayer of healing')
68
+ || name === 'tranquility'
69
+ || name.includes('circle of healing')
70
+ ) {
71
+ return true;
72
+ }
73
+ // Holy Nova is dual heal+damage with aoe:true — group heal when several are hurt.
74
+ if (spell.aoe === true && /nova|tranquility|prayer|chain|circle/.test(name)) return true;
75
+ if ((spell.radiusYd ?? 0) >= 15 && /prayer|nova|tranquility|circle|chain/.test(name)) return true;
76
+ return false;
77
+ }
78
+
79
+ /** Yards gate for nearbyAlliesBelowHpCount (null = party-wide count only). */
80
+ export function aoeHealYards(spell: HealSpellLike): number | null {
81
+ if (!isAoeHealSpell(spell)) return null;
82
+ const name = (spell.name ?? '').toLowerCase();
83
+ if (name.includes('chain heal')) return CHAIN_HEAL_BOUNCE_YD;
84
+ if (typeof spell.radiusYd === 'number' && spell.radiusYd > 0) return Math.round(spell.radiusYd);
85
+ if (name.includes('prayer of healing')) return 30;
86
+ if (name === 'tranquility') return 20;
87
+ if (name.includes('circle of healing')) return 18;
88
+ return 30;
89
+ }
90
+
91
+ const isHealSpell = (spellId: number, ds: DataStore): boolean =>
92
+ spellIsHeal(ds.spell(spellId) as HealSpellLike | undefined);
93
+
94
+ const isTauntSpell = (spellId: number, ds: DataStore): boolean =>
95
+ !!ds.spell(spellId)?.effects.some((e) => e.kind === 'taunt' || e.kind === 'mod_taunt');
96
+
97
+ /** AoE taunts (Challenging Shout/Roar) — pack peel, not the primary single-target taunt. */
98
+ const isAoeTauntSpell = (spellId: number, ds: DataStore): boolean => {
99
+ if (!isTauntSpell(spellId, ds)) return false;
100
+ const n = (ds.spell(spellId)?.name ?? '').toLowerCase();
101
+ return n.includes('challenging');
102
+ };
103
+
104
+ // Prefer single-target taunts (Taunt / Growl / Mocking Blow) over Challenging* AoE. knownSpellIds order
105
+ // alone would lock onto Challenging Roar before Growl.
106
+ function pickPrimaryTaunt(knownSpellIds: readonly number[], ds: DataStore): number | undefined {
107
+ const taunts = knownSpellIds.filter((id) => isTauntSpell(id, ds));
108
+ if (!taunts.length) return undefined;
109
+ const score = (id: number): number => {
110
+ const n = (ds.spell(id)?.name ?? '').toLowerCase();
111
+ if (n.includes('challenging')) return 0;
112
+ if (n.includes('mocking')) return 1;
113
+ return 2; // Taunt, Growl, Hand of Reckoning-class
114
+ };
115
+ return [...taunts].sort((a, b) => score(b) - score(a) || a - b)[0];
116
+ }
117
+
118
+ /** Self threat buff that must stay in buffs with buffPresent — never combat always thrash. */
119
+ const isRighteousFurySpell = (spellId: number, ds: DataStore): boolean =>
120
+ /^righteous fury$/i.test(ds.spell(spellId)?.name ?? '');
121
+
122
+ /** Salvation lowers the tank's own threat — never keep it on a tank default. */
123
+ const isSalvationBlessing = (spellId: number, ds: DataStore): boolean =>
124
+ /blessing of salvation/i.test(ds.spell(spellId)?.name ?? '');
125
+
126
+ const isThreatSpell = (spellId: number, ds: DataStore): boolean => {
127
+ const sp = ds.spell(spellId);
128
+ if (!sp) return false;
129
+ if (isTauntSpell(spellId, ds)) return false;
130
+ if (isRighteousFurySpell(spellId, ds)) return false; // buff phase, not combat dump
131
+ // Never promote blessings/auras as combat threat dumps (spell_threat rows on Salvation
132
+ // etc. are not "spam this on GCD" tank tools — Salvation is anti-tank).
133
+ const n = (sp.name ?? '').toLowerCase();
134
+ if (n.includes('blessing') || n.includes('aura')) return false;
135
+
136
+ if ((sp.threatMultiplier ?? 1) > 1) return true;
137
+ if (sp.effects.some((e) => e.kind === 'threat' && (e as { amount?: number }).amount! > 0)) return true;
138
+ // Classic tank threat names (bake often omits threatMultiplier on Sunder/Maul/HS).
139
+ // Do NOT key solely on threatFlat — many non-tank spells carry small table rows.
140
+ return n === 'sunder armor' || n === 'holy shield' || n === 'devastate'
141
+ || n === 'revenge' || n === 'shield slam' || n === 'maul';
142
+ };
143
+
144
+ /** Multi-target threat tools tanks should prefer on packs (not ST rage dumps). */
145
+ const isMultiThreatSpell = (spellId: number, ds: DataStore): boolean => {
146
+ const sp = ds.spell(spellId);
147
+ if (!sp) return false;
148
+ const n = (sp.name ?? '').toLowerCase();
149
+ if (
150
+ n === 'swipe'
151
+ || n === 'thunder clap'
152
+ || n === 'consecration'
153
+ || n === 'cleave'
154
+ || n === 'demoralizing roar'
155
+ || n === 'demoralizing shout'
156
+ || n.includes('challenging')
157
+ ) {
158
+ return true;
159
+ }
160
+ return isMultiTargetCombatSpell(sp) && (isThreatSpell(spellId, ds) || isAoeTauntSpell(spellId, ds));
161
+ };
162
+
163
+ /** Tank combat form enter: Defensive Stance / Bear / Dire Bear (not Cat / Battle / Berserker). */
164
+ const isTankFormEnter = (spellId: number, ds: DataStore): boolean => {
165
+ const sp = ds.spell(spellId);
166
+ if (!sp) return false;
167
+ const n = (sp.name ?? '').toLowerCase();
168
+ return n === 'defensive stance' || n === 'bear form' || n === 'dire bear form';
169
+ };
170
+
171
+ /** Cat Form must not gate a tank rotation — demote when reshaping for tank role. */
172
+ const isCatFormSpell = (spellId: number, ds: DataStore): boolean =>
173
+ /^cat form$/i.test(ds.spell(spellId)?.name ?? '');
174
+
175
+ // Consecration is the Prot paladin's PRIMARY threat tool, single target included — it must never sit behind
176
+ // the pack (`enemyCount >= 2`) gate the other multi tools get.
177
+ const isConsecrationSpell = (spellId: number, ds: DataStore): boolean =>
178
+ /^consecration$/i.test(ds.spell(spellId)?.name ?? '');
179
+
180
+ // Bucket a heal for the dungeon-healer priority ladder. Pure HoTs last; flash/instant first under pressure;
181
+ // AOE only multi-hurt; then main.
182
+ export function classifyHealTier(spell: HealSpellLike): HealTier | null {
183
+ if (!spellIsHeal(spell)) return null;
184
+ const effects = spell.effects ?? [];
185
+ if (effects.some((e) => e.kind === 'heal_max_health')) return 'panic';
186
+ // Multi-heals before single-target main so 2+ hurt party uses Chain/PoH first.
187
+ if (isAoeHealSpell(spell)) return 'aoe';
188
+
189
+ const hasDirect = effects.some((e) => e.kind === 'heal' || e.kind === 'holy_shock');
190
+ const hasHot = effects.some((e) => e.kind === 'restore_health');
191
+ // Renew / Rejuvenation — restore_health only (Regrowth also has a direct heal effect).
192
+ if (hasHot && !hasDirect) return 'hot';
193
+
194
+ const name = (spell.name ?? '').toLowerCase();
195
+ if (/\bflash\b/.test(name) || name.includes('lesser healing wave')) return 'emergency';
196
+
197
+ const cast = spell.castTimeMs ?? 0;
198
+ // Instant direct (Holy Shock heal mode, Nature's Swiftness free casts, etc.).
199
+ if (hasDirect && cast === 0) return 'emergency';
200
+
201
+ return 'main';
202
+ }
203
+
204
+ function tierThreshold(tier: HealTier, hasMainDirect: boolean): number {
205
+ switch (tier) {
206
+ case 'panic': return HEALER_PANIC_PCT;
207
+ case 'emergency':
208
+ // Solo flash/Lesser-Healing-Wave kits must still heal through the full triage band.
209
+ return hasMainDirect ? HEALER_EMERGENCY_PCT : COMPANION_TRIAGE_PCT;
210
+ case 'aoe': return COMPANION_TRIAGE_PCT; // unused for allyLowest — multi-count gates apply
211
+ case 'main': return COMPANION_TRIAGE_PCT;
212
+ case 'hot': return HEALER_HOT_PCT;
213
+ }
214
+ }
215
+
216
+ function stripSelfHealthGates(conditions: readonly Condition[]): Condition[] {
217
+ return conditions.filter(
218
+ (c) => !(c.subject === 'self' && (
219
+ c.metric === 'healthPct'
220
+ || c.metric === 'allyLowestHpPct'
221
+ || c.metric === 'alliesBelowHpCount'
222
+ || c.metric === 'nearbyAlliesBelowHpCount'
223
+ )),
224
+ );
225
+ }
226
+
227
+ function withTriageGate(rule: RotationRule, pct: number): RotationRule {
228
+ const kept = stripSelfHealthGates(rule.conditions);
229
+ kept.push({ subject: 'self', metric: 'allyLowestHpPct', op: '<', value: pct });
230
+ return { ...rule, conditions: kept };
231
+ }
232
+
233
+ // Pure HoT keep-up: cast only when the triage heal TARGET lacks the buff. Strips the stale
234
+ // self.buffPresent that checked the caster, which had Renew thrashing every GCD into OOM.
235
+ function withHotMissingGate(rule: RotationRule, spellId: number): RotationRule {
236
+ const kept = rule.conditions.filter(
237
+ (c) => !(
238
+ (c.metric === 'buffPresent' || c.metric === 'allyLowestBuffPresent')
239
+ && (c.ref ?? -1) === spellId
240
+ ),
241
+ );
242
+ kept.push({
243
+ subject: 'self',
244
+ metric: 'allyLowestBuffPresent',
245
+ op: '==',
246
+ value: 0,
247
+ ref: spellId,
248
+ });
249
+ return { ...rule, conditions: kept };
250
+ }
251
+
252
+ /** AOE heal: require N hurt party members (and N nearby when a radius applies). */
253
+ function withAoeHealGates(rule: RotationRule, spell: HealSpellLike): RotationRule {
254
+ const kept = stripSelfHealthGates(rule.conditions)
255
+ .filter((c) => c.metric !== 'enemyCount'); // combat multi-damage gate is wrong for heals
256
+ kept.push({
257
+ subject: 'self',
258
+ metric: 'alliesBelowHpCount',
259
+ op: '>=',
260
+ value: HEALER_AOE_MIN_TARGETS,
261
+ ref: COMPANION_TRIAGE_PCT,
262
+ });
263
+ const yards = aoeHealYards(spell);
264
+ if (yards != null) {
265
+ kept.push({
266
+ subject: 'self',
267
+ metric: 'nearbyAlliesBelowHpCount',
268
+ op: '>=',
269
+ value: HEALER_AOE_MIN_TARGETS,
270
+ ref: yards,
271
+ });
272
+ }
273
+ return { ...rule, conditions: kept };
274
+ }
275
+
276
+ /** Family key so rank spam collapses to one rule (Healing Wave r1…r7 → best rank). */
277
+ function healFamilyKey(spell: HealSpellLike): string {
278
+ const name = (spell.name ?? `id:${spell.id}`).toLowerCase().replace(/\s*\(rank\s*\d+\)\s*$/i, '').trim();
279
+ return name || `id:${spell.id}`;
280
+ }
281
+
282
+ function rankScore(spell: HealSpellLike): number {
283
+ const r = (spell as { rank?: number }).rank;
284
+ if (typeof r === 'number' && r > 0) return r;
285
+ const req = (spell as { reqLevel?: number }).reqLevel;
286
+ if (typeof req === 'number') return req;
287
+ return spell.id;
288
+ }
289
+
290
+ // Keep the highest-rank rule per heal family. First-firing would otherwise lock onto rank 1 forever when
291
+ // every trainer rank is injected into recovery.
292
+ function preferBestRankPerFamily(
293
+ rules: readonly RotationRule[],
294
+ lookup: (spellId: number) => HealSpellLike | null | undefined,
295
+ ): RotationRule[] {
296
+ const best = new Map<string, { rule: RotationRule; score: number }>();
297
+ for (const r of rules) {
298
+ if (r.action.kind !== 'spell') continue;
299
+ const sp = lookup(r.action.spellId);
300
+ if (!sp) continue;
301
+ const key = healFamilyKey(sp);
302
+ const score = rankScore(sp);
303
+ const prev = best.get(key);
304
+ if (!prev || score > prev.score) best.set(key, { rule: r, score });
305
+ }
306
+ // Stable-ish: panic/emergency/main/hot callers concat in tier order; within a
307
+ // tier preserve first-seen family order by walking rules again.
308
+ const out: RotationRule[] = [];
309
+ const used = new Set<string>();
310
+ for (const r of rules) {
311
+ if (r.action.kind !== 'spell') continue;
312
+ const sp = lookup(r.action.spellId);
313
+ if (!sp) continue;
314
+ const key = healFamilyKey(sp);
315
+ if (used.has(key)) continue;
316
+ const pick = best.get(key);
317
+ if (!pick) continue;
318
+ used.add(key);
319
+ out.push(pick.rule);
320
+ }
321
+ return out;
322
+ }
323
+
324
+ // Role-adapt a class rotation into a dungeon heal priority list: gate every heal on party triage,
325
+ // move heals into recovery, order panic to HoT, inject known heals and collapse trainer ranks.
326
+ export function adaptHealerRotation(
327
+ rot: Rotation,
328
+ lookup: (spellId: number) => HealSpellLike | null | undefined,
329
+ extraHealIds?: readonly number[],
330
+ ): Rotation {
331
+ const buckets: Record<HealTier, RotationRule[]> = {
332
+ panic: [],
333
+ emergency: [],
334
+ aoe: [],
335
+ main: [],
336
+ hot: [],
337
+ };
338
+ const seenIds = new Set<number>();
339
+
340
+ const collectHeal = (r: RotationRule): boolean => {
341
+ if (r.action.kind !== 'spell') return false;
342
+ const sp = lookup(r.action.spellId);
343
+ const tier = sp ? classifyHealTier(sp) : null;
344
+ if (!tier || !sp) return false;
345
+ if (seenIds.has(r.action.spellId)) return true; // drop duplicate rule, still "consumed"
346
+ seenIds.add(r.action.spellId);
347
+ // Threshold filled after we know whether a main direct exists.
348
+ buckets[tier].push(r);
349
+ return true;
350
+ };
351
+
352
+ const keepNonHeals = (rules: readonly RotationRule[]): RotationRule[] => {
353
+ const out: RotationRule[] = [];
354
+ for (const r of rules) {
355
+ if (collectHeal(r)) continue;
356
+ out.push(r);
357
+ }
358
+ return out;
359
+ };
360
+
361
+ const buffs = keepNonHeals(rot.phases.buffs);
362
+ const pull = keepNonHeals(rot.phases.pull);
363
+ const combat = keepNonHeals(rot.phases.combat);
364
+ const recoveryNonHeal = keepNonHeals(rot.phases.recovery);
365
+
366
+ if (extraHealIds) {
367
+ // Push every known heal rank; preferBestRankPerFamily keeps the highest per family.
368
+ for (const id of extraHealIds) {
369
+ if (seenIds.has(id)) continue;
370
+ const sp = lookup(id);
371
+ const tier = sp ? classifyHealTier(sp) : null;
372
+ if (!tier || !sp) continue;
373
+ seenIds.add(id);
374
+ buckets[tier].push({
375
+ id: `g-healer-${tier}-${id}`,
376
+ action: { kind: 'spell', spellId: id },
377
+ conditions: [],
378
+ enabled: true,
379
+ });
380
+ }
381
+ }
382
+
383
+ // Collapse any multi-rank leftovers (suggestion + role-bucket both listed a heal).
384
+ for (const tier of ['panic', 'emergency', 'aoe', 'main', 'hot'] as const) {
385
+ buckets[tier] = preferBestRankPerFamily(buckets[tier], lookup);
386
+ }
387
+
388
+ const hasMainDirect = buckets.main.length > 0;
389
+ const recoveryHeals: RotationRule[] = [];
390
+ for (const tier of ['panic', 'emergency', 'aoe', 'main', 'hot'] as const) {
391
+ for (const r of buckets[tier]) {
392
+ if (tier === 'aoe' && r.action.kind === 'spell') {
393
+ const sp = lookup(r.action.spellId);
394
+ recoveryHeals.push(sp ? withAoeHealGates(r, sp) : withTriageGate(r, COMPANION_TRIAGE_PCT));
395
+ } else if (tier === 'hot' && r.action.kind === 'spell') {
396
+ // Triage + "not already on the hurt ally" — pure HoTs must never GCD-spam.
397
+ recoveryHeals.push(
398
+ withHotMissingGate(withTriageGate(r, tierThreshold(tier, hasMainDirect)), r.action.spellId),
399
+ );
400
+ } else {
401
+ recoveryHeals.push(withTriageGate(r, tierThreshold(tier, hasMainDirect)));
402
+ }
403
+ }
404
+ }
405
+
406
+ // Non-heal recovery first (PWS / potions / defensives), then the heal ladder.
407
+ // Potions stay self-gated; shields keep their Weakened Soul / buff conditions.
408
+ return {
409
+ ...rot,
410
+ phases: {
411
+ ...rot.phases,
412
+ buffs,
413
+ pull,
414
+ combat,
415
+ recovery: [...recoveryNonHeal, ...recoveryHeals],
416
+ },
417
+ };
418
+ }
419
+
420
+ // Companion healer adaptation — same ladder as the player dungeon-healer path. See {@link
421
+ // adaptHealerRotation}.
422
+ export function healerize(rot: Rotation, ds: DataStore, knownSpellIds?: readonly number[]): Rotation {
423
+ return adaptHealerRotation(
424
+ rot,
425
+ (id) => ds.spell(id) as HealSpellLike | undefined,
426
+ knownSpellIds,
427
+ );
428
+ }
429
+
430
+ /** Collapse trainer ranks of the same family so rank-1 Sunder/Maul never outranks the best known. */
431
+ function preferBestRankSpellIds(
432
+ ids: readonly number[],
433
+ ds: DataStore,
434
+ ): number[] {
435
+ const best = new Map<string, { id: number; score: number }>();
436
+ const order: string[] = [];
437
+ for (const id of ids) {
438
+ const sp = ds.spell(id) as HealSpellLike | undefined;
439
+ if (!sp) continue;
440
+ const key = healFamilyKey(sp);
441
+ const score = rankScore(sp);
442
+ const prev = best.get(key);
443
+ if (!prev) {
444
+ order.push(key);
445
+ best.set(key, { id, score });
446
+ } else if (score > prev.score) {
447
+ best.set(key, { id, score });
448
+ }
449
+ }
450
+ return order.map((k) => best.get(k)!.id);
451
+ }
452
+
453
+ // Tank adaptation: put the combat form, the taunt and the threat tools at the front of combat so a
454
+ // DPS-shaped default still holds aggro. Idempotent for an already-authored single-target taunt.
455
+ export function tankize(rot: Rotation, knownSpellIds: readonly number[], ds: DataStore): Rotation {
456
+ // --- Buffs: RF keep-up; drop Salvation (anti-tank) ---
457
+ let buffs = rot.phases.buffs;
458
+ let buffsDirty = false;
459
+ if (buffs.some((r) => r.action.kind === 'spell' && isSalvationBlessing(r.action.spellId, ds))) {
460
+ buffs = buffs.filter(
461
+ (r) => !(r.action.kind === 'spell' && isSalvationBlessing(r.action.spellId, ds)),
462
+ );
463
+ buffsDirty = true;
464
+ }
465
+ const hasRfBuff = buffs.some(
466
+ (r) => r.action.kind === 'spell' && isRighteousFurySpell(r.action.spellId, ds),
467
+ );
468
+ const rfId = knownSpellIds.find((id) => isRighteousFurySpell(id, ds));
469
+ if (rfId != null && !hasRfBuff) {
470
+ buffs = [
471
+ {
472
+ id: 'g-rf',
473
+ action: { kind: 'spell', spellId: rfId },
474
+ conditions: [{ subject: 'self', metric: 'buffPresent', op: '==', value: 0, ref: rfId }],
475
+ enabled: true,
476
+ },
477
+ ...buffs,
478
+ ];
479
+ buffsDirty = true;
480
+ } else if (hasRfBuff) {
481
+ // Ensure RF is not unconditional thrash (empty-effect bake → "always" in combat fallout).
482
+ const next: RotationRule[] = buffs.map((r) => {
483
+ if (r.action.kind !== 'spell' || !isRighteousFurySpell(r.action.spellId, ds)) return r;
484
+ if (r.conditions.some((c) => c.metric === 'buffPresent')) return r;
485
+ buffsDirty = true;
486
+ const cond: Condition = {
487
+ subject: 'self',
488
+ metric: 'buffPresent',
489
+ op: '==',
490
+ value: 0,
491
+ ref: r.action.spellId,
492
+ };
493
+ return { ...r, conditions: [cond] };
494
+ });
495
+ if (buffsDirty) buffs = next;
496
+ }
497
+
498
+ // Strip RF / Salvation / Cat Form from combat body — handled above or demoted.
499
+ const needsCombatStrip = rot.phases.combat.some((r) => {
500
+ if (r.action.kind !== 'spell') return false;
501
+ const id = r.action.spellId;
502
+ return isRighteousFurySpell(id, ds) || isSalvationBlessing(id, ds) || isCatFormSpell(id, ds);
503
+ });
504
+ const combatIn = needsCombatStrip
505
+ ? rot.phases.combat.filter((r) => {
506
+ if (r.action.kind !== 'spell') return true;
507
+ const id = r.action.spellId;
508
+ if (isRighteousFurySpell(id, ds)) return false;
509
+ if (isSalvationBlessing(id, ds)) return false;
510
+ if (isCatFormSpell(id, ds)) return false;
511
+ return true;
512
+ })
513
+ : rot.phases.combat;
514
+
515
+ // Existing ST taunt rule (not Challenging*)?
516
+ const hasStTauntRule = ROTATION_PHASES.some((p) =>
517
+ (p === 'combat' ? combatIn : rot.phases[p]).some(
518
+ (r) =>
519
+ r.action.kind === 'spell'
520
+ && isTauntSpell(r.action.spellId, ds)
521
+ && !isAoeTauntSpell(r.action.spellId, ds),
522
+ ),
523
+ );
524
+
525
+ const head: RotationRule[] = [];
526
+
527
+ // 1) Combat form enter (Defensive / Bear / Dire Bear) — prefer Dire Bear over Bear.
528
+ const formIds = preferBestRankSpellIds(
529
+ knownSpellIds.filter((id) => isTankFormEnter(id, ds)),
530
+ ds,
531
+ ).sort((a, b) => {
532
+ const na = (ds.spell(a)?.name ?? '').toLowerCase();
533
+ const nb = (ds.spell(b)?.name ?? '').toLowerCase();
534
+ // Dire Bear before Bear; Defensive Stance alone.
535
+ if (na.includes('dire') && !nb.includes('dire')) return -1;
536
+ if (nb.includes('dire') && !na.includes('dire')) return 1;
537
+ return a - b;
538
+ });
539
+ // One form enter only: Dire Bear wins over Bear; Defensive if warrior.
540
+ let formPick: number | undefined;
541
+ for (const id of formIds) {
542
+ const n = (ds.spell(id)?.name ?? '').toLowerCase();
543
+ if (n === 'dire bear form') {
544
+ formPick = id;
545
+ break;
546
+ }
547
+ }
548
+ if (formPick == null) {
549
+ formPick = formIds.find((id) => /^bear form$/i.test(ds.spell(id)?.name ?? ''))
550
+ ?? formIds.find((id) => /defensive stance/i.test(ds.spell(id)?.name ?? ''))
551
+ ?? formIds[0];
552
+ }
553
+ const hasFormRule = combatIn.some(
554
+ (r) => r.action.kind === 'spell' && isTankFormEnter(r.action.spellId, ds),
555
+ );
556
+ if (formPick != null && !hasFormRule) {
557
+ const sp = ds.spell(formPick);
558
+ const fid = sp ? formIdFromSpell(sp) : 0;
559
+ // formId != fid: enter when not already in this form (Bear family treats Dire as match for == BEAR).
560
+ const formConds: Condition[] = fid > 0
561
+ ? [{ subject: 'self', metric: 'formId', op: '!=', value: fid }]
562
+ : [];
563
+ head.push({
564
+ id: 'g-tank-form',
565
+ action: { kind: 'spell', spellId: formPick },
566
+ conditions: formConds,
567
+ enabled: true,
568
+ });
569
+ }
570
+
571
+ // 2) ST taunt when not tanking the focus.
572
+ if (!hasStTauntRule) {
573
+ const taunt = pickPrimaryTaunt(knownSpellIds, ds);
574
+ if (taunt != null) {
575
+ head.push({
576
+ id: 'g-taunt',
577
+ action: { kind: 'spell', spellId: taunt },
578
+ conditions: [{ subject: 'self', metric: 'targetAttackingMe', op: '==', value: 0 }],
579
+ enabled: true,
580
+ });
581
+ }
582
+ }
583
+
584
+ // 3) Promote ST threat tools + inject missing; collapse ranks.
585
+ const threatIds = preferBestRankSpellIds(
586
+ knownSpellIds.filter((id) => isThreatSpell(id, ds) && !isMultiThreatSpell(id, ds)),
587
+ ds,
588
+ );
589
+ // 4) Multi threat tools for packs.
590
+ const multiIds = preferBestRankSpellIds(
591
+ knownSpellIds.filter(
592
+ (id) => (isMultiThreatSpell(id, ds) && !isTauntSpell(id, ds)) || isAoeTauntSpell(id, ds),
593
+ ),
594
+ ds,
595
+ );
596
+
597
+ const promoted: RotationRule[] = [];
598
+ const rest: RotationRule[] = [];
599
+ const seenThreat = new Set<number>();
600
+ const seenMulti = new Set<number>();
601
+ const threatSet = new Set(threatIds);
602
+ const multiSet = new Set(multiIds);
603
+
604
+ // Keep form enter + ST taunt rules already in combat near the head (order: form → taunt → threat).
605
+ const existingForm: RotationRule[] = [];
606
+ const existingStTaunt: RotationRule[] = [];
607
+ for (const r of combatIn) {
608
+ if (r.action.kind === 'spell' && isTankFormEnter(r.action.spellId, ds)) {
609
+ existingForm.push(r);
610
+ continue;
611
+ }
612
+ if (
613
+ r.action.kind === 'spell'
614
+ && isTauntSpell(r.action.spellId, ds)
615
+ && !isAoeTauntSpell(r.action.spellId, ds)
616
+ ) {
617
+ // Ensure targetAttackingMe gate on authored ST taunts.
618
+ const hasTauntGate = r.conditions.some((c) => c.metric === 'targetAttackingMe');
619
+ existingStTaunt.push(
620
+ hasTauntGate
621
+ ? r
622
+ : {
623
+ ...r,
624
+ conditions: [
625
+ ...r.conditions,
626
+ { subject: 'self', metric: 'targetAttackingMe', op: '==', value: 0 },
627
+ ],
628
+ },
629
+ );
630
+ continue;
631
+ }
632
+ if (r.action.kind === 'spell' && multiSet.has(r.action.spellId)) {
633
+ if (!seenMulti.has(r.action.spellId)) {
634
+ seenMulti.add(r.action.spellId);
635
+ if (isConsecrationSpell(r.action.spellId, ds)) {
636
+ // Tank Consecration fires on single targets too — strip any authored pack gate.
637
+ promoted.push({
638
+ ...r,
639
+ conditions: r.conditions.filter((c) => c.metric !== 'enemyCount'),
640
+ });
641
+ } else {
642
+ // Ensure pack gate on multi tools.
643
+ const hasEnemyGate = r.conditions.some((c) => c.metric === 'enemyCount');
644
+ promoted.push(
645
+ hasEnemyGate
646
+ ? r
647
+ : {
648
+ ...r,
649
+ conditions: [
650
+ ...r.conditions,
651
+ { subject: 'self', metric: 'enemyCount', op: '>=', value: 2 },
652
+ ],
653
+ },
654
+ );
655
+ }
656
+ }
657
+ continue;
658
+ }
659
+ if (r.action.kind === 'spell' && threatSet.has(r.action.spellId)) {
660
+ if (!seenThreat.has(r.action.spellId)) {
661
+ seenThreat.add(r.action.spellId);
662
+ promoted.push(r);
663
+ }
664
+ continue;
665
+ }
666
+ rest.push(r);
667
+ }
668
+
669
+ // Multi tools BEFORE ST threat (Sunder): packs get Cleave/TC/Demo first.
670
+ for (const id of multiIds) {
671
+ if (seenMulti.has(id)) continue;
672
+ // AoE taunt: only when not on me AND pack (or always pack peel).
673
+ const conds: Condition[] = isAoeTauntSpell(id, ds)
674
+ ? [
675
+ { subject: 'self', metric: 'targetAttackingMe', op: '==', value: 0 },
676
+ { subject: 'self', metric: 'enemyCount', op: '>=', value: 2 },
677
+ ]
678
+ : isConsecrationSpell(id, ds)
679
+ ? [] // Prot ST threat tool — no pack gate
680
+ : [{ subject: 'self', metric: 'enemyCount', op: '>=', value: 2 }];
681
+ promoted.push({
682
+ id: `g-multi-${id}`,
683
+ action: { kind: 'spell', spellId: id },
684
+ conditions: conds,
685
+ enabled: true,
686
+ });
687
+ seenMulti.add(id);
688
+ }
689
+ for (const id of threatIds) {
690
+ if (seenThreat.has(id)) continue;
691
+ // No enemyCount gate on Sunder. The x5 stack cap already stops the spam and the pack tools sit
692
+ // above it, so a gate only ever meant "the tank stops threat once a second mob exists".
693
+ const conds: Condition[] = [];
694
+ promoted.push({
695
+ id: `g-threat-${id}`,
696
+ action: { kind: 'spell', spellId: id },
697
+ conditions: conds,
698
+ enabled: true,
699
+ });
700
+ seenThreat.add(id);
701
+ }
702
+
703
+ // Order: form enter → ST taunt → threat/multi → rest
704
+ const formHead = head.filter(
705
+ (r) => r.id === 'g-tank-form'
706
+ || (r.action.kind === 'spell' && isTankFormEnter(r.action.spellId, ds)),
707
+ );
708
+ const tauntHead = head.filter(
709
+ (r) => r.id === 'g-taunt'
710
+ || (r.action.kind === 'spell'
711
+ && isTauntSpell(r.action.spellId, ds)
712
+ && !isAoeTauntSpell(r.action.spellId, ds)),
713
+ );
714
+ const otherHead = head.filter((r) => !formHead.includes(r) && !tauntHead.includes(r));
715
+
716
+ const newCombat = [
717
+ ...formHead,
718
+ ...existingForm,
719
+ ...tauntHead,
720
+ ...existingStTaunt,
721
+ ...otherHead,
722
+ ...promoted,
723
+ ...rest,
724
+ ];
725
+
726
+ // Drop rules whose formId gate can never fire under the enforced tank stance — the Berserker and
727
+ // Cat tails under a Defensive or Bear anchor. A rotation that itself enters that form keeps them.
728
+ const enteredCombatForms = new Set<number>();
729
+ for (const r of newCombat) {
730
+ if (r.action.kind !== 'spell') continue;
731
+ const sp = ds.spell(r.action.spellId);
732
+ const fid = sp ? formIdFromSpell(sp) : 0;
733
+ if (fid > 0) enteredCombatForms.add(fid);
734
+ }
735
+ const formGateSatisfiable = (expected: number): boolean => {
736
+ for (const f of enteredCombatForms) {
737
+ if (expected === 5 ? f === 5 || f === 8 : f === expected) return true;
738
+ }
739
+ return false;
740
+ };
741
+ const combatFinal = enteredCombatForms.size === 0
742
+ ? newCombat
743
+ : newCombat.filter((r) => {
744
+ if (r.action.kind !== 'spell') return true;
745
+ // Form ENTER rules gate with `!=` — only ability `==` requirements are strippable.
746
+ const sp = ds.spell(r.action.spellId);
747
+ if (sp && formIdFromSpell(sp) > 0) return true;
748
+ const gate = r.conditions.find((c) => c.metric === 'formId' && c.op === '==');
749
+ if (!gate) return true;
750
+ return formGateSatisfiable(gate.value);
751
+ });
752
+
753
+ // Also strip RF thrash / Cat from pull; ensure Defensive/Bear on pull if missing.
754
+ let pull = rot.phases.pull;
755
+ let pullDirty = false;
756
+ if (pull.some((r) => r.action.kind === 'spell' && (isCatFormSpell(r.action.spellId, ds) || isRighteousFurySpell(r.action.spellId, ds)))) {
757
+ pull = pull.filter((r) => {
758
+ if (r.action.kind !== 'spell') return true;
759
+ if (isCatFormSpell(r.action.spellId, ds)) return false;
760
+ if (isRighteousFurySpell(r.action.spellId, ds)) return false;
761
+ return true;
762
+ });
763
+ pullDirty = true;
764
+ }
765
+ if (formPick != null && !pull.some((r) => r.action.kind === 'spell' && isTankFormEnter(r.action.spellId, ds))) {
766
+ const sp = ds.spell(formPick);
767
+ const fid = sp ? formIdFromSpell(sp) : 0;
768
+ pull = [
769
+ {
770
+ id: 'g-tank-form-pull',
771
+ action: { kind: 'spell', spellId: formPick },
772
+ conditions: fid > 0
773
+ ? [{ subject: 'self', metric: 'formId', op: '!=', value: fid }]
774
+ : [],
775
+ enabled: true,
776
+ },
777
+ ...pull,
778
+ ];
779
+ pullDirty = true;
780
+ }
781
+
782
+ // Recovery: in-form survival first, form-dropping heals last. A bear opening with Healing Touch
783
+ // leaves form mid-tank, so push those to a 25% last resort and prepend the known survival CDs.
784
+ let recovery = rot.phases.recovery;
785
+ let recoveryDirty = false;
786
+ const anchorSpell = formPick != null ? ds.spell(formPick) : undefined;
787
+ const anchorFormId = anchorSpell ? formIdFromSpell(anchorSpell) : 0;
788
+ if (anchorFormId === 5 || anchorFormId === 8) {
789
+ // A rule that LEAVES the form: a caster-form-only heal (stances 0), or the authored
790
+ // "shift out" ladder — a form spell recast while inside it (recast toggles off).
791
+ const leavesForm = (r: RotationRule): boolean => {
792
+ if (r.action.kind !== 'spell') return false;
793
+ const sp = ds.spell(r.action.spellId);
794
+ if (!sp) return false;
795
+ if (spellIsHeal(sp as HealSpellLike) && (sp.stances ?? 0) === 0) return true;
796
+ const fid = formIdFromSpell(sp);
797
+ return fid > 0 && r.conditions.some(
798
+ (c) => c.metric === 'formId' && c.op === '==' && c.value === fid,
799
+ );
800
+ };
801
+ const next = recovery.map((r) => {
802
+ if (!leavesForm(r)) return r;
803
+ const kept = r.conditions.filter(
804
+ (c) => !(c.metric === 'healthPct' && c.subject !== 'target'),
805
+ );
806
+ recoveryDirty = true;
807
+ return {
808
+ ...r,
809
+ conditions: [
810
+ ...kept,
811
+ { subject: 'self', metric: 'healthPct', op: '<', value: 25 } as Condition,
812
+ ],
813
+ };
814
+ });
815
+ if (recoveryDirty) recovery = next;
816
+ }
817
+ const survivalWants: Array<{ match: RegExp; conds: Condition[] }> = [
818
+ { match: /^last stand$/i, conds: [{ subject: 'self', metric: 'healthPct', op: '<', value: 30 }] },
819
+ { match: /^shield wall$/i, conds: [{ subject: 'self', metric: 'healthPct', op: '<', value: 20 }] },
820
+ // Frenzied Regeneration converts rage→health and is bear-only (spell 22842 stances).
821
+ {
822
+ match: /^frenzied regeneration$/i,
823
+ conds: [
824
+ { subject: 'self', metric: 'healthPct', op: '<', value: 50 },
825
+ { subject: 'self', metric: 'formId', op: '==', value: 5 },
826
+ ],
827
+ },
828
+ { match: /^barkskin$/i, conds: [{ subject: 'self', metric: 'healthPct', op: '<', value: 50 }] },
829
+ ];
830
+ const survivalRules: RotationRule[] = [];
831
+ for (const w of survivalWants) {
832
+ const id = preferBestRankSpellIds(
833
+ knownSpellIds.filter((sid) => w.match.test(ds.spell(sid)?.name ?? '')),
834
+ ds,
835
+ )[0];
836
+ if (id == null) continue;
837
+ const present = ROTATION_PHASES.some((p) =>
838
+ (p === 'recovery' ? recovery : rot.phases[p]).some(
839
+ (r) => r.action.kind === 'spell' && r.action.spellId === id,
840
+ ))
841
+ || combatFinal.some((r) => r.action.kind === 'spell' && r.action.spellId === id);
842
+ if (present) continue;
843
+ survivalRules.push({
844
+ id: `g-survival-${id}`,
845
+ action: { kind: 'spell', spellId: id },
846
+ conditions: w.conds,
847
+ enabled: true,
848
+ });
849
+ }
850
+ if (survivalRules.length) {
851
+ recovery = [...survivalRules, ...recovery];
852
+ recoveryDirty = true;
853
+ }
854
+
855
+ const combatDirty =
856
+ head.length > 0
857
+ || promoted.length > 0
858
+ || existingForm.length > 0
859
+ || existingStTaunt.length > 0
860
+ || needsCombatStrip
861
+ || combatFinal.length !== rot.phases.combat.length
862
+ || combatFinal.some((r, i) => r !== rot.phases.combat[i]);
863
+
864
+ if (!buffsDirty && !pullDirty && !combatDirty && !recoveryDirty) return rot;
865
+
866
+ return {
867
+ ...rot,
868
+ phases: {
869
+ ...rot.phases,
870
+ buffs,
871
+ pull,
872
+ combat: combatDirty ? combatFinal : rot.phases.combat,
873
+ recovery: recoveryDirty ? recovery : rot.phases.recovery,
874
+ },
875
+ };
876
+ }
877
+
878
+ // Gate multi-target spells on aoeMode != single so the companion knob and the editor can suppress
879
+ // cleave. Existing gates are left alone, nothing is reordered, and the same reference comes back.
880
+ export function aoeize(rot: Rotation, ds: DataStore): Rotation {
881
+ const suppressSingle: Condition = {
882
+ subject: 'self',
883
+ metric: 'aoeMode',
884
+ op: '!=',
885
+ value: AOE_MODE_CODE.single,
886
+ };
887
+ let changed = false;
888
+ const adapt = (rules: RotationRule[]): RotationRule[] =>
889
+ rules.map((r) => {
890
+ if (r.action.kind !== 'spell') return r;
891
+ if (!isMultiTargetCombatSpell(ds.spell(r.action.spellId))) return r;
892
+ if (r.conditions.some((c) => c.metric === 'aoeMode')) return r;
893
+ changed = true;
894
+ return { ...r, conditions: [...r.conditions, { ...suppressSingle }] };
895
+ });
896
+ const phases = {
897
+ prebuff: adapt(rot.phases.prebuff ?? []),
898
+ buffs: adapt(rot.phases.buffs),
899
+ pull: adapt(rot.phases.pull),
900
+ combat: adapt(rot.phases.combat),
901
+ recovery: adapt(rot.phases.recovery),
902
+ };
903
+ return changed ? { ...rot, phases } : rot;
904
+ }
905
+
906
+ // For a non-healer companion that knows a heal, prepend an emergency off-heal gated on the knob,
907
+ // a dead-or-dry party healer and a low ally. A no-op when such a row already exists.
908
+ export function offHealize(rot: Rotation, knownSpellIds: readonly number[], ds: DataStore): Rotation {
909
+ const hasOffHealGate = ROTATION_PHASES.some((p) =>
910
+ rot.phases[p].some(
911
+ (r) =>
912
+ r.action.kind === 'spell'
913
+ && isHealSpell(r.action.spellId, ds)
914
+ && r.conditions.some((c) => c.metric === 'offHealAllowed'),
915
+ ),
916
+ );
917
+ if (hasOffHealGate) return rot;
918
+ // Prefer a direct heal over a pure HoT for the emergency off-heal button.
919
+ const healId =
920
+ knownSpellIds.find((id) => {
921
+ const tier = classifyHealTier(ds.spell(id) as HealSpellLike);
922
+ return tier === 'emergency' || tier === 'main' || tier === 'panic';
923
+ })
924
+ ?? knownSpellIds.find((id) => isHealSpell(id, ds));
925
+ if (healId == null) return rot;
926
+ const rule: RotationRule = {
927
+ id: 'g-offheal',
928
+ action: { kind: 'spell', spellId: healId },
929
+ conditions: [
930
+ { subject: 'self', metric: 'offHealAllowed', op: '==', value: 1 },
931
+ { subject: 'self', metric: 'healerPowerPct', op: '<', value: HEALER_DRY_POWER_PCT },
932
+ { subject: 'self', metric: 'allyLowestHpPct', op: '<', value: COMPANION_OFF_HEAL_PCT },
933
+ ],
934
+ enabled: true,
935
+ };
936
+ return { ...rot, phases: { ...rot.phases, combat: [rule, ...rot.phases.combat] } };
937
+ }
938
+