azeroth-shell 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +35 -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 +144 -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,1259 @@
1
+ // Peeled from createSession (Wave 4 S30) — CC + DR + charm/root break cluster.
2
+ // Pattern (D): install methods onto CombatContext; no import of combat.ts.
3
+
4
+ import type { CombatContext } from './context.ts';
5
+ import type { ActiveEffect, Fighter } from './types.ts';
6
+ import type {
7
+ BuffMod,
8
+ ControlKind,
9
+ Mechanic,
10
+ School,
11
+ SpellDef,
12
+ SpellEffect,
13
+ } from '../../data/schema.ts';
14
+ import { CREATURE_TYPE } from '../../data/schema.ts';
15
+ import { isCreatureImmuneToSpell, isMechanicImmune } from '../../domain/creature/creatureAi.ts';
16
+ import {
17
+ diminishingGroupForMechanic,
18
+ isDiminishingReturnsGroupDurationDiminished,
19
+ isSubjectToDiminishingLevels,
20
+ } from '../../domain/spells/diminishing.ts';
21
+ import { SPELL_ATTR_EX3, hasAttrEx3 } from '../spells/spellAttributes.ts';
22
+ import { consumeProcCharge } from '../spells/auraStacking.ts';
23
+ import { isSingleTargetSpell, isSingleTargetSpellPair } from '../spells/singleTargetAura.ts';
24
+ import { isAllySide, isEnemySide, type EnemySide, type Side } from '../core/events.ts';
25
+ import {
26
+ applyImmunityAura,
27
+ removeImmunityAura,
28
+ AURA_TYPE,
29
+ MECHANIC,
30
+ SPELL_SCHOOL_MASK_ALL,
31
+ mechanicBit,
32
+ mechanicIdFromName,
33
+ type ImmunityAura,
34
+ } from '../spells/immunity.ts';
35
+ import { schoolMaskFromSchool } from '../spells/procs.ts';
36
+ // The Wave 4 peel left these three unbound — every reference below threw ReferenceError.
37
+ import { TUNING, ccDamageBreakChance, drFactor } from './formulas.ts';
38
+ import { applyBuffMod } from './fighterFactory.ts';
39
+ import { isHostileSpeedEffect } from './speedAura.ts';
40
+ import { tickHeartbeatResist, type HeartbeatHolder } from './heartbeatResist.ts';
41
+ import {
42
+ getAuraScript,
43
+ isPossessInvSpell,
44
+ notePossessLanded,
45
+ possessInvSpellIds,
46
+ } from '../../domain/scriptdev/index.ts';
47
+ import { AURA_INTERRUPT } from '../../domain/spells/stealth.ts';
48
+ import { stealthProcAllowedForSpell } from '../spells/interpreter/class-scripts-kit.ts';
49
+ import {
50
+ SPELL_INTERRUPT_FLAG_DAMAGE_PUSHBACK,
51
+ SPELL_INTERRUPT_FLAG_DAMAGE_CANCELS,
52
+ SPELL_PREVENTION_TYPE,
53
+ } from '../spells/interrupt.ts';
54
+ import { nextPushbackDelayMs, resistsPushback } from '../../domain/spells/spellPushback.ts';
55
+ import {
56
+ channelInterruptCtx,
57
+ expireChannelIfDone,
58
+ interruptChannel,
59
+ isPushbackSubject,
60
+ onChannelOwnerDamaged,
61
+ } from './channel.ts';
62
+ import { delayGroundPoolsForSpell } from '../game/dynamicObject.ts';
63
+ import {
64
+ SPELL_AURA_AOE_CHARM,
65
+ SPELL_AURA_MOD_POSSESS,
66
+ } from '../spells/interpreter/opcodes.ts';
67
+
68
+ // Outgoing-charm uniqueness. Same TakeCharmOf path; the C++ advertised flag + caster type. Cite:
69
+ // SpellAuras.cpp HandleModCharm :2301–2307; HandleAoECharm :2346–2348 (de8f729).
70
+ function charmOutgoingPolicy(opts: {
71
+ playerCaster: boolean;
72
+ aoeCharm: boolean;
73
+ possessUnique: boolean;
74
+ }): 'break_outgoing' | 'offload' | 'refuse' | 'coexist' {
75
+ // AoE charm: non-advertised — co-exists with other charms.
76
+ // Cite: SpellAuras.cpp:2346–2348 TakeCharmOf(target, GetId(), false).
77
+ if (opts.aoeCharm) return 'coexist';
78
+ // Player advertised charm is unique: BreakCharmOutgoing(true) then TakeCharmOf.
79
+ // Cite: SpellAuras.cpp:2304–2305.
80
+ if (opts.playerCaster) return 'break_outgoing';
81
+ // Possess / Anastari single guid: refuse a second land.
82
+ // Cite: SpellAuras.cpp:2219–2222; boss_baroness_anastari.cpp:64.
83
+ if (opts.possessUnique) return 'refuse';
84
+ // NPC MOD_CHARM: overwrite advertised by offloading the existing one (Chromatic Mutation).
85
+ // Cite: SpellAuras.cpp:2301–2303; Unit.cpp:11345–11348.
86
+ return 'offload';
87
+ }
88
+
89
+ export type CcEffect = Extract<SpellEffect, { kind: 'cc' }>;
90
+
91
+ // The key a mechanic's CC-IMMUNITY window is stored under — NOT its diminishing-returns group. This
92
+ // table used to double as that one, which made polymorph, sleep and sap diminish each other.
93
+ export const CC_IMMUNE_KEY: Record<Mechanic, string> = {
94
+ none: 'none',
95
+ charm: 'charm',
96
+ disoriented: 'incapacitate',
97
+ disarm: 'disarm',
98
+ distract: 'none',
99
+ fear: 'fear',
100
+ fumble: 'none',
101
+ root: 'root',
102
+ pacify: 'none',
103
+ silence: 'silence',
104
+ sleep: 'incapacitate',
105
+ snare: 'root',
106
+ stun: 'stun',
107
+ freeze: 'stun',
108
+ knockout: 'stun',
109
+ bleed: 'none',
110
+ bandage: 'none',
111
+ polymorph: 'incapacitate',
112
+ banish: 'banish',
113
+ shield: 'none',
114
+ shackle: 'incapacitate',
115
+ mount: 'none',
116
+ persuade: 'none',
117
+ turn: 'fear',
118
+ horror: 'fear',
119
+ invulnerability: 'none',
120
+ interrupt: 'silence',
121
+ daze: 'root',
122
+ discovery: 'none',
123
+ immune_shield: 'none',
124
+ sap: 'incapacitate',
125
+ };
126
+
127
+ // The diminishing-returns group for a mechanic. Apply uniqueness skips snare, daze and disoriented:
128
+ // the C++ leaves them ungrouped and then skips the unique set.
129
+ export const drGroupOf = (mechanic: Mechanic): string => diminishingGroupForMechanic(mechanic);
130
+
131
+ // The diminishing step already taken on this unit's group, or 0 once the window lapsed. Read BEFORE
132
+ // applyDr advances it — heartbeat resist scales by the level the aura lands at.
133
+ export function drStepOf(tgt: Fighter, mechanic: Mechanic, now: number): number {
134
+ const rec = tgt.dr.get(drGroupOf(mechanic));
135
+ return rec && rec.resetAt > now ? rec.step : 0;
136
+ }
137
+
138
+ export const CASTER_LOCK: ControlKind[] = ['stun', 'fear', 'incapacitate', 'silence'];
139
+
140
+ // The Fighter timer each control kind locks out. Charm and snare own no lockout timer of their own.
141
+ const CONTROL_TIMER = {
142
+ stun: 'stunnedUntil',
143
+ fear: 'fearedUntil',
144
+ incapacitate: 'incapUntil',
145
+ silence: 'silencedUntil',
146
+ disarm: 'disarmedUntil',
147
+ root: 'rootedUntil',
148
+ } as const;
149
+
150
+ /** The control a live aura row locks out, or undefined when it locks none. */
151
+ const controlOfLiveEffect = (e: ActiveEffect): ControlKind | undefined =>
152
+ e.kind === 'cc' ? e.control : e.kind === 'root' ? 'root' : undefined;
153
+
154
+ // Spell 12939 "Polymorph Heal Effect" — why a sheep comes back at full health. Every polymorph holder
155
+ // pushes it, healing a tenth of max health a second, and it is also the whole of IsPolymorphed.
156
+ const POLYMORPH_HEAL_SPELL_ID = 12939;
157
+ const POLYMORPH_HEAL_NAME = 'Polymorph Heal Effect';
158
+ const POLYMORPH_HEAL_PCT_OF_MAX = 10;
159
+ const POLYMORPH_HEAL_TICK_MS = 1000;
160
+
161
+ // The mechanic bitmask a LIVE aura counts as, for the immunity purge. ActiveEffect.mechanic is cc-only,
162
+ // so a root and a hostile snare must be derived from the KIND or the purge side is dead.
163
+ export function mechanicMaskOfLiveEffect(e: ActiveEffect): number {
164
+ if (e.kind === 'cc') return mechanicBit(mechanicIdFromName(e.mechanic));
165
+ // Prefer stamped mechanic on the live row; fall back to kind defaults.
166
+ if (e.kind === 'root') {
167
+ return mechanicBit(mechanicIdFromName(e.mechanic) || MECHANIC.ROOT);
168
+ }
169
+ // Beneficial / self speed (Aspect/Sprint/Stealth cost) are not snares — hostile only.
170
+ // Cite: speedAura.ts isHostileSpeedEffect; SpellAuras.cpp:2907–2917 MOD_DECREASE_SPEED.
171
+ if (e.kind === 'speed' && isHostileSpeedEffect(e)) {
172
+ return mechanicBit(mechanicIdFromName(e.mechanic) || MECHANIC.SNARE);
173
+ }
174
+ return 0;
175
+ }
176
+
177
+ export function installCrowdControl(ctx: CombatContext): void {
178
+ const touchEffect = (e: ActiveEffect): void => { ctx.touchEffect(e); };
179
+ // The fighter holding `side`, or undefined once it has left the session. This walked the enemy
180
+ // array then the ally array then the pet, which is the store's own answer spelled four ways.
181
+ const fighterBySide = (side: Side): Fighter | undefined => ctx.sideFighter?.(side);
182
+ const fadeEffectNow = (e: ActiveEffect): void => {
183
+ e.broken = true;
184
+ e.expiresAt = Math.min(e.expiresAt, ctx.t);
185
+ touchEffect(e);
186
+ };
187
+ // Charm is NOT hard control — possessed units still act (against former allies).
188
+ // Cite: charm-possess.md TakeCharmOf (PetAI / side-switch, not stun).
189
+ ctx.isControlledHard = (f: Fighter): boolean =>
190
+ f.stunnedUntil > ctx.t || f.fearedUntil > ctx.t || f.incapUntil > ctx.t;
191
+ // Holding CC our own damage would destroy — Polymorph, Sap, Gouge, Shackle, Blind. A STUN is
192
+ // deliberately not counted, and neither is fear: this is only "do not auto-target the sheep".
193
+ ctx.ccProtected = (f: Fighter): boolean => {
194
+ const t = ctx.t;
195
+ if (!(f.incapUntil > t || f.stunnedUntil > t)) return false;
196
+ return ctx.effectsOn(f.side).some(
197
+ (e) => e.kind === 'cc' && !e.broken && e.expiresAt > t && e.breakOnDamage === true,
198
+ );
199
+ };
200
+ // Can `spell` be cast despite the hard control on the caster? CheckCasterAuras lets one through when
201
+ // its own immunity matches the blocking mechanic — that is how a feared player pops the insignia.
202
+ ctx.spellCancelsOwnControl = (f: Fighter, spell: SpellDef): boolean => {
203
+ let immuneMask = 0;
204
+ for (const e of spell.effects) {
205
+ if (e.kind === 'mechanic_immunity') immuneMask |= mechanicBit(e.mechanic | 0);
206
+ }
207
+ if (immuneMask === 0) return false;
208
+ // Every live hard-control row on this unit must be one the spell purges — a second,
209
+ // unmatched control still prevents the cast (foundNotMechanic in the C++ loop).
210
+ let sawControl = false;
211
+ for (const e of ctx.effectsOn(f.side)) {
212
+ if (e.expiresAt <= ctx.t || e.broken) continue;
213
+ const m = mechanicMaskOfLiveEffect(e);
214
+ if (m === 0) continue;
215
+ // Only the states that actually block acting; a snare never stopped a cast.
216
+ if (e.kind !== 'cc') continue;
217
+ sawControl = true;
218
+ if ((m & immuneMask) === 0) return false;
219
+ }
220
+ return sawControl;
221
+ };
222
+ ctx.isControlled = (f: Fighter): boolean => {
223
+ const t = ctx.t;
224
+ return (ctx.isControlledHard as (f: Fighter) => boolean)(f)
225
+ || f.rootedUntil > t || f.silencedUntil > t || f.disarmedUntil > t
226
+ || f.pacifiedUntil > t || ctx.isCharmed(f);
227
+ };
228
+ ctx.controlActive = (f: Fighter, c: ControlKind): boolean => {
229
+ const t = ctx.t;
230
+ return c === 'stun' ? f.stunnedUntil > t : c === 'fear' ? f.fearedUntil > t
231
+ : c === 'incapacitate' ? f.incapUntil > t : c === 'silence' ? f.silencedUntil > t
232
+ : c === 'disarm' ? f.disarmedUntil > t : c === 'charm' ? ctx.isCharmed(f)
233
+ : c === 'root' ? f.rootedUntil > t
234
+ // Snare = hostile speed aura (MECHANIC_SNARE). Cite: mechanicMaskOfLiveEffect;
235
+ // speedAura.ts isHostileSpeedEffect (hostile:false self-slows are not snares).
236
+ : c === 'snare' ? ctx.effectsOn(f.side).some(
237
+ (e) => e.kind === 'speed' && e.expiresAt > t && !e.broken
238
+ && isHostileSpeedEffect(e),
239
+ )
240
+ : false;
241
+ };
242
+ // The most-severe active control for the UI badge / "can't act" banner (root is surfaced by the
243
+ // separate `rooted` flag, so it's not repeated here). Charm shows as Possessed (acts, flipped).
244
+ ctx.activeControl = (f: Fighter): { control: ControlKind; until: number } | null =>
245
+ f.stunnedUntil > ctx.t ? { control: 'stun', until: f.stunnedUntil }
246
+ : f.fearedUntil > ctx.t ? { control: 'fear', until: f.fearedUntil }
247
+ : f.incapUntil > ctx.t ? { control: 'incapacitate', until: f.incapUntil }
248
+ : ctx.isCharmed(f) ? { control: 'charm', until: f.charmedUntil }
249
+ : f.silencedUntil > ctx.t ? { control: 'silence', until: f.silencedUntil }
250
+ : f.disarmedUntil > ctx.t ? { control: 'disarm', until: f.disarmedUntil }
251
+ : null;
252
+
253
+ // Refused because the main hand is unusable — the DISARM gate. The rule is the spell's own
254
+ // requires-main-hand bit, not what it looks like: ranged and off-hand are exempt, on-next-swing is not.
255
+ ctx.isWeaponAction = (s: SpellDef): boolean => !!s.requiresMainHandWeapon;
256
+ // A control-breaker usable under the control on this unit, by two paths: a cleanse with no hostile
257
+ // cc, or a mechanic immunity that purges the active CC. Will of the Forsaken under a stun is false.
258
+ const breakerShape = new WeakMap<SpellDef, { cc: boolean; cleanse: boolean; mechImm: boolean }>();
259
+ const breakerShapeOf = (s: SpellDef): { cc: boolean; cleanse: boolean; mechImm: boolean } => {
260
+ let hit = breakerShape.get(s);
261
+ if (hit) return hit;
262
+ hit = {
263
+ cc: s.effects.some((e) => e.kind === 'cc'),
264
+ cleanse: s.effects.some((e) => e.kind === 'cleanse'),
265
+ mechImm: s.effects.some((e) => e.kind === 'mechanic_immunity'),
266
+ };
267
+ breakerShape.set(s, hit);
268
+ return hit;
269
+ };
270
+ ctx.isBreaker = (s: SpellDef, f?: Fighter): boolean => {
271
+ const shape = breakerShapeOf(s);
272
+ if (shape.cc) return false;
273
+ const hasCleanse = shape.cleanse;
274
+ const hasMechImm = shape.mechImm;
275
+ if (!hasCleanse && !hasMechImm) return false;
276
+
277
+ // Type-only: cleanse always; mechanic_immunity only when it covers a hard-CC mechanic
278
+ // (stun/fear/sleep/charm/knockout/freeze/sap — not root/snare alone like Free Action).
279
+ if (!f) {
280
+ if (hasCleanse) return true;
281
+ return s.effects.some((e) => {
282
+ if (e.kind !== 'mechanic_immunity') return false;
283
+ const m = e.mechanic | 0;
284
+ return m === MECHANIC.STUN || m === MECHANIC.FEAR || m === MECHANIC.SLEEP
285
+ || m === MECHANIC.CHARM || m === MECHANIC.KNOCKOUT || m === MECHANIC.FREEZE
286
+ || m === MECHANIC.SAPPED || m === MECHANIC.HORROR || m === MECHANIC.BANISH
287
+ || m === MECHANIC.SHACKLE || m === MECHANIC.POLYMORPH || m === MECHANIC.DISORIENTED;
288
+ });
289
+ }
290
+
291
+ // Path 2 first: mechanic_immunity that cancels every live hard-control row.
292
+ // spellCancelsOwnControl already encodes foundNotMechanic (stun+fear vs Blink → false).
293
+ if (
294
+ hasMechImm
295
+ && (ctx.spellCancelsOwnControl as (unit: Fighter, spell: SpellDef) => boolean)(f, s)
296
+ ) {
297
+ return true;
298
+ }
299
+
300
+ // Path 1: cleanse strip list (WotF fear-only under stun → false).
301
+ if (!hasCleanse) return false;
302
+ const strip = new Set<ControlKind>();
303
+ for (const e of s.effects) {
304
+ if (e.kind === 'cleanse') {
305
+ for (const c of e.controls ?? []) strip.add(c);
306
+ }
307
+ }
308
+ const hard = (ctx.isControlledHard as (x: Fighter) => boolean)(f);
309
+ if (hard) {
310
+ if (f.stunnedUntil > ctx.t && !strip.has('stun')) return false;
311
+ if (f.fearedUntil > ctx.t && !strip.has('fear')) return false;
312
+ if (f.incapUntil > ctx.t && !strip.has('incapacitate')) return false;
313
+ return true;
314
+ }
315
+ if (ctx.isCharmed(f)) return strip.has('charm');
316
+ return false;
317
+ };
318
+
319
+ // Immunity: an active category immunity, the only-types gating, and undead MOBS immune to ordinary
320
+ // fear. The polymorph creature-type gate is whole-spell off the baked mask, not a hard type list.
321
+ ctx.ccImmune = (tgt: Fighter, mechanic: Mechanic, onlyTypes?: number[]): boolean => {
322
+ const cat = CC_IMMUNE_KEY[mechanic] ?? 'none';
323
+ if ((tgt.ccImmuneUntil.get(cat) ?? 0) > ctx.t) return true;
324
+ if (onlyTypes && !onlyTypes.includes(tgt.creatureType)) return true;
325
+ if (mechanic === 'fear' && !onlyTypes && tgt.creatureType === CREATURE_TYPE.UNDEAD && tgt.side !== 'player') return true;
326
+ // Template MechanicImmuneMask (Creature::IsImmuneToSpell / IsImmuneToSpellEffect).
327
+ if (isMechanicImmune(tgt.spec.mechanicImmuneMask, mechanic)) return true;
328
+ return false;
329
+ };
330
+
331
+ /** SchoolImmuneMask / MechanicImmuneMask / creature_immunities dispel set vs the spell. */
332
+ ctx.spellTemplateImmune = (tgt: Fighter, spell: SpellDef): boolean =>
333
+ isCreatureImmuneToSpell(
334
+ {
335
+ schoolImmuneMask: tgt.spec.schoolImmuneMask,
336
+ mechanicImmuneMask: tgt.spec.mechanicImmuneMask,
337
+ immunities: tgt.spec.immunities,
338
+ },
339
+ spell,
340
+ );
341
+
342
+ // Diminishing returns: each repeat within a category is halved, then quartered, then immune, with
343
+ // the window resetting after the last wears off. Two gates — do LEVELS apply, and is duration scaled.
344
+ ctx.applyDr = (src: Fighter, tgt: Fighter, mechanic: Mechanic, baseMs: number): number => {
345
+ const key = drGroupOf(mechanic);
346
+ const rec = tgt.dr.get(key);
347
+ const step = rec && rec.resetAt > ctx.t ? rec.step : 0;
348
+ // Unit.cpp ApplyDiminishingToDuration: pvp = both UNIT_FLAG_PLAYER_CONTROLLED.
349
+ const isPvp =
350
+ (src.spec.isPlayerOrPet ?? (src.side === 'player' || src.side === 'pet' || isAllySide(src.side))) &&
351
+ (tgt.spec.isPlayerOrPet ?? (tgt.side === 'player' || tgt.side === 'pet' || isAllySide(tgt.side)));
352
+
353
+ // DRTYPE_PLAYER vs creatures: full duration forever; never track/advance steps.
354
+ // Cite: Spell.cpp ~1440 — IncrDiminishing gated on IsSubjectToDiminishingLevels.
355
+ if (!isSubjectToDiminishingLevels(key, isPvp)) {
356
+ return baseMs;
357
+ }
358
+
359
+ const durationDim = isDiminishingReturnsGroupDurationDiminished(key, isPvp);
360
+ const factor = durationDim ? drFactor(step) : 1;
361
+ const durMs = Math.round(baseMs * factor);
362
+ // When duration is not diminished, keep the full base for the reset clock. Levels still advance,
363
+ // so the step clock keeps progressing for groups that ARE subject to them.
364
+ const clockMs = durationDim ? durMs : baseMs;
365
+ tgt.dr.set(key, { step: step + 1, resetAt: ctx.t + clockMs + TUNING.drResetMs });
366
+ return durMs;
367
+ };
368
+
369
+ // Apply an immunity aura: purge the matching live effects, fold the state, mirror the effect row.
370
+ // Peeled with the CC cluster because the ScriptDev charm helpers need it; applySpell uses it too.
371
+ ctx.grantImmunityAura = (
372
+ owner: Fighter,
373
+ aura: ImmunityAura,
374
+ expiresAt: number,
375
+ name: string,
376
+ source: Side,
377
+ ): void => {
378
+ // Include the CC mechanic bits so a mechanic immunity can purge matching live CC through the
379
+ // apply-time removal. Cite: SpellAuras.cpp HandleModMechanicImmunity.
380
+ const now = ctx.t;
381
+ const removable = ctx.effectsOn(owner.side)
382
+ .filter((e) => e.expiresAt > now)
383
+ .map((e) => ({
384
+ spellId: e.spellId,
385
+ schoolMask: e.school ? schoolMaskFromSchool(e.school) : 0,
386
+ mechanicMask: mechanicMaskOfLiveEffect(e),
387
+ // The aura's own spell Dispel column — the dispel-immunity branch matches on it. Resolved
388
+ // through the same helper the dispel effect uses, so immunity and Cleanse cannot disagree.
389
+ dispelType: ctx.dispelTypeOfSpell!(e.spellId),
390
+ positive: e.kind === 'buff' || e.kind === 'school_absorb' || e.kind === 'mana_shield',
391
+ }));
392
+ const res = applyImmunityAura(owner.immunity, aura, removable);
393
+ owner.immunity = res.state;
394
+ // Drop purged spell instances through the canonical holder-remove, the C++ call the purge is
395
+ // specified in terms of. A hand-rolled splice left a purged root's timer set with no aura.
396
+ for (const spellId of res.removedSpellIds) {
397
+ ctx.removeAurasDueToSpell!(owner, spellId);
398
+ }
399
+ if (
400
+ aura.positive &&
401
+ (aura.auraType === AURA_TYPE.EFFECT_IMMUNITY ||
402
+ (aura.auraType === AURA_TYPE.SCHOOL_IMMUNITY && aura.immunityPurgesEffect))
403
+ ) {
404
+ ctx.removeAurasWithInterruptFlags?.(owner, AURA_INTERRUPT.INVULNERABILITY_BUFF_CANCELS, 'invulnerability');
405
+ }
406
+
407
+ // Refresh immunity mirror effect.
408
+ const live = ctx.effectsOn(owner.side).find(
409
+ (e) => e.kind === 'immunity' && e.spellId === aura.spellId,
410
+ );
411
+ const charges = (aura.charges != null && aura.charges > 0) ? aura.charges : undefined;
412
+ const immunityMechanics = aura.mechanic != null && aura.mechanic > 0
413
+ ? [aura.mechanic]
414
+ : undefined;
415
+ if (live) {
416
+ live.expiresAt = expiresAt;
417
+ if (charges != null) live.charges = charges;
418
+ if (immunityMechanics) live.immunityMechanics = immunityMechanics;
419
+ touchEffect(live);
420
+ } else {
421
+ ctx.effects.push({
422
+ kind: 'immunity',
423
+ source,
424
+ target: owner.side,
425
+ spellId: aura.spellId,
426
+ name,
427
+ expiresAt,
428
+ ...(charges != null ? { charges } : {}),
429
+ ...(immunityMechanics ? { immunityMechanics } : {}),
430
+ });
431
+ }
432
+ owner.buffActive.add(aura.spellId);
433
+ };
434
+
435
+ ctx.consumeChargedImmunityOnMechanicBlock = (
436
+ owner: Fighter,
437
+ mechanic: Mechanic | string,
438
+ ): void => {
439
+ const want = mechanicIdFromName(mechanic);
440
+ if (want <= 0) return;
441
+ const t = ctx.t;
442
+ const mine = ctx.effectsOn(owner.side);
443
+ for (let i = mine.length - 1; i >= 0; i--) {
444
+ const e = mine[i]!;
445
+ if (e.kind !== 'immunity') continue;
446
+ if (e.charges == null || e.charges <= 0) continue;
447
+ if (e.expiresAt <= t) continue;
448
+ const mechs = e.immunityMechanics ?? [];
449
+ if (!mechs.includes(want)) continue;
450
+ const { remaining, remove } = consumeProcCharge(e.charges);
451
+ if (remove) {
452
+ ctx.removeAurasDueToSpell?.(owner, e.spellId);
453
+ } else {
454
+ e.charges = remaining;
455
+ }
456
+ return;
457
+ }
458
+ };
459
+
460
+ // ScriptDev aura-driven charm helpers, the Anastari Possess pattern. The side-switch stays and the
461
+ // hardcoded spell ids are retired — the IR owns the helpers now.
462
+ const resolveAuraUnit = (
463
+ on: 'aura_target' | 'caster',
464
+ caster: Fighter,
465
+ target: Fighter,
466
+ ): Fighter => (on === 'caster' ? caster : target);
467
+
468
+ const applyHelperSpellEffects = (
469
+ src: Fighter,
470
+ tgt: Fighter,
471
+ helper: SpellDef,
472
+ until: number,
473
+ ): void => {
474
+ // Apply resolvable combat effects from helper SpellDef (triggered).
475
+ // Cite: AnastariPossess OnApply CastSpell TRIGGERED_OLD_TRIGGERED.
476
+ if (tgt.buffActive.has(helper.id)) return;
477
+ for (const he of helper.effects ?? []) {
478
+ if (he.kind === 'buff' && he.mod) {
479
+ applyBuffMod(tgt, he.mod as BuffMod, he.amount ?? 0, he.school);
480
+ ctx.effects.push({
481
+ kind: 'buff',
482
+ source: src.side,
483
+ target: tgt.side,
484
+ spellId: helper.id,
485
+ name: helper.name,
486
+ mod: he.mod,
487
+ amount: he.amount ?? 0,
488
+ expiresAt: until,
489
+ });
490
+ ctx.push({ t: ctx.t, kind: 'aura', actor: tgt.side, name: helper.name, applied: true });
491
+ } else if (he.kind === 'heal_max_health') {
492
+ tgt.hp = tgt.spec.maxHealth;
493
+ } else if (he.kind === 'school_immunity') {
494
+ (ctx.grantImmunityAura as Function)(tgt, {
495
+ spellId: helper.id,
496
+ auraType: AURA_TYPE.SCHOOL_IMMUNITY,
497
+ schoolMask: he.schoolMask ?? SPELL_SCHOOL_MASK_ALL,
498
+ positive: he.positive !== false,
499
+ durationMs: Math.max(0, until - ctx.t),
500
+ }, until, helper.name, src.side);
501
+ ctx.push({ t: ctx.t, kind: 'aura', actor: tgt.side, name: helper.name, applied: true });
502
+ } else if (he.kind === 'cc' && he.control === 'stun') {
503
+ tgt.stunnedUntil = Math.max(tgt.stunnedUntil, until);
504
+ ctx.effects.push({
505
+ kind: 'cc',
506
+ source: src.side,
507
+ target: tgt.side,
508
+ spellId: helper.id,
509
+ name: helper.name,
510
+ school: he.school ?? 'shadow',
511
+ control: 'stun',
512
+ mechanic: he.mechanic ?? 'stun',
513
+ expiresAt: until,
514
+ });
515
+ ctx.push({ t: ctx.t, kind: 'aura', actor: tgt.side, name: helper.name, applied: true });
516
+ }
517
+ }
518
+ tgt.buffActive.add(helper.id);
519
+ };
520
+
521
+ const runAuraScriptActions = (
522
+ actions: readonly import('../../domain/scriptdev/types.ts').ScriptDevAuraAction[] | undefined,
523
+ caster: Fighter,
524
+ target: Fighter,
525
+ until: number,
526
+ ): void => {
527
+ if (!actions) return;
528
+ for (const act of actions) {
529
+ if (act.kind === 'cast_helper') {
530
+ const unit = resolveAuraUnit(act.on, caster, target);
531
+ let helper =
532
+ caster.spec.spells.find((s) => s.id === act.spellId)
533
+ ?? target.spec.spells.find((s) => s.id === act.spellId)
534
+ ?? ctx.spellOf?.(act.spellId);
535
+ if (!helper) continue;
536
+
537
+ if (act.basepoints !== undefined) {
538
+ helper = {
539
+ ...helper,
540
+ effects: helper.effects?.map((e, i) => i === 0 ? { ...e, min: act.basepoints!, max: act.basepoints!, amount: act.basepoints! } : e),
541
+ rawEffects: helper.rawEffects?.map((e, i) => i === 0 ? { ...e, basePoints: act.basepoints! - 1 } : e),
542
+ };
543
+ }
544
+
545
+ // CMaNGOS CastSpell(TRIGGERED_OLD_TRIGGERED) — land the real holder (17250 school immune + invis +
546
+ // stun), not a bake-stun stub. Cite: boss_baroness_anastari.cpp:188-189.
547
+ if (ctx.applySpell) ctx.applySpell(caster, unit, helper);
548
+ else applyHelperSpellEffects(caster, unit, helper, until);
549
+ } else if (act.kind === 'remove_auras') {
550
+ const unit = resolveAuraUnit(act.on, caster, target);
551
+ ctx.removeAurasDueToSpell!(unit, act.spellId);
552
+ } else if (act.kind === 'heal_full') {
553
+ const unit = resolveAuraUnit(act.on, caster, target);
554
+ unit.hp = unit.spec.maxHealth;
555
+ } else if (act.kind === 'set_unattackable') {
556
+ const unit = resolveAuraUnit(act.on, caster, target);
557
+ unit.unattackableUntil = Math.max(unit.unattackableUntil, until);
558
+ unit.stunnedUntil = Math.max(unit.stunnedUntil, until);
559
+ } else if (act.kind === 'clear_unattackable') {
560
+ const unit = resolveAuraUnit(act.on, caster, target);
561
+ unit.unattackableUntil = 0;
562
+ if (unit.stunnedUntil > ctx.t) unit.stunnedUntil = ctx.t;
563
+ } else if (act.kind === 'break_charm_if_hp_below') {
564
+ if (ctx.isCharmed(target) && target.hp <= target.spec.maxHealth * act.frac) {
565
+ ctx.breakCharm!(target);
566
+ }
567
+ } else if (act.kind === 'flavor') {
568
+ const who = act.text.includes('vanishes') ? caster : target;
569
+ ctx.push({ t: ctx.t, kind: 'flavor', text: `${who.spec.name} ${act.text}` });
570
+ }
571
+ }
572
+ };
573
+
574
+ const stripPossessInv = (unit: Fighter, invId: number): void => {
575
+ if (!invId) return;
576
+ ctx.removeAurasDueToSpell!(unit, invId);
577
+ unit.immunity = removeImmunityAura(unit.immunity, invId);
578
+ unit.buffActive.delete(invId);
579
+ unit.unattackableUntil = 0;
580
+ if (unit.stunnedUntil > ctx.t) unit.stunnedUntil = ctx.t;
581
+ // Splice leftover 17250 stun rows — view treats every kind:cc as a debuff chip.
582
+ // Cite: boss_baroness_anastari.cpp:196 RemoveAurasDueToSpell(SPELL_POSSESS_INV).
583
+ for (let i = ctx.effects.length - 1; i >= 0; i--) {
584
+ const e = ctx.effects[i]!;
585
+ if (e.spellId === invId && e.target === unit.side) ctx.effects.splice(i, 1);
586
+ }
587
+ };
588
+
589
+ const fighterOnSide = (side: Fighter['side'] | null | undefined): Fighter | null => {
590
+ if (side == null) return null;
591
+ return ctx.sideFighter?.(side) ?? null;
592
+ };
593
+
594
+ const stripOrphanPossessInv = (): void => {
595
+ const invIds = possessInvSpellIds();
596
+ if (invIds.length === 0) return;
597
+ const order = ctx.fightersOrder();
598
+ const heldBy = new Map<Fighter, number[]>();
599
+ const add = (unit: Fighter, id: number): void => {
600
+ const arr = heldBy.get(unit);
601
+ if (arr) {
602
+ if (!arr.includes(id)) arr.push(id);
603
+ } else {
604
+ heldBy.set(unit, [id]);
605
+ }
606
+ };
607
+ let anyCharm = false;
608
+ for (const unit of order) {
609
+ if (ctx.isCharmed(unit)) anyCharm = true;
610
+ for (const id of invIds) if (unit.buffActive.has(id)) add(unit, id);
611
+ }
612
+ // Idle 40-man: no Anastari, no inv chip. Skip the per-seat effect walk.
613
+ if (heldBy.size === 0 && !anyCharm) return;
614
+ for (const unit of order) {
615
+ for (const e of ctx.effectsOn(unit.side)) {
616
+ if (e.spellId == null || !isPossessInvSpell(e.spellId)) continue;
617
+ add(unit, e.spellId);
618
+ }
619
+ }
620
+ if (heldBy.size === 0) return;
621
+ for (const [unit, held] of heldBy) {
622
+ const ownedLive = order.some(
623
+ (u) => u !== unit && u.hp > 0 && ctx.isCharmed(u) && u.charmedBy === unit.side,
624
+ );
625
+ if (ownedLive) continue;
626
+ for (const id of held) stripPossessInv(unit, id);
627
+ }
628
+ };
629
+
630
+ ctx.dropIdlePossessInv = (): void => {
631
+ const order = ctx.fightersOrder();
632
+ for (const u of order) {
633
+ if (!ctx.isCharmed(u) || u.charmPeriodicAt <= 0 || ctx.t < u.charmPeriodicAt) continue;
634
+ const as = getAuraScript(u.charmSpellId);
635
+ if (!as?.periodMs || !as.onPeriodic) {
636
+ u.charmPeriodicAt = 0;
637
+ continue;
638
+ }
639
+ u.charmPeriodicAt = ctx.t + as.periodMs;
640
+ const owner = fighterOnSide(u.charmedBy);
641
+ if (owner) runAuraScriptActions(as.onPeriodic, owner, u, u.charmedUntil);
642
+ }
643
+ stripOrphanPossessInv();
644
+ };
645
+
646
+ ctx.breakCharm = (f: Fighter): void => {
647
+ const spellId = f.charmSpellId;
648
+ const charmerSide = f.charmedBy;
649
+ const hadCharm = ctx.isCharmed(f) || f.charmedBy != null || f.charmedUntil > 0 || spellId !== 0;
650
+ const auraScript = spellId ? getAuraScript(spellId) : undefined;
651
+ const charmer = fighterOnSide(charmerSide);
652
+
653
+ if (!hadCharm) {
654
+ stripOrphanPossessInv();
655
+ return;
656
+ }
657
+
658
+ // Clear charm flags first so removeAurasDueToSpell does not re-enter breakCharm.
659
+ f.charmedUntil = 0;
660
+ f.charmedBy = null;
661
+ f.charmSpellId = 0;
662
+ f.charmBreakHpFrac = 0;
663
+ f.charmPeriodicAt = 0;
664
+ ctx.bumpPool?.(); // faction flip undoes — friendly/hostile pools
665
+
666
+ // ScriptDev onRemove (strip Possessed / Possess Inv). Cite: AnastariPossess apply=false.
667
+ if (auraScript && charmer) {
668
+ runAuraScriptActions(auraScript.onRemove, charmer, f, ctx.t);
669
+ }
670
+
671
+ // set_unattackable is not an ActiveEffect — RemoveAurasDueToSpell(17250) must
672
+ // still drop the vanish window. Cite: boss_baroness_anastari.cpp:102,110,196.
673
+ const invId = auraScript?.possessInvSpellId ?? 0;
674
+ if (charmer && invId) stripPossessInv(charmer, invId);
675
+ else stripOrphanPossessInv();
676
+
677
+ if (charmer && isEnemySide(charmer.side) && !ctx.isUnattackable(charmer)) {
678
+ if (ctx.playerFocusFighter === f || ctx.isUnattackable(ctx.playerFocusFighter)) {
679
+ ctx.playerFocusFighter = charmer;
680
+ }
681
+ if (ctx.explicitEnemyFocus == null || ctx.explicitEnemyFocus === charmer.side) {
682
+ ctx.explicitEnemyFocus = charmer.side;
683
+ }
684
+ }
685
+
686
+ for (const e of ctx.effectsOn(f.side)) {
687
+ if (e.kind === 'cc' && e.control === 'charm' && !e.broken) {
688
+ fadeEffectNow(e);
689
+ }
690
+ // Wider than the row above on purpose: Seduction is control:'stun' with mechanic charm, and
691
+ // breaking the charm must fade it too.
692
+ if (
693
+ e.kind === 'cc' && e.mechanic === 'charm' && !e.broken
694
+ && (e.spellId === spellId || spellId === 0)
695
+ ) {
696
+ fadeEffectNow(e);
697
+ }
698
+ }
699
+ ctx.push({ t: ctx.t, kind: 'control', target: f.side, control: 'charm', applied: false });
700
+
701
+ // Strip remaining holder-spell residues (e.g. 17244 max_health +2000).
702
+ // Cite: RemoveAurasDueToSpell(SPELL_POSSESS); GAPS C.4 +HP strip on break.
703
+ if (spellId) {
704
+ ctx.removeAurasDueToSpell!(f, spellId);
705
+ }
706
+ };
707
+
708
+ // TakeCharmOf side-switch, not an incapacitate, in both directions: enemy to player, and player to
709
+ // enemy for Enslave Demon or a Mind Control thrall. Cite: Unit.cpp:11328; SpellAuras.cpp:2282.
710
+ const applyCharm = (src: Fighter, tgt: Fighter, spell: SpellDef, eff: CcEffect, durMs: number, heartbeat?: HeartbeatHolder | null): void => {
711
+ // Not charm / possess yourself — HandleModCharm and HandleModPossess both return.
712
+ // Cite: SpellAuras.cpp:2289–2291, :2195–2197; charm-possess.md.
713
+ if (src === tgt || src.side === tgt.side) return;
714
+ const playerCaster = !isEnemySide(src.side);
715
+ const rawAuras = (spell.rawEffects ?? []).map((r) => (r.auraName | 0));
716
+ const outgoing = charmOutgoingPolicy({
717
+ playerCaster,
718
+ aoeCharm: rawAuras.includes(SPELL_AURA_AOE_CHARM),
719
+ possessUnique: rawAuras.includes(SPELL_AURA_MOD_POSSESS)
720
+ || Boolean(getAuraScript(spell.id)?.possessInvSpellId),
721
+ });
722
+ for (const u of ctx.fightersOrder()) {
723
+ if (u === tgt) continue;
724
+ if (ctx.isCharmed(u) && u.charmedBy === src.side) {
725
+ if (outgoing === 'break_outgoing') ctx.breakCharm!(u);
726
+ else if (outgoing === 'refuse') return;
727
+ // coexist / offload: keep the existing charm (private set / advertised offload).
728
+ // Cite: Unit.cpp:11345–11351 TakeCharmOf advertised offload vs m_charmedUnitsPrivate.
729
+ }
730
+ }
731
+ if (ctx.isCharmed(tgt)) ctx.breakCharm!(tgt);
732
+ // Clear residual hard-CC timers — charm is PetAI thrall, not stun/incap.
733
+ tgt.incapUntil = Math.min(tgt.incapUntil, ctx.t);
734
+ tgt.stunnedUntil = Math.min(tgt.stunnedUntil, ctx.t);
735
+ const until = ctx.t + durMs;
736
+ tgt.charmedBy = src.side;
737
+ tgt.charmedUntil = until;
738
+ tgt.charmSpellId = spell.id;
739
+ tgt.charmBreakHpFrac = 0;
740
+ tgt.charmPeriodicAt = 0;
741
+ tgt.castingSpell = null; // interrupt current cast on possess (cancel() takes no power)
742
+ tgt.castCost = 0;
743
+ tgt.castCostPower = undefined;
744
+ ctx.bumpPool?.(); // TakeCharmOf faction flip — friendly/hostile pools
745
+
746
+ // ScriptDev AuraScript extras (possess chain, HP break frac, unattackable).
747
+ // Cite: boss_baroness_anastari.cpp AnastariPossess; registry auraScripts.
748
+ const auraScript = getAuraScript(spell.id);
749
+ if (auraScript) {
750
+ if (auraScript.charmBreakHpFrac != null && auraScript.charmBreakHpFrac > 0) {
751
+ tgt.charmBreakHpFrac = auraScript.charmBreakHpFrac;
752
+ }
753
+ if (auraScript.periodMs != null && auraScript.periodMs > 0) {
754
+ tgt.charmPeriodicAt = ctx.t + auraScript.periodMs;
755
+ }
756
+ runAuraScriptActions(auraScript.onApply, src, tgt, until);
757
+ // Arm possess-end timer on ScriptDev runtime when inv helper is present.
758
+ const cs = isEnemySide(src.side)
759
+ ? ctx.enemyCastBySide.get(src.side as EnemySide)
760
+ : undefined;
761
+ if (cs?.scriptDev && auraScript.possessInvSpellId) {
762
+ notePossessLanded(cs.scriptDev, tgt.side);
763
+ // Vanish makes the boss unattackable, so pin the focus on the possessed body — the party
764
+ // burns it down instead of standing on the invisible caster.
765
+ ctx.playerFocusFighter = tgt;
766
+ if (ctx.explicitEnemyFocus === src.side) ctx.explicitEnemyFocus = null;
767
+ }
768
+ }
769
+
770
+ ctx.effects.push({
771
+ kind: 'cc', source: src.side, target: tgt.side, spellId: spell.id, name: spell.name,
772
+ school: eff.school, control: 'charm', mechanic: 'charm', breakOnDamage: eff.breakOnDamage,
773
+ expiresAt: until,
774
+ ...(heartbeat ? { heartbeat } : {}),
775
+ });
776
+ ctx.push({ t: ctx.t, kind: 'control', target: tgt.side, control: 'charm', applied: true, name: spell.name });
777
+
778
+ // Enemy-to-player solo softlock: possess with no hostiles left breaks the charm, the Anastari
779
+ // leash. A player thrall stays, since a 1v1 thrall is a valid pet state.
780
+ if (!playerCaster && ctx.livingHostilesFor(tgt).length === 0) {
781
+ ctx.breakCharm!(tgt);
782
+ }
783
+ };
784
+
785
+ /** Attach / extend the 12939 boost holder. Cite: HandleSpellSpecificBoosts(apply=true). */
786
+ const applyPolymorphHeal = (src: Fighter, tgt: Fighter, until: number): void => {
787
+ const live = ctx.effectsOn(tgt.side).find(
788
+ (e) => e.kind === 'restore_health' && e.spellId === POLYMORPH_HEAL_SPELL_ID
789
+ && !e.broken,
790
+ );
791
+ if (live) {
792
+ live.expiresAt = Math.max(live.expiresAt, until);
793
+ live.broken = false;
794
+ ctx.bumpEffectsVersion();
795
+ touchEffect(live);
796
+ return;
797
+ }
798
+ ctx.effects.push({
799
+ kind: 'restore_health',
800
+ source: src.side,
801
+ target: tgt.side,
802
+ spellId: POLYMORPH_HEAL_SPELL_ID,
803
+ name: POLYMORPH_HEAL_NAME,
804
+ // pctOfMax drives floor(maxHealth × 10 / 100) per tick in the stepSession restore pass.
805
+ pctOfMax: POLYMORPH_HEAL_PCT_OF_MAX,
806
+ tickMs: POLYMORPH_HEAL_TICK_MS,
807
+ nextTickAt: ctx.t + POLYMORPH_HEAL_TICK_MS,
808
+ expiresAt: until,
809
+ });
810
+ // Deliberately NOT in buffActive: 12939 is hiddenClientside, so it ticks without a chip.
811
+ };
812
+
813
+ // Drop the polymorph heal holder once no polymorph aura is left: the boost is removed with the aura
814
+ // that pushed it, so a sheep broken at 3 s stops healing at 3 s.
815
+ const dropPolymorphHealIfFree = (f: Fighter): void => {
816
+ const t = ctx.t;
817
+ const stillPoly = ctx.effectsOn(f.side).some(
818
+ (e) => e.kind === 'cc' && !e.broken && e.expiresAt > t && e.mechanic === 'polymorph',
819
+ );
820
+ if (stillPoly) return;
821
+ for (const e of ctx.effectsOn(f.side)) {
822
+ if (
823
+ e.kind !== 'restore_health' || e.spellId !== POLYMORPH_HEAL_SPELL_ID
824
+ || e.broken
825
+ ) continue;
826
+ fadeEffectNow(e);
827
+ }
828
+ };
829
+
830
+ // Drop ONE spell's holder off a unit, the way the tracked-aura map does it — unlike breakControl,
831
+ // which drops every caster's row of that control. Cite: Unit.cpp:4801 RemoveAurasDueToSpell(itr).
832
+ const removeTrackedHolder = (f: Fighter, spellId: number): boolean => {
833
+ // The lockout each control slot of THIS holder was carrying, read before the rows go.
834
+ let held: Map<ControlKind, number> | null = null;
835
+ for (const e of ctx.effectsOn(f.side)) {
836
+ if (e.spellId !== spellId || e.broken) continue;
837
+ const c = controlOfLiveEffect(e);
838
+ if (c) (held ??= new Map()).set(c, Math.max(held.get(c) ?? 0, e.expiresAt));
839
+ }
840
+ if (!(ctx.removeAurasDueToSpell?.(f, spellId) ?? 0)) return false;
841
+ for (const [c, until] of held ?? []) {
842
+ const key = CONTROL_TIMER[c as keyof typeof CONTROL_TIMER];
843
+ if (key) {
844
+ let rest = 0;
845
+ for (const e of ctx.effectsOn(f.side)) {
846
+ if (e.broken || controlOfLiveEffect(e) !== c) continue;
847
+ rest = Math.max(rest, e.expiresAt);
848
+ }
849
+ // Only the lockout this holder was carrying comes down: an AI immobilize that set the timer
850
+ // with no aura row behind it keeps its own. Cite: crowdControl breakRootOnDamage sawRootAura.
851
+ if (f[key] <= until) f[key] = rest;
852
+ }
853
+ if (c === 'root') ctx.push({ t: ctx.t, kind: 'root', target: f.side, applied: false });
854
+ else ctx.push({ t: ctx.t, kind: 'control', target: f.side, control: c, applied: false });
855
+ }
856
+ dropPolymorphHealIfFree(f);
857
+ return true;
858
+ };
859
+
860
+ // A tracked aura sits on ONE target per caster, so landing it elsewhere drops the seat it held —
861
+ // 71 baked rows, Fear to Hunter's Mark. Cite: Unit.cpp:4774-4830; SpellMgr.h:2230, :2261.
862
+ ctx.trackedAuraSeats = (src: Fighter, spell: SpellDef): { side: Side; spellId: number }[] => {
863
+ if (!isSingleTargetSpell(spell)) return [];
864
+ const seats: { side: Side; spellId: number }[] = [];
865
+ const seen = new Set<string>();
866
+ for (const e of ctx.effectsFrom(src.side)) {
867
+ if (e.broken || e.expiresAt <= ctx.t) continue;
868
+ if (seen.has(`${e.target}/${e.spellId}`)) continue;
869
+ // The caster's own kit first, then the global store — `spellOf` is absent in fixtures.
870
+ // Cite: dispelReflect.ts:330 the same two-step lookup.
871
+ const other = e.spellId === spell.id
872
+ ? spell
873
+ : src.spec.spells.find((s) => s.id === e.spellId) ?? ctx.spellOf?.(e.spellId);
874
+ if (!other) continue;
875
+ const chain = ctx.spellChainOf;
876
+ const first = chain?.(other.id)?.first;
877
+ const sameChain = first != null && first === chain?.(spell.id)?.first;
878
+ if (!isSingleTargetSpellPair(other, spell, sameChain)) continue;
879
+ // A seat on a body that has died is free: the holder's rows go with it.
880
+ const holder = fighterBySide(e.target);
881
+ if (!holder || !(holder.hp > 0)) continue;
882
+ seen.add(`${e.target}/${e.spellId}`);
883
+ seats.push({ side: e.target, spellId: e.spellId });
884
+ }
885
+ return seats;
886
+ };
887
+
888
+ ctx.purgeTrackedAuraTargets = (src: Fighter, tgt: Fighter, spell: SpellDef): number => {
889
+ let purged = 0;
890
+ for (const seat of ctx.trackedAuraSeats!(src, spell)) {
891
+ if (seat.side === tgt.side) continue;
892
+ const holder = fighterBySide(seat.side);
893
+ if (holder && removeTrackedHolder(holder, seat.spellId)) purged++;
894
+ }
895
+ return purged;
896
+ };
897
+
898
+ // Land a control: set its lockout timer, cut short an in-progress cast if it locks casting, and
899
+ // push both the ActiveEffect mirror (drives expiry + view.debuffs) and the applied event.
900
+ ctx.applyControl = (src: Fighter, tgt: Fighter, spell: SpellDef, eff: { control: ControlKind; mechanic: Mechanic; school?: School; breakOnDamage?: boolean }, durMs: number, heartbeat?: HeartbeatHolder | null): void => {
901
+ const ccEff = eff as CcEffect;
902
+ // The charm family takes the side-switch in both directions. Seduction is a stun with a charm
903
+ // mechanic and must stay one, so this reads `control`, never the mechanic alone.
904
+ if (eff.control === 'charm') {
905
+ applyCharm(src, tgt, spell, ccEff, durMs, heartbeat);
906
+ return;
907
+ }
908
+
909
+ const until = ctx.t + durMs;
910
+ const control = eff.control;
911
+ if (control === 'stun') tgt.stunnedUntil = Math.max(tgt.stunnedUntil, until);
912
+ else if (control === 'fear') tgt.fearedUntil = Math.max(tgt.fearedUntil, until);
913
+ else if (control === 'incapacitate') tgt.incapUntil = Math.max(tgt.incapUntil, until);
914
+ else if (control === 'silence') tgt.silencedUntil = Math.max(tgt.silencedUntil, until);
915
+ else if (control === 'disarm') tgt.disarmedUntil = Math.max(tgt.disarmedUntil, until);
916
+ else if (control === 'root') tgt.rootedUntil = Math.max(tgt.rootedUntil, until);
917
+ // Silence only cancels silence-prevention casts (SpellAuras HandleAuraModSilence).
918
+ // Stun/fear/incap always cut the cast (CASTER_LOCK).
919
+ if (tgt.castingSpell && CASTER_LOCK.includes(control)) {
920
+ if (control === 'silence') {
921
+ const prev = (tgt.castingSpell.preventionType ?? 0) | 0;
922
+ // Prefer baked field: only cancel when PreventionType == SILENCE.
923
+ // Absent preventionType: cancel (legacy bakes / tests without the field).
924
+ if (prev === SPELL_PREVENTION_TYPE.SILENCE || tgt.castingSpell.preventionType == null) {
925
+ tgt.castingSpell = null;
926
+ }
927
+ } else {
928
+ tgt.castingSpell = null;
929
+ }
930
+ // Cancelled cast → Spell::cancel(), which never takes power. Drop the held cost.
931
+ if (!tgt.castingSpell) { tgt.castCost = 0; tgt.castCostPower = undefined; }
932
+ }
933
+ // The same lock takes the CHANNEL down, and only the damage branch existed for that — a stunned
934
+ // caster kept channelling Mind Flay for its full duration. Cite: Unit.cpp:10010 SetStunned.
935
+ if (tgt.channeling && CASTER_LOCK.includes(control)) {
936
+ const prev = (tgt.channeling.spell.preventionType ?? 0) | 0;
937
+ const silenceStops = prev === SPELL_PREVENTION_TYPE.SILENCE
938
+ || tgt.channeling.spell.preventionType == null;
939
+ if (control !== 'silence' || silenceStops) interruptChannel(tgt, channelInterruptCtx(ctx));
940
+ }
941
+ // The same diminishing group is mutual exclusion on apply: a second root replaces the first
942
+ // rather than running beside it. Scoped to a DIFFERENT spell id, or one cast would purge its rider.
943
+ {
944
+ const group = drGroupOf(eff.mechanic);
945
+ // SpellMgr.cpp:2684-2709 does not list snare / daze / disoriented, so they are
946
+ // DIMINISHING_NONE; Unit.cpp:4980 then skips uniqueness (`if (drGroup && …)`).
947
+ if (group !== 'none' && eff.mechanic !== 'snare' && eff.mechanic !== 'daze'
948
+ && eff.mechanic !== 'disoriented') {
949
+ let purge: Set<ControlKind> | null = null;
950
+ for (const e of ctx.effectsOn(tgt.side)) {
951
+ if (e.kind !== 'cc' || e.broken) continue;
952
+ if (e.spellId === spell.id || e.expiresAt <= ctx.t) continue;
953
+ if (!e.mechanic || !e.control) continue;
954
+ if (e.mechanic === 'snare' || e.mechanic === 'daze' || e.mechanic === 'disoriented') {
955
+ continue;
956
+ }
957
+ if (drGroupOf(e.mechanic) !== group) continue;
958
+ (purge ??= new Set()).add(e.control);
959
+ }
960
+ if (purge) for (const c of purge) ctx.breakControl!(tgt, c);
961
+ }
962
+ }
963
+ // The caster's own seat for this aura moves here: the sheep on the last mob drops.
964
+ ctx.purgeTrackedAuraTargets!(src, tgt, spell);
965
+ ctx.effects.push({
966
+ kind: 'cc', source: src.side, target: tgt.side, spellId: spell.id, name: spell.name,
967
+ // Fall back to the spell's school: cc effects often bake without one, and the frozen state plus the
968
+ // school-immunity purge both read GetSpellSchoolMask(proto). Cite: SpellAuras.cpp:2463-2464; :3013.
969
+ school: eff.school ?? spell.school, control, mechanic: eff.mechanic, breakOnDamage: eff.breakOnDamage,
970
+ expiresAt: until,
971
+ ...(heartbeat ? { heartbeat } : {}),
972
+ });
973
+ // The sheep regenerates. Keyed on the SPELL's Mechanic, exactly as the C++ boost is.
974
+ if (spell.mechanic === 'polymorph' || eff.mechanic === 'polymorph') {
975
+ applyPolymorphHeal(src, tgt, until);
976
+ }
977
+ ctx.push({ t: ctx.t, kind: 'control', target: tgt.side, control, applied: true, name: spell.name });
978
+ };
979
+
980
+ // One heartbeat-resist pass over every armed holder. Safe inside the effect sweep, since a break
981
+ // flags the mirror instead of splicing. A fight with no heartbeat spell takes no extra draw.
982
+ let heartbeatScanAt = -1;
983
+ let anyHeartbeat = false;
984
+ ctx.tickHeartbeatResists = (): void => {
985
+ const ver = ctx.effectsVersion();
986
+ if (ver === heartbeatScanAt && !anyHeartbeat) return;
987
+ anyHeartbeat = false;
988
+ for (const e of ctx.effects) {
989
+ if (!e.heartbeat || e.broken) continue;
990
+ anyHeartbeat = true;
991
+ if (e.expiresAt <= ctx.t) continue;
992
+ if (!tickHeartbeatResist(e.heartbeat, ctx.t, ctx.rng)) continue;
993
+ const holder = fighterBySide(e.target);
994
+ if (!holder) continue;
995
+ // RemoveSpellAuraHolder — reuse breakControl so timers, siblings and the faded event stay one path.
996
+ // Cite: SpellAuras.cpp:6021-6026.
997
+ if (e.kind === 'root') ctx.breakControl!(holder, 'root');
998
+ else if (e.kind === 'speed') ctx.breakControl!(holder, 'snare');
999
+ else if (e.kind === 'cc' && e.control) ctx.breakControl!(holder, e.control);
1000
+ }
1001
+ heartbeatScanAt = ver;
1002
+ };
1003
+ ctx.breakControl = (f: Fighter, control: ControlKind): void => {
1004
+ if (control === 'charm') {
1005
+ ctx.breakCharm!(f);
1006
+ return;
1007
+ }
1008
+ if (control === 'stun') f.stunnedUntil = 0;
1009
+ else if (control === 'fear') f.fearedUntil = 0;
1010
+ else if (control === 'incapacitate') f.incapUntil = 0;
1011
+ else if (control === 'silence') f.silencedUntil = 0;
1012
+ else if (control === 'disarm') f.disarmedUntil = 0;
1013
+ else if (control === 'root') f.rootedUntil = 0;
1014
+ let faded = false;
1015
+ let rootFaded = false;
1016
+ let snareFaded = false;
1017
+ // Holders whose cc slot just went — their remaining slots go with it (below).
1018
+ let holders: Set<number> | null = null;
1019
+ for (const e of ctx.effectsOn(f.side)) {
1020
+ if (e.kind === 'cc' && e.control === control && !e.broken) {
1021
+ fadeEffectNow(e); faded = true;
1022
+ (holders ??= new Set()).add(e.spellId);
1023
+ }
1024
+ // Root effects land as kind:'root' (mapAura MOD_ROOT), not kind:'cc' control:root.
1025
+ if (control === 'root' && e.kind === 'root' && !e.broken) {
1026
+ fadeEffectNow(e); rootFaded = true;
1027
+ }
1028
+ // Snares land as a hostile speed aura, not as cc. Cite: SharedDefines MECHANIC_SNARE=11;
1029
+ // SpellEffects.cpp EffectDispelMechanic (Escape Artist slot 2).
1030
+ if (
1031
+ control === 'snare'
1032
+ && e.kind === 'speed'
1033
+ && !e.broken
1034
+ && isHostileSpeedEffect(e)
1035
+ ) {
1036
+ fadeEffectNow(e);
1037
+ snareFaded = true;
1038
+ }
1039
+ }
1040
+ // A break removes the HOLDER, not one slot. Fear lands its cc and a +25% run aura under one spell
1041
+ // id, so fading only the cc row left the icon up and the victim faster while free to swing.
1042
+ if (holders) {
1043
+ for (const e of ctx.effectsOn(f.side)) {
1044
+ if (e.broken || !holders.has(e.spellId)) continue;
1045
+ if (e.kind === 'cc' || e.kind === 'root') continue;
1046
+ fadeEffectNow(e);
1047
+ }
1048
+ }
1049
+ dropPolymorphHealIfFree(f);
1050
+ if (faded) ctx.push({ t: ctx.t, kind: 'control', target: f.side, control, applied: false });
1051
+ if (rootFaded) ctx.push({ t: ctx.t, kind: 'root', target: f.side, applied: false });
1052
+ if (snareFaded) ctx.push({ t: ctx.t, kind: 'control', target: f.side, control: 'snare', applied: false });
1053
+ };
1054
+
1055
+ // A damaging hit ROLLS against each live root; it does not shatter it. The aura-26 proc is the same
1056
+ // handler fear uses — a 200-damage swing on a level-60 mob is about 15%, not 100%.
1057
+ const breakRootOnDamage = (f: Fighter, fromSpellId?: number, amount = 0): void => {
1058
+ if (f.rootedUntil <= ctx.t) return;
1059
+ let broke = false;
1060
+ let keepUntil = 0;
1061
+ let sawRootAura = false;
1062
+ const breakChance = ccDamageBreakChance(f.spec.level, amount);
1063
+ for (const e of ctx.effectsOn(f.side)) {
1064
+ if (e.broken) continue;
1065
+ const isRoot = e.kind === 'root' || (e.kind === 'cc' && e.control === 'root');
1066
+ if (!isRoot) continue;
1067
+ sawRootAura = true;
1068
+ // Same spell that applied the root does not break it (Frost Nova's own damage + root).
1069
+ if (fromSpellId != null && e.spellId === fromSpellId) {
1070
+ if (e.expiresAt > ctx.t) keepUntil = Math.max(keepUntil, e.expiresAt);
1071
+ continue;
1072
+ }
1073
+ // White swing the same 100 ms tick as apply is not a later AttackerStateUpdate.
1074
+ // Cite: Spell.cpp DoAllEffectOnTarget then melee timer; KI-1062.
1075
+ if (e.appliedAt != null && e.appliedAt === ctx.t) {
1076
+ if (e.expiresAt > ctx.t) keepUntil = Math.max(keepUntil, e.expiresAt);
1077
+ continue;
1078
+ }
1079
+ if (!ctx.rng.chance(breakChance)) {
1080
+ if (e.expiresAt > ctx.t) keepUntil = Math.max(keepUntil, e.expiresAt);
1081
+ continue;
1082
+ }
1083
+ fadeEffectNow(e);
1084
+ broke = true;
1085
+ }
1086
+ // Only rewrite the root timer when spell-root auras were present: an AI immobilize sets it with
1087
+ // no root effect and must survive white swings.
1088
+ if (sawRootAura) {
1089
+ f.rootedUntil = keepUntil;
1090
+ if (broke) ctx.push({ t: ctx.t, kind: 'root', target: f.side, applied: false });
1091
+ }
1092
+ };
1093
+
1094
+ // Called from every damage site. CC flagged break-on-damage shatters, a charm without the flag
1095
+ // survives, fear rolls, and roots roll above. Same-spell damage never drops its own CC.
1096
+ const breakBreakableCcOnDamage = (f: Fighter, fromSpellId?: number, attacker?: Fighter): void => {
1097
+ /** Shatter one control timer when its mirrors carry breakOnDamage. */
1098
+ const shatter = (
1099
+ control: ControlKind,
1100
+ timerKey: 'incapUntil' | 'stunnedUntil' | 'silencedUntil' | 'disarmedUntil',
1101
+ ): void => {
1102
+ if (f[timerKey] <= ctx.t) return;
1103
+ let broke = false;
1104
+ let keepUntil = 0;
1105
+ let sawAura = false;
1106
+ for (const e of ctx.effectsOn(f.side)) {
1107
+ if (e.kind !== 'cc' || e.control !== control || e.broken) continue;
1108
+ sawAura = true;
1109
+ // Own package damage does not cancel the aura (Iron Grenade etc.).
1110
+ if (fromSpellId != null && e.spellId === fromSpellId) {
1111
+ if (e.expiresAt > ctx.t) keepUntil = Math.max(keepUntil, e.expiresAt);
1112
+ continue;
1113
+ }
1114
+ // Only shatter when the flag is explicitly true — absent/false (charm, HoJ) holds.
1115
+ if (e.breakOnDamage === true) {
1116
+ fadeEffectNow(e);
1117
+ broke = true;
1118
+ // Our own damage took the CC off, so remember the caster and its rule stops re-applying it.
1119
+ // Only when the hit came from the other faction — a boss AoE is not the party's choice.
1120
+ if (!attacker || ctx.factionOf(attacker.side) !== ctx.factionOf(f.side)) {
1121
+ (f.ccDamageBrokenBy ??= new Set<Side>()).add(e.source);
1122
+ }
1123
+ } else if (e.expiresAt > ctx.t) {
1124
+ keepUntil = Math.max(keepUntil, e.expiresAt);
1125
+ }
1126
+ }
1127
+ // Only rewrite the timer when mirrors were present. set_unattackable stuns with no cc
1128
+ // effect (:624) and damage must not clear it. Cite: boss_baroness_anastari.cpp:188-189.
1129
+ if (!sawAura) return;
1130
+ f[timerKey] = keepUntil;
1131
+ if (broke) ctx.push({ t: ctx.t, kind: 'control', target: f.side, control, applied: false });
1132
+ };
1133
+ shatter('incapacitate', 'incapUntil');
1134
+ shatter('stun', 'stunnedUntil');
1135
+ shatter('silence', 'silencedUntil');
1136
+ shatter('disarm', 'disarmedUntil');
1137
+ dropPolymorphHealIfFree(f);
1138
+ };
1139
+
1140
+ ctx.onDamaged = (
1141
+ f: Fighter,
1142
+ amount: number,
1143
+ damageType?: string,
1144
+ fromSpellId?: number,
1145
+ info?: { absorbed?: number; attacker?: Fighter; skipChannelDelay?: boolean },
1146
+ ): void => {
1147
+ const absorbed = info?.absorbed ?? 0;
1148
+ // Unit.cpp:915–917 — interrupt when takenOrAbsorbedDamage, not only leftover HP.
1149
+ if (amount <= 0 && absorbed <= 0) return;
1150
+ const treatAsPeriodic = fromSpellId != null && hasAttrEx3(ctx.spellOf?.(fromSpellId), SPELL_ATTR_EX3.TREAT_AS_PERIODIC);
1151
+ const stripAuras = ctx.removeAurasWithInterruptFlags;
1152
+ // Earthbind 3600 does not break Stealth. Cite: Rogue.cpp:47-52 OnCheckProc false.
1153
+ if (stripAuras && stealthProcAllowedForSpell(fromSpellId ?? 0)) {
1154
+ let actionInterruptFlags = AURA_INTERRUPT.DAMAGE;
1155
+ if (damageType !== 'dot') {
1156
+ actionInterruptFlags |= AURA_INTERRUPT.NON_PERIODIC_DAMAGE;
1157
+ }
1158
+ stripAuras(f, actionInterruptFlags, 'damage');
1159
+ }
1160
+ if (f.incapUntil > ctx.t || f.stunnedUntil > ctx.t || f.silencedUntil > ctx.t || f.disarmedUntil > ctx.t) {
1161
+ breakBreakableCcOnDamage(f, fromSpellId, info?.attacker);
1162
+ }
1163
+ // Independent of the shatter above: fear is its own holder with its own roll, so a unit that is
1164
+ // stunned AND feared must still roll it. As an else-if, any stun made the fear damage-immune.
1165
+ if (
1166
+ amount > 0
1167
+ && f.fearedUntil > ctx.t
1168
+ && ctx.rng.chance(ccDamageBreakChance(f.spec.level, amount))
1169
+ ) ctx.breakControl!(f, 'fear');
1170
+ // Independent of incap/fear: each root rolls its own damage-break (not the applying spell).
1171
+ if (amount > 0) breakRootOnDamage(f, fromSpellId, amount);
1172
+ // Channel cancel / pushback on damage (Unit.cpp:1285–1302; Spell.cpp DelayedChannel).
1173
+ if (f.channeling) {
1174
+ expireChannelIfDone(f, ctx.t);
1175
+ if (f.channeling) {
1176
+ onChannelOwnerDamaged(f, {
1177
+ t: ctx.t,
1178
+ selfDamage: info?.attacker === f,
1179
+ hpDamage: amount,
1180
+ absorbed,
1181
+ skipChannelDelay: info?.skipChannelDelay,
1182
+ treatAsPeriodic,
1183
+ damageType,
1184
+ chance01: (p) => ctx.rng.chance(p),
1185
+ notLoseCastingTimePct: f.channeling.notLoseCastingTimePct | 0,
1186
+ push: (ev) => ctx.push(ev),
1187
+ removeAurasDueToSpell: (u, id) => (ctx.removeAurasDueToSpell?.(u, id) ?? 0),
1188
+ bySide: fighterBySide,
1189
+ delaySpellAuras: (u, spellId, delayMs) => {
1190
+ for (const e of ctx.effectsOn(u.side)) {
1191
+ if (e.spellId !== spellId || e.broken) continue;
1192
+ e.expiresAt = Math.max(ctx.t, e.expiresAt - delayMs);
1193
+ touchEffect(e);
1194
+ }
1195
+ },
1196
+ delayDynamicObjects: (spellId, delayMs) => {
1197
+ const hits = delayGroundPoolsForSpell(
1198
+ ctx.groundPools,
1199
+ f.side,
1200
+ spellId,
1201
+ delayMs,
1202
+ (side) => ctx.effectsOn(side).some(
1203
+ (e) => e.spellId === spellId && e.source === f.side && !e.broken,
1204
+ ),
1205
+ );
1206
+ for (const { pool, delayAuras } of hits) {
1207
+ ctx.touchGroundPool(pool);
1208
+ for (const side of delayAuras) {
1209
+ for (const e of ctx.effectsOn(side)) {
1210
+ if (e.spellId !== spellId || e.broken) continue;
1211
+ e.expiresAt = Math.max(ctx.t, e.expiresAt - delayMs);
1212
+ touchEffect(e);
1213
+ }
1214
+ }
1215
+ }
1216
+ },
1217
+ });
1218
+ }
1219
+ }
1220
+ // Cast pushback / cancel — InterruptOrDelaySpell only after leftover HP > 0.
1221
+ // Cite: Unit.cpp:953–960 then :1246; :1268–1284 (no DoT / shield / split delay).
1222
+ const casting = f.castingSpell;
1223
+ if (
1224
+ amount > 0
1225
+ && !info?.skipChannelDelay
1226
+ // Spell::Delayed is TYPEID_PLAYER only, and every character seat counts (Spell.cpp:6806).
1227
+ && isPushbackSubject(f)
1228
+ && casting
1229
+ && damageType !== 'dot'
1230
+ && !treatAsPeriodic
1231
+ && damageType !== 'shield'
1232
+ ) {
1233
+ const flags = casting.interruptFlags ?? 0;
1234
+ if ((flags & SPELL_INTERRUPT_FLAG_DAMAGE_CANCELS) !== 0) {
1235
+ ctx.push({ t: ctx.t, kind: 'flavor', text: `${casting.name} interrupted!` });
1236
+ f.castingSpell = null;
1237
+ f.castStartedAt = 0;
1238
+ f.castFinishAt = 0;
1239
+ // Damage-cancelled before Spell::cast() — no TakePower, so the cast was free.
1240
+ f.castCost = 0;
1241
+ f.castCostPower = undefined;
1242
+ } else if ((flags & SPELL_INTERRUPT_FLAG_DAMAGE_PUSHBACK) !== 0) {
1243
+ // Resist stack: SPELLMOD_NOT_LOSE_CASTING_TIME + SPELL_AURA_RESIST_PUSHBACK (149).
1244
+ // Cite: Spell.cpp:6817–6821 Delayed (same formula as DelayedChannel).
1245
+ const notLose = Number((casting as { notLoseCastingTimePct?: number }).notLoseCastingTimePct ?? 0) | 0;
1246
+ if (resistsPushback(f.pushbackResistChance | 0, notLose, (p) => ctx.rng.chance(p))) {
1247
+ // Full resist — no delay, ladder index unchanged.
1248
+ } else {
1249
+ // Ladder 1000/800/600/400/200ms; remaining time capped at the full
1250
+ // (haste-modified) cast time — Spell.cpp:6825–6828.
1251
+ const remaining = Math.max(0, f.castFinishAt - ctx.t);
1252
+ const delay = Math.min(nextPushbackDelayMs(f.castPushbacks), Math.max(0, f.castBaseMs - remaining));
1253
+ if (delay > 0) f.castFinishAt += delay;
1254
+ f.castPushbacks++;
1255
+ }
1256
+ }
1257
+ }
1258
+ };
1259
+ }