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,1115 @@
1
+ // PURE: no DOM/RNG/Date. Runtime rotation condition evaluator.
2
+ import type {
3
+ AllyFilter,
4
+ CondSubject,
5
+ Condition,
6
+ CondOp,
7
+ HaltDpsSchool,
8
+ Rotation,
9
+ RotationRule,
10
+ RuleAction,
11
+ RuleAfter,
12
+ RuleBefore,
13
+ RuleSpellTarget,
14
+ RotationSnapshot,
15
+ SubjectState,
16
+ } from './types.ts';
17
+ import { allyFilterIsAny, pullWindowMs, timeToDieMs } from './types.ts';
18
+ import { encounterTacticRows, isRaidRuleId } from '../raid/raidEncounterTactics.ts';
19
+ import { HAPPINESS_MAX } from '../pet/pet.ts';
20
+ import { totemSlotFromIndex } from '../spells/totem.ts';
21
+ import { IN_MELEE_YD } from './stand.ts';
22
+ import { sanitizeRuleAfter } from './ruleAfterChain.ts';
23
+ import { raidRuleWithYd } from './raidRowTuning.ts';
24
+ import { killOrderEntryMatches } from './killOrder.ts';
25
+
26
+ // `c` is passed because subject 'ally' carries its own filter: which friendly the row means.
27
+ function subjectOf(snap: RotationSnapshot, s: CondSubject, c?: Condition): SubjectState | null {
28
+ if (s === 'ally') return snap.allySubject?.(c?.ally) ?? null;
29
+ return s === 'self' ? snap.self : s === 'target' ? snap.target : snap.pet;
30
+ }
31
+
32
+ function idsHave(snap: RotationSnapshot, ids: ReadonlySet<number> | undefined, ref: number): boolean {
33
+ if (!ids) return false;
34
+ if (ids.has(ref)) return true;
35
+ return snap.auraCovered?.(ids, ref) === true;
36
+ }
37
+
38
+ /** Every metric that reads the condition's SUBJECT. Disjoint from every `c.metric ===` test in
39
+ * metricValue below — checked, and the fast path at the top of it depends on that. */
40
+ const SUBJECT_METRICS: ReadonlySet<string> = new Set([
41
+ 'healthPct', 'powerPct', 'manaPct', 'comboPoints', 'drLevel', 'formId', 'level',
42
+ 'buffPresent', 'debuffPresent',
43
+ 'castingSchool', 'castingIsHeal', 'buffStacks', 'debuffStacks', 'petHappiness', 'classId',
44
+ 'buffRemainingMs', 'debuffRemainingMs', 'canAfford',
45
+ ]);
46
+
47
+ // One-element scratch for the rank walk below. auraCovered takes a SET, and allocating one per
48
+ // candidate rank turned a cold miss into garbage on every pick. Read and discarded inside one call.
49
+ const ONE_ID = new Set<number>([0]);
50
+
51
+ // Ms left on the aura `ref` names, through the SAME rank coverage idsHave uses: a row written
52
+ // against Rejuvenation r1 must read the r4 chip that is actually up, or it reads permanent and the
53
+ // refresh never fires. Exact hit first, so the walk only pays on a rank mismatch. A missing entry
54
+ // is permanent (Infinity), which is what an aura with no expiry means.
55
+ function auraRemainingMs(
56
+ snap: RotationSnapshot,
57
+ remaining: ReadonlyMap<number, number> | undefined,
58
+ ids: ReadonlySet<number>,
59
+ ref: number,
60
+ ): number {
61
+ if (!remaining) return Number.POSITIVE_INFINITY;
62
+ const exact = remaining.get(ref);
63
+ if (exact != null) return exact;
64
+ if (!snap.auraCovered) return Number.POSITIVE_INFINITY;
65
+ for (const id of ids) {
66
+ if (id === ref) continue;
67
+ const ms = remaining.get(id);
68
+ if (ms == null) continue;
69
+ ONE_ID.clear();
70
+ ONE_ID.add(id);
71
+ if (snap.auraCovered(ONE_ID, ref)) return ms;
72
+ }
73
+ return Number.POSITIVE_INFINITY;
74
+ }
75
+ function subjectMetricValue(snap: RotationSnapshot, c: Condition): number | null {
76
+ const subj = subjectOf(snap, c.subject, c);
77
+ if (!subj || !subj.alive) return null; // any other metric on an absent/dead subject can't apply
78
+ switch (c.metric) {
79
+ case 'healthPct': return subj.healthPct;
80
+ case 'powerPct': return subj.powerPct;
81
+ // The mana bar, never the form bar. A druid in Cat Form reads energy on powerPct, so a mana
82
+ // potion row gated there drank on an empty energy bar and never on empty mana.
83
+ case 'manaPct': return subj.manaPct ?? null;
84
+ case 'comboPoints': return subj.comboPoints ?? 0;
85
+ // `ref` indexes DIMINISHING_GROUP_ORDER. A group nobody has touched, an expired window and
86
+ // a missing snapshot all read 0 — the same answer, and the one that means "full duration".
87
+ case 'drLevel': return subj.drSteps?.[(c.ref ?? -1) | 0] ?? 0;
88
+ case 'formId': return subj.formId ?? 0;
89
+ // A creature has no class, so it reads 0 and every `classId ==` row is simply false in PvE.
90
+ case 'classId': return subj.classId ?? 0;
91
+ case 'level': return subj.level;
92
+ case 'buffPresent': return idsHave(snap, subj.buffs, c.ref ?? -1) ? 1 : 0;
93
+ // A keep-up whose aura CANNOT land on this target reads as already present, or the gate is
94
+ // unsatisfiable and re-fires forever — Hamstring 15 times on a snare-immune boss.
95
+ case 'debuffPresent':
96
+ if (idsHave(snap, subj.debuffs, c.ref ?? -1)) return 1;
97
+ return subj.immuneAuraSpellIds?.has(c.ref ?? -1) ? 1 : 0;
98
+ // School / heal-ness of the in-flight cast: no cast → null → condition false, so an
99
+ // "interrupt fire casts" rule simply never fires while the subject isn't casting.
100
+ case 'castingSchool': return subj.casting ? subj.casting.school : null;
101
+ case 'castingIsHeal': return subj.casting ? (subj.casting.isHeal ? 1 : 0) : null;
102
+ case 'castingSpellId': return subj.casting ? (subj.casting.spellId ?? 0) : null;
103
+ // Stacks: 0 when the aura is absent, 1 when present unstacked — so both
104
+ // "reapply below 5 stacks" (< 5) and "spend at 5 stacks" (>= 5) read naturally.
105
+ case 'buffStacks': {
106
+ const ref = c.ref ?? -1;
107
+ if (subj.buffs.has(ref)) return subj.buffStacks?.get(ref) ?? 1;
108
+ return idsHave(snap, subj.buffs, ref) ? 1 : 0;
109
+ }
110
+ case 'debuffStacks': {
111
+ const ref = c.ref ?? -1;
112
+ if (subj.debuffs.has(ref)) return subj.debuffStacks?.get(ref) ?? 1;
113
+ return idsHave(snap, subj.debuffs, ref) ? 1 : 0;
114
+ }
115
+ // Absent reads 0 and permanent reads Infinity, so `< 3000` is "gone or nearly gone" in one row
116
+ // and `>= 1` is plain presence. An immune target reads Infinity for the same reason
117
+ // debuffPresent reads 1: the keep-up is unsatisfiable and must not re-fire.
118
+ case 'buffRemainingMs': {
119
+ const ref = c.ref ?? -1;
120
+ if (!idsHave(snap, subj.buffs, ref)) return 0;
121
+ return auraRemainingMs(snap, subj.buffRemainingMs, subj.buffs, ref);
122
+ }
123
+ case 'debuffRemainingMs': {
124
+ const ref = c.ref ?? -1;
125
+ if (subj.immuneAuraSpellIds?.has(ref)) return Number.POSITIVE_INFINITY;
126
+ if (!idsHave(snap, subj.debuffs, ref)) return 0;
127
+ return auraRemainingMs(snap, subj.debuffRemainingMs, subj.debuffs, ref);
128
+ }
129
+ // Absent set reads null (condition false), not 0: a snapshot that never priced the kit must not
130
+ // read as "cannot afford anything" and fire every out-of-mana row at once.
131
+ case 'canAfford':
132
+ return subj.affordableSpellIds ? (subj.affordableSpellIds.has(c.ref ?? -1) ? 1 : 0) : null;
133
+ // Editor speaks in percent ("pet happiness %"); happiness stores 0..1000 → normalize.
134
+ case 'petHappiness': return subj.happiness != null ? (subj.happiness / HAPPINESS_MAX) * 100 : 0;
135
+ default: return null;
136
+ }
137
+ }
138
+
139
+ /** Resolve a condition's left-hand metric to a number, or null when it can't apply. */
140
+ function metricValue(snap: RotationSnapshot, c: Condition): number | null {
141
+ // The hot metrics sat behind ~37 sequential compares: healthPct alone is 12,957 of 67,594 calls
142
+ // in a 300-tick 40-man fight. The two sets are disjoint, so answering these first is safe.
143
+ if (SUBJECT_METRICS.has(c.metric)) return subjectMetricValue(snap, c);
144
+ // These three build their own subject and sat at positions ~212-221 of ~230 tests. Each metric
145
+ // name appears exactly once in this function, so position cannot change an answer.
146
+ if (c.metric === 'alive') { const s = subjectOf(snap, c.subject, c); return s && s.alive ? 1 : 0; }
147
+ if (c.metric === 'castingPresent') {
148
+ const s = subjectOf(snap, c.subject, c);
149
+ if (s?.alive && s.casting) return 1;
150
+ // Kick/CS: the bar may be on an add, not the kill focus.
151
+ if (c.subject === 'target' && snap.kickableCastPresent) return 1;
152
+ return 0;
153
+ }
154
+ if (c.metric === 'dodgedRecently') { const s = subjectOf(snap, c.subject, c); return s?.alive && s.dodgedRecently ? 1 : 0; }
155
+ // Not a SUBJECT_METRIC: it reads the subject's health AND the fight clock, and null (unknown)
156
+ // has to reach the comparison so the row is simply off rather than true at 0.
157
+ if (c.metric === 'timeToDieMs') {
158
+ const s = subjectOf(snap, c.subject, c);
159
+ if (!s?.alive) return null;
160
+ return timeToDieMs(s.healthPct, snap.combatTimeMs);
161
+ }
162
+ // Subject-independent metrics first.
163
+ if (c.metric === 'combatTimeMs') return snap.combatTimeMs;
164
+ if (c.metric === 'inCombat') return snap.inCombat ? 1 : 0;
165
+ if (c.metric === 'enemyCount') return snap.enemyCount;
166
+ // Group metrics (S1): the healer triage gate degrades to self health when the snapshot
167
+ // has no group data (solo autopilot / OOC), so a companion rotation still self-heals solo.
168
+ if (c.metric === 'allyLowestHpPct') return snap.allyLowestHpPct ?? snap.self.healthPct;
169
+ // HoT keep-up on the triage target. firstFiring stamps `{ lowestHp }` so the aura lands there.
170
+ if (c.metric === 'allyLowestBuffPresent') {
171
+ const ref = c.ref ?? -1;
172
+ if (snap.allyTriageBuffs) return idsHave(snap, snap.allyTriageBuffs, ref) ? 1 : 0;
173
+ return idsHave(snap, snap.self.buffs, ref) ? 1 : 0;
174
+ }
175
+ // Count of living friendlies under ref% HP (default 75). AOE heal gates.
176
+ if (c.metric === 'alliesBelowHpCount') {
177
+ const thr = c.ref != null && c.ref > 0 ? c.ref : 75;
178
+ const pcts = snap.allyHpPcts ?? [snap.self.healthPct];
179
+ let n = 0;
180
+ for (const p of pcts) if (p < thr) n++;
181
+ return n;
182
+ }
183
+ // Hurt friendlies within ref yards (default 30). No geometry → all allies count as in range.
184
+ if (c.metric === 'nearbyAlliesBelowHpCount') {
185
+ const yards = c.ref != null && c.ref > 0 ? c.ref : 30;
186
+ const hpThr = 75;
187
+ const list = snap.allyDistHp
188
+ ?? (snap.allyHpPcts ?? [snap.self.healthPct]).map((hpPct) => ({ hpPct, distYd: null as number | null }));
189
+ let n = 0;
190
+ for (const a of list) {
191
+ if (a.hpPct >= hpThr) continue;
192
+ if (a.distYd != null && a.distYd > yards) continue;
193
+ n++;
194
+ }
195
+ return n;
196
+ }
197
+ // S4C companion knobs: absent → auto (0) / off-heal disallowed (0).
198
+ if (c.metric === 'aoeMode') return snap.aoeMode ?? 0;
199
+ if (c.metric === 'offHealAllowed') return snap.offHealAllowed ?? 0;
200
+ // E6 raid plan — acting unit only (subject ignored). Absent → 0 / false.
201
+ if (c.metric === 'isMainTank') return snap.isMainTank ? 1 : 0;
202
+ if (c.metric === 'isOffTank') return snap.isOffTank ? 1 : 0;
203
+ if (c.metric === 'myJob') return snap.myJob ?? 0;
204
+ if (c.metric === 'myCamp') return snap.myCamp ?? 0;
205
+ if (c.metric === 'assignedHealSubgroup') {
206
+ return snap.assignedHealSubgroup == null ? null : snap.assignedHealSubgroup;
207
+ }
208
+ // Aggro booleans: absent (OOC / legacy snapshot) → null → the condition is false.
209
+ if (c.metric === 'hasAggro') return snap.hasAggro == null ? null : (snap.hasAggro ? 1 : 0);
210
+ // Party peel: a hostile's GetVictim is an ally other than self. Cite: PlayerbotRogueAI.cpp:208-219.
211
+ if (c.metric === 'allyHasAggro') return snap.allyHasAggro == null ? null : (snap.allyHasAggro ? 1 : 0);
212
+ if (c.metric === 'targetAttackingMe') return snap.targetAttackingMe == null ? null : (snap.targetAttackingMe ? 1 : 0);
213
+ if (c.metric === 'aggroHolderDebuffPresent') {
214
+ return snap.aggroHolderDebuffs?.has(c.ref ?? -1) ? 1 : 0;
215
+ }
216
+ // Unstacked-but-present reads 1, the same shape `debuffStacks` uses on a subject.
217
+ if (c.metric === 'aggroHolderDebuffStacks') {
218
+ const ref = c.ref ?? -1;
219
+ if (!snap.aggroHolderDebuffs?.has(ref)) return 0;
220
+ return snap.aggroHolderDebuffStacks?.get(ref) ?? 1;
221
+ }
222
+ // Raid-tab "anyone has it": any living friendly holds `ref`. Absent getter → 0.
223
+ if (c.metric === 'allyAuraPresent') {
224
+ return (snap.allyAuraStacks?.(c.ref ?? -1) ?? 0) > 0 ? 1 : 0;
225
+ }
226
+ if (c.metric === 'allyAuraStacks') {
227
+ return snap.allyAuraStacks?.(c.ref ?? -1) ?? 0;
228
+ }
229
+ // Charm/possess group view: absent → 0 (no charmed hostile).
230
+ if (c.metric === 'charmedHostileCount') return snap.charmedHostileCount ?? 0;
231
+ // Ground-pool edge yards. Absent / no pools → null → condition false.
232
+ if (c.metric === 'hazardProximityYd') {
233
+ return snap.hazardProximityYd == null ? null : snap.hazardProximityYd;
234
+ }
235
+ if (c.metric === 'targetDistanceYd') {
236
+ return snap.targetDistanceYd == null ? null : snap.targetDistanceYd;
237
+ }
238
+ if (c.metric === 'creatureDistanceYd') {
239
+ const d = snap.creatureDistanceYd?.(c.ref ?? -1);
240
+ return d == null ? null : d;
241
+ }
242
+ // Nearest friendly lacking `ref`, in yards. Absent / nobody placed -> null -> false.
243
+ if (c.metric === 'allyWithoutAuraNearYd') {
244
+ const d = snap.allyWithoutAuraNearYd?.(c.ref ?? -1);
245
+ return d == null ? null : d;
246
+ }
247
+ // Standing in the target's frontal arc. Absent (no geometry / no facing) → null → false,
248
+ // so a "step out of the breath" row cannot fire on a session that cannot answer it.
249
+ if (c.metric === 'inTargetCone') {
250
+ return snap.inTargetCone == null ? null : (snap.inTargetCone ? 1 : 0);
251
+ }
252
+ // Group composition: melee friendlies for Windfury-class buffs. Absent → 0.
253
+ if (c.metric === 'meleeAllyCount') return snap.meleeAllyCount ?? 0;
254
+ // Party rez gate: dead friendlies. Absent / solo → 0 (never thrash Redemption OOC).
255
+ if (c.metric === 'deadAllyCount') return snap.deadAllyCount ?? 0;
256
+ if (c.metric === 'itemCount') return snap.itemCounts.get(c.ref ?? -1) ?? 0;
257
+ if (c.metric === 'itemEquipped') return snap.equippedItemEntries?.has(c.ref ?? -1) ? 1 : 0;
258
+ // ---- BG / PvP vocabulary (W10, runsheet §4) ------------------------------------------
259
+ // Every one is defined when the BG view is absent and reads 0 there, so no PvE decision moves.
260
+ if (c.metric === 'hasFlag') {
261
+ const s = subjectOf(snap, c.subject, c);
262
+ return s?.alive && s.carryingFlag ? 1 : 0;
263
+ }
264
+ if (c.metric === 'isFlagCarrier') {
265
+ const s = subjectOf(snap, c.subject, c);
266
+ return s?.alive && s.carryingFlag ? 1 : 0;
267
+ }
268
+ if (c.metric === 'allyHasFlag') {
269
+ if (snap.self.carryingFlag) return 1;
270
+ return snap.bg?.allyUnits.some((a) => a.carryingFlag) ? 1 : 0;
271
+ }
272
+ if (c.metric === 'enemyHasFlag') {
273
+ return snap.bg?.enemyUnits.some((e) => e.carryingFlag) ? 1 : 0;
274
+ }
275
+ if (c.metric === 'enemyCastingOnAlly') return snap.enemyCastingOnAlly ? 1 : 0;
276
+ if (c.metric === 'enemyCastingOnSelf') return snap.enemyCastingOnSelf ? 1 : 0;
277
+ if (c.metric === 'isHealer') {
278
+ const s = subjectOf(snap, c.subject, c);
279
+ return s?.groupRole === 'healer' ? 1 : 0;
280
+ }
281
+ if (c.metric === 'isCaster') {
282
+ const s = subjectOf(snap, c.subject, c);
283
+ return s?.isCaster ? 1 : 0;
284
+ }
285
+ if (c.metric === 'isHunter') {
286
+ const s = subjectOf(snap, c.subject, c);
287
+ return s?.isHunter ? 1 : 0;
288
+ }
289
+ if (c.metric === 'enemiesWithinYd') {
290
+ const r = c.ref && c.ref > 0 ? c.ref : 10;
291
+ // subject target → density at the focus (ground rain). self/pet → density at caster (PBAoE).
292
+ if (c.subject === 'target') {
293
+ if (snap.enemyDistsFromTarget) {
294
+ let n = 0;
295
+ for (const d of snap.enemyDistsFromTarget) if (d <= r) n++;
296
+ return n;
297
+ }
298
+ // No focus geometry: fight-wide pack (still better than hard-false).
299
+ return snap.enemyCount;
300
+ }
301
+ // BG view first (perceived enemies), then PvE geometry from actor, then fight-wide.
302
+ if (snap.bg?.enemyUnits) {
303
+ return snap.bg.enemyUnits.filter((e) => e.distYd <= r).length;
304
+ }
305
+ if (snap.enemyDists) {
306
+ let n = 0;
307
+ for (const d of snap.enemyDists) if (d <= r) n++;
308
+ return n;
309
+ }
310
+ return snap.enemyCount;
311
+ }
312
+ if (c.metric === 'alliesWithinYd') {
313
+ const r = c.ref && c.ref > 0 ? c.ref : 10;
314
+ if (snap.bg?.allyUnits) return snap.bg.allyUnits.filter((a) => a.distYd <= r).length;
315
+ if (snap.allyDistHp) {
316
+ let n = 0;
317
+ for (const a of snap.allyDistHp) {
318
+ if (a.distYd === 0) continue;
319
+ if (a.distYd != null && a.distYd > r) continue;
320
+ n++;
321
+ }
322
+ return n;
323
+ }
324
+ return 0;
325
+ }
326
+ if (c.metric === 'healersWithinYd') {
327
+ const r = c.ref && c.ref > 0 ? c.ref : 30;
328
+ if (snap.bg?.allyUnits) {
329
+ return snap.bg.allyUnits.filter((a) => a.isHealer && a.distYd <= r).length;
330
+ }
331
+ if (snap.allyDistHp) {
332
+ let n = 0;
333
+ for (const a of snap.allyDistHp) {
334
+ if (!a.isHealer) continue;
335
+ if (a.distYd != null && a.distYd > r) continue;
336
+ n++;
337
+ }
338
+ return n;
339
+ }
340
+ return 0;
341
+ }
342
+ // The party healer's mana (dead seat = 0). No healer seat → null → the condition is false,
343
+ // which is what keeps the off-heal rows off solo and all-DPS rotations.
344
+ if (c.metric === 'healerPowerPct') {
345
+ const pcts = snap.healerPowerPcts;
346
+ if (!pcts || pcts.length === 0) return null;
347
+ let low = pcts[0]!;
348
+ for (const p of pcts) if (p < low) low = p;
349
+ return low;
350
+ }
351
+ // Seats, not living bodies: a party mid-wipe must not read as solo and turn the damage rows back on.
352
+ if (c.metric === 'partySeats') return snap.partySeats ?? 1;
353
+ if (c.metric === 'enemyHealersWithinYd') {
354
+ const r = c.ref && c.ref > 0 ? c.ref : 30;
355
+ if (snap.bg?.enemyUnits) {
356
+ return snap.bg.enemyUnits.filter((e) => e.isHealer && e.distYd <= r).length;
357
+ }
358
+ if (snap.enemyHealerDists) {
359
+ let n = 0;
360
+ for (const d of snap.enemyHealerDists) if (d <= r) n++;
361
+ return n;
362
+ }
363
+ return 0;
364
+ }
365
+ if (c.metric === 'rooted') { const s = subjectOf(snap, c.subject, c); return s?.alive && s.rooted ? 1 : 0; }
366
+ if (c.metric === 'feared') { const s = subjectOf(snap, c.subject, c); return s?.alive && s.feared ? 1 : 0; }
367
+ if (c.metric === 'stunned') { const s = subjectOf(snap, c.subject, c); return s?.alive && s.stunned ? 1 : 0; }
368
+ if (c.metric === 'snared') { const s = subjectOf(snap, c.subject, c); return s?.alive && s.snared ? 1 : 0; }
369
+ // Cite: Spell.cpp:6049-6050 UNIT_FLAG_SILENCED -> SPELL_FAILED_SILENCED.
370
+ if (c.metric === 'silenced') { const s = subjectOf(snap, c.subject, c); return s?.alive && s.silenced ? 1 : 0; }
371
+ if (c.metric === 'controlled') {
372
+ const s = subjectOf(snap, c.subject, c);
373
+ if (!s?.alive) return 0;
374
+ return (s.rooted || s.feared || s.stunned || s.snared) ? 1 : 0;
375
+ }
376
+ if (c.metric === 'objectiveContested') return snap.bg?.objectiveContested ? 1 : 0;
377
+ if (c.metric === 'objectiveOwnedByUs') return snap.bg?.objectiveOwnedByUs ? 1 : 0;
378
+ if (c.metric === 'objectiveDistanceYd') {
379
+ // Absent → null so the condition is FALSE rather than "distance 0", which would make
380
+ // "objective distance < 10" fire everywhere there is no objective.
381
+ return snap.bg?.objectiveDistYd ?? null;
382
+ }
383
+ // Wave 1D: slot occupancy (any summon in the element), not a specific spell id.
384
+ if (c.metric === 'totemPresent') {
385
+ const slot = totemSlotFromIndex(c.ref ?? -1);
386
+ if (!slot) return 0;
387
+ return snap.totemSlots.has(slot) ? 1 : 0;
388
+ }
389
+ // Weapon temp enchant (MH/OH): any imbue/poison on the hand, not a specific apply spell.
390
+ // ref 2 = fully coated (no open eligible weapon hand) — dual-wield keep-up MH then OH.
391
+ if (c.metric === 'weaponImbuePresent') {
392
+ const r = c.ref ?? 0;
393
+ if (r === 2) {
394
+ if (snap.weaponImbueOpenHands) return snap.weaponImbueOpenHands.size === 0 ? 1 : 0;
395
+ return snap.weaponImbueHands?.has('mainhand') ? 1 : 0;
396
+ }
397
+ const hand = r === 1 ? 'offhand' : 'mainhand';
398
+ if (snap.weaponImbueHands?.has(hand)) return 1;
399
+ // A hand that can never take a coat (nothing equipped there, a shield, a two-hander's offhand)
400
+ // reads as covered, so a per-hand coat row does not fire into a refusal every prep tick.
401
+ if (snap.weaponImbueOpenHands && !snap.weaponImbueOpenHands.has(hand)) return 1;
402
+ return 0;
403
+ }
404
+ // Omitted/negative ref = any category, the question GetDispelTarget / NOTHING_TO_DISPEL ask.
405
+ // Cite: PlayerbotPriestAI.cpp:442; Spell.cpp:4890–4919.
406
+ if (c.metric === 'dispellablePresent') {
407
+ const set = c.subject === 'target' ? snap.dispellableOnTarget : snap.dispellableOnFriendlies;
408
+ if (!set) return 0;
409
+ const cat = c.ref;
410
+ if (cat == null || cat < 0) return set.size > 0 ? 1 : 0;
411
+ return set.has(cat) ? 1 : 0;
412
+ }
413
+ // Creature-type mask gate: defined when subject is absent/dead as 0 so "mask match == 1"
414
+ // fails cleanly (never fire Exorcism with no target).
415
+ if (c.metric === 'creatureTypeMask') {
416
+ const s = subjectOf(snap, c.subject, c);
417
+ if (!s?.alive) return 0;
418
+ const type = s.creatureType ?? 0;
419
+ if (!type) return 0;
420
+ const mask = c.ref ?? 0;
421
+ if (!mask) return 0;
422
+ return ((1 << (type - 1)) & mask) !== 0 ? 1 : 0;
423
+ }
424
+ // Defense react (Revenge): dodge/parry/block gate on the subject. Absent → 0 (never free).
425
+ if (c.metric === 'defenseReactRecently') {
426
+ const s = subjectOf(snap, c.subject, c);
427
+ return s?.alive && s.defenseReactRecently ? 1 : 0;
428
+ }
429
+ // Hunter parry (Counterattack): AURA_STATE_HUNTER_PARRY. Absent → 0.
430
+ if (c.metric === 'hunterParryRecently') {
431
+ const s = subjectOf(snap, c.subject, c);
432
+ return s?.alive && s.hunterParryRecently ? 1 : 0;
433
+ }
434
+ // Melee band vs the acting unit. Absent / unknown → 0 so "at range" is not a no-op
435
+ // (the old degrade-to-1 made every inMelee==1 rule fire at 30 yd).
436
+ if (c.metric === 'inMelee') {
437
+ const s = subjectOf(snap, c.subject, c);
438
+ if (!s?.alive) return 0;
439
+ if (s.inMelee === true) return 1;
440
+ if (s.inMelee === false) return 0;
441
+ if (c.subject === 'self' && snap.enemyDists) {
442
+ for (const d of snap.enemyDists) if (d <= IN_MELEE_YD) return 1;
443
+ }
444
+ return 0;
445
+ }
446
+ // Charm/possess on a subject: defined when absent/dead as 0 so "target charmed == 1" fails cleanly.
447
+ if (c.metric === 'charmed') {
448
+ const s = subjectOf(snap, c.subject, c);
449
+ return s?.alive && s.charmed ? 1 : 0;
450
+ }
451
+ return subjectMetricValue(snap, c);
452
+ }
453
+
454
+ function compare(a: number, op: CondOp, b: number): boolean {
455
+ switch (op) {
456
+ case '<': return a < b;
457
+ case '<=': return a <= b;
458
+ case '>': return a > b;
459
+ case '>=': return a >= b;
460
+ case '==': return a === b;
461
+ case '!=': return a !== b;
462
+ default: return false;
463
+ }
464
+ }
465
+
466
+ // Bear Form family for rotation gates: Classic Bear (5) and Dire Bear (8) both satisfy "in Bear Form" /
467
+ // "not in Bear Form" when the expected id is BEAR. Exact DIREBEAR / CAT / stance ids stay exact. Wave 1A.
468
+ function formIdEquals(actual: number, expected: number): boolean {
469
+ // SHAPESHIFT_FORM.BEAR = 5, DIREBEAR = 8 (schema) — keep numeric to avoid domain↔schema cycles in tests.
470
+ if (expected === 5) return actual === 5 || actual === 8;
471
+ return actual === expected;
472
+ }
473
+
474
+ function compareFormId(actual: number, op: CondOp, expected: number): boolean {
475
+ if (op === '==') return formIdEquals(actual, expected);
476
+ if (op === '!=') return !formIdEquals(actual, expected);
477
+ return compare(actual, op, expected);
478
+ }
479
+
480
+ /** Evaluate a single condition. A metric that can't apply (absent subject) is FALSE. */
481
+ export function evalCondition(snap: RotationSnapshot, c: Condition): boolean {
482
+ const v = metricValue(snap, c);
483
+ if (v === null) return false;
484
+ if (c.metric === 'formId') return compareFormId(v, c.op, c.value);
485
+ return compare(v, c.op, c.value);
486
+ }
487
+
488
+ function hasPackDensityGate(rule: RotationRule): boolean {
489
+ return rule.conditions.some((c) => c.metric === 'enemyCount' || c.metric === 'enemiesWithinYd');
490
+ }
491
+
492
+ function ruleWantsTriageAim(rule: RotationRule): boolean {
493
+ return rule.conditions.some(
494
+ (c) => c.metric === 'allyLowestHpPct' || c.metric === 'allyLowestBuffPresent'
495
+ || c.subject === 'ally',
496
+ );
497
+ }
498
+
499
+ // The filter the row's ALLY gates read, so the aim lands on the body they measured. First
500
+ // ally-subject condition wins: two different filters on one row is two rows.
501
+ export function allyFilterOfRule(rule: RotationRule): AllyFilter | undefined {
502
+ for (const c of rule.conditions) {
503
+ if (c.subject === 'ally' && !allyFilterIsAny(c.ally)) return c.ally;
504
+ }
505
+ return undefined;
506
+ }
507
+
508
+ export function isLowestHpSpellTarget(
509
+ t: RuleSpellTarget | undefined,
510
+ ): t is { lowestHp: true; among?: AllyFilter } {
511
+ return !!t && typeof t === 'object' && 'lowestHp' in t && t.lowestHp === true;
512
+ }
513
+
514
+ function spellActionWithTriageAim(rule: RotationRule, action: RuleAction): RuleAction {
515
+ if (action.kind !== 'spell' || action.target != null || !ruleWantsTriageAim(rule)) return action;
516
+ const among = allyFilterOfRule(rule);
517
+ return { ...action, target: among ? { lowestHp: true, among } : { lowestHp: true } };
518
+ }
519
+
520
+ function isNotFearedKeepUp(rule: RotationRule): boolean {
521
+ return rule.conditions.some(
522
+ (c) => c.metric === 'feared' && c.op === '==' && c.value === 0,
523
+ );
524
+ }
525
+
526
+ // Stock Fear is `target: other` (a second mob). Deleting the 2+ if does not clear that. With no
527
+ // pack-density gate, fire at the focus — otherwise 1v1 never casts.
528
+ function actionForEval(rule: RotationRule, snap?: RotationSnapshot): RuleAction {
529
+ const a = rule.action;
530
+ if (a.kind === 'spell' && a.target === 'other' && !hasPackDensityGate(rule)) {
531
+ const { target: _drop, ...rest } = a;
532
+ return rest;
533
+ }
534
+ // A family row is a ladder: swap in the best rank actually carried. No provider (or nothing
535
+ // carried) leaves the authored rung, which usableItem then refuses like any empty-bag row.
536
+ if (a.kind === 'item' && a.family && snap?.bestFamilyEntry) {
537
+ const best = snap.bestFamilyEntry(a.family);
538
+ if (best != null && best !== a.itemEntry) return { ...a, itemEntry: best };
539
+ }
540
+ // A multi-entry click resolves to the one entry usable now: the row names the SET and the
541
+ // snapshot names the member. Thermaplugg lights one of six buttons at a time.
542
+ if (a.kind === 'use_object' && a.goEntries?.length && snap?.usableObject) {
543
+ const live = a.goEntries.find((e) => snap.usableObject?.(e) === true);
544
+ if (live != null && live !== a.goEntry) return { ...a, goEntry: live };
545
+ }
546
+ return a;
547
+ }
548
+
549
+ // A `target: 'other'` row casts at a SECOND body, so a subject-`target` gate read against the focus
550
+ // describes one unit and types another: the gate never closes and the row repeats every GCD.
551
+ function snapForAction(snap: RotationSnapshot, action: RuleAction): RotationSnapshot {
552
+ if (action.kind !== 'spell' || action.target !== 'other' || !snap.offTargetSubject) return snap;
553
+ const other = snap.offTargetSubject(action.spellId);
554
+ return other ? { ...snap, target: other } : snap;
555
+ }
556
+
557
+ // Pack `ACTION_SPELL` (whenMap.ts:40) and a missing editor ref both mean this row's spell.
558
+ const PRESENCE_BIND_METRICS: ReadonlySet<string> = new Set([
559
+ 'buffPresent', 'debuffPresent', 'buffStacks', 'debuffStacks',
560
+ 'allyLowestBuffPresent', 'allyAuraPresent', 'allyAuraStacks',
561
+ 'aggroHolderDebuffPresent', 'aggroHolderDebuffStacks',
562
+ ]);
563
+
564
+ function presenceRefUnbound(ref: unknown): boolean {
565
+ return ref == null || ref === 'ACTION_SPELL' || (typeof ref === 'number' && ref <= 0);
566
+ }
567
+
568
+ function bindActionSpellRefs(rule: RotationRule, conds: readonly Condition[]): readonly Condition[] {
569
+ if (rule.action.kind !== 'spell') return conds;
570
+ const spellId = rule.action.spellId;
571
+ if (!(spellId > 0)) return conds;
572
+ let dirty = false;
573
+ const out: Condition[] = [];
574
+ for (const c of conds) {
575
+ if (!PRESENCE_BIND_METRICS.has(c.metric) || !presenceRefUnbound(c.ref)) {
576
+ out.push(c);
577
+ continue;
578
+ }
579
+ dirty = true;
580
+ out.push({ ...c, ref: spellId });
581
+ }
582
+ return dirty ? out : conds;
583
+ }
584
+
585
+ // Drop conditions the evaluator hides, keeping the OR groups the player authored: a dropped group
586
+ // head hands its `or` to the next survivor, or the rest of that group would AND into the group above.
587
+ function dropConditions(
588
+ conds: readonly Condition[],
589
+ drop: (c: Condition) => boolean,
590
+ ): readonly Condition[] {
591
+ const out: Condition[] = [];
592
+ let owedOr = false;
593
+ for (const c of conds) {
594
+ if (drop(c)) {
595
+ if (c.join === 'or') owedOr = true;
596
+ continue;
597
+ }
598
+ out.push(owedOr && c.join !== 'or' ? { ...c, join: 'or' } : c);
599
+ owedOr = false;
600
+ }
601
+ return out;
602
+ }
603
+
604
+ // The same list split into its OR groups, for a caller that has to REWRITE it rather than read it
605
+ // (the macro compile ANDs its own guard onto each group, since (A and B or C) and V distributes).
606
+ export function conditionGroups(conds: readonly Condition[]): Condition[][] {
607
+ const groups: Condition[][] = [];
608
+ for (let i = 0; i < conds.length; i++) {
609
+ const c = conds[i]!;
610
+ if (i === 0 || c.join === 'or' || groups.length === 0) groups.push([]);
611
+ groups[groups.length - 1]!.push(c);
612
+ }
613
+ return groups;
614
+ }
615
+
616
+ // A rule's condition list as OR-of-AND runs: a condition carrying `join: 'or'` STARTS a new group,
617
+ // every other one ANDs into the group it sits in, and the rule fires when any group passes. An
618
+ // all-`and` list (every rotation written before the join field) is one group, so this is `.every`.
619
+ export function conditionsPass(
620
+ snap: RotationSnapshot,
621
+ conds: readonly Condition[],
622
+ ): boolean {
623
+ if (conds.length === 0) return true;
624
+ let group = true;
625
+ let passed = false;
626
+ for (let i = 0; i < conds.length; i++) {
627
+ const c = conds[i]!;
628
+ if (i > 0 && c.join === 'or') {
629
+ if (group) passed = true;
630
+ group = true;
631
+ }
632
+ if (group && !evalCondition(snap, c)) group = false;
633
+ }
634
+ return passed || group;
635
+ }
636
+
637
+ function conditionsForEval(rule: RotationRule): readonly Condition[] {
638
+ const base = isNotFearedKeepUp(rule)
639
+ ? dropConditions(rule.conditions, (c) => c.metric === 'castingPresent')
640
+ : rule.conditions;
641
+ return bindActionSpellRefs(rule, base);
642
+ }
643
+
644
+ function actionUsable(snap: RotationSnapshot, a: RuleAction): boolean {
645
+ // A macro rule is compiled away before the evaluator sees it (domain/macro/rotation.ts). One
646
+ // reaching here is a hand-edited save or a call site that skipped rotationForCombat: refuse.
647
+ if (a.kind === 'macro') return false;
648
+ if (a.kind === 'item') return snap.usableItem(a.itemEntry);
649
+ // Move / halt_dps have no GCD cost — usable whenever their conditions hold.
650
+ if (a.kind === 'move' || a.kind === 'halt_dps' || a.kind === 'kill_order' || a.kind === 'keep_aura') return true;
651
+ if (a.kind === 'use_object') {
652
+ const entries = a.goEntries?.length ? a.goEntries : [a.goEntry];
653
+ return entries.some((e) => snap.usableObject?.(e) === true);
654
+ }
655
+ // Creature-entry target: canCast against THAT body, not the focus. Faerlina is charm-immune, so
656
+ // usableSpell(605) read false while the row aimed at a Worshipper. Cite: boss_faerlina.cpp:47.
657
+ if (a.target && typeof a.target === 'object' && 'creatureEntry' in a.target) {
658
+ const entries = snap.hostileCreatureEntries;
659
+ if (!entries || !entries.has(a.target.creatureEntry)) return false;
660
+ if (snap.usableOnCreatureEntry) {
661
+ return snap.usableOnCreatureEntry(a.spellId, a.target.creatureEntry);
662
+ }
663
+ return snap.usableSpell(a.spellId);
664
+ }
665
+ if (!snap.usableSpell(a.spellId)) return false;
666
+ // The engine asks it once, so no authored row has to carry the condition: a DoT already on the
667
+ // focus, or a HoT already on the body it would land on, never wins the GCD.
668
+ if (snap.periodicAlreadyOnAimedBody?.(a.spellId, isLowestHpSpellTarget(a.target)) === true) return false;
669
+ // Target school / damage immunity — skip so the next rule takes the GCD (Divine Shield,
670
+ // Blessing of Protection, fire-immune mob). Cite: Unit.cpp IsImmuneToSpell ~7329.
671
+ if (snap.target?.immuneSpellIds?.has(a.spellId)) return false;
672
+ // A CC rule aimed at a second mob is usable only when a second mob it can hold exists. Deciding
673
+ // it here, rather than letting the engine veto the pick, is what lets the next rule take the tick.
674
+ if (a.target === 'other' && snap.offTargetSpell && !snap.offTargetSpell(a.spellId)) return false;
675
+ // Focus-aimed break-on-damage CC on a body our own damage already broke it on. This is also the
676
+ // shape a player creates by deleting the pack-density condition in the editor.
677
+ if (a.target !== 'other' && snap.ccBrokenOnTarget?.(a.spellId) === true) return false;
678
+ if (a.target && typeof a.target === 'object' && 'priority' in a.target) {
679
+ if (firstLivingKillOrderEntry(a.target.priority, snap.hostileCreatureEntries) == null) return false;
680
+ }
681
+ // Usable only while somebody else still lacks the aura, or the row eats the caster's GCD forever.
682
+ if (a.target && typeof a.target === 'object' && 'allyMissingAura' in a.target) {
683
+ if (snap.allyMissingAura?.(a.target.allyMissingAura, a.target.among) !== true) return false;
684
+ }
685
+ return true;
686
+ }
687
+
688
+ function ruleAfterOf(rule: RotationRule): RuleAfter | undefined {
689
+ // A macro action carries no `after` of its own — the rule's is the only one (see RuleAction).
690
+ return sanitizeRuleAfter(rule.after ?? (
691
+ rule.action.kind === 'spell' || rule.action.kind === 'item' ? rule.action.after : undefined
692
+ ));
693
+ }
694
+
695
+ function ruleBeforeOf(rule: RotationRule): RuleBefore | undefined {
696
+ const id = rule.before?.spellId;
697
+ return typeof id === 'number' && id > 0 ? { spellId: id | 0 } : undefined;
698
+ }
699
+
700
+ // True when a `before` clause is already paid for: nothing to cast, the prep is not castable now,
701
+ // its aura is already on us, or we already stand in the form it enters. A satisfied prep must never
702
+ // eat the GCD - that is the whole difference between a pre-cast and a thrash.
703
+ function beforeSatisfied(snap: RotationSnapshot, spellId: number): boolean {
704
+ if (!snap.usableSpell(spellId)) return true;
705
+ if (idsHave(snap, snap.self.buffs, spellId)) return true;
706
+ const enters = snap.formEnteredBySpell?.(spellId) ?? null;
707
+ return enters != null && enters === ((snap.self.formId ?? 0) | 0);
708
+ }
709
+
710
+ // The prep cast a rule's `before` clause owes, or null. It arms the rule's OWN spell as the
711
+ // after-cast follow-up, so the next GCD is the action itself: chooseCombatAction reads
712
+ // `followUpSpellId` ahead of the list (see the follow-up branch there).
713
+ function beforeCastFor(
714
+ rule: RotationRule,
715
+ action: RuleAction,
716
+ snap: RotationSnapshot,
717
+ ): RuleAction | null {
718
+ const before = ruleBeforeOf(rule);
719
+ if (!before) return null;
720
+ // Only a spell action can be re-armed as the follow-up; an item rule has no spellId to queue.
721
+ if (action.kind !== 'spell' || action.spellId === before.spellId) return null;
722
+ if (beforeSatisfied(snap, before.spellId)) return null;
723
+ return { kind: 'spell', spellId: before.spellId, after: { spellId: action.spellId } };
724
+ }
725
+
726
+ // Per-fight tactic rows ride ON TOP of whatever list the player authored. They are not defaults: a
727
+ // spread that is right in one room is wrong in another, so they exist only while that entry is focus.
728
+ function withEncounterRows(
729
+ targetEntry: number | undefined,
730
+ list: readonly RotationRule[],
731
+ living?: ReadonlySet<number>,
732
+ tuning?: Pick<Rotation, 'raidRowsOff' | 'raidRowYd'>,
733
+ ): readonly RotationRule[] {
734
+ const extras: RotationRule[] = [];
735
+ const seen = new Set<string>();
736
+ // The Raid tab's switch and its retuned distance ride on the COMPOSED rotation
737
+ // (withRaidTacticRows) because this prepend happens per focus, after composition.
738
+ const off = tuning?.raidRowsOff?.length ? new Set(tuning.raidRowsOff) : null;
739
+ const yd = tuning?.raidRowYd;
740
+ const add = (entry: number): void => {
741
+ for (const r of encounterTacticRows(entry)) {
742
+ if (seen.has(r.id)) continue;
743
+ seen.add(r.id);
744
+ if (off?.has(r.id)) continue;
745
+ const tunedYd = yd?.[r.id];
746
+ extras.push(tunedYd != null ? raidRuleWithYd(r, tunedYd) : r);
747
+ }
748
+ };
749
+ if (targetEntry != null) add(targetEntry);
750
+ if (living) for (const e of living) add(e);
751
+ return extras.length ? [...extras, ...list] : list;
752
+ }
753
+
754
+ function actionWithAfter(rule: RotationRule): RuleAction {
755
+ const after = ruleAfterOf(rule);
756
+ if (!after || (rule.action.kind !== 'spell' && rule.action.kind !== 'item')) return rule.action;
757
+ return { ...rule.action, after };
758
+ }
759
+
760
+ // The highest-priority halt_dps whose conditions pass, in list order. The engine arms the school
761
+ // BEFORE evaluate, so a school-specific halt can refuse one school while allowing a lower rule.
762
+ export type HaltDpsPeek = {
763
+ school: HaltDpsSchool;
764
+ /** creature_template.entry bodies this halt covers. Absent = every body. */
765
+ entries?: readonly number[];
766
+ };
767
+ export function peekHaltDpsSchool(
768
+ rot: Rotation,
769
+ snap: RotationSnapshot,
770
+ targetEntry?: number,
771
+ ): HaltDpsPeek | null {
772
+ const combatList = withEncounterRows(
773
+ targetEntry,
774
+ (targetEntry != null && rot.overrides?.[targetEntry]) || rot.phases.combat,
775
+ snap.hostileCreatureEntries,
776
+ rot,
777
+ );
778
+ for (const list of [rot.phases.recovery, combatList] as const) {
779
+ for (const rule of list) {
780
+ if (!rule.enabled || rule.action.kind !== 'halt_dps') continue;
781
+ if (!conditionsPass(snap, conditionsForEval(rule))) continue;
782
+ return { school: rule.action.school ?? 'all', entries: rule.action.entries };
783
+ }
784
+ }
785
+ return null;
786
+ }
787
+
788
+ // True when an armed halt covers the body being attacked. No entries means every body. An absent
789
+ // entry reads as not covered: a halt that cannot name what it stops must not stop everything.
790
+ export function haltDpsCoversEntry(
791
+ entries: readonly number[] | undefined,
792
+ entry: number | undefined,
793
+ ): boolean {
794
+ if (entries == null || entries.length === 0) return true;
795
+ if (entry == null) return false;
796
+ for (const e of entries) if (e === entry) return true;
797
+ return false;
798
+ }
799
+
800
+ // True when an armed halt refuses this spell's school. 'all' is every damaging school, a string is
801
+ // one, an array is that set. Physical is refused only when listed or the halt is 'all'.
802
+ export function haltDpsRefusesSchool(
803
+ halt: HaltDpsSchool | null | undefined,
804
+ school: string | undefined,
805
+ ): boolean {
806
+ if (halt == null) return false;
807
+ if (halt === 'all') return true;
808
+ if (!school) return false;
809
+ if (typeof halt === 'string') return school === halt;
810
+ for (const s of halt) {
811
+ if (s === school) return true;
812
+ }
813
+ return false;
814
+ }
815
+
816
+ /** creature_template.entry list on a kill-order filter or a spell `{ priority }` target. */
817
+ export function killOrderEntriesFromAction(action: RuleAction): readonly number[] | null {
818
+ if (action.kind === 'kill_order') return action.entries;
819
+ if (action.kind === 'spell' && action.target && typeof action.target === 'object'
820
+ && 'priority' in action.target) {
821
+ return action.target.priority;
822
+ }
823
+ return null;
824
+ }
825
+
826
+ /** First entry in `order` that is currently alive. */
827
+ export function firstLivingKillOrderEntry(
828
+ order: readonly number[],
829
+ living: ReadonlySet<number> | undefined,
830
+ ): number | null {
831
+ if (!living || order.length === 0) return null;
832
+ for (const e of order) {
833
+ if (e >= 0) {
834
+ if (living.has(e)) return e;
835
+ continue;
836
+ }
837
+ for (const alive of living) {
838
+ if (killOrderEntryMatches(e, alive)) return alive;
839
+ }
840
+ }
841
+ return null;
842
+ }
843
+
844
+ /** Spell ids a keep_aura row forbids auto-dispelling. Enabled only; conditions are the sentence. */
845
+ export function keepAuraIdsOf(rot: Rotation): ReadonlySet<number> {
846
+ const out = new Set<number>();
847
+ const take = (rules: readonly RotationRule[] | undefined): void => {
848
+ for (const r of rules ?? []) {
849
+ if (!r.enabled || r.action.kind !== 'keep_aura') continue;
850
+ const id = r.action.spellId | 0;
851
+ if (id > 0) out.add(id);
852
+ }
853
+ };
854
+ take(rot.phases.recovery);
855
+ take(rot.phases.combat);
856
+ for (const list of Object.values(rot.overrides ?? {})) take(list);
857
+ return out;
858
+ }
859
+
860
+ // Highest-priority kill-order list whose conditions pass (encounter rows first via withEncounterRows). Used
861
+ // to retarget DPS onto the first living add. Cite: AUTOPILOT-PLAN §4 row 17.
862
+ export function peekKillOrderEntries(
863
+ rot: Rotation,
864
+ snap: RotationSnapshot,
865
+ targetEntry?: number,
866
+ ): readonly number[] | null {
867
+ const combatList = withEncounterRows(
868
+ targetEntry,
869
+ (targetEntry != null && rot.overrides?.[targetEntry]) || rot.phases.combat,
870
+ snap.hostileCreatureEntries,
871
+ rot,
872
+ );
873
+ for (const list of [rot.phases.recovery, combatList] as const) {
874
+ for (const rule of list) {
875
+ if (!rule.enabled) continue;
876
+ const entries = killOrderEntriesFromAction(rule.action);
877
+ if (!entries?.length) continue;
878
+ if (!conditionsPass(snap, conditionsForEval(rule))) continue;
879
+ return entries;
880
+ }
881
+ }
882
+ return null;
883
+ }
884
+
885
+ // The form a row demands, from the `self formId ==` guard build.ts:1115 injects on a stance-gated
886
+ // spell. 0 when the row names none. Nothing else in the vocabulary names a form.
887
+ function wantedFormOf(conds: readonly Condition[]): number {
888
+ for (const c of conds) {
889
+ if (c.subject === 'self' && c.metric === 'formId' && c.op === '==') return c.value | 0;
890
+ }
891
+ return 0;
892
+ }
893
+
894
+ // The stance this row would need entering, when the ROW ITSELF is otherwise ready. Disarm needs
895
+ // Defensive Stance (spells.json 676 stances=131072) and every warrior pvp band stands in Battle, so
896
+ // without this the row is skipped forever instead of swapping for it. The press-side twin is
897
+ // stancePrereq.ts:22, which combatView.ts:1676 already runs for a hand-pressed button.
898
+ function stanceSwapFor(
899
+ rule: RotationRule,
900
+ conds: readonly Condition[],
901
+ snap: RotationSnapshot,
902
+ ): number | null {
903
+ if (!snap.usableSpellIgnoringForm || !snap.stanceSpellForForm) return null;
904
+ if (rule.action.kind !== 'spell') return null;
905
+ const want = wantedFormOf(conds);
906
+ if (!want || want === (snap.self.formId ?? 0)) return null;
907
+ // Every OTHER guard must already pass, so a row on cooldown or out of rage never dances.
908
+ for (const c of conds) {
909
+ if (c.subject === 'self' && c.metric === 'formId' && c.op === '==') continue;
910
+ if (!evalCondition(snap, c)) return null;
911
+ }
912
+ if (!snap.usableSpellIgnoringForm(rule.action.spellId)) return null;
913
+ const stance = snap.stanceSpellForForm(want);
914
+ return stance != null && snap.usableSpell(stance) ? stance : null;
915
+ }
916
+
917
+ // A default-stance row must not pop the unit back out of a stance a LOWER row is mid-way through
918
+ // using. Arms@60 puts `Battle Stance when formId != 17` above Shield Wall and Revenge, so the tick
919
+ // after the swap the setup row won on priority and the payload never fired.
920
+ function formRowPopsAServedStance(
921
+ action: RuleAction,
922
+ rest: readonly RotationRule[],
923
+ snap: RotationSnapshot,
924
+ ): boolean {
925
+ if (action.kind !== 'spell' || !snap.formEnteredBySpell || !snap.usableSpellIgnoringForm) return false;
926
+ const here = (snap.self.formId ?? 0) | 0;
927
+ const enters = snap.formEnteredBySpell(action.spellId) ?? 0;
928
+ if (!here || !enters || enters === here) return false;
929
+ for (const r of rest) {
930
+ if (!r.enabled || r.action.kind !== 'spell') continue;
931
+ const conds = conditionsForEval(r);
932
+ if (wantedFormOf(conds) !== here) continue;
933
+ if (!conditionsPass(snap, conds)) continue;
934
+ if (snap.usableSpellIgnoringForm(r.action.spellId)) return true;
935
+ }
936
+ return false;
937
+ }
938
+
939
+ // First enabled rule whose conditions pass and whose action is usable, else null. halt_dps is a
940
+ // filter, not a winner: remember it and keep scanning, and return it only if nothing else fires.
941
+ function firstFiring(
942
+ rules: readonly RotationRule[],
943
+ snap: RotationSnapshot,
944
+ pred?: (rule: RotationRule) => boolean,
945
+ // `before` clauses ride the after-cast follow-up channel, which only the combat pick arms
946
+ // (pickAction armRuleAfter). An OOC caller passes false or the prep would fire with nothing behind it.
947
+ allowBefore = true,
948
+ ): RuleAction | null {
949
+ let pendingHalt: RuleAction | null = null;
950
+ for (let i = 0; i < rules.length; i++) {
951
+ const rule = rules[i]!;
952
+ if (!rule.enabled) continue;
953
+ if (pred && !pred(rule)) continue;
954
+ const conds = conditionsForEval(rule);
955
+ // Resolve the action first ONLY for an off-target row, so the gate reads the body it will hit.
956
+ const early = rule.action.kind === 'spell' && rule.action.target === 'other'
957
+ ? actionForEval(rule, snap)
958
+ : null;
959
+ const condSnap = early ? snapForAction(snap, early) : snap;
960
+ if (!conditionsPass(condSnap, conds)) {
961
+ const swap = stanceSwapFor(rule, conds, snap);
962
+ if (swap != null) return { kind: 'spell', spellId: swap };
963
+ continue;
964
+ }
965
+ const action = spellActionWithTriageAim(rule, early ?? actionForEval(rule, snap));
966
+ if (action.kind === 'halt_dps') {
967
+ if (!pendingHalt) pendingHalt = action;
968
+ continue;
969
+ }
970
+ // Kill-order is armed by peekKillOrderEntries; keep_aura by keepAuraIdsOf. Neither spends the GCD.
971
+ if (action.kind === 'kill_order' || action.kind === 'keep_aura') continue;
972
+ if (!actionUsable(snap, action)) continue;
973
+ if (formRowPopsAServedStance(action, rules.slice(i + 1), snap)) continue;
974
+ if (allowBefore) {
975
+ const prep = beforeCastFor(rule, action, snap);
976
+ if (prep) return prep;
977
+ }
978
+ return actionWithAfter({ ...rule, action });
979
+ }
980
+ return pendingHalt;
981
+ }
982
+
983
+ function isPullActionSpent(action: RuleAction, snap: RotationSnapshot): boolean {
984
+ if (action.kind === 'spell') return snap.pullSpentSpellIds?.has(action.spellId) ?? false;
985
+ if (action.kind === 'item') return snap.pullSpentItemEntries?.has(action.itemEntry) ?? false;
986
+ return false; // a macro rule never reaches the evaluator (actionUsable refuses it)
987
+ }
988
+
989
+ // Pull opener as a SEQUENCE, not a thrash priority list: walk top to bottom, skip spent and
990
+ // unneeded rules, fire the first that is needed and usable, and wait on a needed head.
991
+ function pullSequenceAction(
992
+ rules: readonly RotationRule[],
993
+ snap: RotationSnapshot,
994
+ ): { kind: 'fire'; action: RuleAction } | { kind: 'wait' } | { kind: 'done' } {
995
+ for (const rule of rules) {
996
+ if (!rule.enabled) continue;
997
+ // Resolve first: a family row's spent-check has to name the rank it would actually drink.
998
+ const action = spellActionWithTriageAim(rule, actionForEval(rule, snap));
999
+ if (isPullActionSpent(action, snap)) continue;
1000
+ const condSnap = snapForAction(snap, action);
1001
+ if (!conditionsPass(condSnap, conditionsForEval(rule))) continue; // not needed this pull
1002
+ if (!actionUsable(snap, action)) {
1003
+ const inMelee = snap.self.inMelee === true
1004
+ || (!!snap.enemyDists && snap.enemyDists.some((d) => d <= IN_MELEE_YD));
1005
+ if (inMelee || snap.enemyCount === 0) continue;
1006
+ return { kind: 'wait' }; // walking into range / GCD / CD
1007
+ }
1008
+ // Opener prep: a spent prep is done, so the opener steps on to the action it guards.
1009
+ const prep = beforeCastFor(rule, action, snap);
1010
+ if (prep && !isPullActionSpent(prep, snap)) return { kind: 'fire', action: prep };
1011
+ return { kind: 'fire', action: actionWithAfter({ ...rule, action }) };
1012
+ }
1013
+ return { kind: 'done' };
1014
+ }
1015
+
1016
+ // Choose the in-combat action for this tick. Raid-response, interrupts and dispels beat recovery;
1017
+ // then recovery, the pull opener and combat. `targetEntry` selects a per-creature override.
1018
+ export function chooseCombatAction(
1019
+ rot: Rotation,
1020
+ snap: RotationSnapshot,
1021
+ targetEntry?: number,
1022
+ opts?: { skipMove?: boolean },
1023
+ ): RuleAction | null {
1024
+ const combatList = withEncounterRows(
1025
+ targetEntry,
1026
+ (targetEntry != null && rot.overrides?.[targetEntry]) || rot.phases.combat,
1027
+ snap.hostileCreatureEntries,
1028
+ rot,
1029
+ );
1030
+ // `skipMove` asks the same list what it would do without the run-out row. A move pick spends the
1031
+ // whole tick, so this is how the engine finds the instant it may still fire while walking.
1032
+ const noMove = opts?.skipMove === true
1033
+ ? (rule: RotationRule) => actionForEval(rule, snap).kind !== 'move'
1034
+ : undefined;
1035
+ // halt_dps stays a filter in the combat scan; hoisting it here would skip heals.
1036
+ const raidPick = firstFiring(combatList, snap, (r) => {
1037
+ if (!isRaidRuleId(r.id)) return false;
1038
+ if (r.action.kind === 'halt_dps' || r.action.kind === 'keep_aura') return false;
1039
+ if (noMove && !noMove(r)) return false;
1040
+ return true;
1041
+ });
1042
+ if (raidPick) return raidPick;
1043
+ const kickPick = firstFiring(combatList, snap, (r) => {
1044
+ if (r.action.kind !== 'spell') return false;
1045
+ if (noMove && !noMove(r)) return false;
1046
+ return r.conditions.some(
1047
+ (c) => c.metric === 'castingPresent' && c.subject === 'target' && c.op === '==' && c.value === 1,
1048
+ );
1049
+ });
1050
+ if (kickPick) return kickPick;
1051
+ const isDispelRule = (r: RotationRule): boolean =>
1052
+ r.action.kind === 'spell'
1053
+ && r.conditions.some((c) => c.metric === 'dispellablePresent');
1054
+ const dispelPick = firstFiring(combatList, snap, (r) => {
1055
+ if (!isDispelRule(r)) return false;
1056
+ if (noMove && !noMove(r)) return false;
1057
+ return true;
1058
+ }) ?? firstFiring(rot.phases.recovery, snap, (r) => {
1059
+ if (!isDispelRule(r)) return false;
1060
+ if (noMove && !noMove(r)) return false;
1061
+ return true;
1062
+ });
1063
+ if (dispelPick) return dispelPick;
1064
+ // Party off-heal (healer dead or dry) before recovery self-heals. Recovery is own-HP
1065
+ // gated, so a topped hybrid never mended the tank. Cite: invent.ts HEALER_DRY_POWER_PCT.
1066
+ const offHealPick = firstFiring(combatList, snap, (r) => {
1067
+ if (r.action.kind !== 'spell') return false;
1068
+ if (noMove && !noMove(r)) return false;
1069
+ return r.conditions.some((c) => c.metric === 'healerPowerPct');
1070
+ });
1071
+ if (offHealPick) return offHealPick;
1072
+ const recovery = firstFiring(rot.phases.recovery, snap, noMove);
1073
+ // halt_dps is a school filter, not a recovery exclusive — fall through so combat can swap.
1074
+ if (recovery && recovery.kind !== 'halt_dps') return recovery;
1075
+
1076
+ const pullRules = rot.phases.pull;
1077
+ const enabledPull = pullRules.reduce((n, r) => n + (r.enabled ? 1 : 0), 0);
1078
+ // Per-unit combat: OOC units stay in pull (Charge) even if the session clock is old.
1079
+ // Absent inCombat → legacy snapshots keep the combatTimeMs window only.
1080
+ const inOpener = snap.inCombat === false || snap.combatTimeMs < pullWindowMs(enabledPull);
1081
+ // A pull needs a live target. OOC (target null) the opener re-armed every idle tick
1082
+ // (pull-spent clears), so pull Battle Stance vs combat Defensive Stance danced forever.
1083
+ if (inOpener && enabledPull > 0 && snap.target != null) {
1084
+ const step = pullSequenceAction(pullRules, snap);
1085
+ if (step.kind === 'fire') return step.action;
1086
+ // Wait out GCD/range for the next opener spell — don't let combat steal the GCD.
1087
+ if (step.kind === 'wait') return null;
1088
+ // done → fall through to combat inside the window
1089
+ }
1090
+ // After-cast follow-up: the "then cast this" chain, one step per GCD, still under recovery. A step
1091
+ // whose OWN gate is false is SKIPPED, so "then Rejuvenation if it has under 2s left" hands the GCD
1092
+ // to the next step instead of stalling the chain.
1093
+ for (const step of snap.followUpChain ?? []) {
1094
+ const follow = step.spellId | 0;
1095
+ if (!(follow > 0) || !snap.usableSpell(follow)) continue;
1096
+ if (snap.target?.immuneSpellIds?.has(follow)) continue;
1097
+ if (step.conditions?.length && !conditionsPass(snap, step.conditions)) continue;
1098
+ // A follow-up a `before` clause armed IS that rule's action, so it still owes the rule's own
1099
+ // after-cast. Only a before-owning rule re-attaches, or two rows could arm each other forever.
1100
+ const owner = combatList.find((r) => r.enabled && r.before?.spellId
1101
+ && r.action.kind === 'spell' && r.action.spellId === follow);
1102
+ const ownerAfter = owner ? ruleAfterOf(owner) : undefined;
1103
+ return { kind: 'spell', spellId: follow, ...(ownerAfter ? { after: ownerAfter } : {}) };
1104
+ }
1105
+ return firstFiring(combatList, snap, noMove);
1106
+ }
1107
+
1108
+ /** Choose an out-of-combat action (buff / conjure / summon / feed), or null when nothing applies. */
1109
+ export function chooseOocAction(rot: Rotation, snap: RotationSnapshot): RuleAction | null {
1110
+ const buffs = firstFiring(rot.phases.buffs, snap, undefined, false);
1111
+ if (buffs) return buffs;
1112
+ // Mend Pet / Health Funnel live in recovery (combat-first), but the pet is still
1113
+ // hurt between pulls. Only pet-subject rules — do not quaff panic CDs OOC.
1114
+ return firstFiring(rot.phases.recovery, snap, (r) => r.conditions.some((c) => c.subject === 'pet'), false);
1115
+ }