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,1823 @@
1
+ import { formStanceMask } from '../../domain/character/forms.ts';
2
+ import { SPIRIT_OF_REDEMPTION_TALENT_SPELL_ID, SPIRIT_OF_REDEMPTION_AURA_SPELL_ID, SUICIDE_SPELL_ID } from '../spells/cmangos-special-cases.ts';
3
+ // Peeled from createSession (Wave 4 hub: procsDamage). Pattern (D) alias install.
4
+ // Extracted modules must not import combat.ts.
5
+
6
+ import type { CombatContext } from './context.ts';
7
+ import type { Fighter } from './types.ts';
8
+ import { maybeAggroZonePulse, touchCombatFlag } from './combatFlag.ts';
9
+ import type { Mechanic, School, SpellDef } from '../../data/schema.ts';
10
+ import { MECHANIC_ID } from '../../domain/creature/creatureAiConstants.ts';
11
+ import type { Side, EnemySide } from '../core/events.ts';
12
+ import { isEnemySide, isAllySide, isDuelistSide, isPlayerCharacterSide } from '../core/events.ts';
13
+ import {
14
+ TUNING,
15
+ critMultiplierWithVersus,
16
+ rollBetween,
17
+ armorMitigation,
18
+ rewardRage,
19
+ spellCritChance,
20
+ ccResistChance,
21
+ } from './formulas.ts';
22
+ import { fighterSpellCrit, fighterEffectiveArmor, applyBuffMod } from './fighterFactory.ts';
23
+ import {
24
+ powerOfType, maxPowerOfType, setPowerOfType, casterPowerTypeId, powerTypeName,
25
+ } from './powerPools.ts';
26
+ import {
27
+ tryProc, procCooldownReadyAt, procExFromOutcome, meleeSwingDealFlags, killDealFlags,
28
+ meleeAbilityDealFlags, harmfulSpellDealFlags, rangedAttackDealFlags, rangedAbilityDealFlags,
29
+ meleeSwingTakeFlags, meleeAbilityTakeFlags, harmfulSpellTakeFlags,
30
+ schoolMaskFromSchool, SCHOOL_MASK,
31
+ type ProcEventContext, type ProcFlagsHint,
32
+ } from '../spells/procs.ts';
33
+ import { consumeProcCharge } from '../spells/auraStacking.ts';
34
+ import { combustionEffectOf, combustionOnSpellLand } from './combustion.ts';
35
+ import { COMBUSTION_SPELL_ID } from '../spells/cmangos-special-cases-combustion.ts';
36
+ import {
37
+ applySchoolAbsorb,
38
+ applyManaShield,
39
+ applySplitDamagePct,
40
+ applySplitDamageFlat,
41
+ } from '../spells/absorbs.ts';
42
+ import { isImmuneToDamage } from '../spells/immunity.ts';
43
+ import { isTrivialLevelDifference } from '../../progression/xpGain.ts';
44
+ import { spellCanCrit, SPELL_ATTR_EX3, hasAttrEx3, SPELL_ATTR, hasAttr, SPELL_ATTR_EX4, hasAttrEx4, spellPiercesSchoolImmunity } from '../spells/spellAttributes.ts';
45
+ import {
46
+ effectiveMagicResistancePercent,
47
+ resolveSpellBinary,
48
+ rollMagicResistanceMultiplier,
49
+ } from './mitigation.ts';
50
+ import { PROC_FLAGS_TAKE_MELEE, isDrainSoulSpell, gearProcIsOffensive, isHostileBuffEffect } from './predicates.ts';
51
+ import {
52
+ BLACKOUT_STUN_SPELL_ID,
53
+ blackoutProcAllowed,
54
+ RETALIATION_AURA_SPELL_ID,
55
+ SETUP_PROC_COOLDOWN_MS,
56
+ setupProcAllowed,
57
+ } from '../spells/interpreter/class-scripts-kit.ts';
58
+ import { buffProcChargesForSpell } from './cmangosCastGates.ts';
59
+ import { canUseEquippedWeapon } from './equippedItemGate.ts';
60
+ import { PROC_FLAG } from '../spells/procs.ts';
61
+ import { resolveGearProcApplySpell, tryItemChanceOnHit } from '../spells/interpreter/index.ts';
62
+ import { applySpellDamageBonusIfPresent } from '../spells/spellBonus.ts';
63
+ import {
64
+ IMPROVED_SAP_TRIGGER_SPELL_ID,
65
+ SETUP_TRIGGER_SPELL_ID,
66
+ syntheticTalentTriggerPayload,
67
+ } from '../spells/interpreter/class-scripts-kit.ts';
68
+ import {
69
+ EYE_FOR_AN_EYE_DAMAGE_SPELL_ID,
70
+ DEEP_WOUND_DOT_DURATION_MS,
71
+ DEEP_WOUND_DOT_SPELL_ID,
72
+ DEEP_WOUND_DOT_TICK_MS,
73
+ ENTRAPMENT_RANK1_SPELL_ID,
74
+ ENTRAPMENT_RANK2_SPELL_ID,
75
+ ENTRAPMENT_RANK3_SPELL_ID,
76
+ ENTRAPMENT_ROOT_DURATION_MS,
77
+ ENTRAPMENT_ROOT_SPELL_ID,
78
+ FROST_TRAP_AURA_FOR_ENTRAPMENT_SPELL_ID,
79
+ IGNITE_DOT_SPELL_ID,
80
+ PYROCLASM_STUN_DURATION_MS,
81
+ PYROCLASM_STUN_SPELL_ID,
82
+ deepWoundsAverageWeaponDamage,
83
+ deepWoundsTickBasepoints,
84
+ entrapmentChanceFromAuraIds,
85
+ entrapmentRootFires,
86
+ eyeForAnEyeReflectAmount,
87
+ igniteBasepoints,
88
+ isDeferredFinisherComboTrigger,
89
+ pyroclasmChancePct,
90
+ pyroclasmStunFires,
91
+ pyroclasmTickCountForFamilyFlags,
92
+ DEADLY_SWIFTNESS_SPELL_ID,
93
+ SPEED_BONUS_10_PCT_SPELL_ID,
94
+ deadlySwiftnessVictimEligible,
95
+ } from '../spells/cmangos-special-cases.ts';
96
+ import {
97
+ MANA_DRAIN_SELF_GAIN_SPELL_ID,
98
+ MANA_DRAIN_TRIGGER_SPELL_ID,
99
+ MANA_DRAIN_VICTIM_DRAIN_SPELL_ID,
100
+ obsidianArmorTriggeredSpellId,
101
+ } from '../spells/cmangos-special-cases-s19.ts';
102
+ import {
103
+ adaptiveWardingTriggeredSpellId,
104
+ CHEAT_DEATH_HEALING_TAKEN_SPELL_ID,
105
+ cheatDeathShouldProc,
106
+ MAGE_ARMOR_SPELL_IDS,
107
+ masterOfElementsRefundAmount,
108
+ MASTER_OF_ELEMENTS_TRIGGER_SPELL_ID,
109
+ } from '../spells/cmangos-special-cases-s20.ts';
110
+ import { combatDistanceYd } from './combatGeometry.ts';
111
+ import {
112
+ HUNTER_TRAP_RADIUS_YD,
113
+ isHunterTrapTriggerSpellId,
114
+ syntheticHunterTrapPayload,
115
+ } from '../spells/interpreter/hunter-kit.ts';
116
+
117
+ // The victim-side proc flags for a landed damage packet. Melee and ranged share the take bits while
118
+ // magic and dots use the harmful-spell ones, and any real HP hit always sets take-any-damage.
119
+ function takeDamageEventFlags(
120
+ damageType: string | undefined,
121
+ opts?: { ranged?: boolean; spell?: SpellDef | null },
122
+ ): number {
123
+ let flags = PROC_FLAG.TAKE_ANY_DAMAGE;
124
+ if (damageType === 'weapon') {
125
+ // Ability (Sinister Strike) vs white swing: onNextSwing still rides the white table.
126
+ if (opts?.spell && !opts.spell.onNextSwing) flags |= PROC_FLAG.TAKE_MELEE_ABILITY;
127
+ else flags |= PROC_FLAG.TAKE_MELEE_SWING;
128
+ } else if (damageType === 'spell' && opts?.ranged) {
129
+ flags |= PROC_FLAG.TAKE_RANGED_ATTACK | PROC_FLAG.TAKE_RANGED_ABILITY;
130
+ } else if (damageType === 'dot') {
131
+ flags |= PROC_FLAG.TAKE_HARMFUL_PERIODIC;
132
+ } else if (damageType === 'spell') {
133
+ flags |= PROC_FLAG.TAKE_HARMFUL_SPELL;
134
+ } else if (damageType == null || damageType === 'shield') {
135
+ // Unlabeled residual / shield feedback — only TAKE_ANY_DAMAGE.
136
+ } else {
137
+ // Unknown weapon-like residual (physical flat) — treat as melee take.
138
+ flags |= PROC_FLAG.TAKE_MELEE_SWING;
139
+ }
140
+ return flags;
141
+ }
142
+
143
+
144
+ export type installProcsDamageDeps = {
145
+ dead: (f: Fighter) => boolean;
146
+ gainRage: (f: Fighter, amount: number) => void;
147
+ };
148
+
149
+ export function installProcsDamage(ctx: CombatContext, deps: installProcsDamageDeps): void {
150
+ const push = (e: any) => ctx.push(e);
151
+ const enemyCastBySide = ctx.enemyCastBySide;
152
+ const player = ctx.player;
153
+ const pet = ctx.pet;
154
+ const effects = ctx.effects;
155
+ const rng = ctx.rng;
156
+ const tickMs = ctx.tickMs;
157
+ const livingFriendlies = ctx.livingFriendlies;
158
+ const fightersOrder = ctx.fightersOrder;
159
+ // Side -> unit is one store read (E1.1). The order array answers it too, by walking every
160
+ // fighter, and a reachable second answer is the shape this phase exists to remove.
161
+ const sideFighter = ctx.sideFighter;
162
+ const isCharmed = ctx.isCharmed;
163
+ const applyDamageThreat = (...args: any[]) => (ctx.applyDamageThreat as Function)(...args);
164
+ const breakCharm = (...args: any[]) => (ctx.breakCharm as Function)(...args);
165
+ const applyControl = (...args: any[]) => (ctx.applyControl as Function)(...args);
166
+ const applyDr = (...args: any[]) => (ctx.applyDr as Function)(...args);
167
+ const ccImmune = (...args: any[]) => (ctx.ccImmune as Function)(...args);
168
+ const onDamaged = (...args: any[]) => (ctx.onDamaged as Function)(...args);
169
+ const applySpell = (...args: any[]) => (ctx.applySpell as Function)(...args);
170
+ const dead = deps.dead;
171
+ const gainRage = deps.gainRage;
172
+
173
+ const makeProcCtx = (
174
+ src: Fighter,
175
+ wasCrit: boolean,
176
+ flagsHint: ProcFlagsHint = 'spell',
177
+ fromSpell?: SpellDef | null,
178
+ ): ProcEventContext => {
179
+ const t = ctx.t;
180
+ const procFlags =
181
+ flagsHint === 'melee_swing' ? meleeSwingDealFlags()
182
+ : flagsHint === 'melee_ability' ? meleeAbilityDealFlags()
183
+ : flagsHint === 'ranged_attack' ? rangedAttackDealFlags()
184
+ : flagsHint === 'ranged_ability' ? rangedAbilityDealFlags()
185
+ : flagsHint === 'kill' ? killDealFlags()
186
+ : harmfulSpellDealFlags();
187
+ const procCtx: ProcEventContext = {
188
+ procFlags,
189
+ procEx: procExFromOutcome(wasCrit ? 'crit' : 'hit'),
190
+ weaponSpeedMs: src.spec.weapon.speedMs,
191
+ nowMs: t,
192
+ outdoors: ctx.cfg.outdoors,
193
+ };
194
+ if (fromSpell) {
195
+ procCtx.schoolMask = schoolMaskFromSchool(fromSpell.school);
196
+ if (fromSpell.spellFamilyName != null) procCtx.spellFamilyName = fromSpell.spellFamilyName;
197
+ if (fromSpell.spellFamilyFlags != null) procCtx.spellFamilyFlags = fromSpell.spellFamilyFlags;
198
+ } else if (flagsHint !== 'spell') {
199
+ // A weapon hit without a spellInfo → NORMAL school (SpellMgr.cpp ~899–903).
200
+ procCtx.schoolMask = SCHOOL_MASK.NORMAL;
201
+ }
202
+ return procCtx;
203
+ };
204
+
205
+ // The chip shows crits left and stacks up. EVERY 11129 row is synced: the inert DUMMY holder
206
+ // carries procCharges too, and the view reads the MAX. Cite: sessionView chargesBySpell.
207
+ const syncCombustionChip = (src: Fighter): void => {
208
+ for (const e of ctx.effectsOn(src.side)) {
209
+ if (e.spellId !== COMBUSTION_SPELL_ID) continue;
210
+ e.charges = src.combustionCharges;
211
+ e.stacks = src.combustionStacks;
212
+ ctx.touchEffect(e);
213
+ }
214
+ };
215
+
216
+ // Combustion ends: both holders leave the bar together. Cite: Unit.cpp:5372-5375.
217
+ const dropCombustionAuras = (src: Fighter, stackSpellId: number): void => {
218
+ for (const id of [COMBUSTION_SPELL_ID, stackSpellId]) {
219
+ src.buffActive.delete(id);
220
+ for (let i = effects.length - 1; i >= 0; i--) {
221
+ const e = effects[i]!;
222
+ if (e.spellId !== id || e.target !== src.side) continue;
223
+ effects.splice(i, 1);
224
+ push({ t: ctx.t, kind: 'aura', actor: src.side, name: e.name, applied: false });
225
+ }
226
+ }
227
+ };
228
+
229
+ // After a successful aura proc fire, decrement charges on matching source auras and remove at 0. Cite:
230
+ // SpellEntry.procCharges; auraStacking.consumeProcCharge.
231
+ const consumeAuraChargesOnProc = (src: Fighter, triggerSpellId: number): void => {
232
+ // Prefer aura whose spellId equals the triggered spell; else first charged buff on src.
233
+ // DropAuraCharge removes the whole holder (not one effect row). Cite: SpellAuras.cpp:5750.
234
+ const onSrc = ctx.effectsOn(src.side);
235
+ let e = onSrc.find(
236
+ (row) => row.kind === 'buff' && row.charges != null && row.spellId === triggerSpellId,
237
+ );
238
+ if (!e) {
239
+ e = onSrc.find(
240
+ (row) => row.kind === 'buff' && row.charges != null,
241
+ );
242
+ }
243
+ if (!e) return;
244
+ const { remaining, remove } = consumeProcCharge(e.charges);
245
+ if (remove) {
246
+ // DropAuraCharge removes the whole holder, not one effect row.
247
+ ctx.removeAurasDueToSpell?.(src, e.spellId);
248
+ } else {
249
+ e.charges = remaining;
250
+ }
251
+ };
252
+
253
+
254
+ // RemoveAuraHolderFromStack on proc. Cite: UnitAuraProcHandler.cpp.
255
+ const consumeAuraStacksOnProc = (src: Fighter, stackSpellId: number): void => {
256
+ const idx = effects.findIndex(
257
+ (e) => e.target === src.side && e.kind === 'buff' && e.stacks != null && e.spellId === stackSpellId,
258
+ );
259
+ if (idx < 0) return;
260
+ const e = effects[idx]!;
261
+ if (e.stacks != null && e.stacks > 1) {
262
+ e.stacks = e.stacks - 1;
263
+ ctx.touchEffect(e);
264
+ } else {
265
+ effects.splice(idx, 1);
266
+ push({ t: ctx.t, kind: 'aura', actor: src.side, name: e.name, applied: false });
267
+ }
268
+ };
269
+
270
+ // Proc support: match the flags and the per-minute rate, then fire the trigger spell's effects as a
271
+ // free, instant, no-cost side effect. The injected rng keeps it deterministic, and procs do not nest.
272
+ const triggerProcs = (
273
+ src: Fighter,
274
+ tgt: Fighter,
275
+ wasCrit: boolean,
276
+ flagsHint: ProcFlagsHint = 'spell',
277
+ fromSpell?: SpellDef | null,
278
+ ): void => {
279
+ const t = ctx.t;
280
+ // SUPPRESS_CASTER_PROCS zeroes the attacker mask for this cast, so the CASTER's own procs do not
281
+ // fire from it. 12 rows, including the Hunter's Mark ranks and Righteous Fury.
282
+ if (fromSpell && hasAttrEx3(fromSpell, SPELL_ATTR_EX3.SUPPRESS_CASTER_PROCS)) return;
283
+ // Combustion is a DUMMY proc with no trigger row, so it runs ahead of the trigger loop and
284
+ // outlives a caster with no procs at all. Cite: UnitAuraProcHandler.cpp:991-1003.
285
+ if (src.combustionCharges > 0 && flagsHint === 'spell' && fromSpell) {
286
+ const ce = combustionEffectOf(src);
287
+ if (ce) {
288
+ const ended = combustionOnSpellLand(src, ce, fromSpell.school, wasCrit);
289
+ if (ended) dropCombustionAuras(src, ce.stackSpellId);
290
+ else syncCombustionChip(src);
291
+ }
292
+ }
293
+ const procs = src.procs || [];
294
+ if (!procs.length) return;
295
+ const procCtx = makeProcCtx(src, wasCrit, flagsHint, fromSpell);
296
+ for (const p of procs) {
297
+ if (!p.triggerSpellId) continue;
298
+ // A form-gated talent aura is not on the body outside its form, so it cannot proc there.
299
+ // Cite: DBCStructure.h:864 CanBeUsedInForm; SpellAuras.cpp:4296-4304.
300
+ if (p.stances && !(p.stances & formStanceMask(src.form))) continue;
301
+ // Blackout: not from heals / non-damage. Cite: Priest.cpp:50-51 OnCheckProc.
302
+ if (p.triggerSpellId === BLACKOUT_STUN_SPELL_ID && !blackoutProcAllowed(fromSpell)) {
303
+ continue;
304
+ }
305
+ // Unstable Power (24658) consumes 24659 stack on proc. Cite: UnitAuraProcHandler.cpp case 24658.
306
+ if (p.triggerSpellId === 24658) {
307
+ if (!fromSpell || fromSpell.id === 24659) continue;
308
+ consumeAuraStacksOnProc(src, 24659);
309
+ continue;
310
+ }
311
+ const readyAt = src.procCooldowns.get(p.triggerSpellId) ?? 0;
312
+ const fired = tryProc({
313
+ procFlags: p.procFlags,
314
+ procEx: p.procEx,
315
+ chance: p.chance,
316
+ ppm: p.ppm,
317
+ customChance: p.customChance,
318
+ cooldownReadyAt: readyAt,
319
+ cooldownMs: p.cooldownMs,
320
+ schoolMask: p.schoolMask,
321
+ spellFamilyName: p.spellFamilyName,
322
+ spellFamilyMask: p.spellFamilyMask,
323
+ },
324
+ procCtx,
325
+ rng,
326
+ );
327
+ if (!fired) {
328
+ // PROC_FAILURE_BURNS_CHARGE, on 12 rows: a failed roll still spends a charge. Without it a
329
+ // charged Windfury holder could roll forever on one charge.
330
+ if (p.failureBurnsCharge === true) consumeAuraChargesOnProc(src, p.triggerSpellId);
331
+ continue;
332
+ }
333
+ // Illumination: holy spell crit (heals/damage) -> refund original manaCost via 20272.
334
+ // Cite: UnitAuraProcHandler.cpp HandleProcTriggerSpellAuraProc SPELLFAMILY_PALADIN Illumination.
335
+ if (p.spellIconId === 241 && fromSpell) {
336
+ let originalSpellId = fromSpell.id;
337
+ if (originalSpellId === 25914) originalSpellId = 20473;
338
+ else if (originalSpellId === 25913) originalSpellId = 20929;
339
+ else if (originalSpellId === 25903) originalSpellId = 20930;
340
+
341
+ const originalSpell = src.spec.spells.find((s) => s.id === originalSpellId);
342
+ const manaCost = originalSpell?.cost ?? 0;
343
+ if (manaCost > 0) {
344
+ const trigId = 20272; // Illumination refund trigger spell
345
+ const trigTemplate = ctx.spellOf?.(trigId);
346
+ if (trigTemplate) {
347
+ const scaled = {
348
+ ...trigTemplate,
349
+ effects: trigTemplate.effects.map((e) => (
350
+ e.kind === 'energize' ? { ...e, min: manaCost, max: manaCost } as typeof e : e
351
+ )),
352
+ };
353
+ applyProc(src, src, scaled);
354
+ }
355
+ }
356
+ continue;
357
+ }
358
+ // Kit first, then the global store (E6.0) - a PROC_TRIGGER payload in no kit is still real.
359
+ // Cite: Unit.cpp:1447-1449 sSpellTemplate.LookupEntry.
360
+ const tid = p.triggerSpellId | 0;
361
+ let trigSpell = src.spec.spells.find((s) => (s.id | 0) === tid)
362
+ ?? ctx.spellOf?.(tid);
363
+ if (!trigSpell || (tid === SETUP_TRIGGER_SPELL_ID
364
+ && !trigSpell.effects.some((e) => e.kind === 'combo_points'))) {
365
+ trigSpell = syntheticTalentTriggerPayload(tid) ?? trigSpell;
366
+ }
367
+ if (!trigSpell) continue;
368
+ if (src.sitting && !hasAttr(trigSpell, SPELL_ATTR.ALLOW_WHILE_SITTING)) continue;
369
+ const cdAt = procCooldownReadyAt(t, p.cooldownMs);
370
+ if (cdAt != null) src.procCooldowns.set(p.triggerSpellId, cdAt);
371
+ // 14093 / 15250: scripted payloads, not the inline damage/buff loop.
372
+ // Cite: Rogue.cpp:105–109; SpellEffects.cpp:4213–4222; sql/scriptdev2/spell.sql 14093.
373
+ if (tid === IMPROVED_SAP_TRIGGER_SPELL_ID || tid === SETUP_TRIGGER_SPELL_ID) {
374
+ applyProc(src, tid === IMPROVED_SAP_TRIGGER_SPELL_ID ? src : tgt, trigSpell);
375
+ consumeAuraChargesOnProc(src, tid);
376
+ continue;
377
+ }
378
+ // Seal Fate and Ruthlessness attach to the current ability cast. A white swing has no generic
379
+ // spell in flight, so the proc fails; an ability defers the points until after it finishes.
380
+ if (isDeferredFinisherComboTrigger(trigSpell.id)) {
381
+ if (!fromSpell) {
382
+ // No current generic spell (white swing / pure auto) — fail the proc.
383
+ continue;
384
+ }
385
+ const cp = trigSpell.comboPointsGenerated
386
+ ?? (trigSpell.effects.find((e) => e.kind === 'combo_points')?.amount ?? 1);
387
+ src.pendingFinisherComboPoints += Math.max(1, cp | 0);
388
+ consumeAuraChargesOnProc(src, p.triggerSpellId);
389
+ continue;
390
+ }
391
+ // A next-cast modifier has to go through applySpell so the pending mod is armed and the prior
392
+ // chip dropped. The inline loop below cannot consume one.
393
+ if (trigSpell.effects.some((e) => e.kind === 'next_cast_mod')) {
394
+ applyProc(src, src, trigSpell);
395
+ consumeAuraChargesOnProc(src, p.triggerSpellId);
396
+ continue;
397
+ }
398
+ // Hostile buffs (Shadow Vulnerability / Vindication) apply to tgt via applySpell.
399
+ // The inline buff branch always landed on src. Cite: SpellTargetDefines TARGET_UNIT_ENEMY=6.
400
+ if (trigSpell.effects.some((e) => e.kind === 'buff' && isHostileBuffEffect(e, e.amount ?? 0))) {
401
+ applyProc(src, tgt, trigSpell);
402
+ consumeAuraChargesOnProc(src, p.triggerSpellId);
403
+ continue;
404
+ }
405
+ // Apply its effects directly (bypass cost/gcd/cd).
406
+ for (const eff of trigSpell.effects) {
407
+ if (eff.kind === 'direct_damage') {
408
+ let r = rollBetween(eff.min, eff.max, rng.next());
409
+ r = applySpellDamageBonusIfPresent(r, trigSpell, eff.effectSlot ?? 0, {
410
+ spellPower: src.spec.spellPower,
411
+ spellDamageBySchool: src.spec.spellDamageBySchool,
412
+ school: eff.school,
413
+ attackPower: src.attackPower,
414
+ damagePct: src.damagePctBySchool[eff.school],
415
+ });
416
+ const critHere = spellCanCrit(trigSpell)
417
+ && rng.chance(spellCritChance(fighterSpellCrit(src), src.spec.level, tgt.spec.level, trigSpell.dmgClass));
418
+ if (critHere) r *= critMultiplierWithVersus(TUNING.spellCritMult, src.critDamageVersusPct, tgt.creatureType);
419
+ const bin = resolveSpellBinary(trigSpell);
420
+ const amount = dealDamage(tgt, r, eff.school, src.spec.level, {
421
+ binary: bin, damageType: 'spell', attacker: src,
422
+ });
423
+ push({ t, kind: 'spell_hit', actor: src.side, target: tgt.side, spellId: trigSpell.id, name: trigSpell.name, result: critHere ? 'crit' : 'hit', amount, school: eff.school });
424
+ } else if (eff.kind === 'heal') {
425
+ if (!dead(src)) {
426
+ const amt = Math.round(rollBetween(eff.min, eff.max, rng.next()));
427
+ if (Number.isFinite(amt) && amt > 0) {
428
+ src.hp = Math.min(src.spec.maxHealth, Math.round(src.hp + amt));
429
+ push({ t, kind: 'heal', actor: src.side, amount: amt });
430
+ }
431
+ }
432
+ } else if (eff.kind === 'buff') {
433
+ // Refresh-or-add, as the apply path does. Pushing a new row per proc gave a fury companion
434
+ // 19 concurrent Flurry rows, and reading charges off the bake meant they never expired.
435
+ const charges = buffProcChargesForSpell(trigSpell.id, trigSpell.procCharges);
436
+ const live = ctx.effectsOn(src.side).find(
437
+ (x) => x.kind === 'buff' && x.spellId === trigSpell.id
438
+ && x.source === src.side && x.mod === eff.mod,
439
+ );
440
+ if (live) {
441
+ live.expiresAt = t + eff.durationMs;
442
+ live.amount = eff.amount;
443
+ if (charges != null) live.charges = charges; // proc re-arms the charge count
444
+ ctx.touchEffect(live);
445
+ } else {
446
+ applyBuffMod(src, eff.mod, eff.amount);
447
+ src.buffActive.add(trigSpell.id);
448
+ effects.push({
449
+ kind: 'buff', source: src.side, target: src.side, spellId: trigSpell.id, name: trigSpell.name,
450
+ mod: eff.mod, amount: eff.amount, expiresAt: t + eff.durationMs,
451
+ ...(charges != null ? { charges } : {}),
452
+ });
453
+ push({ t, kind: 'aura', actor: src.side, name: trigSpell.name, applied: true });
454
+ }
455
+ } else if (eff.kind === 'dot') {
456
+ effects.push({
457
+ kind: 'dot', source: src.side, target: tgt.side, spellId: trigSpell.id, name: trigSpell.name,
458
+ school: eff.school, min: eff.min, max: eff.max, nextTickAt: t + eff.tickMs, tickMs: eff.tickMs,
459
+ expiresAt: t + eff.durationMs,
460
+ });
461
+ push({ t, kind: 'aura', actor: tgt.side, name: trigSpell.name, applied: true });
462
+ } else if (eff.kind === 'cc') {
463
+ // Improved Kick / Counterspell / Shield Bash silence companions (aura 27).
464
+ // Cite: talent proc → EffectTriggerSpell silence spell; SpellAuras HandleAuraModSilence.
465
+ if (ccImmune(tgt, eff.mechanic, eff.onlyTypes)) {
466
+ push({ t, kind: 'cc_immune', target: tgt.side, control: eff.control, name: trigSpell.name });
467
+ continue;
468
+ }
469
+ {
470
+ let ccChance = ccResistChance(
471
+ tgt.resistances[eff.school] ?? 0,
472
+ src.spec.level,
473
+ tgt.spec.level,
474
+ );
475
+ const mechId = MECHANIC_ID[eff.mechanic as Mechanic] ?? 0;
476
+ const mechBonus = mechId ? (tgt.spec.mechanicResistPct?.[mechId] ?? 0) : 0;
477
+ if (mechBonus) ccChance = Math.min(0.9, ccChance + mechBonus / 100);
478
+ if (rng.chance(ccChance)) {
479
+ push({ t, kind: 'cc_resist', target: tgt.side, control: eff.control, name: trigSpell.name });
480
+ continue;
481
+ }
482
+ }
483
+ const durMs = applyDr(src, tgt, eff.mechanic, eff.durationMs);
484
+ if (durMs <= 0) {
485
+ push({ t, kind: 'cc_immune', target: tgt.side, control: eff.control, name: trigSpell.name });
486
+ continue;
487
+ }
488
+ applyControl(src, tgt, trigSpell, eff, durMs);
489
+ }
490
+ }
491
+ consumeAuraChargesOnProc(src, p.triggerSpellId);
492
+ }
493
+ };
494
+
495
+ // Defender-side Setup 15250 only. Cite: Rogue.cpp:112–118; UnitAuraProcHandler victim path.
496
+ const triggerTakenProcs = (
497
+ victim: Fighter,
498
+ attacker: Fighter,
499
+ outcome: 'dodge' | 'parry' | 'block' | 'resist',
500
+ flagsHint: ProcFlagsHint,
501
+ ): void => {
502
+ if (!victim.setupTriggerSpellId) return;
503
+ const procs = victim.procs || [];
504
+ const t = ctx.t;
505
+ const procFlags =
506
+ flagsHint === 'melee_swing' ? meleeSwingTakeFlags()
507
+ : flagsHint === 'melee_ability' ? meleeAbilityTakeFlags()
508
+ : flagsHint === 'ranged_attack' ? PROC_FLAG.TAKE_RANGED_ATTACK
509
+ : flagsHint === 'ranged_ability' ? PROC_FLAG.TAKE_RANGED_ABILITY
510
+ : harmfulSpellTakeFlags();
511
+ const procCtx: ProcEventContext = {
512
+ procFlags,
513
+ procEx: procExFromOutcome(outcome),
514
+ weaponSpeedMs: attacker.spec.weapon.speedMs,
515
+ nowMs: t,
516
+ schoolMask: SCHOOL_MASK.NORMAL,
517
+ outdoors: ctx.cfg.outdoors,
518
+ };
519
+ for (const p of procs) {
520
+ if (p.stances && !(p.stances & formStanceMask(victim.form))) continue;
521
+ // Setup only until other TAKE talents get a real applyProc path (rolling them here
522
+ // burns ICD without applying Blood Craze / Enrage / Shield Spec payloads).
523
+ if (p.triggerSpellId !== SETUP_TRIGGER_SPELL_ID) continue;
524
+ const foe = ctx.foeOf?.(victim);
525
+ if (!setupProcAllowed(foe?.side, attacker.side)) continue;
526
+ const readyAt = victim.procCooldowns.get(p.triggerSpellId) ?? 0;
527
+ const fired = tryProc({
528
+ procFlags: p.procFlags,
529
+ procEx: p.procEx,
530
+ chance: p.chance,
531
+ ppm: p.ppm,
532
+ customChance: p.customChance,
533
+ cooldownReadyAt: readyAt,
534
+ cooldownMs: SETUP_PROC_COOLDOWN_MS,
535
+ schoolMask: p.schoolMask,
536
+ spellFamilyName: p.spellFamilyName,
537
+ spellFamilyMask: p.spellFamilyMask,
538
+ }, procCtx, rng);
539
+ if (!fired) continue;
540
+ const trigSpell = victim.spec.spells.find((s) => s.id === p.triggerSpellId);
541
+ if (!trigSpell) continue;
542
+ if (victim.sitting && !hasAttr(trigSpell, SPELL_ATTR.ALLOW_WHILE_SITTING)) continue;
543
+ const cdAt = procCooldownReadyAt(t, SETUP_PROC_COOLDOWN_MS);
544
+ if (cdAt != null) victim.procCooldowns.set(p.triggerSpellId, cdAt);
545
+ const cpEff = trigSpell.effects.find((e) => e.kind === 'combo_points');
546
+ const cp = trigSpell.comboPointsGenerated ?? (cpEff && 'amount' in cpEff ? cpEff.amount : 0);
547
+ if (cp) ctx.addComboPoints?.(victim, attacker, Math.max(1, cp | 0));
548
+ }
549
+ };
550
+
551
+ // Apply a proc spell's effects directly (free, no cost/gcd/cd, instant). Temporarily
552
+ // clear procs on src to avoid recursive proc nesting from the applied spell's damage.
553
+ const applyProc = (src: Fighter, tgt: Fighter, spell: SpellDef): void => {
554
+ const t = ctx.t;
555
+ // A spell cast BY a proc is triggered, and an aura procs from one only when the PAYLOAD is flagged
556
+ // not-a-proc or the AURA may proc from procs. A proc with no holder keeps the blunt refusal.
557
+ const payloadIsNotAProc = hasAttrEx3(spell, SPELL_ATTR_EX3.NOT_A_PROC);
558
+ const savedP = src.procs;
559
+ const savedG = src.gearProcs;
560
+ if (!payloadIsNotAProc) {
561
+ src.procs = savedP.filter((p) => p.canProcFromProcs === true);
562
+ src.gearProcs = [];
563
+ }
564
+ applySpell(src, tgt, spell);
565
+ // The Frost Trap aura spring into the Entrapment root. The C++ rolls on every pulse; our trap model
566
+ // springs once on melee, which is the same gate.
567
+ if ((spell.id | 0) === FROST_TRAP_AURA_FOR_ENTRAPMENT_SPELL_ID && !dead(tgt)) {
568
+ const auraIds: number[] = [];
569
+ for (const e of ctx.effectsOn(src.side)) {
570
+ if (e.expiresAt > t && e.spellId) auraIds.push(e.spellId | 0);
571
+ }
572
+ // Passive talent may be kit-only until cast; include known rank ids on the kit.
573
+ for (const s of src.spec.spells) {
574
+ const sid = s.id | 0;
575
+ if (
576
+ sid === ENTRAPMENT_RANK1_SPELL_ID
577
+ || sid === ENTRAPMENT_RANK2_SPELL_ID
578
+ || sid === ENTRAPMENT_RANK3_SPELL_ID
579
+ ) {
580
+ auraIds.push(sid);
581
+ }
582
+ }
583
+ const chance = entrapmentChanceFromAuraIds(auraIds);
584
+ if (entrapmentRootFires(chance, rng.next())) {
585
+ const root =
586
+ src.spec.spells.find((s) => s.id === ENTRAPMENT_ROOT_SPELL_ID)
587
+ ?? {
588
+ id: ENTRAPMENT_ROOT_SPELL_ID,
589
+ name: 'Entrapment',
590
+ school: 'physical' as const,
591
+ effects: [{
592
+ kind: 'root' as const,
593
+ durationMs: ENTRAPMENT_ROOT_DURATION_MS,
594
+ mechanic: 'root' as const,
595
+ }],
596
+ };
597
+ applySpell(src, tgt, root);
598
+ }
599
+ }
600
+ src.procs = savedP;
601
+ src.gearProcs = savedG;
602
+ };
603
+
604
+ // Roll the item procs for on-hit and on-equip. The hit path uses the per-minute and cooldown rules
605
+ // and resolves empty-effect proc spells through the interpreter; the equip path is a flat chance.
606
+ const rollGearProcs = (
607
+ src: Fighter,
608
+ tgt: Fighter,
609
+ context: 'hit' | 'crit' | 'equip' = 'hit',
610
+ fromSpell?: SpellDef | null,
611
+ ): void => {
612
+ const t = ctx.t;
613
+ const gprocs = src.gearProcs || [];
614
+ if (!gprocs.length) return;
615
+ const procCtx: ProcEventContext = {
616
+ procFlags: context === 'equip' ? 0 : meleeSwingDealFlags() | harmfulSpellDealFlags(),
617
+ procEx: procExFromOutcome(context === 'crit' ? 'crit' : 'hit'),
618
+ weaponSpeedMs: src.spec.weapon.speedMs,
619
+ nowMs: t,
620
+ outdoors: ctx.cfg.outdoors,
621
+ };
622
+ if (fromSpell) {
623
+ procCtx.schoolMask = schoolMaskFromSchool(fromSpell.school);
624
+ if (fromSpell.spellFamilyName != null) procCtx.spellFamilyName = fromSpell.spellFamilyName;
625
+ if (fromSpell.spellFamilyFlags != null) procCtx.spellFamilyFlags = fromSpell.spellFamilyFlags;
626
+ } else if (context !== 'equip') {
627
+ procCtx.schoolMask = SCHOOL_MASK.NORMAL;
628
+ }
629
+ // An item's holder spell and its apply payload have no reason to be in the wearer's
630
+ // spellbook, so the kit answer is a fallback here, not the source. Cite: Unit.cpp:1447-1449.
631
+ const spellById = (id: number): SpellDef | undefined =>
632
+ src.spec.spells.find((s) => s.id === id) ?? ctx.spellOf?.(id);
633
+ for (const p of gprocs) {
634
+ if (!p.spell) continue;
635
+ const trig = p.trigger ?? 0;
636
+ const matches =
637
+ (context === 'equip' && trig === 1) ||
638
+ ((context === 'hit' || context === 'crit') && trig === 2);
639
+ if (!matches) continue;
640
+ const readyAt = src.procCooldowns.get(p.spell) ?? 0;
641
+ const holderSpell = spellById(p.spell);
642
+
643
+ // Equip procs: flat chance only (no PPM on equip). Unchanged roll surface.
644
+ if (context === 'equip') {
645
+ let ch = p.chancePct != null ? p.chancePct / 100 : undefined;
646
+ if (typeof ch !== 'number' || ch <= 0 || !rng.chance(ch)) continue;
647
+ if (!holderSpell) continue;
648
+ const cdAt = procCooldownReadyAt(t, p.cooldownMs);
649
+ if (cdAt != null) src.procCooldowns.set(p.spell, cdAt);
650
+ const applySpellDef = resolveGearProcApplySpell(holderSpell, spellById) ?? holderSpell;
651
+ if (!applySpellDef.effects.length) continue;
652
+ if (src.sitting && !hasAttr(applySpellDef, SPELL_ATTR.ALLOW_WHILE_SITTING)) continue;
653
+ applyProc(src, gearProcIsOffensive(applySpellDef) ? tgt : src, applySpellDef);
654
+ continue;
655
+ }
656
+
657
+ // On-hit / crit: SI-PROC subsystem (tryProcTriggerSpell via tryItemChanceOnHit).
658
+ // Rates from ItemDef.procs + holder spell_proc_event — never invented.
659
+ const roll = tryItemChanceOnHit(p, holderSpell, procCtx, rng, { cooldownReadyAt: readyAt });
660
+ if (!roll.fired) continue;
661
+ if (!holderSpell) continue;
662
+ const applySpellDef = resolveGearProcApplySpell(holderSpell, spellById);
663
+ if (!applySpellDef || !applySpellDef.effects.length) continue;
664
+ if (src.sitting && !hasAttr(applySpellDef, SPELL_ATTR.ALLOW_WHILE_SITTING)) continue;
665
+ if (roll.cooldownReadyAt != null) {
666
+ src.procCooldowns.set(p.spell, roll.cooldownReadyAt);
667
+ } else {
668
+ // Fallback ICD when meta has cooldown but tryProc only sets on fire+cd.
669
+ const cdAt = procCooldownReadyAt(t, p.cooldownMs ?? roll.meta.cooldownMs);
670
+ if (cdAt != null) src.procCooldowns.set(p.spell, cdAt);
671
+ }
672
+ applyProc(src, gearProcIsOffensive(applySpellDef) ? tgt : src, applySpellDef);
673
+ }
674
+ };
675
+
676
+ // The damage funnel: physical through armor, magic through the partial-resist table with gated RNG.
677
+ // A binary direct hit skips the table, since its full resist is on the land roll; a binary DoT does not.
678
+ const dealDamage = (
679
+ target: Fighter,
680
+ raw: number,
681
+ school: School,
682
+ attackerLevel: number,
683
+ opts?: {
684
+ binary?: boolean;
685
+ /** 'spell' | 'dot' | 'weapon' — see mitigation MagicDamageType. */
686
+ damageType?: 'spell' | 'dot' | 'weapon' | 'shield';
687
+ // True for a ranged weapon attack — SPELL_AURA_MOD_RANGED_DAMAGE_TAKEN (113) applies only to these.
688
+ // Cite: SpellAuras.cpp :174 "implemented in Unit::MeleeDamageBonusTaken".
689
+ ranged?: boolean;
690
+ attacker?: Fighter;
691
+ /** Spell that caused this damage (for spell_threat multiplier). */
692
+ spell?: SpellDef | null;
693
+ /** Crit outcome for CalcThreat crit path. */
694
+ crit?: boolean;
695
+ // The melee clean damage for the victim's rage reward — post-outcome, pre-block and adjusted for
696
+ // a glance or crush. It defaults to the raw amount, which is what the spell and dot paths want.
697
+ cleanDamage?: number;
698
+ },
699
+ ): number => {
700
+ const t = ctx.t;
701
+ if (target.buffActive.has(SPIRIT_OF_REDEMPTION_AURA_SPELL_ID)) return 0;
702
+ // Corpse / already-dead: no further damage (avoids NaN and post-death side effects).
703
+ if (dead(target)) return 0;
704
+ // Reject non-finite raw (missing effect min/max, broken bonus math) — never write NaN into HP.
705
+ if (!Number.isFinite(raw)) return 0;
706
+ let dmg = raw;
707
+ // Victim rage base: cleanDamage when weapon path supplies it (includes blocked / glance).
708
+ let rageRaw = opts?.cleanDamage != null && Number.isFinite(opts.cleanDamage)
709
+ ? opts.cleanDamage
710
+ : raw;
711
+ // Attacker penetration from SPELL_AURA_MOD_TARGET_RESISTANCE (Wave 2B).
712
+ // Cite: Unit.cpp GetResistancePenetration ~3890; CalcArmorReducedDamage ~2328.
713
+ const pen = opts?.attacker?.spec.resistancePenetration?.[school] ?? 0;
714
+ if (school === 'physical') {
715
+ // aura 101 TOTAL_PCT (Pierce Armor −50% / Stoneform +10%): scale armor before mitigation.
716
+ const effArmor = fighterEffectiveArmor(target);
717
+ const armorFactor = 1 - armorMitigation(effArmor, attackerLevel, pen);
718
+ dmg = dmg * armorFactor;
719
+ rageRaw = rageRaw * armorFactor;
720
+ if (!hasAttrEx4(opts?.spell, SPELL_ATTR_EX4.IGNORE_DAMAGE_TAKEN_MODIFIERS)) {
721
+ // Stoneskin etc.: flat physical taken mod (negative reduces damage). Applied after armor %.
722
+ if (target.physicalTakenMod) {
723
+ dmg = dmg + target.physicalTakenMod;
724
+ rageRaw = rageRaw + target.physicalTakenMod;
725
+ }
726
+ // Windwall Totem (aura 113 MOD_RANGED_DAMAGE_TAKEN): flat, ranged attacks only.
727
+ // Cite: Unit::MeleeDamageBonusTaken; spell_template 15108 EffectBasePoints -33.
728
+ if (opts?.ranged && target.rangedTakenMod) {
729
+ dmg = dmg + target.rangedTakenMod;
730
+ rageRaw = rageRaw + target.rangedTakenMod;
731
+ }
732
+ // Barkskin / form / aura 87 physical mask: percent taken after flat.
733
+ // Cite: Unit.cpp:7628 / :7634 TakenTotalMod ×= (100+amount)/100.
734
+ {
735
+ const takenPct =
736
+ (target.physicalTakenPctMod || 0)
737
+ + (target.damageTakenPctBySchool.physical ?? 0);
738
+ if (takenPct) {
739
+ const f = 1 + takenPct / 100;
740
+ dmg = dmg * f;
741
+ rageRaw = rageRaw * f;
742
+ }
743
+ }
744
+ }
745
+ } else {
746
+ // Magic: partial resist when canResist && (!binary || DOT) — Unit.cpp ~2357.
747
+ const binary = opts?.binary ?? false;
748
+ const damageType: import('./mitigation.ts').MagicDamageType =
749
+ opts?.damageType === 'dot' || opts?.damageType === 'weapon' || opts?.damageType === 'spell'
750
+ ? opts.damageType
751
+ : 'spell';
752
+ if (!binary || damageType === 'dot') {
753
+ const attacker = opts?.attacker;
754
+ const percent = effectiveMagicResistancePercent({
755
+ school,
756
+ resistance: target.resistances[school] ?? 0,
757
+ penetration: pen,
758
+ attackerLevel,
759
+ victimLevel: target.spec.level,
760
+ binary,
761
+ victimIsNpc: isEnemySide(target.side),
762
+ });
763
+ const resisted = rollMagicResistanceMultiplier({
764
+ percent,
765
+ binary,
766
+ damageType,
767
+ attackerPlayerControlled: attacker
768
+ ? (attacker.side === 'player' || attacker.side === 'pet' || isAllySide(attacker.side))
769
+ : true,
770
+ rng,
771
+ });
772
+ dmg = dmg * (1 - resisted);
773
+ rageRaw = rageRaw * (1 - resisted);
774
+ }
775
+ if (!hasAttrEx4(opts?.spell, SPELL_ATTR_EX4.IGNORE_DAMAGE_TAKEN_MODIFIERS)) {
776
+ // Dampen/Amplify Magic: flat spell taken mod (mirror physicalTakenMod). Negative = less taken.
777
+ if (target.spellTakenMod) {
778
+ dmg = dmg + target.spellTakenMod;
779
+ rageRaw = rageRaw + target.spellTakenMod;
780
+ }
781
+ // CoE / CoS / Stormstrike: school-scoped MOD_DAMAGE_PERCENT_TAKEN after flat.
782
+ // Cite: Unit.cpp:7628 GetTotalAuraMultiplierByMiscMask; KI-767.
783
+ {
784
+ const takenPct = target.damageTakenPctBySchool[school] ?? 0;
785
+ if (takenPct) {
786
+ const f = 1 + takenPct / 100;
787
+ dmg = dmg * f;
788
+ rageRaw = rageRaw * f;
789
+ }
790
+ }
791
+ }
792
+ }
793
+ // Template and aura immunities together, which replaces the bare school test while keeping the
794
+ // creature-mask path. Cite: Unit.cpp ~7310; immunity.ts isImmuneToDamage.
795
+ if (isImmuneToDamage(target.immunity, school, {
796
+ schoolImmuneMask: target.spec.schoolImmuneMask,
797
+ mechanicImmuneMask: target.spec.mechanicImmuneMask,
798
+ }, spellPiercesSchoolImmunity(opts?.spell))) {
799
+ return 0;
800
+ }
801
+ if (!Number.isFinite(dmg) || !Number.isFinite(rageRaw)) return 0;
802
+ dmg = Math.max(0, Math.round(dmg));
803
+ rageRaw = Math.max(0, rageRaw);
804
+ // Full block: HP packet 0 but cleanDamage > 0 → still victim rage (Unit.cpp:951–956 physical).
805
+ if (dmg <= 0) {
806
+ if (
807
+ school === 'physical'
808
+ && rageRaw > 0
809
+ && target.spec.powerType === 'rage'
810
+ && opts?.attacker !== target
811
+ ) {
812
+ gainRage(target, rewardRage(Math.round(rageRaw), target.spec.level, false));
813
+ }
814
+ return 0;
815
+ }
816
+
817
+ // Absorbs come AFTER resist and BEFORE HP, in the C++ order. The melee shield block stays on the
818
+ // weapon path only, and the absorbed amount also lowers the clean damage for taken rage.
819
+ const dmgSchoolMask = schoolMaskFromSchool(school);
820
+ let absorbed = 0;
821
+ if (target.schoolAbsorbs.length > 0 && dmg > 0) {
822
+ const sa = applySchoolAbsorb(target.schoolAbsorbs, dmgSchoolMask, dmg);
823
+ target.schoolAbsorbs = sa.auras;
824
+ // Drop broken school-absorb effects (amount depleted).
825
+ if (sa.absorbed > 0) {
826
+ absorbed += sa.absorbed;
827
+ rageRaw = Math.max(0, rageRaw - sa.absorbed);
828
+ const absorbMine = ctx.effectsOn(target.side);
829
+ for (let i = absorbMine.length - 1; i >= 0; i--) {
830
+ const e = absorbMine[i]!;
831
+ if (e.kind !== 'school_absorb') continue;
832
+ if (!target.schoolAbsorbs.some((a) => a.id === e.spellId)) {
833
+ target.buffActive.delete(e.spellId);
834
+ const idx = effects.lastIndexOf(e);
835
+ if (idx >= 0) effects.splice(idx, 1);
836
+ push({ t, kind: 'aura', actor: target.side, name: e.name, applied: false });
837
+ }
838
+ }
839
+ }
840
+ dmg = sa.leftover;
841
+ }
842
+ if (target.manaShields.length > 0 && dmg > 0) {
843
+ const manaNow = currentManaForShield(target);
844
+ const ms = applyManaShield(target.manaShields, dmgSchoolMask, dmg, manaNow);
845
+ target.manaShields = ms.auras;
846
+ if (ms.manaSpent > 0) setManaForShield(target, ms.mana);
847
+ if (ms.absorbed > 0) {
848
+ absorbed += ms.absorbed;
849
+ rageRaw = Math.max(0, rageRaw - ms.absorbed);
850
+ const shieldMine = ctx.effectsOn(target.side);
851
+ for (let i = shieldMine.length - 1; i >= 0; i--) {
852
+ const e = shieldMine[i]!;
853
+ if (e.kind !== 'mana_shield') continue;
854
+ if (!target.manaShields.some((a) => a.id === e.spellId)) {
855
+ target.buffActive.delete(e.spellId);
856
+ const idx = effects.lastIndexOf(e);
857
+ if (idx >= 0) effects.splice(idx, 1);
858
+ push({ t, kind: 'aura', actor: target.side, name: e.name, applied: false });
859
+ }
860
+ }
861
+ }
862
+ dmg = ms.leftover;
863
+ }
864
+ // Split damage flat (Blessing of Sacrifice) — before pct. Cite: Unit.cpp:2501–2537 then pct.
865
+ if (target.splitDamageFlat && dmg > 0) {
866
+ const sp = applySplitDamageFlat(target.splitDamageFlat, dmgSchoolMask, dmg, target.side);
867
+ if (sp.split > 0 && sp.redirectToId) {
868
+ const linked = sideFighter?.(sp.redirectToId as Side);
869
+ if (linked && !dead(linked)) {
870
+ const curL = Number.isFinite(linked.hp) ? linked.hp : 0;
871
+ linked.hp = Math.max(0, Math.round(curL - sp.split));
872
+ // SPLIT_DAMAGE DealDamage on the linked unit — aura interrupt, no channel delay.
873
+ // Cite: Unit.cpp:2537–2538, 1269–1270.
874
+ onDamaged(linked, sp.split, opts?.damageType, opts?.spell?.id, {
875
+ attacker: opts?.attacker,
876
+ skipChannelDelay: true,
877
+ });
878
+ }
879
+ // Split portion is dealt to the linked unit; victim cleanDamage follows leftover HP path.
880
+ rageRaw = Math.max(0, rageRaw - sp.split);
881
+ dmg = sp.leftover;
882
+ }
883
+ }
884
+ // Split damage (Soul Link) — structure only; redirect to linked unit when present.
885
+ if (target.splitDamage && dmg > 0) {
886
+ const sp = applySplitDamagePct(target.splitDamage, dmgSchoolMask, dmg, target.side);
887
+ if (sp.split > 0 && sp.redirectToId) {
888
+ const linked = sideFighter?.(sp.redirectToId as Side);
889
+ if (linked && !dead(linked)) {
890
+ // Apply split as residual HP hit without re-entering absorb stack for the same packet.
891
+ const curL = Number.isFinite(linked.hp) ? linked.hp : 0;
892
+ linked.hp = Math.max(0, Math.round(curL - sp.split));
893
+ onDamaged(linked, sp.split, opts?.damageType, opts?.spell?.id, {
894
+ attacker: opts?.attacker,
895
+ skipChannelDelay: true,
896
+ });
897
+ }
898
+ rageRaw = Math.max(0, rageRaw - sp.split);
899
+ dmg = sp.leftover;
900
+ }
901
+ }
902
+
903
+ // Full absorb of the HP packet: physical residual cleanDamage still yields taken rage.
904
+ // Cite: Unit.cpp:951–956 (damage==0 branch, SPELL_SCHOOL_MASK_NORMAL only).
905
+ if (dmg <= 0) {
906
+ if (
907
+ school === 'physical'
908
+ && rageRaw > 0
909
+ && target.spec.powerType === 'rage'
910
+ && opts?.attacker !== target
911
+ ) {
912
+ gainRage(target, rewardRage(Math.round(rageRaw), target.spec.level, false));
913
+ }
914
+ // takenOrAbsorbedDamage still strips AIF_DAMAGE auras / DAMAGE-flagged channels.
915
+ // InterruptOrDelaySpell does not run (no DelayedChannel). Cite: Unit.cpp:915–950, 953–960.
916
+ if (absorbed > 0) {
917
+ onDamaged(target, 0, opts?.damageType, opts?.spell?.id, {
918
+ absorbed,
919
+ attacker: opts?.attacker,
920
+ });
921
+ }
922
+ return 0;
923
+ }
924
+ // At least 1 residual damage reaches HP after absorbs only when leftover > 0.
925
+ dmg = Math.max(1, dmg);
926
+ const cur = Number.isFinite(target.hp) ? target.hp : 0;
927
+ let nextHp = Math.max(0, Math.round(cur - dmg));
928
+
929
+ if (nextHp <= 0 && target.buffActive.has(SPIRIT_OF_REDEMPTION_TALENT_SPELL_ID) && !target.buffActive.has(SPIRIT_OF_REDEMPTION_AURA_SPELL_ID) && opts?.spell?.id !== SUICIDE_SPELL_ID) {
930
+ nextHp = target.spec.maxHealth;
931
+ if (ctx.removeAllAurasOnDeath) ctx.removeAllAurasOnDeath(target);
932
+ const spiritSpell = ctx.spellOf?.(SPIRIT_OF_REDEMPTION_AURA_SPELL_ID);
933
+ if (spiritSpell && ctx.applySpell) ctx.applySpell(target, target, spiritSpell);
934
+ }
935
+
936
+ // UNKILLABLE / SetDeathPrevention — leave 1 HP. INSTAKILL sets hp=0 elsewhere.
937
+ // Cite: CreatureDefines.h UNKILLABLE=0x08; CreatureAI.cpp:130-135; Unit.cpp:1000-1001.
938
+ if (nextHp <= 0 && isEnemySide(target.side)) {
939
+ const cs = enemyCastBySide.get(target.side as EnemySide);
940
+ if (cs?.eventAi.deathPrevention || (((target.spec.staticFlags ?? 0) & 0x08) !== 0)) {
941
+ nextHp = 1;
942
+ if (cs && !cs.eventAi.deathPreventionFired) {
943
+ cs.eventAi.deathPreventionFired = true;
944
+ const acts = cs.scriptDev?.ir.lifecycle?.onPreventedDeath ?? [];
945
+ if (acts.length) ctx.applyScriptDevCombatActions?.(target, acts);
946
+ }
947
+ }
948
+ }
949
+ // A duel never kills: the killing blow floors the loser at 1 HP and ends the fight. The clamp lives
950
+ // HERE because finalize names the winner off the wipe flags, so a yield would report a timeout.
951
+ if (nextHp <= 0 && ctx.cfg.duel === true && isDuelistSide(target.side)
952
+ && opts?.attacker != null && opts.attacker !== target) {
953
+ nextHp = 1;
954
+ if (!ctx.ended) {
955
+ ctx.ended = true;
956
+ ctx.winner = target.side === 'player' ? 'enemy' : 'player';
957
+ push({ t, kind: 'flavor', text: `${target.spec.name} yields.` });
958
+ }
959
+ }
960
+ target.hp = nextHp;
961
+ // EventAI DamageTaken health-step AI events (90/50/10). Cite: CreatureEventAI.cpp:1841–1871.
962
+ ctx.notifyEventAiDamageTaken?.(target, cur, dmg);
963
+ // FrostWeakness / Freeze OnProc. Cite: boss_viscidus.cpp:552-565,422-428.
964
+ if (isEnemySide(target.side)) {
965
+ ctx.notifyScriptDevHitCount?.(target, {
966
+ school,
967
+ melee: opts?.damageType === 'weapon' && !opts?.ranged,
968
+ });
969
+ }
970
+ // EVENT_T_KILL fires the KILLER's AI once when the blow lands, not on the corpse sweep, because
971
+ // that is where the killer is known. 25 DB rows, 14 on dungeon creatures.
972
+ if (nextHp <= 0 && cur > 0 && opts?.attacker && opts.attacker !== target) {
973
+ const killer = opts.attacker;
974
+ if (isEnemySide(killer.side) && !isEnemySide(target.side)) ctx.notifyEventAiKill?.(killer, target);
975
+ // PROC_FLAG_KILL, at the killing blow for the same reason the AI hook is here. Most rows need
976
+ // the corpse to have been worth XP or honor. Cite: Unit.cpp:1080; UnitAuraProcHandler.cpp:536.
977
+ if (!isTrivialLevelDifference(killer.spec.level, target.spec.level)) {
978
+ triggerProcs(killer, target, false, 'kill', null);
979
+ }
980
+ }
981
+ // PvP combat timer: damage dealt/taken refreshes IsInCombat for both parties.
982
+ // Cite: Unit.cpp DealDamage → SetInCombatState; CombatManager TriggerCombatTimer(pvp).
983
+ touchCombatFlag(target, t);
984
+ if (opts?.attacker && opts.attacker !== target) touchCombatFlag(opts.attacker, t);
985
+ if (isEnemySide(target.side)) maybeAggroZonePulse(target, ctx.partyUnits(), t);
986
+ if (
987
+ target.hp <= 0
988
+ && opts?.spell
989
+ && isDrainSoulSpell(opts.spell)
990
+ && opts.attacker
991
+ && isPlayerCharacterSide(opts.attacker.side)
992
+ ) {
993
+ target.soulShardLethalCredit = opts.attacker.side;
994
+ }
995
+ onDamaged(target, dmg, opts?.damageType, opts?.spell?.id, {
996
+ absorbed,
997
+ attacker: opts?.attacker,
998
+ });
999
+ // Anastari Possess: break when possessed unit ≤ 50% max health.
1000
+ // Cite: boss_baroness_anastari.cpp AnastariPossess OnPeriodicTickEnd + AI ≤ .5f.
1001
+ if (
1002
+ isCharmed(target)
1003
+ && target.charmBreakHpFrac > 0
1004
+ && target.hp <= target.spec.maxHealth * target.charmBreakHpFrac
1005
+ ) {
1006
+ breakCharm(target);
1007
+ }
1008
+ // Threat: damage to enemy → dealer gains threat (Unit.cpp HandleDamageDealt).
1009
+ applyDamageThreat(opts?.attacker, target, dmg, !!opts?.crit, opts?.spell);
1010
+ // Rage from damage taken — Unit.cpp DealDamage victim path ~1233 (cleanDamage, not HP packet).
1011
+ // Attacker rage is only from melee DIRECT_DAMAGE (weaponStrike); spell damage dealt does not RewardRage.
1012
+ if (rageRaw > 0 && target.spec.powerType === 'rage' && opts?.attacker !== target) {
1013
+ gainRage(target, rewardRage(Math.round(rageRaw), target.spec.level, false));
1014
+ }
1015
+ // Damage shields and charged take-hit buffs. Skipped for shield retaliation damage so the two
1016
+ // cannot loop, and a dot is rejected by the flag match rather than by a special case.
1017
+ if (
1018
+ dmg > 0
1019
+ && opts?.attacker
1020
+ && opts.attacker !== target
1021
+ && opts.damageType !== 'shield'
1022
+ ) {
1023
+ // Charge consume runs even if attacker already died mid-packet; shields need a live foe.
1024
+ const hit = {
1025
+ damageType: opts.damageType,
1026
+ ranged: opts.ranged,
1027
+ spell: opts.spell ?? null,
1028
+ };
1029
+ if (!dead(opts.attacker)) {
1030
+ fireDamageShields(target, opts.attacker, hit);
1031
+ } else {
1032
+ // Attacker dead — still drop Inner Fire charges on the real damage packet.
1033
+ consumeChargedBuffsOnTakeHit(
1034
+ target,
1035
+ takeDamageEventFlags(hit.damageType, hit),
1036
+ );
1037
+ }
1038
+ }
1039
+ // Vampiric Embrace: shadow damage the VE CASTER deals to the afflicted target heals the caster and
1040
+ // party for a percent. That percent lives on the target, not on the caster.
1041
+ if (
1042
+ dmg > 0
1043
+ && opts?.attacker
1044
+ && target.vampiricEmbracePct > 0
1045
+ && target.vampiricEmbraceCasterSide != null
1046
+ && opts.attacker.side === target.vampiricEmbraceCasterSide
1047
+ && school === 'shadow'
1048
+ && (opts.damageType === 'spell' || opts.damageType === 'dot')
1049
+ && opts.attacker !== target
1050
+ ) {
1051
+ const caster = opts.attacker;
1052
+ const healAmt = Math.round((dmg * target.vampiricEmbracePct) / 100);
1053
+ if (healAmt > 0) {
1054
+ const casterFriendly = caster.side === 'player' || caster.side === 'pet' || isAllySide(caster.side);
1055
+ for (const fr of (casterFriendly ? livingFriendlies() : [caster])) {
1056
+ if (dead(fr)) continue;
1057
+ const before = fr.hp;
1058
+ fr.hp = Math.min(fr.spec.maxHealth, fr.hp + healAmt);
1059
+ if (fr.hp !== before) push({ t, kind: 'heal', actor: fr.side, amount: fr.hp - before, ...(fr !== caster && ctx.allyBlock().length > 0 ? { source: caster.side } : {}) });
1060
+ }
1061
+ }
1062
+ }
1063
+ // Eye for an Eye: magic damage taken → return pct% (damage+absorb) to attacker, cap 50% max HP.
1064
+ // Weapon specials (physical/melee DmgClass) do not proc. Cite: UnitAuraProcHandler.cpp:795–808.
1065
+ if (
1066
+ dmg > 0
1067
+ && target.eyeForAnEyePct > 0
1068
+ && opts?.attacker
1069
+ && opts.attacker !== target
1070
+ && opts.damageType !== 'shield'
1071
+ && opts.damageType !== 'weapon'
1072
+ && school !== 'physical'
1073
+ && !dead(opts.attacker)
1074
+ ) {
1075
+ const reflect = eyeForAnEyeReflectAmount(
1076
+ target.eyeForAnEyePct,
1077
+ dmg, // absorb already stripped above; C++ uses damage+absorb pre-HP — residual dmg is the landed part
1078
+ target.spec.maxHealth,
1079
+ );
1080
+ if (reflect > 0) {
1081
+ const before = opts.attacker.hp;
1082
+ opts.attacker.hp = Math.max(0, Math.round(opts.attacker.hp - reflect));
1083
+ if (opts.attacker.hp !== before) {
1084
+ push({
1085
+ t,
1086
+ kind: 'spell_hit',
1087
+ actor: target.side,
1088
+ target: opts.attacker.side,
1089
+ spellId: EYE_FOR_AN_EYE_DAMAGE_SPELL_ID,
1090
+ name: 'Eye for an Eye',
1091
+ result: 'hit',
1092
+ amount: before - opts.attacker.hp,
1093
+ school: 'holy',
1094
+ });
1095
+ }
1096
+ }
1097
+ }
1098
+ // Obsidian Armor: non-physical school damage taken → cast school resist buff on self.
1099
+ // Cite: UnitAuraProcHandler.cpp:910–930 case 27539 GetFirstSchoolInMask → 27533–27540.
1100
+ if (
1101
+ dmg > 0
1102
+ && target.obsidianArmorActive
1103
+ && opts?.attacker
1104
+ && opts.attacker !== target
1105
+ && school !== 'physical'
1106
+ ) {
1107
+ const trigId = obsidianArmorTriggeredSpellId(school);
1108
+ if (trigId > 0) {
1109
+ const trig =
1110
+ target.spec.spells.find((s) => s.id === trigId)
1111
+ ?? opts.attacker.spec.spells.find((s) => s.id === trigId);
1112
+ // Payload must be on the kit (bake may omit trinket triggers). Cite: 27533–27540.
1113
+ if (trig) applyProc(target, target, trig);
1114
+ }
1115
+ }
1116
+ // Adaptive Warding: Mage Armor gate + school resist buff (ignore physical/holy).
1117
+ // Cite: UnitAuraProcHandler.cpp:870–907 case 28764.
1118
+ if (
1119
+ dmg > 0
1120
+ && target.adaptiveWardingActive
1121
+ && opts?.attacker
1122
+ && opts.attacker !== target
1123
+ && school !== 'physical'
1124
+ && school !== 'holy'
1125
+ ) {
1126
+ const hasMageArmor = MAGE_ARMOR_SPELL_IDS.some((id) => target.buffActive.has(id));
1127
+ if (hasMageArmor) {
1128
+ const trigId = adaptiveWardingTriggeredSpellId(school);
1129
+ if (trigId > 0) {
1130
+ const trig =
1131
+ target.spec.spells.find((s) => s.id === trigId)
1132
+ ?? opts.attacker.spec.spells.find((s) => s.id === trigId);
1133
+ if (trig) applyProc(target, target, trig);
1134
+ }
1135
+ }
1136
+ }
1137
+ // Cheat Death: damage that crosses below 20% max HP → cast 28846 healing-taken buff.
1138
+ // Gate uses pre-hit HP. Cite: UnitAuraProcHandler.cpp:1378–1383.
1139
+ if (
1140
+ dmg > 0
1141
+ && target.cheatDeathActive
1142
+ && opts?.attacker
1143
+ && opts.attacker !== target
1144
+ ) {
1145
+ // dealDamage already subtracted; reconstruct pre-hit as current + dmg (approx post-mitigation).
1146
+ const preHitHp = target.hp + dmg;
1147
+ if (cheatDeathShouldProc(preHitHp, target.spec.maxHealth, dmg)) {
1148
+ const trig =
1149
+ target.spec.spells.find((s) => s.id === CHEAT_DEATH_HEALING_TAKEN_SPELL_ID);
1150
+ if (trig) applyProc(target, target, trig);
1151
+ }
1152
+ }
1153
+ // Master of Elements: spell crit → refund pct% of mana cost via 29077.
1154
+ // Cite: UnitAuraProcHandler.cpp:949–964.
1155
+ if (
1156
+ dmg > 0
1157
+ && opts?.attacker
1158
+ && opts.attacker.masterOfElementsPct > 0
1159
+ && opts.crit
1160
+ && (opts.damageType === 'spell' || opts.damageType === 'dot')
1161
+ && opts.attacker !== target
1162
+ && opts.spell
1163
+ && opts.attacker.spec.powerType === 'mana'
1164
+ ) {
1165
+ const refund = masterOfElementsRefundAmount(
1166
+ opts.spell.cost ?? 0,
1167
+ opts.attacker.masterOfElementsPct,
1168
+ );
1169
+ if (refund > 0) {
1170
+ const caster = opts.attacker;
1171
+ const before = caster.power;
1172
+ caster.power = Math.min(caster.spec.maxPower, caster.power + refund);
1173
+ if (caster.power !== before) {
1174
+ push({
1175
+ t,
1176
+ kind: 'resource',
1177
+ actor: caster.side,
1178
+ power: 'mana',
1179
+ value: caster.power,
1180
+ delta: caster.power - before,
1181
+ });
1182
+ // Mention payload id for census + event surface.
1183
+ push({
1184
+ t,
1185
+ kind: 'spell_hit',
1186
+ actor: caster.side,
1187
+ target: caster.side,
1188
+ spellId: MASTER_OF_ELEMENTS_TRIGGER_SPELL_ID,
1189
+ name: 'Master of Elements',
1190
+ result: 'hit',
1191
+ amount: caster.power - before,
1192
+ school: 'arcane',
1193
+ });
1194
+ }
1195
+ }
1196
+ }
1197
+ // Ignite: fire spell damage the caster deals → DoT 12654 with rank% basepoints.
1198
+ // Cite: UnitAuraProcHandler.cpp:970–985 triggered_spell_id = 12654.
1199
+ if (
1200
+ dmg > 0
1201
+ && opts?.attacker
1202
+ && opts.attacker.igniteRankSpellId > 0
1203
+ && school === 'fire'
1204
+ && (opts.damageType === 'spell' || opts.damageType === 'dot')
1205
+ && opts.attacker !== target
1206
+ && !dead(target)
1207
+ ) {
1208
+ const bp = igniteBasepoints(opts.attacker.igniteRankSpellId, dmg);
1209
+ if (bp > 0) {
1210
+ // Ignite: a 4000 ms duration at a 2000 ms amplitude, so two ticks, and the custom base points
1211
+ // are the per-tick amount. Cite: UnitAuraProcHandler.cpp:978–985.
1212
+ const tickAmt = bp;
1213
+ const durationMs = 4000;
1214
+ const tickMs = 2000;
1215
+ const existing = ctx.effectsOn(target.side).find(
1216
+ (e) => e.kind === 'dot' && e.spellId === IGNITE_DOT_SPELL_ID && e.source === opts.attacker!.side,
1217
+ );
1218
+ if (existing && existing.kind === 'dot') {
1219
+ // Refresh/stack: classic re-applies; keep stronger tick.
1220
+ existing.min = Math.max(existing.min ?? 0, tickAmt);
1221
+ existing.max = Math.max(existing.max ?? 0, tickAmt);
1222
+ existing.expiresAt = t + durationMs;
1223
+ existing.nextTickAt = t + tickMs;
1224
+ ctx.touchEffect(existing);
1225
+ } else {
1226
+ effects.push({
1227
+ kind: 'dot',
1228
+ source: opts.attacker.side,
1229
+ target: target.side,
1230
+ spellId: IGNITE_DOT_SPELL_ID,
1231
+ name: 'Ignite',
1232
+ school: 'fire',
1233
+ min: tickAmt,
1234
+ max: tickAmt,
1235
+ expiresAt: t + durationMs,
1236
+ tickMs,
1237
+ nextTickAt: t + tickMs,
1238
+ });
1239
+ push({ t, kind: 'aura', actor: target.side, name: 'Ignite', applied: true });
1240
+ }
1241
+ }
1242
+ }
1243
+ // Deep Wounds: physical melee crit → DoT 12721 with weaponDamage×rank%/6 per tick.
1244
+ // Cite: UnitAuraProcHandler.cpp:1316–1342.
1245
+ if (
1246
+ dmg > 0
1247
+ && opts?.attacker
1248
+ && opts.attacker.deepWoundsRankSpellId > 0
1249
+ && opts.crit
1250
+ && school === 'physical'
1251
+ && (opts.damageType === 'weapon' || opts.damageType === 'spell')
1252
+ && opts.attacker !== target
1253
+ && !dead(target)
1254
+ ) {
1255
+ const wpn = opts.attacker.spec.weapon;
1256
+ const avg = deepWoundsAverageWeaponDamage(wpn.dmgMin, wpn.dmgMax);
1257
+ const tickAmt = deepWoundsTickBasepoints(opts.attacker.deepWoundsRankSpellId, avg);
1258
+ if (tickAmt > 0) {
1259
+ const durationMs = DEEP_WOUND_DOT_DURATION_MS;
1260
+ const tickMs = DEEP_WOUND_DOT_TICK_MS;
1261
+ const existing = ctx.effectsOn(target.side).find(
1262
+ (e) => e.kind === 'dot'
1263
+ && e.spellId === DEEP_WOUND_DOT_SPELL_ID
1264
+ && e.source === opts.attacker!.side,
1265
+ );
1266
+ if (existing && existing.kind === 'dot') {
1267
+ existing.min = Math.max(existing.min ?? 0, tickAmt);
1268
+ existing.max = Math.max(existing.max ?? 0, tickAmt);
1269
+ existing.expiresAt = t + durationMs;
1270
+ existing.nextTickAt = t + tickMs;
1271
+ ctx.touchEffect(existing);
1272
+ } else {
1273
+ effects.push({
1274
+ kind: 'dot',
1275
+ source: opts.attacker.side,
1276
+ target: target.side,
1277
+ spellId: DEEP_WOUND_DOT_SPELL_ID,
1278
+ name: 'Deep Wound',
1279
+ school: 'physical',
1280
+ min: tickAmt,
1281
+ max: tickAmt,
1282
+ expiresAt: t + durationMs,
1283
+ tickMs,
1284
+ nextTickAt: t + tickMs,
1285
+ });
1286
+ push({ t, kind: 'aura', actor: target.side, name: 'Deep Wound', applied: true });
1287
+ }
1288
+ }
1289
+ }
1290
+ // Pyroclasm: Hellfire/RoF periodic damage the caster deals → chance stun 18093.
1291
+ // chance = baseChance / tickCount. Cite: UnitAuraProcHandler.cpp:1347–1375.
1292
+ if (
1293
+ dmg > 0
1294
+ && opts?.attacker
1295
+ && opts.attacker.pyroclasmRankSpellId > 0
1296
+ && opts.damageType === 'dot'
1297
+ && opts.spell
1298
+ && opts.attacker !== target
1299
+ && !dead(target)
1300
+ ) {
1301
+ const famFlags = (opts.spell.spellFamilyFlags ?? 0) | 0;
1302
+ const ticks = pyroclasmTickCountForFamilyFlags(famFlags);
1303
+ if (ticks > 0) {
1304
+ const chance = pyroclasmChancePct(opts.attacker.pyroclasmRankSpellId, ticks);
1305
+ if (pyroclasmStunFires(chance, rng.next())) {
1306
+ // applyControl(src, tgt, spell, ccEff, durMs). Cite: createSession applyControl.
1307
+ const stunSpell: SpellDef = {
1308
+ id: PYROCLASM_STUN_SPELL_ID,
1309
+ name: 'Pyroclasm',
1310
+ rank: 0,
1311
+ school: 'fire',
1312
+ role: 'control',
1313
+ powerType: 'mana',
1314
+ cost: 0,
1315
+ castTimeMs: 0,
1316
+ cooldownMs: 0,
1317
+ gcdMs: 0,
1318
+ rangeMax: 30,
1319
+ reqLevel: 1,
1320
+ effects: [],
1321
+ };
1322
+ const stunEff = {
1323
+ kind: 'cc' as const,
1324
+ control: 'stun' as const,
1325
+ durationMs: PYROCLASM_STUN_DURATION_MS,
1326
+ school: 'fire' as const,
1327
+ mechanic: 'stun' as const,
1328
+ };
1329
+ const durMs = applyDr(opts.attacker, target, stunEff.mechanic, PYROCLASM_STUN_DURATION_MS);
1330
+ if (durMs > 0 && !ccImmune(target, stunEff.mechanic, undefined)) {
1331
+ applyControl(opts.attacker, target, stunSpell, stunEff, durMs);
1332
+ push({
1333
+ t,
1334
+ kind: 'aura',
1335
+ actor: target.side,
1336
+ name: 'Pyroclasm',
1337
+ applied: true,
1338
+ spellId: PYROCLASM_STUN_SPELL_ID,
1339
+ });
1340
+ }
1341
+ }
1342
+ }
1343
+ }
1344
+ return dmg;
1345
+ };
1346
+
1347
+ /** Mana used by Mana Shield — primary mana bar or parallel (druid form) mana. */
1348
+ function currentManaForShield(f: Fighter): number {
1349
+ if (f.spec.powerType === 'mana') return f.power;
1350
+ return f.mana;
1351
+ }
1352
+ function setManaForShield(f: Fighter, mana: number): void {
1353
+ const m = Math.max(0, Math.trunc(mana));
1354
+ if (f.spec.powerType === 'mana') {
1355
+ f.power = Math.min(f.spec.maxPower, m);
1356
+ } else {
1357
+ f.mana = Math.min(f.maxMana, m);
1358
+ }
1359
+ }
1360
+
1361
+ const grantImmunityAura = (
1362
+ owner: Fighter,
1363
+ aura: import('../spells/immunity.ts').ImmunityAura,
1364
+ expiresAt: number,
1365
+ name: string,
1366
+ source: Side,
1367
+ ): void => (ctx.grantImmunityAura as Function)(owner, aura, expiresAt, name, source);
1368
+
1369
+ // Consume proc charges on buff auras when the bearer takes a real damage packet whose event flags
1370
+ // match. The Priest family only needs damage above 0; the generic system still gates on the flags.
1371
+ const consumeChargedBuffsOnTakeHit = (victim: Fighter, eventFlags: number): void => {
1372
+ if (eventFlags === 0) return;
1373
+ const t = ctx.t;
1374
+ const mine = ctx.effectsOn(victim.side);
1375
+ for (let i = mine.length - 1; i >= 0; i--) {
1376
+ const e = mine[i]!;
1377
+ if (e.kind !== 'buff') continue;
1378
+ if (e.charges == null || e.charges <= 0) continue;
1379
+ if (e.expiresAt <= t) continue;
1380
+ // ProcFlags on the live row (OOC seed / in-combat apply). Kit lookup is fallback
1381
+ // when the holder is also a known combat spell.
1382
+ const spell =
1383
+ victim.spec.spells.find((s) => s.id === e.spellId)
1384
+ ?? null;
1385
+ const pf = (e.procFlags ?? spell?.procFlags ?? 0) | 0;
1386
+ // No flags on the def → do not invent a take-hit consumer (unrelated charged buffs).
1387
+ if (pf === 0 || (pf & eventFlags) === 0) continue;
1388
+ const { remaining, remove } = consumeProcCharge(e.charges);
1389
+ if (remove) {
1390
+ ctx.removeAurasDueToSpell?.(victim, e.spellId);
1391
+ } else {
1392
+ e.charges = remaining;
1393
+ }
1394
+ }
1395
+ };
1396
+
1397
+ // Fire damage shields and take-side proc auras on the victim at the attacker, with the shield damage
1398
+ // type preventing re-entry. A deal-only aura is skipped here — the deal path owns it.
1399
+ // Depth 1, which is CMaNGOS's shape: Unit::DealDamage deals damage-shield damage with a direct
1400
+ // DealDamage that does not re-enter proc handling. Without it two units that both punish being hit
1401
+ // never unwind - a warrior mirror with Retaliation on both sides overflowed the stack and killed
1402
+ // round 2026-08-29-r7 mid-cell (KI-1071, repro-duel-shield-recursion.mjs).
1403
+ let shieldDepth = 0;
1404
+
1405
+ const fireDamageShields = (
1406
+ victim: Fighter,
1407
+ attacker: Fighter,
1408
+ hit?: { damageType?: string; ranged?: boolean; spell?: SpellDef | null },
1409
+ ): void => {
1410
+ if (shieldDepth > 0) return;
1411
+ shieldDepth++;
1412
+ try {
1413
+ fireDamageShieldsInner(victim, attacker, hit);
1414
+ } finally {
1415
+ shieldDepth--;
1416
+ }
1417
+ };
1418
+
1419
+ const fireDamageShieldsInner = (
1420
+ victim: Fighter,
1421
+ attacker: Fighter,
1422
+ hit?: { damageType?: string; ranged?: boolean; spell?: SpellDef | null },
1423
+ ): void => {
1424
+ const t = ctx.t;
1425
+ // SUPPRESS_TARGET_PROCS zeroes the VICTIM's mask for this cast, so nothing on the target fires from
1426
+ // it. 23 rows, and it exists because Sap must not proc the target's own responses.
1427
+ if (hit?.spell && hasAttrEx3(hit.spell, SPELL_ATTR_EX3.SUPPRESS_TARGET_PROCS)) return;
1428
+ // Charged mod-buffs first: drop a charge on matching take-hit flags. Independent of the shield
1429
+ // retaliation, since a charge drop needs only the damage packet and not a living attacker.
1430
+ if (hit) {
1431
+ consumeChargedBuffsOnTakeHit(victim, takeDamageEventFlags(hit.damageType, hit));
1432
+ }
1433
+ const mine = ctx.effectsOn(victim.side);
1434
+ for (let i = mine.length - 1; i >= 0; i--) {
1435
+ // Shield damage can removeAurasDueToSpell the holder; skip already-dropped snapshot rows.
1436
+ // Repro: repro-duel-damage-shield-index.mjs.
1437
+ const e = mine[i];
1438
+ if (!e) continue;
1439
+ if (e.kind !== 'damage_shield' && e.kind !== 'proc_trigger_spell') continue;
1440
+ if (e.expiresAt <= t) continue;
1441
+ if (e.charges != null && e.charges <= 0) continue;
1442
+ if ((e.nextProcAt ?? 0) > t) continue;
1443
+
1444
+ if (e.kind === 'damage_shield') {
1445
+ const school = e.school ?? 'nature';
1446
+ const lo = e.min ?? 1;
1447
+ const hi = e.max ?? lo;
1448
+ let raw = rollBetween(lo, hi, rng.next());
1449
+ // Fold spell power in, as the trigger path does — the shield path went straight to damage, so a
1450
+ // shield ignored it entirely. The magnitude is the CASTER's, and a shield can sit on someone else.
1451
+ const shieldCaster = e.source === victim.side
1452
+ ? victim
1453
+ : (sideFighter?.(e.source as Side) ?? victim);
1454
+ const shieldSpell = shieldCaster.spec.spells.find((s) => s.id === e.spellId);
1455
+ if (shieldSpell) {
1456
+ // The live effect carries no slot; recover it from the def effect it was applied from.
1457
+ const shieldSlot = shieldSpell.effects.find((x) => x.kind === 'damage_shield')?.effectSlot ?? 0;
1458
+ raw = applySpellDamageBonusIfPresent(raw, shieldSpell, shieldSlot, {
1459
+ spellPower: shieldCaster.spec.spellPower,
1460
+ spellDamageBySchool: shieldCaster.spec.spellDamageBySchool,
1461
+ school,
1462
+ attackPower: shieldCaster.attackPower,
1463
+ damagePct: shieldCaster.damagePctBySchool[school],
1464
+ });
1465
+ }
1466
+ const amount = dealDamage(attacker, raw, school, victim.spec.level, {
1467
+ damageType: 'shield',
1468
+ attacker: victim,
1469
+ });
1470
+ if (amount > 0) {
1471
+ push({
1472
+ t, kind: 'spell_hit', actor: victim.side, target: attacker.side, spellId: e.spellId, name: e.name,
1473
+ result: 'hit', amount, school,
1474
+ });
1475
+ applyDamageThreat(victim, attacker, amount, false, undefined);
1476
+ }
1477
+ } else if (e.kind === 'proc_trigger_spell') {
1478
+ // PERIODIC_TRIGGER_SPELL (tickMs) fires on the channel schedule, not on taken hits.
1479
+ // Cite: SpellAuras SPELL_AURA_PERIODIC_TRIGGER_SPELL (23) vs PROC_TRIGGER_SPELL (42).
1480
+ if (e.tickMs && e.tickMs > 0) continue;
1481
+ // Removal-body holder: fires when the aura ends, never on a hit (and it carries no
1482
+ // procFlags, which the pf === 0 rule below would otherwise read as "any taken hit").
1483
+ if (e.onRemove) continue;
1484
+
1485
+ // Cite: UnitAuraProcHandler.cpp:585. ONLY_PROC_ON_CASTER refuse aura procs unless holder target is the caster.
1486
+ const holderSpell = e.spellId ? ctx.spellOf?.(e.spellId) : null;
1487
+ if (holderSpell && hasAttrEx3(holderSpell, SPELL_ATTR_EX3.ONLY_PROC_ON_CASTER)) {
1488
+ if (e.target !== e.source) continue;
1489
+ }
1490
+
1491
+ // Retaliation fails when the attack comes from behind, using the same behind test the openers
1492
+ // do rather than inventing a cone. Cite: Warrior.cpp:55–57; UnitAuraProcHandler.cpp:846.
1493
+ if (e.spellId === RETALIATION_AURA_SPELL_ID) {
1494
+ const behindOk = ctx.behindOk;
1495
+ if (behindOk && behindOk(attacker, victim)) continue;
1496
+ }
1497
+ // Deal-only aura-42 (SoJ: procFlags 20) must not retaliate on taken hits.
1498
+ const pf = e.procFlags ?? 0;
1499
+ if (pf !== 0 && (pf & PROC_FLAGS_TAKE_MELEE) === 0) continue;
1500
+ // Roll the sourced chance or rate. The deal path already did; the take path used to fire at 100%
1501
+ // whenever the flags matched.
1502
+ if ((e.procChance != null && e.procChance > 0) || (e.procPpm != null && e.procPpm > 0)) {
1503
+ const takeFlags = hit
1504
+ ? takeDamageEventFlags(hit.damageType, hit)
1505
+ : (PROC_FLAG.TAKE_MELEE_SWING | PROC_FLAG.TAKE_MELEE_ABILITY | PROC_FLAG.TAKE_ANY_DAMAGE);
1506
+ const takeCtx: ProcEventContext = {
1507
+ procFlags: takeFlags,
1508
+ procEx: procExFromOutcome('hit'),
1509
+ weaponSpeedMs: attacker.spec.weapon.speedMs,
1510
+ nowMs: t,
1511
+ schoolMask: SCHOOL_MASK.NORMAL,
1512
+ outdoors: ctx.cfg.outdoors,
1513
+ };
1514
+ const fired = tryProc(
1515
+ {
1516
+ procFlags: pf !== 0 ? pf : takeFlags,
1517
+ chance: e.procChance ?? null,
1518
+ ppm: e.procPpm ?? null,
1519
+ customChance: e.procChance ?? null,
1520
+ cooldownReadyAt: 0,
1521
+ cooldownMs: e.cooldownMs,
1522
+ },
1523
+ takeCtx,
1524
+ rng,
1525
+ );
1526
+ if (!fired) continue;
1527
+ }
1528
+
1529
+ // Judgement of Light and Wisdom heal or restore the STRIKER on a landed melee hit. The
1530
+ // intermediate carriers are absent in the live bake, so the amounts are applied here.
1531
+ if (e.onHitHeal != null && e.onHitHeal > 0) {
1532
+ if (!dead(attacker)) {
1533
+ const before = attacker.hp;
1534
+ const heal = Number.isFinite(e.onHitHeal) ? e.onHitHeal : 0;
1535
+ attacker.hp = Math.min(attacker.spec.maxHealth, Math.round(attacker.hp + heal));
1536
+ if (attacker.hp !== before) {
1537
+ push({ t, kind: 'heal', actor: attacker.side, amount: attacker.hp - before });
1538
+ }
1539
+ }
1540
+ } else if (e.onHitMana != null && e.onHitMana > 0) {
1541
+ if (attacker.spec.powerType === 'mana' && attacker.spec.maxPower > 0) {
1542
+ const before = attacker.power;
1543
+ const gain = Number.isFinite(e.onHitMana) ? e.onHitMana : 0;
1544
+ attacker.power = Math.min(attacker.spec.maxPower, attacker.power + gain);
1545
+ if (attacker.power !== before) {
1546
+ push({
1547
+ t,
1548
+ kind: 'resource',
1549
+ actor: attacker.side,
1550
+ power: 'mana',
1551
+ value: Math.round(attacker.power),
1552
+ delta: attacker.power - before,
1553
+ });
1554
+ }
1555
+ }
1556
+ } else if (e.triggerSpellId) {
1557
+ // Resolve the trigger off the fighter's spell list, since the kit may include proc ids. The
1558
+ // full apply covers damage, CC and debuffs; a trap payload may only exist as a synthetic row.
1559
+ const tid = e.triggerSpellId | 0;
1560
+ // GO trap Use is radius-of-placement, not "anyone who hits the hunter".
1561
+ // Geometry present → proximity path owns the spring. Cite: GameObject.cpp:436–503.
1562
+ if (isHunterTrapTriggerSpellId(tid)) {
1563
+ const trapPos = e.trapPosYd ?? ctx.posOf?.(victim.side);
1564
+ if (trapPos != null) continue;
1565
+ }
1566
+ const trig =
1567
+ victim.spec.spells.find((s) => s.id === tid)
1568
+ ?? attacker.spec.spells.find((s) => s.id === tid)
1569
+ ?? (isHunterTrapTriggerSpellId(tid) ? syntheticHunterTrapPayload(tid) : null)
1570
+ ?? ctx.spellOf?.(tid);
1571
+ if (trig) {
1572
+ applyProc(victim, attacker, trig);
1573
+ // Freezing Trap Effect is control-only; surface enemy aura for UI/QA (KI-608).
1574
+ if (isHunterTrapTriggerSpellId(tid)) {
1575
+ push({ t, kind: 'aura', actor: attacker.side, name: trig.name, applied: true });
1576
+ }
1577
+ }
1578
+ } else {
1579
+ continue; // no payload
1580
+ }
1581
+ }
1582
+
1583
+ const cd = e.cooldownMs ?? 0;
1584
+ e.nextProcAt = t + cd;
1585
+ if (e.charges != null) {
1586
+ const { remaining, remove } = consumeProcCharge(e.charges);
1587
+ if (remove) {
1588
+ ctx.removeAurasDueToSpell?.(victim, e.spellId);
1589
+ } else {
1590
+ e.charges = remaining;
1591
+ }
1592
+ }
1593
+ }
1594
+ };
1595
+
1596
+ // Aura-42 self-buffs that proc when the bearer DEALS melee, like a seal's stun. The session rng is
1597
+ // drawn only when a matching aura is live, so a fight without one stays byte-identical.
1598
+ const fireDealMeleeAura42 = (
1599
+ attacker: Fighter,
1600
+ victim: Fighter,
1601
+ wasCrit: boolean,
1602
+ flagsHint: 'melee_swing' | 'melee_ability',
1603
+ hand: 'mainhand' | 'offhand' = 'mainhand',
1604
+ ): void => {
1605
+ const t = ctx.t;
1606
+ const mine = ctx.effectsOn(attacker.side);
1607
+ let procCtx: ReturnType<typeof makeProcCtx> | undefined;
1608
+ for (let i = mine.length - 1; i >= 0; i--) {
1609
+ const e = mine[i]!;
1610
+ if (e.kind !== 'proc_trigger_spell' || !e.triggerSpellId) continue;
1611
+ if (e.expiresAt <= t) continue;
1612
+ if (e.tickMs && e.tickMs > 0) continue; // channel schedule only
1613
+ if (e.onRemove) continue; // removal-body holder — fires at expiry, not on a swing
1614
+ if (e.charges != null && e.charges <= 0) continue;
1615
+ if ((e.nextProcAt ?? 0) > t) continue;
1616
+
1617
+ // Cite: UnitAuraProcHandler.cpp:585. ONLY_PROC_ON_CASTER refuse aura procs unless holder target is the caster.
1618
+ const holderSpell = e.spellId ? ctx.spellOf?.(e.spellId) : null;
1619
+ if (holderSpell && hasAttrEx3(holderSpell, SPELL_ATTR_EX3.ONLY_PROC_ON_CASTER)) {
1620
+ if (e.target !== e.source) continue;
1621
+ }
1622
+
1623
+ // A weapon enchant cannot proc off paws, nor off a disarmed main hand: the C++ checks the hand is
1624
+ // usable before the roll. Windfury Totem hands its enchant to druids too, who were procing it.
1625
+ if (e.fromWeaponEnchant
1626
+ && !canUseEquippedWeapon(hand, attacker.form, attacker.disarmedUntil > t)) continue;
1627
+ // Deal path requires DEAL-side flags (SoJ = 20). No flags → take-path only.
1628
+ const pf = e.procFlags ?? 0;
1629
+ if (pf === 0) continue;
1630
+ procCtx ??= makeProcCtx(attacker, wasCrit, flagsHint);
1631
+ if ((procCtx.procFlags & pf) === 0) continue;
1632
+
1633
+ const fired = tryProc({
1634
+ procFlags: pf,
1635
+ chance: e.procChance ?? null,
1636
+ ppm: e.procPpm ?? null,
1637
+ customChance: e.procChance ?? null,
1638
+ cooldownReadyAt: 0, // nextProcAt already gated above
1639
+ cooldownMs: e.cooldownMs,
1640
+ },
1641
+ procCtx,
1642
+ rng,
1643
+ );
1644
+ if (!fired) continue;
1645
+
1646
+ // Mana Drain Trigger: dual cast self 29471 + victim 27526 (not single DBC trigger 18350).
1647
+ // Cite: UnitAuraProcHandler.cpp:1284–1291 case 27522.
1648
+ if ((e.spellId | 0) === MANA_DRAIN_TRIGGER_SPELL_ID) {
1649
+ if (!dead(attacker)) {
1650
+ const selfGain = attacker.spec.spells.find((s) => s.id === MANA_DRAIN_SELF_GAIN_SPELL_ID);
1651
+ if (selfGain) applyProc(attacker, attacker, selfGain);
1652
+ }
1653
+ if (!dead(victim)) {
1654
+ const drain = attacker.spec.spells.find((s) => s.id === MANA_DRAIN_VICTIM_DRAIN_SPELL_ID)
1655
+ ?? victim.spec.spells.find((s) => s.id === MANA_DRAIN_VICTIM_DRAIN_SPELL_ID);
1656
+ if (drain) applyProc(attacker, victim, drain);
1657
+ }
1658
+ } else if ((e.spellId | 0) === DEADLY_SWIFTNESS_SPELL_ID) {
1659
+ // Deadly Swiftness (Rank 1): deal damage to target below 20% health triggers 10% speed buff on self.
1660
+ // Cite: UnitAuraProcHandler.cpp:1293–1301.
1661
+ if (deadlySwiftnessVictimEligible(victim.hp, victim.spec.maxHealth)) {
1662
+ const buff = attacker.spec.spells.find((s) => s.id === SPEED_BONUS_10_PCT_SPELL_ID)
1663
+ ?? ctx.spellOf?.(SPEED_BONUS_10_PCT_SPELL_ID);
1664
+ if (buff) applyProc(attacker, attacker, buff);
1665
+ }
1666
+ } else if (e.triggerSpellId === -1 && (e.spellId | 0) === 4533) {
1667
+ // Dreamwalker Raiment 2 pieces bonus (override script 4533).
1668
+ // Trigger is chosen from victim power type at proc time. Cite: UnitAuraProcHandler.cpp:1629–1643.
1669
+ const victimPowerType = victim.spec.powerType;
1670
+ let triggered_spell_id = 0;
1671
+ if (victimPowerType === 'mana') {
1672
+ triggered_spell_id = 28722;
1673
+ } else if (victimPowerType === 'rage') {
1674
+ triggered_spell_id = 28723;
1675
+ } else if (victimPowerType === 'energy') {
1676
+ triggered_spell_id = 28724;
1677
+ }
1678
+ if (triggered_spell_id > 0 && !dead(victim)) {
1679
+ const trig = ctx.spellOf?.(triggered_spell_id);
1680
+ if (trig) {
1681
+ applyProc(attacker, victim, trig);
1682
+ }
1683
+ }
1684
+ } else {
1685
+ const trig =
1686
+ attacker.spec.spells.find((s) => s.id === e.triggerSpellId)
1687
+ ?? victim.spec.spells.find((s) => s.id === e.triggerSpellId)
1688
+ // Weapon-enchant procs handed over by a totem (Windfury 8516) live here — the party
1689
+ // member never had them in its own kit. Cite: EffectEnchantHeldItem TEMP_ENCHANTMENT_SLOT.
1690
+ ?? attacker.totemTriggerSpells?.find((s) => s.id === e.triggerSpellId)
1691
+ // Last is the global store (E6.2): a creature's proc payload is on no cast list.
1692
+ ?? ctx.spellOf?.(e.triggerSpellId | 0);
1693
+ // not allow proc extra attack spell at extra attack
1694
+ // Cite: Player.cpp:7383–7385 CastItemCombatSpell; UnitAuraProcHandler.cpp:1550–1551.
1695
+ if (
1696
+ trig
1697
+ && attacker.extraAttacksExecuting
1698
+ && trig.effects.some((x) => x.kind === 'extra_attacks')
1699
+ ) {
1700
+ continue;
1701
+ }
1702
+ if (trig) applyProc(attacker, victim, trig);
1703
+ }
1704
+
1705
+ const cd = e.cooldownMs ?? 0;
1706
+ e.nextProcAt = t + cd;
1707
+ if (e.charges != null) {
1708
+ const { remaining, remove } = consumeProcCharge(e.charges);
1709
+ if (remove) {
1710
+ attacker.buffActive.delete(e.spellId);
1711
+ const idx = effects.lastIndexOf(e);
1712
+ if (idx >= 0) effects.splice(idx, 1);
1713
+ push({ t, kind: 'aura', actor: attacker.side, name: e.name, applied: false });
1714
+ } else {
1715
+ e.charges = remaining;
1716
+ }
1717
+ }
1718
+ }
1719
+ };
1720
+
1721
+ // Resolve and apply a hunter trap's payload, then consume the arm charge. A freezing effect lands as
1722
+ // control rather than an aura, so an enemy aura row is emitted too for the debuff bar.
1723
+ const fireHunterTrapSpring = (
1724
+ owner: Fighter,
1725
+ tgt: Fighter,
1726
+ e: (typeof effects)[number],
1727
+ effectIndex: number,
1728
+ ): boolean => {
1729
+ const t = ctx.t;
1730
+ const tid = (e.triggerSpellId ?? 0) | 0;
1731
+ const trig =
1732
+ owner.spec.spells.find((s) => s.id === tid)
1733
+ ?? tgt.spec.spells.find((s) => s.id === tid)
1734
+ ?? syntheticHunterTrapPayload(tid)
1735
+ ?? ctx.spellOf?.(tid)
1736
+ ?? null;
1737
+ if (!trig) return false;
1738
+
1739
+ applyProc(owner, tgt, trig);
1740
+ // Always surface enemy aura chip (CC path only emits kind:control; DoT path already
1741
+ // emits aura — a second applied:true is harmless and keeps QA filters simple).
1742
+ push({ t, kind: 'aura', actor: tgt.side, name: trig.name, applied: true });
1743
+
1744
+ const cd = e.cooldownMs ?? 0;
1745
+ e.nextProcAt = t + cd;
1746
+ if (e.charges != null) {
1747
+ const { remaining, remove } = consumeProcCharge(e.charges);
1748
+ if (remove) {
1749
+ owner.buffActive.delete(e.spellId);
1750
+ effects.splice(effectIndex, 1);
1751
+ push({ t, kind: 'aura', actor: owner.side, name: e.name, applied: false });
1752
+ } else {
1753
+ e.charges = remaining;
1754
+ }
1755
+ } else {
1756
+ owner.buffActive.delete(e.spellId);
1757
+ effects.splice(effectIndex, 1);
1758
+ push({ t, kind: 'aura', actor: owner.side, name: e.name, applied: false });
1759
+ }
1760
+ return true;
1761
+ };
1762
+
1763
+ // Spring hunter traps when an unfriendly unit enters the GO radius of the PLACED position — the caster's
1764
+ // feet at arm time, since the object does not follow the hunter. For that lock, diameter IS radius.
1765
+ const springProximityHunterTraps = (): void => {
1766
+ const t = ctx.t;
1767
+ const posOf = ctx.posOf;
1768
+ if (typeof posOf !== 'function') return;
1769
+ // `livingEnemies` is required on CombatContext (context.ts:367), so the old
1770
+ // `typeof === 'function'` else-branch was unreachable and filtered a different array.
1771
+ const living = ctx.livingEnemies();
1772
+ if (!living.length) return;
1773
+
1774
+ for (let i = effects.length - 1; i >= 0; i--) {
1775
+ const e = effects[i]!;
1776
+ if (e.kind !== 'proc_trigger_spell') continue;
1777
+ if (!e.triggerSpellId || !isHunterTrapTriggerSpellId(e.triggerSpellId)) continue;
1778
+ if (e.expiresAt <= t) continue;
1779
+ if (e.charges != null && e.charges <= 0) continue;
1780
+ if ((e.nextProcAt ?? 0) > t) continue;
1781
+ if (e.tickMs && e.tickMs > 0) continue;
1782
+
1783
+ const owner = sideFighter?.(e.target as Side);
1784
+ if (!owner || dead(owner)) continue;
1785
+
1786
+ // A trap is a GO at the point it was armed and it never moves (GameObject.cpp:436-503, Use is
1787
+ // radius-of-placement). Re-reading the owner every tick instead let an unstamped trap ride the
1788
+ // hunter around the floor, springing on whatever the party walked past.
1789
+ if (!e.trapPosYd) {
1790
+ const p = posOf(owner.side);
1791
+ if (!p) continue;
1792
+ e.trapPosYd = { ...p };
1793
+ }
1794
+ const trapPos = e.trapPosYd;
1795
+
1796
+ let tgt: Fighter | undefined;
1797
+ for (const en of living) {
1798
+ const d = combatDistanceYd(trapPos, posOf(en.side));
1799
+ if (d != null && d <= HUNTER_TRAP_RADIUS_YD) {
1800
+ tgt = en;
1801
+ break;
1802
+ }
1803
+ }
1804
+ if (!tgt || dead(tgt)) continue;
1805
+
1806
+ fireHunterTrapSpring(owner, tgt, e, i);
1807
+ }
1808
+ };
1809
+
1810
+ ctx.triggerProcs = triggerProcs;
1811
+ ctx.triggerTakenProcs = triggerTakenProcs;
1812
+ ctx.applyProc = applyProc;
1813
+ ctx.rollGearProcs = rollGearProcs;
1814
+ ctx.dealDamage = dealDamage;
1815
+ ctx.fireDamageShields = fireDamageShields;
1816
+ ctx.fireDealMeleeAura42 = fireDealMeleeAura42;
1817
+ ctx.springProximityHunterTraps = springProximityHunterTraps;
1818
+ ctx.powerOfType = powerOfType;
1819
+ ctx.maxPowerOfType = maxPowerOfType;
1820
+ ctx.setPowerOfType = setPowerOfType;
1821
+ ctx.casterPowerTypeId = casterPowerTypeId;
1822
+ ctx.powerTypeName = powerTypeName;
1823
+ }