azeroth-shell 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1163) hide show
  1. package/README.md +138 -2
  2. package/bin/azsh.mjs +19 -0
  3. package/docs/aifid/eventai-texts.json +4414 -0
  4. package/docs/dungeons/atlas/atlas-rows-dungeons.json +8989 -0
  5. package/docs/dungeons/survey/pack-id-migration.json +2418 -0
  6. package/package.json +26 -4
  7. package/src/autopilot/bossSkip.ts +26 -0
  8. package/src/autopilot/combatRotation.ts +824 -0
  9. package/src/autopilot/config.ts +533 -0
  10. package/src/autopilot/consumables.ts +58 -0
  11. package/src/autopilot/encounterStand.ts +121 -0
  12. package/src/autopilot/objectGatePlan.ts +53 -0
  13. package/src/autopilot/pathEngage.ts +229 -0
  14. package/src/autopilot/plan.ts +1643 -0
  15. package/src/autopilot/questPolicy.ts +274 -0
  16. package/src/autopilot/routeStep.ts +69 -0
  17. package/src/autopilot/sessionReport.ts +88 -0
  18. package/src/autopilot/topOffPower.ts +26 -0
  19. package/src/autopilot/view.ts +476 -0
  20. package/src/cli/bootCheck.mjs +34 -0
  21. package/src/cli/bootData.mjs +57 -0
  22. package/src/cli/combatLogFilter.ts +153 -0
  23. package/src/cli/combatLogFormat.ts +613 -0
  24. package/src/cli/fileRepository.mjs +105 -0
  25. package/src/cli/gzLoader.mjs +31 -0
  26. package/src/cli/liveGauntletEscort.ts +978 -0
  27. package/src/cli/liveStratUndead.ts +305 -0
  28. package/src/cli/macroHostCombat.ts +545 -0
  29. package/src/cli/macroHostWorld.ts +330 -0
  30. package/src/cli/prefs.mjs +31 -0
  31. package/src/cli/register.mjs +4 -0
  32. package/src/cli/repl.mjs +693 -0
  33. package/src/cli/rotationText.mjs +226 -0
  34. package/src/cli/squad.mjs +105 -0
  35. package/src/cli/term.mjs +126 -0
  36. package/src/cli/text.mjs +122 -0
  37. package/src/cli/tui.mjs +626 -0
  38. package/src/cli/tuiArena.mjs +109 -0
  39. package/src/cli/tuiAutopilotSettings.mjs +93 -0
  40. package/src/cli/tuiBags.mjs +180 -0
  41. package/src/cli/tuiBars.mjs +133 -0
  42. package/src/cli/tuiBg.mjs +243 -0
  43. package/src/cli/tuiBulk.mjs +162 -0
  44. package/src/cli/tuiCheck.mjs +1882 -0
  45. package/src/cli/tuiCity.mjs +396 -0
  46. package/src/cli/tuiCompanionKit.mjs +300 -0
  47. package/src/cli/tuiCompanions.mjs +471 -0
  48. package/src/cli/tuiDev.mjs +62 -0
  49. package/src/cli/tuiDungeon.mjs +606 -0
  50. package/src/cli/tuiEnchantTarget.mjs +120 -0
  51. package/src/cli/tuiFight.mjs +577 -0
  52. package/src/cli/tuiFightPanes.mjs +228 -0
  53. package/src/cli/tuiFloor.mjs +506 -0
  54. package/src/cli/tuiGear.mjs +158 -0
  55. package/src/cli/tuiHonor.mjs +74 -0
  56. package/src/cli/tuiLoot.mjs +97 -0
  57. package/src/cli/tuiMacros.mjs +144 -0
  58. package/src/cli/tuiOutfitter.mjs +115 -0
  59. package/src/cli/tuiPet.mjs +116 -0
  60. package/src/cli/tuiProfessions.mjs +800 -0
  61. package/src/cli/tuiRaid.mjs +127 -0
  62. package/src/cli/tuiRep.mjs +65 -0
  63. package/src/cli/tuiSaves.mjs +69 -0
  64. package/src/cli/tuiSearch.mjs +363 -0
  65. package/src/cli/tuiSettings.mjs +151 -0
  66. package/src/cli/tuiSimLab.mjs +80 -0
  67. package/src/cli/tuiTalents.mjs +60 -0
  68. package/src/cli/tuiViews.mjs +1178 -0
  69. package/src/cli/tuiWiki.mjs +181 -0
  70. package/src/cli/tuiWorld.mjs +361 -0
  71. package/src/cli/wikiIndex.ts +259 -0
  72. package/src/cli/wikiSources.ts +355 -0
  73. package/src/data/alteracValley.ts +176 -0
  74. package/src/data/appearanceOptions.ts +534 -0
  75. package/src/data/applyLootChanceSanity.ts +21 -0
  76. package/src/data/createFlavor.ts +96 -0
  77. package/src/data/customItems.ts +33 -0
  78. package/src/data/dataStore.ts +1904 -0
  79. package/src/data/dungeonGameObjects.ts +84 -0
  80. package/src/data/dungeonPlateCover.ts +66 -0
  81. package/src/data/dungeonSpawns.ts +294 -0
  82. package/src/data/dungeons/alterac-valley.ts +135 -0
  83. package/src/data/dungeons/apply-dungeon-rooms.ts +29 -0
  84. package/src/data/dungeons/apply-encounter-outdoors.ts +72 -0
  85. package/src/data/dungeons/apply-instance-geometry.ts +65 -0
  86. package/src/data/dungeons/apply-spawn-placement.ts +171 -0
  87. package/src/data/dungeons/blackfathom-deeps.ts +539 -0
  88. package/src/data/dungeons/blackrock-depths.ts +1410 -0
  89. package/src/data/dungeons/blackwing-lair.ts +129 -0
  90. package/src/data/dungeons/boss-eventai.ts +8 -0
  91. package/src/data/dungeons/deadmines.ts +410 -0
  92. package/src/data/dungeons/dire-maul-east.ts +980 -0
  93. package/src/data/dungeons/dire-maul-north.ts +661 -0
  94. package/src/data/dungeons/dire-maul-west.ts +785 -0
  95. package/src/data/dungeons/dungeon-rooms.json +5479 -0
  96. package/src/data/dungeons/encounter-outdoors.json +359 -0
  97. package/src/data/dungeons/eventai/blackfathom-deeps.ts +308 -0
  98. package/src/data/dungeons/eventai/blackrock-depths.ts +1257 -0
  99. package/src/data/dungeons/eventai/deadmines.ts +264 -0
  100. package/src/data/dungeons/eventai/dire-maul-east.ts +320 -0
  101. package/src/data/dungeons/eventai/gnomeregan.ts +213 -0
  102. package/src/data/dungeons/eventai/helpers.ts +79 -0
  103. package/src/data/dungeons/eventai/index.ts +133 -0
  104. package/src/data/dungeons/eventai/lbrs.ts +372 -0
  105. package/src/data/dungeons/eventai/maraudon.ts +323 -0
  106. package/src/data/dungeons/eventai/ragefire-chasm.ts +108 -0
  107. package/src/data/dungeons/eventai/razorfen-downs.ts +207 -0
  108. package/src/data/dungeons/eventai/razorfen-kraul.ts +326 -0
  109. package/src/data/dungeons/eventai/scholomance.ts +680 -0
  110. package/src/data/dungeons/eventai/shadowfang-keep.ts +230 -0
  111. package/src/data/dungeons/eventai/sm-cathedral.ts +75 -0
  112. package/src/data/dungeons/eventai/sm-graveyard.ts +150 -0
  113. package/src/data/dungeons/eventai/sm-library.ts +55 -0
  114. package/src/data/dungeons/eventai/stratholme-undead.ts +127 -0
  115. package/src/data/dungeons/eventai/sunken-temple.ts +582 -0
  116. package/src/data/dungeons/eventai/the-stockade.ts +228 -0
  117. package/src/data/dungeons/eventai/ubrs.ts +225 -0
  118. package/src/data/dungeons/eventai/uldaman.ts +355 -0
  119. package/src/data/dungeons/eventai/wailing-caverns.ts +396 -0
  120. package/src/data/dungeons/eventai/zul-farrak.ts +591 -0
  121. package/src/data/dungeons/gnomeregan.ts +909 -0
  122. package/src/data/dungeons/index.ts +126 -0
  123. package/src/data/dungeons/instance-geometry.json +5233 -0
  124. package/src/data/dungeons/key-sources.json +169 -0
  125. package/src/data/dungeons/lbrs.ts +996 -0
  126. package/src/data/dungeons/maraudon.ts +786 -0
  127. package/src/data/dungeons/molten-core.ts +194 -0
  128. package/src/data/dungeons/naxxramas.ts +173 -0
  129. package/src/data/dungeons/onyxias-lair.ts +71 -0
  130. package/src/data/dungeons/pack-from-clusters.ts +1256 -0
  131. package/src/data/dungeons/ragefire-chasm.ts +238 -0
  132. package/src/data/dungeons/razorfen-downs.ts +698 -0
  133. package/src/data/dungeons/razorfen-kraul.ts +766 -0
  134. package/src/data/dungeons/ruins-of-ahnqiraj.ts +121 -0
  135. package/src/data/dungeons/scholomance.ts +1076 -0
  136. package/src/data/dungeons/shadowfang-keep.ts +740 -0
  137. package/src/data/dungeons/sm-armory.ts +266 -0
  138. package/src/data/dungeons/sm-cathedral.ts +430 -0
  139. package/src/data/dungeons/sm-graveyard.ts +482 -0
  140. package/src/data/dungeons/sm-library.ts +337 -0
  141. package/src/data/dungeons/stratholme-live.ts +808 -0
  142. package/src/data/dungeons/stratholme-undead.ts +1164 -0
  143. package/src/data/dungeons/sunken-temple.ts +931 -0
  144. package/src/data/dungeons/temple-of-ahnqiraj.ts +119 -0
  145. package/src/data/dungeons/the-stockade.ts +282 -0
  146. package/src/data/dungeons/ubrs.ts +1286 -0
  147. package/src/data/dungeons/uldaman.ts +685 -0
  148. package/src/data/dungeons/wailing-caverns.ts +356 -0
  149. package/src/data/dungeons/zul-farrak.ts +686 -0
  150. package/src/data/dungeons/zul-gurub.ts +142 -0
  151. package/src/data/dungeons.ts +42 -0
  152. package/src/data/fixtures.ts +446 -0
  153. package/src/data/gameobject-data.json.gz +0 -0
  154. package/src/data/item-data.json.gz +0 -0
  155. package/src/data/loot-data.json.gz +0 -0
  156. package/src/data/maps/atlas-approach-maps.json +1425 -0
  157. package/src/data/maps/atlas-landmarks.json +1711 -0
  158. package/src/data/maps/dungeon-viewports.json +392 -0
  159. package/src/data/materializeModDamagePercentTaken.ts +70 -0
  160. package/src/data/materializeModHealthRegen.ts +89 -0
  161. package/src/data/materializeSpellReputation.ts +26 -0
  162. package/src/data/pageTexts.ts +19 -0
  163. package/src/data/profession-data.json.gz +0 -0
  164. package/src/data/raid-plans/blackfathom-deeps.json +86 -0
  165. package/src/data/raid-plans/blackrock-depths.json +419 -0
  166. package/src/data/raid-plans/blackwing-lair.json +328 -0
  167. package/src/data/raid-plans/deadmines.json +55 -0
  168. package/src/data/raid-plans/dire-maul-east.json +94 -0
  169. package/src/data/raid-plans/dire-maul-north.json +70 -0
  170. package/src/data/raid-plans/dire-maul-west.json +75 -0
  171. package/src/data/raid-plans/gnomeregan.json +131 -0
  172. package/src/data/raid-plans/lbrs.json +176 -0
  173. package/src/data/raid-plans/maraudon.json +102 -0
  174. package/src/data/raid-plans/molten-core.json +169 -0
  175. package/src/data/raid-plans/naxxramas.json +354 -0
  176. package/src/data/raid-plans/onyxias-lair.json +18 -0
  177. package/src/data/raid-plans/ragefire-chasm.json +30 -0
  178. package/src/data/raid-plans/razorfen-downs.json +75 -0
  179. package/src/data/raid-plans/razorfen-kraul.json +53 -0
  180. package/src/data/raid-plans/ruins-of-ahnqiraj.json +110 -0
  181. package/src/data/raid-plans/scholomance.json +170 -0
  182. package/src/data/raid-plans/shadowfang-keep.json +87 -0
  183. package/src/data/raid-plans/sm-armory.json +53 -0
  184. package/src/data/raid-plans/sm-cathedral.json +72 -0
  185. package/src/data/raid-plans/sm-graveyard.json +63 -0
  186. package/src/data/raid-plans/sm-library.json +23 -0
  187. package/src/data/raid-plans/stratholme-live.json +129 -0
  188. package/src/data/raid-plans/stratholme-undead.json +73 -0
  189. package/src/data/raid-plans/sunken-temple.json +159 -0
  190. package/src/data/raid-plans/temple-of-ahnqiraj.json +163 -0
  191. package/src/data/raid-plans/the-stockade.json +42 -0
  192. package/src/data/raid-plans/ubrs.json +53 -0
  193. package/src/data/raid-plans/uldaman.json +131 -0
  194. package/src/data/raid-plans/wailing-caverns.json +90 -0
  195. package/src/data/raid-plans/zul-farrak.json +156 -0
  196. package/src/data/raid-plans/zul-gurub.json +307 -0
  197. package/src/data/raidInstanceCreatures.mjs +473 -0
  198. package/src/data/rotations/index.ts +152 -0
  199. package/src/data/rotations/layout.mjs +72 -0
  200. package/src/data/rotations/pve/druid/balance.json +754 -0
  201. package/src/data/rotations/pve/druid/feral-tank.json +2448 -0
  202. package/src/data/rotations/pve/druid/feral.json +2660 -0
  203. package/src/data/rotations/pve/druid/restoration.json +424 -0
  204. package/src/data/rotations/pve/hunter/beast-mastery.json +1288 -0
  205. package/src/data/rotations/pve/hunter/marksmanship.json +1260 -0
  206. package/src/data/rotations/pve/hunter/survival.json +1373 -0
  207. package/src/data/rotations/pve/mage/arcane-leveling.json +1798 -0
  208. package/src/data/rotations/pve/mage/fire-leveling.json +2003 -0
  209. package/src/data/rotations/pve/mage/frost-leveling.json +1912 -0
  210. package/src/data/rotations/pve/paladin/holy.json +755 -0
  211. package/src/data/rotations/pve/paladin/protection.json +962 -0
  212. package/src/data/rotations/pve/paladin/retribution.json +924 -0
  213. package/src/data/rotations/pve/priest/discipline.json +772 -0
  214. package/src/data/rotations/pve/priest/holy.json +707 -0
  215. package/src/data/rotations/pve/priest/shadow.json +787 -0
  216. package/src/data/rotations/pve/rogue/assassination.json +2477 -0
  217. package/src/data/rotations/pve/rogue/combat.json +2483 -0
  218. package/src/data/rotations/pve/rogue/subtlety.json +2523 -0
  219. package/src/data/rotations/pve/shaman/elemental.json +1157 -0
  220. package/src/data/rotations/pve/shaman/enhancement.json +1085 -0
  221. package/src/data/rotations/pve/shaman/restoration.json +592 -0
  222. package/src/data/rotations/pve/warlock/affliction.json +1185 -0
  223. package/src/data/rotations/pve/warlock/demonology.json +1165 -0
  224. package/src/data/rotations/pve/warlock/destruction.json +1177 -0
  225. package/src/data/rotations/pve/warrior/arms.json +3020 -0
  226. package/src/data/rotations/pve/warrior/fury.json +2996 -0
  227. package/src/data/rotations/pve/warrior/protection.json +2509 -0
  228. package/src/data/rotations/pvp/druid/balance.json +593 -0
  229. package/src/data/rotations/pvp/druid/feral.json +3276 -0
  230. package/src/data/rotations/pvp/druid/restoration.json +662 -0
  231. package/src/data/rotations/pvp/hunter/beast-mastery.json +665 -0
  232. package/src/data/rotations/pvp/hunter/marksmanship.json +2406 -0
  233. package/src/data/rotations/pvp/hunter/survival.json +635 -0
  234. package/src/data/rotations/pvp/mage/arcane.json +574 -0
  235. package/src/data/rotations/pvp/mage/fire.json +570 -0
  236. package/src/data/rotations/pvp/mage/frost.json +4048 -0
  237. package/src/data/rotations/pvp/paladin/holy.json +1498 -0
  238. package/src/data/rotations/pvp/paladin/protection.json +567 -0
  239. package/src/data/rotations/pvp/paladin/retribution.json +1110 -0
  240. package/src/data/rotations/pvp/priest/discipline.json +1848 -0
  241. package/src/data/rotations/pvp/priest/holy.json +6340 -0
  242. package/src/data/rotations/pvp/priest/shadow.json +2329 -0
  243. package/src/data/rotations/pvp/rogue/assassination.json +1322 -0
  244. package/src/data/rotations/pvp/rogue/combat.json +5258 -0
  245. package/src/data/rotations/pvp/rogue/subtlety.json +586 -0
  246. package/src/data/rotations/pvp/shaman/elemental.json +1404 -0
  247. package/src/data/rotations/pvp/shaman/enhancement.json +1277 -0
  248. package/src/data/rotations/pvp/shaman/restoration.json +2490 -0
  249. package/src/data/rotations/pvp/warlock/affliction.json +1017 -0
  250. package/src/data/rotations/pvp/warlock/demonology.json +1503 -0
  251. package/src/data/rotations/pvp/warlock/destruction.json +638 -0
  252. package/src/data/rotations/pvp/warrior/arms.json +5665 -0
  253. package/src/data/rotations/pvp/warrior/fury.json +850 -0
  254. package/src/data/rotations/pvp/warrior/protection.json +777 -0
  255. package/src/data/schema/bundle.ts +511 -0
  256. package/src/data/schema/constants.ts +49 -0
  257. package/src/data/schema/creature.ts +454 -0
  258. package/src/data/schema/encounter.ts +155 -0
  259. package/src/data/schema/faction.ts +48 -0
  260. package/src/data/schema/item.ts +297 -0
  261. package/src/data/schema/itemEnchant.ts +105 -0
  262. package/src/data/schema/npc.ts +44 -0
  263. package/src/data/schema/primitives.ts +295 -0
  264. package/src/data/schema/profession.ts +226 -0
  265. package/src/data/schema/quest.ts +154 -0
  266. package/src/data/schema/spell.ts +1149 -0
  267. package/src/data/schema/talent.ts +74 -0
  268. package/src/data/schema/world.ts +124 -0
  269. package/src/data/schema.ts +19 -0
  270. package/src/data/skinNodeGapFill.ts +68 -0
  271. package/src/data/spell-data.json.gz +0 -0
  272. package/src/data/spell-script-targets.json +1 -0
  273. package/src/data/tables/ahItemValues.json.gz +0 -0
  274. package/src/data/tables/alteracValley.json.gz +0 -0
  275. package/src/data/tables/areatriggerTeleports.json.gz +0 -0
  276. package/src/data/tables/botNames.json.gz +0 -0
  277. package/src/data/tables/botPopulation.json.gz +0 -0
  278. package/src/data/tables/botSpecs.json.gz +0 -0
  279. package/src/data/tables/classLevelStats.json.gz +0 -0
  280. package/src/data/tables/combatConditions.json.gz +0 -0
  281. package/src/data/tables/combatSkillMeta.json.gz +0 -0
  282. package/src/data/tables/conditions.json.gz +0 -0
  283. package/src/data/tables/createInfo.json.gz +0 -0
  284. package/src/data/tables/creatureAggroGroups.json.gz +0 -0
  285. package/src/data/tables/creatureClassLevelStats.json.gz +0 -0
  286. package/src/data/tables/creaturePatrolPacks.json.gz +0 -0
  287. package/src/data/tables/creatureWeapons.json.gz +0 -0
  288. package/src/data/tables/creatures.json.gz +0 -0
  289. package/src/data/tables/dbScripts.json.gz +0 -0
  290. package/src/data/tables/dungeonAuthoredRoutes.json.gz +0 -0
  291. package/src/data/tables/dungeonCensus.json.gz +0 -0
  292. package/src/data/tables/dungeonGameObjects.json.gz +0 -0
  293. package/src/data/tables/dungeonPacks.json.gz +0 -0
  294. package/src/data/tables/dungeonPlateCover.json.gz +0 -0
  295. package/src/data/tables/dungeonSpawns.json.gz +0 -0
  296. package/src/data/tables/dungeonSurvey.json.gz +0 -0
  297. package/src/data/tables/durabilityCosts.json.gz +0 -0
  298. package/src/data/tables/durabilityQuality.json.gz +0 -0
  299. package/src/data/tables/enchantApplicators.json.gz +0 -0
  300. package/src/data/tables/entranceYards.json.gz +0 -0
  301. package/src/data/tables/explorationBasexp.json.gz +0 -0
  302. package/src/data/tables/factionTemplates.json.gz +0 -0
  303. package/src/data/tables/factions.json.gz +0 -0
  304. package/src/data/tables/gameEvents.json.gz +0 -0
  305. package/src/data/tables/gameObjects.json.gz +0 -0
  306. package/src/data/tables/gossipMenus.json.gz +0 -0
  307. package/src/data/tables/graveyards.json.gz +0 -0
  308. package/src/data/tables/instanceMaps.json.gz +0 -0
  309. package/src/data/tables/itemEnchantmentTemplates.json.gz +0 -0
  310. package/src/data/tables/itemEnchants.json.gz +0 -0
  311. package/src/data/tables/itemRandomProperties.json.gz +0 -0
  312. package/src/data/tables/itemSets.json.gz +0 -0
  313. package/src/data/tables/items.json.gz +0 -0
  314. package/src/data/tables/levelStats.json.gz +0 -0
  315. package/src/data/tables/locks.json.gz +0 -0
  316. package/src/data/tables/map-pin-overrides.json.gz +0 -0
  317. package/src/data/tables/nonCombatTrash.json.gz +0 -0
  318. package/src/data/tables/npcTexts.json.gz +0 -0
  319. package/src/data/tables/pageTexts.json.gz +0 -0
  320. package/src/data/tables/petAbilityTP.json.gz +0 -0
  321. package/src/data/tables/petGenericSpells.json.gz +0 -0
  322. package/src/data/tables/petLevelStats.json.gz +0 -0
  323. package/src/data/tables/playerbot-weightscales.json.gz +0 -0
  324. package/src/data/tables/pools.json.gz +0 -0
  325. package/src/data/tables/quests.json.gz +0 -0
  326. package/src/data/tables/races.json.gz +0 -0
  327. package/src/data/tables/reputationRanks.json.gz +0 -0
  328. package/src/data/tables/reputationRates.json.gz +0 -0
  329. package/src/data/tables/reputationSpillover.json.gz +0 -0
  330. package/src/data/tables/ridingSpells.json.gz +0 -0
  331. package/src/data/tables/scriptTexts.json.gz +0 -0
  332. package/src/data/tables/soundClipVolume.json.gz +0 -0
  333. package/src/data/tables/spellChain.json.gz +0 -0
  334. package/src/data/tables/spellTeleports.json.gz +0 -0
  335. package/src/data/tables/spells.json.gz +0 -0
  336. package/src/data/tables/talents.json.gz +0 -0
  337. package/src/data/tables/tavernTriggers.json.gz +0 -0
  338. package/src/data/tables/taxiNodes.json.gz +0 -0
  339. package/src/data/tables/taxiRoutes.json.gz +0 -0
  340. package/src/data/tables/trainers.json.gz +0 -0
  341. package/src/data/tables/transports.json.gz +0 -0
  342. package/src/data/tables/travelAnchors.json.gz +0 -0
  343. package/src/data/tables/travelEdges.json.gz +0 -0
  344. package/src/data/tables/unitConditions.json.gz +0 -0
  345. package/src/data/tables/vendors.json.gz +0 -0
  346. package/src/data/tables/weaponEnchantProcs.json.gz +0 -0
  347. package/src/data/tables/xp.json.gz +0 -0
  348. package/src/data/tables/zoneWeather.json.gz +0 -0
  349. package/src/data/tables/zones.json.gz +0 -0
  350. package/src/data/world-data.ts +56 -0
  351. package/src/data/zone-music-sets.json +2751 -0
  352. package/src/dev/etl/lib/loot-chance-sanity.mjs +180 -0
  353. package/src/domain/audioQuality.mjs +145 -0
  354. package/src/domain/bg/ab.ts +433 -0
  355. package/src/domain/bg/arenaComps.ts +644 -0
  356. package/src/domain/bg/arenaLab.ts +695 -0
  357. package/src/domain/bg/arenaPace.ts +31 -0
  358. package/src/domain/bg/arenaRating.ts +110 -0
  359. package/src/domain/bg/av.ts +758 -0
  360. package/src/domain/bg/avEnemyPush.ts +219 -0
  361. package/src/domain/bg/avField.ts +283 -0
  362. package/src/domain/bg/avInstanceNodes.ts +197 -0
  363. package/src/domain/bg/avInstanceScore.ts +123 -0
  364. package/src/domain/bg/avQuestItems.ts +55 -0
  365. package/src/domain/bg/avQuests.ts +210 -0
  366. package/src/domain/bg/avSession.ts +196 -0
  367. package/src/domain/bg/bgConsumes.ts +269 -0
  368. package/src/domain/bg/bgPrebuff.ts +153 -0
  369. package/src/domain/bg/bgRest.ts +91 -0
  370. package/src/domain/bg/bgRoster.ts +305 -0
  371. package/src/domain/bg/bgTier.ts +61 -0
  372. package/src/domain/bg/bgWorld.ts +862 -0
  373. package/src/domain/bg/match.ts +215 -0
  374. package/src/domain/bg/strategies.ts +552 -0
  375. package/src/domain/bg/types.ts +291 -0
  376. package/src/domain/bg/wsg.ts +264 -0
  377. package/src/domain/bots/actionProviderSpec.ts +38 -0
  378. package/src/domain/bots/autoConsumables.ts +285 -0
  379. package/src/domain/bots/betweenPullRest.ts +239 -0
  380. package/src/domain/bots/botGear.ts +248 -0
  381. package/src/domain/bots/botInventory.ts +486 -0
  382. package/src/domain/bots/botPet.ts +535 -0
  383. package/src/domain/bots/botPool.ts +291 -0
  384. package/src/domain/bots/botProfessions.ts +334 -0
  385. package/src/domain/bots/companionActionBar.ts +209 -0
  386. package/src/domain/bots/companionHireCost.ts +236 -0
  387. package/src/domain/bots/companionMissions.ts +582 -0
  388. package/src/domain/bots/companionPrep.ts +254 -0
  389. package/src/domain/bots/companionQuests.ts +270 -0
  390. package/src/domain/bots/companionRep.ts +75 -0
  391. package/src/domain/bots/companionRotation.ts +708 -0
  392. package/src/domain/bots/companionXp.ts +66 -0
  393. package/src/domain/bots/consumableShare.ts +811 -0
  394. package/src/domain/bots/dpsMate.ts +1072 -0
  395. package/src/domain/bots/groupCompReport.ts +132 -0
  396. package/src/domain/bots/healthstoneSupply.ts +198 -0
  397. package/src/domain/bots/outfitter.ts +1056 -0
  398. package/src/domain/bots/paladinPrePull.ts +231 -0
  399. package/src/domain/bots/partyRole.ts +40 -0
  400. package/src/domain/bots/playerbotEquipCache.ts +412 -0
  401. package/src/domain/bots/playerbotItemEligibility.ts +60 -0
  402. package/src/domain/bots/playerbotStatWeight.ts +853 -0
  403. package/src/domain/bots/playerbotUnavailableItems.ts +147 -0
  404. package/src/domain/bots/prePullBuffs.ts +219 -0
  405. package/src/domain/bots/prePullStealth.ts +82 -0
  406. package/src/domain/bots/runSupply.ts +251 -0
  407. package/src/domain/bots/runSupplyPrefs.ts +28 -0
  408. package/src/domain/bots/sendTradeGoods.ts +394 -0
  409. package/src/domain/bots/soulShardCap.ts +35 -0
  410. package/src/domain/bots/squad.ts +842 -0
  411. package/src/domain/bots/squadRun.ts +81 -0
  412. package/src/domain/character/actionbar.ts +346 -0
  413. package/src/domain/character/appearance.ts +165 -0
  414. package/src/domain/character/buildCombatant.ts +1726 -0
  415. package/src/domain/character/character.ts +356 -0
  416. package/src/domain/character/characterName.ts +43 -0
  417. package/src/domain/character/characterRatings.ts +424 -0
  418. package/src/domain/character/characterSpells.ts +282 -0
  419. package/src/domain/character/characterStats.ts +1559 -0
  420. package/src/domain/character/characterTalents.ts +398 -0
  421. package/src/domain/character/combatSkills.ts +994 -0
  422. package/src/domain/character/displayForm.ts +72 -0
  423. package/src/domain/character/formTalentAuras.ts +171 -0
  424. package/src/domain/character/forms.ts +499 -0
  425. package/src/domain/character/races.ts +40 -0
  426. package/src/domain/character/resurrectionSickness.ts +49 -0
  427. package/src/domain/character/spellbook.ts +169 -0
  428. package/src/domain/character/stanceBar.ts +354 -0
  429. package/src/domain/character/stancePrereq.ts +70 -0
  430. package/src/domain/character/talentBuilds.ts +63 -0
  431. package/src/domain/character/talentImport.ts +130 -0
  432. package/src/domain/character/talentLink.ts +123 -0
  433. package/src/domain/character/talents.ts +201 -0
  434. package/src/domain/core/conditionSnapshot.ts +92 -0
  435. package/src/domain/core/hover.ts +14 -0
  436. package/src/domain/core/ids.ts +58 -0
  437. package/src/domain/core/internalNameTags.ts +5 -0
  438. package/src/domain/core/runtimeDataPatches.ts +357 -0
  439. package/src/domain/creature/aiTargeting.ts +588 -0
  440. package/src/domain/creature/combatCondition.ts +157 -0
  441. package/src/domain/creature/creatureAggro.ts +804 -0
  442. package/src/domain/creature/creatureAi.ts +206 -0
  443. package/src/domain/creature/creatureAiConstants.ts +661 -0
  444. package/src/domain/creature/creatureCastList.ts +1007 -0
  445. package/src/domain/creature/creatureEventAi.ts +1409 -0
  446. package/src/domain/creature/creatureGroups.ts +144 -0
  447. package/src/domain/creature/creatureLinking.ts +333 -0
  448. package/src/domain/creature/creatureScaling.ts +320 -0
  449. package/src/domain/creature/creatureTrigger.ts +53 -0
  450. package/src/domain/creature/creatureWander.ts +74 -0
  451. package/src/domain/creature/eventAiTexts.ts +145 -0
  452. package/src/domain/creature/faction.ts +287 -0
  453. package/src/domain/creature/forcedReaction.ts +48 -0
  454. package/src/domain/creature/outdoorHuntable.ts +74 -0
  455. package/src/domain/creature/patrolPacks.ts +210 -0
  456. package/src/domain/creature/reputation.ts +967 -0
  457. package/src/domain/creature/unitFlags.ts +74 -0
  458. package/src/domain/creature/worldBosses.ts +393 -0
  459. package/src/domain/dungeonMusic.mjs +48 -0
  460. package/src/domain/economy/auction.ts +282 -0
  461. package/src/domain/economy/auctionBot.ts +1060 -0
  462. package/src/domain/economy/honorPrice.ts +100 -0
  463. package/src/domain/economy/mail.ts +958 -0
  464. package/src/domain/economy/money.ts +42 -0
  465. package/src/domain/economy/syntheticClassTrainers.ts +62 -0
  466. package/src/domain/economy/trading.ts +296 -0
  467. package/src/domain/economy/vendorSellAll.ts +252 -0
  468. package/src/domain/economy/vendorStock.ts +146 -0
  469. package/src/domain/items/ammo.ts +817 -0
  470. package/src/domain/items/bank.ts +162 -0
  471. package/src/domain/items/consumableFamily.ts +205 -0
  472. package/src/domain/items/craftUpgrades.ts +83 -0
  473. package/src/domain/items/createItem.ts +93 -0
  474. package/src/domain/items/durability.ts +581 -0
  475. package/src/domain/items/equipment.ts +441 -0
  476. package/src/domain/items/equipmentHelper.ts +767 -0
  477. package/src/domain/items/inventory.ts +561 -0
  478. package/src/domain/items/itemBonding.ts +93 -0
  479. package/src/domain/items/itemContainer.ts +245 -0
  480. package/src/domain/items/itemDisplayName.ts +33 -0
  481. package/src/domain/items/itemInstance.ts +181 -0
  482. package/src/domain/items/itemRandomEnchantOutcomes.ts +315 -0
  483. package/src/domain/items/itemRandomProps.ts +77 -0
  484. package/src/domain/items/itemRestrictions.ts +329 -0
  485. package/src/domain/items/itemTeaches.ts +82 -0
  486. package/src/domain/items/itemUseCooldown.ts +52 -0
  487. package/src/domain/items/itemUseTarget.ts +90 -0
  488. package/src/domain/items/junkCategories.ts +174 -0
  489. package/src/domain/items/junkFilter.ts +368 -0
  490. package/src/domain/items/keyring.ts +131 -0
  491. package/src/domain/items/loot.ts +221 -0
  492. package/src/domain/items/mounts.ts +190 -0
  493. package/src/domain/items/onUsePayload.ts +102 -0
  494. package/src/domain/items/pendingLoot.ts +107 -0
  495. package/src/domain/items/relic.ts +105 -0
  496. package/src/domain/items/rocketBoots.ts +36 -0
  497. package/src/domain/items/sortContainer.ts +139 -0
  498. package/src/domain/items/spareContainers.ts +71 -0
  499. package/src/domain/items/spellReagentIndex.ts +131 -0
  500. package/src/domain/items/staleConsumables.ts +66 -0
  501. package/src/domain/items/weaponSpender.ts +110 -0
  502. package/src/domain/listDungeonIds.mjs +28 -0
  503. package/src/domain/macro/compose.ts +239 -0
  504. package/src/domain/macro/conditions.ts +260 -0
  505. package/src/domain/macro/creatureTypes.ts +40 -0
  506. package/src/domain/macro/describe.ts +169 -0
  507. package/src/domain/macro/icon.ts +60 -0
  508. package/src/domain/macro/index.ts +20 -0
  509. package/src/domain/macro/normalize.ts +112 -0
  510. package/src/domain/macro/parse.ts +316 -0
  511. package/src/domain/macro/plan.ts +288 -0
  512. package/src/domain/macro/rotation.ts +590 -0
  513. package/src/domain/macro/rotationEnv.ts +173 -0
  514. package/src/domain/macro/slots.ts +101 -0
  515. package/src/domain/macro/snapshot.ts +114 -0
  516. package/src/domain/macro/templates.ts +446 -0
  517. package/src/domain/macro/types.ts +70 -0
  518. package/src/domain/macro/units.ts +93 -0
  519. package/src/domain/macro/weaponTypes.ts +83 -0
  520. package/src/domain/nav/cityTeleports.ts +10 -0
  521. package/src/domain/nav/mounting.ts +113 -0
  522. package/src/domain/nav/nav.ts +43 -0
  523. package/src/domain/nav/navCollision.ts +606 -0
  524. package/src/domain/nav/navOutdoors.ts +91 -0
  525. package/src/domain/nav/navRegistry.ts +179 -0
  526. package/src/domain/nav/navRoomGraph.ts +302 -0
  527. package/src/domain/nav/navTypes.ts +43 -0
  528. package/src/domain/nav/navmesh.ts +1715 -0
  529. package/src/domain/nav/speed.ts +200 -0
  530. package/src/domain/nav/storey.ts +10 -0
  531. package/src/domain/nav/travel.ts +725 -0
  532. package/src/domain/nav/travelTypes.ts +75 -0
  533. package/src/domain/pet/pet.ts +1150 -0
  534. package/src/domain/pet/petActionBar.ts +180 -0
  535. package/src/domain/pet/petSpellbook.ts +39 -0
  536. package/src/domain/pet/petStable.ts +81 -0
  537. package/src/domain/professions/autoDisenchant.ts +236 -0
  538. package/src/domain/professions/characterEnchants.ts +176 -0
  539. package/src/domain/professions/craftCooldown.ts +52 -0
  540. package/src/domain/professions/enchantApply.ts +62 -0
  541. package/src/domain/professions/enchantTargets.ts +114 -0
  542. package/src/domain/professions/enchanting.ts +236 -0
  543. package/src/domain/professions/gatherFilter.ts +149 -0
  544. package/src/domain/professions/gearEnchant.ts +137 -0
  545. package/src/domain/professions/heldTools.ts +29 -0
  546. package/src/domain/professions/learnAllRecipes.ts +146 -0
  547. package/src/domain/professions/miningHarvest.ts +69 -0
  548. package/src/domain/professions/partyDisenchant.ts +294 -0
  549. package/src/domain/professions/partyWork.ts +83 -0
  550. package/src/domain/professions/pickpocket.ts +172 -0
  551. package/src/domain/professions/poisons.ts +240 -0
  552. package/src/domain/professions/professions.ts +677 -0
  553. package/src/domain/professions/rareNodes.ts +45 -0
  554. package/src/domain/professions/skinLoot.ts +27 -0
  555. package/src/domain/professions/vendorReagents.ts +152 -0
  556. package/src/domain/professions/weaponImbueHand.ts +171 -0
  557. package/src/domain/questFarmFilter.mjs +42 -0
  558. package/src/domain/quests/doQuest.ts +1204 -0
  559. package/src/domain/quests/gossip.ts +308 -0
  560. package/src/domain/quests/gossipTalk.ts +298 -0
  561. package/src/domain/quests/interpreter/canComplete.ts +150 -0
  562. package/src/domain/quests/interpreter/claim.ts +137 -0
  563. package/src/domain/quests/interpreter/dbscript/handlers.ts +1371 -0
  564. package/src/domain/quests/interpreter/dbscript/handlersBatch.ts +1251 -0
  565. package/src/domain/quests/interpreter/dbscript/hooks.ts +331 -0
  566. package/src/domain/quests/interpreter/dbscript/index.ts +212 -0
  567. package/src/domain/quests/interpreter/dbscript/opcodes.ts +257 -0
  568. package/src/domain/quests/interpreter/dbscript/registry.ts +1194 -0
  569. package/src/domain/quests/interpreter/dbscript/registryAreatriggerScripts.ts +539 -0
  570. package/src/domain/quests/interpreter/dbscript/registryAshenvale.ts +553 -0
  571. package/src/domain/quests/interpreter/dbscript/registryAzshara.ts +272 -0
  572. package/src/domain/quests/interpreter/dbscript/registryDarkshore.ts +637 -0
  573. package/src/domain/quests/interpreter/dbscript/registryDesolace.ts +501 -0
  574. package/src/domain/quests/interpreter/dbscript/registryDustwallowMarsh.ts +735 -0
  575. package/src/domain/quests/interpreter/dbscript/registryFelwood.ts +493 -0
  576. package/src/domain/quests/interpreter/dbscript/registryFeralas.ts +485 -0
  577. package/src/domain/quests/interpreter/dbscript/registryGoScripts.ts +203 -0
  578. package/src/domain/quests/interpreter/dbscript/registryMoonglade.ts +288 -0
  579. package/src/domain/quests/interpreter/dbscript/registryNpcProfessions.ts +230 -0
  580. package/src/domain/quests/interpreter/dbscript/registryNpcsSpecial.ts +545 -0
  581. package/src/domain/quests/interpreter/dbscript/registryRazorfenDowns.ts +293 -0
  582. package/src/domain/quests/interpreter/dbscript/registryRazorfenKraul.ts +221 -0
  583. package/src/domain/quests/interpreter/dbscript/registryRedridge.ts +112 -0
  584. package/src/domain/quests/interpreter/dbscript/registrySearingGorge.ts +125 -0
  585. package/src/domain/quests/interpreter/dbscript/registrySiblings.ts +51 -0
  586. package/src/domain/quests/interpreter/dbscript/registrySilverpineForest.ts +172 -0
  587. package/src/domain/quests/interpreter/dbscript/registryStonetalonMountains.ts +180 -0
  588. package/src/domain/quests/interpreter/dbscript/registryStormwindCity.ts +369 -0
  589. package/src/domain/quests/interpreter/dbscript/registryStranglethornVale.ts +115 -0
  590. package/src/domain/quests/interpreter/dbscript/registryStratholme.ts +146 -0
  591. package/src/domain/quests/interpreter/dbscript/registrySunkenTemple.ts +149 -0
  592. package/src/domain/quests/interpreter/dbscript/registrySwampOfSorrows.ts +163 -0
  593. package/src/domain/quests/interpreter/dbscript/registryTanaris.ts +318 -0
  594. package/src/domain/quests/interpreter/dbscript/registryTeldrassil.ts +120 -0
  595. package/src/domain/quests/interpreter/dbscript/registryTheBarrens.ts +603 -0
  596. package/src/domain/quests/interpreter/dbscript/registryThousandNeedles.ts +499 -0
  597. package/src/domain/quests/interpreter/dbscript/registryTirisfalGlades.ts +222 -0
  598. package/src/domain/quests/interpreter/dbscript/registryUngoroCrater.ts +291 -0
  599. package/src/domain/quests/interpreter/dbscript/registryWarEffort.ts +773 -0
  600. package/src/domain/quests/interpreter/dbscript/registryWesternPlaguelands.ts +206 -0
  601. package/src/domain/quests/interpreter/dbscript/registryWestfall.ts +190 -0
  602. package/src/domain/quests/interpreter/dbscript/registryWetlands.ts +186 -0
  603. package/src/domain/quests/interpreter/dbscript/registryWinterspring.ts +235 -0
  604. package/src/domain/quests/interpreter/dbscript/types.ts +817 -0
  605. package/src/domain/quests/interpreter/dbscript/vm.ts +171 -0
  606. package/src/domain/quests/interpreter/diff.ts +349 -0
  607. package/src/domain/quests/interpreter/fixtures/oracle-questlog-qi2.json +2417 -0
  608. package/src/domain/quests/interpreter/flags.ts +101 -0
  609. package/src/domain/quests/interpreter/index.ts +113 -0
  610. package/src/domain/quests/interpreter/live.ts +178 -0
  611. package/src/domain/quests/interpreter/types.ts +81 -0
  612. package/src/domain/quests/interpreter/updaters.ts +412 -0
  613. package/src/domain/quests/questCorpseUse.ts +155 -0
  614. package/src/domain/quests/questDialog.ts +43 -0
  615. package/src/domain/quests/questFight.ts +34 -0
  616. package/src/domain/quests/questObjectiveLabel.ts +39 -0
  617. package/src/domain/quests/questRewardChoice.ts +85 -0
  618. package/src/domain/quests/questScriptCredit.ts +26 -0
  619. package/src/domain/quests/questScriptItemUse.ts +74 -0
  620. package/src/domain/quests/questTameRods.ts +79 -0
  621. package/src/domain/quests/questlog.ts +1545 -0
  622. package/src/domain/quests/talkDeliver.ts +170 -0
  623. package/src/domain/raid/globalActionBar.ts +195 -0
  624. package/src/domain/raid/instanceRoster.ts +194 -0
  625. package/src/domain/raid/partySummon.ts +198 -0
  626. package/src/domain/raid/partyVault.ts +320 -0
  627. package/src/domain/raid/raidBossAbilities.ts +268 -0
  628. package/src/domain/raid/raidComp.ts +335 -0
  629. package/src/domain/raid/raidCustomRows.ts +453 -0
  630. package/src/domain/raid/raidEncounterGear.ts +298 -0
  631. package/src/domain/raid/raidEncounterTactics.ts +412 -0
  632. package/src/domain/raid/raidPlan.ts +509 -0
  633. package/src/domain/raid/raidPlanEdits.ts +98 -0
  634. package/src/domain/raid/raidPlanPacks.ts +480 -0
  635. package/src/domain/raid/raidRecovery.ts +183 -0
  636. package/src/domain/raid/raidRosterTemplate.ts +138 -0
  637. package/src/domain/rotation/auraSide.ts +68 -0
  638. package/src/domain/rotation/build.ts +3031 -0
  639. package/src/domain/rotation/classes/druid.ts +58 -0
  640. package/src/domain/rotation/classes/hooks.ts +59 -0
  641. package/src/domain/rotation/classes/hunter.ts +156 -0
  642. package/src/domain/rotation/classes/index.ts +104 -0
  643. package/src/domain/rotation/classes/mage.ts +99 -0
  644. package/src/domain/rotation/classes/paladin.ts +296 -0
  645. package/src/domain/rotation/classes/priest.ts +94 -0
  646. package/src/domain/rotation/classes/pullOpener.ts +92 -0
  647. package/src/domain/rotation/classes/rogue.ts +69 -0
  648. package/src/domain/rotation/classes/shaman.ts +29 -0
  649. package/src/domain/rotation/classes/supportSpec.ts +94 -0
  650. package/src/domain/rotation/classes/warlock.ts +81 -0
  651. package/src/domain/rotation/classes/warrior.ts +402 -0
  652. package/src/domain/rotation/classify.ts +1381 -0
  653. package/src/domain/rotation/condMetricPicker.ts +379 -0
  654. package/src/domain/rotation/consumables.ts +242 -0
  655. package/src/domain/rotation/evaluate.ts +1115 -0
  656. package/src/domain/rotation/finisherLadder.ts +145 -0
  657. package/src/domain/rotation/invent.ts +938 -0
  658. package/src/domain/rotation/killOrder.ts +30 -0
  659. package/src/domain/rotation/openerForm.ts +22 -0
  660. package/src/domain/rotation/paladinBuffs.ts +568 -0
  661. package/src/domain/rotation/prebuff.ts +283 -0
  662. package/src/domain/rotation/raidRowTuning.ts +34 -0
  663. package/src/domain/rotation/rotation.ts +11 -0
  664. package/src/domain/rotation/rotationProfiles.ts +180 -0
  665. package/src/domain/rotation/rotationSuggestions.ts +851 -0
  666. package/src/domain/rotation/ruleAfterChain.ts +49 -0
  667. package/src/domain/rotation/spec.ts +70 -0
  668. package/src/domain/rotation/spellPicker.ts +140 -0
  669. package/src/domain/rotation/stand.ts +143 -0
  670. package/src/domain/rotation/types.ts +718 -0
  671. package/src/domain/run/battlegroundRun.ts +111 -0
  672. package/src/domain/run/canEnterContext.ts +32 -0
  673. package/src/domain/run/death.ts +561 -0
  674. package/src/domain/run/deathRecovery.ts +289 -0
  675. package/src/domain/run/duel.ts +141 -0
  676. package/src/domain/run/dungeonRun.ts +344 -0
  677. package/src/domain/run/floorRest.ts +159 -0
  678. package/src/domain/run/honor.ts +557 -0
  679. package/src/domain/run/honorWeek.ts +180 -0
  680. package/src/domain/run/whenMap.ts +977 -0
  681. package/src/domain/scriptdev/arathiHighlands.ts +445 -0
  682. package/src/domain/scriptdev/ashenvale.ts +635 -0
  683. package/src/domain/scriptdev/ashenvaleHelpers.ts +1215 -0
  684. package/src/domain/scriptdev/azshara.ts +892 -0
  685. package/src/domain/scriptdev/blackfathomDeeps.ts +212 -0
  686. package/src/domain/scriptdev/blackrockDepths.ts +1434 -0
  687. package/src/domain/scriptdev/blackrockSpire.ts +730 -0
  688. package/src/domain/scriptdev/blackwingLair.ts +521 -0
  689. package/src/domain/scriptdev/burningSteppes.ts +62 -0
  690. package/src/domain/scriptdev/classFilter.ts +43 -0
  691. package/src/domain/scriptdev/darkshore.ts +817 -0
  692. package/src/domain/scriptdev/darkshoreHelpers.ts +853 -0
  693. package/src/domain/scriptdev/deadmines.ts +331 -0
  694. package/src/domain/scriptdev/desolace.ts +1239 -0
  695. package/src/domain/scriptdev/direMaul.ts +859 -0
  696. package/src/domain/scriptdev/durotar.ts +532 -0
  697. package/src/domain/scriptdev/dustwallowMarsh.ts +758 -0
  698. package/src/domain/scriptdev/dustwallowMarshHelpers.ts +994 -0
  699. package/src/domain/scriptdev/easternPlaguelands.ts +21 -0
  700. package/src/domain/scriptdev/felwood.ts +1055 -0
  701. package/src/domain/scriptdev/feralas.ts +571 -0
  702. package/src/domain/scriptdev/feralasHelpers.ts +838 -0
  703. package/src/domain/scriptdev/gnomeregan.ts +466 -0
  704. package/src/domain/scriptdev/guards.ts +616 -0
  705. package/src/domain/scriptdev/hinterlands.ts +693 -0
  706. package/src/domain/scriptdev/host.ts +1134 -0
  707. package/src/domain/scriptdev/index.ts +123 -0
  708. package/src/domain/scriptdev/instanceRaids.ts +136 -0
  709. package/src/domain/scriptdev/itemScripts.ts +37 -0
  710. package/src/domain/scriptdev/lochModan.ts +283 -0
  711. package/src/domain/scriptdev/maraudon.ts +232 -0
  712. package/src/domain/scriptdev/moltenCore.ts +831 -0
  713. package/src/domain/scriptdev/moonglade.ts +659 -0
  714. package/src/domain/scriptdev/moongladeHelpers.ts +933 -0
  715. package/src/domain/scriptdev/naxxramas.ts +577 -0
  716. package/src/domain/scriptdev/naxxramasConstruct.ts +364 -0
  717. package/src/domain/scriptdev/naxxramasFrostwyrm.ts +378 -0
  718. package/src/domain/scriptdev/naxxramasMilitary.ts +276 -0
  719. package/src/domain/scriptdev/npcProfessions.ts +1025 -0
  720. package/src/domain/scriptdev/npcsSpecial.ts +1379 -0
  721. package/src/domain/scriptdev/onyxiasLair.ts +313 -0
  722. package/src/domain/scriptdev/orgrimmar.ts +147 -0
  723. package/src/domain/scriptdev/professionSpecConsumer.ts +481 -0
  724. package/src/domain/scriptdev/razorfenDowns.ts +468 -0
  725. package/src/domain/scriptdev/razorfenKraul.ts +410 -0
  726. package/src/domain/scriptdev/redridgeMountains.ts +163 -0
  727. package/src/domain/scriptdev/registry.ts +945 -0
  728. package/src/domain/scriptdev/ruinsOfAhnqiraj.ts +356 -0
  729. package/src/domain/scriptdev/scarletMonastery.ts +713 -0
  730. package/src/domain/scriptdev/scholomance.ts +682 -0
  731. package/src/domain/scriptdev/searingGorge.ts +295 -0
  732. package/src/domain/scriptdev/shadowfangKeep.ts +181 -0
  733. package/src/domain/scriptdev/silithus.ts +71 -0
  734. package/src/domain/scriptdev/silverpineForest.ts +662 -0
  735. package/src/domain/scriptdev/stonetalonMountains.ts +493 -0
  736. package/src/domain/scriptdev/stormwindCity.ts +1192 -0
  737. package/src/domain/scriptdev/stranglethornVale.ts +402 -0
  738. package/src/domain/scriptdev/stratholme.ts +650 -0
  739. package/src/domain/scriptdev/stratholmeScripts.ts +505 -0
  740. package/src/domain/scriptdev/sunkenTemple.ts +736 -0
  741. package/src/domain/scriptdev/swampOfSorrows.ts +22 -0
  742. package/src/domain/scriptdev/tanaris.ts +391 -0
  743. package/src/domain/scriptdev/tanarisHelpers.ts +661 -0
  744. package/src/domain/scriptdev/teldrassil.ts +143 -0
  745. package/src/domain/scriptdev/templeOfAhnqiraj.ts +1013 -0
  746. package/src/domain/scriptdev/theBarrens.ts +709 -0
  747. package/src/domain/scriptdev/theBarrensHelpers.ts +1026 -0
  748. package/src/domain/scriptdev/thousandNeedles.ts +426 -0
  749. package/src/domain/scriptdev/thousandNeedlesHelpers.ts +566 -0
  750. package/src/domain/scriptdev/tirisfalGlades.ts +533 -0
  751. package/src/domain/scriptdev/types.ts +1026 -0
  752. package/src/domain/scriptdev/uldaman.ts +437 -0
  753. package/src/domain/scriptdev/ungoroCrater.ts +549 -0
  754. package/src/domain/scriptdev/ungoroCraterHelpers.ts +1006 -0
  755. package/src/domain/scriptdev/wailingCaverns.ts +700 -0
  756. package/src/domain/scriptdev/warEffort.ts +371 -0
  757. package/src/domain/scriptdev/westernPlaguelands.ts +952 -0
  758. package/src/domain/scriptdev/westfall.ts +767 -0
  759. package/src/domain/scriptdev/wetlands.ts +660 -0
  760. package/src/domain/scriptdev/winterspring.ts +482 -0
  761. package/src/domain/scriptdev/winterspringHelpers.ts +801 -0
  762. package/src/domain/scriptdev/worldBosses.ts +605 -0
  763. package/src/domain/scriptdev/zulfarrak.ts +478 -0
  764. package/src/domain/scriptdev/zulgurub.ts +1152 -0
  765. package/src/domain/sim/dummySim.ts +408 -0
  766. package/src/domain/sim/targetDummy.ts +29 -0
  767. package/src/domain/social/guild.ts +148 -0
  768. package/src/domain/spells/areaAuraMembership.ts +93 -0
  769. package/src/domain/spells/breakableStunCc.ts +166 -0
  770. package/src/domain/spells/cancelAura.ts +80 -0
  771. package/src/domain/spells/diminishing.ts +167 -0
  772. package/src/domain/spells/dispel.ts +149 -0
  773. package/src/domain/spells/dispelMechanic.ts +105 -0
  774. package/src/domain/spells/equipSpells.ts +247 -0
  775. package/src/domain/spells/learnOnTargets.ts +260 -0
  776. package/src/domain/spells/mergeSchoolImmunityFromRaw.ts +66 -0
  777. package/src/domain/spells/resurrectSpell.ts +124 -0
  778. package/src/domain/spells/spellPushback.ts +39 -0
  779. package/src/domain/spells/spellReflect.ts +43 -0
  780. package/src/domain/spells/stealth.ts +183 -0
  781. package/src/domain/spells/totem.ts +394 -0
  782. package/src/domain/spells/worldCast.ts +187 -0
  783. package/src/domain/world/areaTriggerLocks.ts +177 -0
  784. package/src/domain/world/dungeonMusic.ts +108 -0
  785. package/src/domain/world/gameEvents.ts +119 -0
  786. package/src/domain/world/gameObjects.ts +1030 -0
  787. package/src/domain/world/goEventFire.ts +234 -0
  788. package/src/domain/world/goMoTransport.ts +74 -0
  789. package/src/domain/world/goTransport.ts +81 -0
  790. package/src/domain/world/hazards.ts +459 -0
  791. package/src/domain/world/openLock.ts +398 -0
  792. package/src/domain/world/pools.ts +470 -0
  793. package/src/domain/world/restArea.ts +37 -0
  794. package/src/domain/world/weather.ts +201 -0
  795. package/src/domain/zoneAmbience.mjs +87 -0
  796. package/src/domain/zoneMusic.mjs +62 -0
  797. package/src/domain/zoneMusicQueue.mjs +7 -0
  798. package/src/engine/combat/activeSeal.ts +39 -0
  799. package/src/engine/combat/aiTargetResolve.ts +64 -0
  800. package/src/engine/combat/aoeCast.ts +289 -0
  801. package/src/engine/combat/applyConsumeAura.ts +74 -0
  802. package/src/engine/combat/applyDemonicSacrifice.ts +70 -0
  803. package/src/engine/combat/applyDispelMechanic.ts +30 -0
  804. package/src/engine/combat/applyHealEffect.ts +301 -0
  805. package/src/engine/combat/applySchoolAbsorb.ts +114 -0
  806. package/src/engine/combat/applySpell.ts +3169 -0
  807. package/src/engine/combat/applySpellLate.ts +2026 -0
  808. package/src/engine/combat/applySpellLateBag.ts +144 -0
  809. package/src/engine/combat/areaAura.ts +283 -0
  810. package/src/engine/combat/attackTable.ts +508 -0
  811. package/src/engine/combat/battleRez.ts +87 -0
  812. package/src/engine/combat/castAction.ts +36 -0
  813. package/src/engine/combat/castTargetGates.ts +75 -0
  814. package/src/engine/combat/castableKit.ts +68 -0
  815. package/src/engine/combat/ccMirrorUpgrade.ts +39 -0
  816. package/src/engine/combat/chainFan.ts +210 -0
  817. package/src/engine/combat/channel.ts +423 -0
  818. package/src/engine/combat/chaseCloseness.ts +95 -0
  819. package/src/engine/combat/cmangosCastGates.ts +129 -0
  820. package/src/engine/combat/combatFlag.ts +77 -0
  821. package/src/engine/combat/combatGeometry.ts +1329 -0
  822. package/src/engine/combat/combustion.ts +68 -0
  823. package/src/engine/combat/context.ts +730 -0
  824. package/src/engine/combat/createSession.ts +1165 -0
  825. package/src/engine/combat/creatureLeash.ts +38 -0
  826. package/src/engine/combat/crowdControl.ts +1259 -0
  827. package/src/engine/combat/dispelDuty.ts +54 -0
  828. package/src/engine/combat/dispelReflect.ts +609 -0
  829. package/src/engine/combat/displaceFloor.ts +113 -0
  830. package/src/engine/combat/duelBounds.ts +47 -0
  831. package/src/engine/combat/equippedItemGate.ts +297 -0
  832. package/src/engine/combat/evadeLifecycle.ts +63 -0
  833. package/src/engine/combat/eventAi.ts +2220 -0
  834. package/src/engine/combat/facing.ts +74 -0
  835. package/src/engine/combat/fearFlee.ts +137 -0
  836. package/src/engine/combat/fighterFactory.ts +1144 -0
  837. package/src/engine/combat/forceDespawnAction.ts +51 -0
  838. package/src/engine/combat/formulas.ts +807 -0
  839. package/src/engine/combat/friendlyFan.ts +55 -0
  840. package/src/engine/combat/groundPoolTick.ts +210 -0
  841. package/src/engine/combat/hazards.ts +114 -0
  842. package/src/engine/combat/healTargets.ts +226 -0
  843. package/src/engine/combat/heartbeatResist.ts +49 -0
  844. package/src/engine/combat/hostileItemUse.ts +75 -0
  845. package/src/engine/combat/interruptAuras.ts +66 -0
  846. package/src/engine/combat/itemRescue.ts +49 -0
  847. package/src/engine/combat/itemUse.ts +260 -0
  848. package/src/engine/combat/kitById.ts +23 -0
  849. package/src/engine/combat/knockBack.ts +201 -0
  850. package/src/engine/combat/leap.ts +89 -0
  851. package/src/engine/combat/mitigation.ts +587 -0
  852. package/src/engine/combat/modTaunt.ts +43 -0
  853. package/src/engine/combat/motionClaim.ts +268 -0
  854. package/src/engine/combat/motionGate.ts +150 -0
  855. package/src/engine/combat/motionGenerator.ts +27 -0
  856. package/src/engine/combat/navSight.ts +138 -0
  857. package/src/engine/combat/perfSink.ts +28 -0
  858. package/src/engine/combat/periodicDue.ts +55 -0
  859. package/src/engine/combat/petAuras.ts +86 -0
  860. package/src/engine/combat/pickAction.ts +3449 -0
  861. package/src/engine/combat/powerForm.ts +735 -0
  862. package/src/engine/combat/powerPools.ts +49 -0
  863. package/src/engine/combat/predicates.ts +538 -0
  864. package/src/engine/combat/procsDamage.ts +1823 -0
  865. package/src/engine/combat/reactiveGates.ts +135 -0
  866. package/src/engine/combat/roles.ts +389 -0
  867. package/src/engine/combat/rotationSubject.ts +78 -0
  868. package/src/engine/combat/ruleAfter.ts +106 -0
  869. package/src/engine/combat/seedDisplayAuras.ts +203 -0
  870. package/src/engine/combat/seedOocEffects.ts +317 -0
  871. package/src/engine/combat/seedSchoolImmunities.ts +45 -0
  872. package/src/engine/combat/session-types.ts +458 -0
  873. package/src/engine/combat/sessionApi.ts +1785 -0
  874. package/src/engine/combat/sessionCharge.ts +124 -0
  875. package/src/engine/combat/sessionChase.ts +869 -0
  876. package/src/engine/combat/sessionDeaths.ts +469 -0
  877. package/src/engine/combat/sessionFocus.ts +506 -0
  878. package/src/engine/combat/sessionFollow.ts +167 -0
  879. package/src/engine/combat/sessionGeometry.ts +188 -0
  880. package/src/engine/combat/sessionHome.ts +75 -0
  881. package/src/engine/combat/sessionInject.ts +719 -0
  882. package/src/engine/combat/sessionMechanicMove.ts +555 -0
  883. package/src/engine/combat/sessionMotion.ts +1553 -0
  884. package/src/engine/combat/sessionMotionKit.ts +247 -0
  885. package/src/engine/combat/sessionMoveIntent.ts +49 -0
  886. package/src/engine/combat/sessionMoveOrder.ts +200 -0
  887. package/src/engine/combat/sessionPathTo.ts +657 -0
  888. package/src/engine/combat/sessionPools.ts +688 -0
  889. package/src/engine/combat/sessionSteer.ts +144 -0
  890. package/src/engine/combat/sessionStepBack.ts +151 -0
  891. package/src/engine/combat/sessionStepWrap.ts +116 -0
  892. package/src/engine/combat/sessionView.ts +442 -0
  893. package/src/engine/combat/simEffects.ts +32 -0
  894. package/src/engine/combat/simFacade.ts +620 -0
  895. package/src/engine/combat/simPump.ts +264 -0
  896. package/src/engine/combat/simSnapshot.ts +165 -0
  897. package/src/engine/combat/simThreadPool.ts +35 -0
  898. package/src/engine/combat/skeramImages.ts +57 -0
  899. package/src/engine/combat/slowUpdate.ts +178 -0
  900. package/src/engine/combat/snapToFloor.ts +42 -0
  901. package/src/engine/combat/speedAura.ts +29 -0
  902. package/src/engine/combat/spellReagents.ts +71 -0
  903. package/src/engine/combat/stealthDetect.ts +93 -0
  904. package/src/engine/combat/stepBackMotion.ts +171 -0
  905. package/src/engine/combat/stepSession.ts +3102 -0
  906. package/src/engine/combat/summonCast.ts +243 -0
  907. package/src/engine/combat/thaddiusPolarity.ts +97 -0
  908. package/src/engine/combat/threat.ts +388 -0
  909. package/src/engine/combat/threatOps.ts +272 -0
  910. package/src/engine/combat/tickWorkerBody.ts +211 -0
  911. package/src/engine/combat/totemAuras.ts +122 -0
  912. package/src/engine/combat/triggerPayload.ts +42 -0
  913. package/src/engine/combat/triggeredCompanion.ts +63 -0
  914. package/src/engine/combat/types.ts +1897 -0
  915. package/src/engine/combat/useObjectTrap.ts +140 -0
  916. package/src/engine/combat/walkSurface.ts +54 -0
  917. package/src/engine/combat/weapon.ts +1248 -0
  918. package/src/engine/combat/worldFrame.ts +43 -0
  919. package/src/engine/combat.solo-golden.json +2242 -0
  920. package/src/engine/combat.ts +84 -0
  921. package/src/engine/core/detMath.ts +289 -0
  922. package/src/engine/core/events.ts +357 -0
  923. package/src/engine/core/rng.ts +36 -0
  924. package/src/engine/game/actionBars.ts +124 -0
  925. package/src/engine/game/activateObjectFromSpell.ts +320 -0
  926. package/src/engine/game/applyActiveObjectFromDbScript.ts +98 -0
  927. package/src/engine/game/applyDeathAndSpellScripts.ts +268 -0
  928. package/src/engine/game/applyDoorFromDbScript.ts +388 -0
  929. package/src/engine/game/applyEscortFromDbScript.ts +290 -0
  930. package/src/engine/game/applyGoSpellcasterCast.ts +40 -0
  931. package/src/engine/game/applyHoverFromDbScript.ts +110 -0
  932. package/src/engine/game/applyOocResurrect.ts +71 -0
  933. package/src/engine/game/applySpellDurability.ts +115 -0
  934. package/src/engine/game/applyTailFromDbScript.ts +478 -0
  935. package/src/engine/game/applyTerminateFromDbScript.ts +95 -0
  936. package/src/engine/game/auctionMail.ts +269 -0
  937. package/src/engine/game/bagOverflow.ts +130 -0
  938. package/src/engine/game/bank.ts +110 -0
  939. package/src/engine/game/barAutofill.ts +26 -0
  940. package/src/engine/game/battleground.ts +369 -0
  941. package/src/engine/game/combatEnemySpec.ts +216 -0
  942. package/src/engine/game/combatObjectTraps.ts +167 -0
  943. package/src/engine/game/combatSeed.ts +320 -0
  944. package/src/engine/game/combatSession.ts +3241 -0
  945. package/src/engine/game/combatSessionClock.ts +62 -0
  946. package/src/engine/game/companionQuestActions.ts +242 -0
  947. package/src/engine/game/companionTraining.ts +40 -0
  948. package/src/engine/game/consumables.ts +624 -0
  949. package/src/engine/game/context.ts +53 -0
  950. package/src/engine/game/createHealthstone.ts +45 -0
  951. package/src/engine/game/deathFlow.ts +127 -0
  952. package/src/engine/game/devGrant.ts +117 -0
  953. package/src/engine/game/dynamicObject.ts +365 -0
  954. package/src/engine/game/enchantItem.ts +177 -0
  955. package/src/engine/game/equipmentHelper.ts +806 -0
  956. package/src/engine/game/factionState.ts +108 -0
  957. package/src/engine/game/garmentsOoc.ts +69 -0
  958. package/src/engine/game/globalActionBar.ts +179 -0
  959. package/src/engine/game/goInteractQuestCredit.ts +51 -0
  960. package/src/engine/game/goMoTransportUse.ts +61 -0
  961. package/src/engine/game/goTransportUse.ts +60 -0
  962. package/src/engine/game/healthstoneUpkeep.ts +59 -0
  963. package/src/engine/game/killSkin.ts +124 -0
  964. package/src/engine/game/learnFromItem.ts +118 -0
  965. package/src/engine/game/loadReconcile.ts +551 -0
  966. package/src/engine/game/loot.ts +294 -0
  967. package/src/engine/game/macros.ts +125 -0
  968. package/src/engine/game/mail.ts +293 -0
  969. package/src/engine/game/meetingStoneQueue.ts +75 -0
  970. package/src/engine/game/meetingStoneUse.ts +54 -0
  971. package/src/engine/game/newGameState.ts +363 -0
  972. package/src/engine/game/oocAuraInterrupt.ts +92 -0
  973. package/src/engine/game/oocCast.ts +241 -0
  974. package/src/engine/game/oocCastEffects.ts +803 -0
  975. package/src/engine/game/oocCastMethods.ts +1077 -0
  976. package/src/engine/game/openLockFromSpell.ts +174 -0
  977. package/src/engine/game/outdoorPvp.ts +187 -0
  978. package/src/engine/game/outfitter.ts +644 -0
  979. package/src/engine/game/paladinBoard.ts +393 -0
  980. package/src/engine/game/partyAuras.ts +121 -0
  981. package/src/engine/game/persistCombatAuras.ts +91 -0
  982. package/src/engine/game/pets.ts +459 -0
  983. package/src/engine/game/prePullHealthstones.ts +154 -0
  984. package/src/engine/game/professions.ts +2227 -0
  985. package/src/engine/game/pruneStaleRuntime.ts +39 -0
  986. package/src/engine/game/questFightCredit.ts +36 -0
  987. package/src/engine/game/questItemOnCorpse.ts +166 -0
  988. package/src/engine/game/questItemOnCreature.ts +103 -0
  989. package/src/engine/game/questLog.ts +879 -0
  990. package/src/engine/game/questSpellComplete.ts +21 -0
  991. package/src/engine/game/questTurnIn.ts +598 -0
  992. package/src/engine/game/raidParty.ts +32 -0
  993. package/src/engine/game/reagentBuyIn.ts +39 -0
  994. package/src/engine/game/regenRest.ts +186 -0
  995. package/src/engine/game/rotation.ts +425 -0
  996. package/src/engine/game/runFullDungeon.ts +611 -0
  997. package/src/engine/game/runSupply.ts +80 -0
  998. package/src/engine/game/sanitizeDriftedItems.ts +135 -0
  999. package/src/engine/game/scriptDevGoUse.ts +397 -0
  1000. package/src/engine/game/scriptDevInnkeeper.ts +150 -0
  1001. package/src/engine/game/scriptDevProfessionSpec.ts +99 -0
  1002. package/src/engine/game/sendEventFromSpell.ts +87 -0
  1003. package/src/engine/game/sortContainers.ts +85 -0
  1004. package/src/engine/game/spellReputation.ts +69 -0
  1005. package/src/engine/game/squad/squadDungeonSeating.ts +197 -0
  1006. package/src/engine/game/squadDungeon.ts +4222 -0
  1007. package/src/engine/game/squadQuestShare.ts +260 -0
  1008. package/src/engine/game/summonObjectSlot.ts +69 -0
  1009. package/src/engine/game/summonObjectWild.ts +73 -0
  1010. package/src/engine/game/summoningRitualUse.ts +141 -0
  1011. package/src/engine/game/talents.ts +356 -0
  1012. package/src/engine/game/tamingRodUse.ts +66 -0
  1013. package/src/engine/game/tempSpawnCreature.ts +136 -0
  1014. package/src/engine/game/tickDungeonPatrolScripts.ts +205 -0
  1015. package/src/engine/game/tickTimedQuests.ts +28 -0
  1016. package/src/engine/game/trade.ts +586 -0
  1017. package/src/engine/game/travel.ts +1155 -0
  1018. package/src/engine/game/types.ts +1855 -0
  1019. package/src/engine/game/vendorOffers.ts +244 -0
  1020. package/src/engine/game/weaponImbueItem.ts +281 -0
  1021. package/src/engine/game/worldSession.ts +183 -0
  1022. package/src/engine/game/worldState.ts +166 -0
  1023. package/src/engine/game.ts +968 -0
  1024. package/src/engine/spells/GAP.md +67 -0
  1025. package/src/engine/spells/absorbs.ts +376 -0
  1026. package/src/engine/spells/addTargetTrigger.ts +53 -0
  1027. package/src/engine/spells/auraPerSecondCost.ts +50 -0
  1028. package/src/engine/spells/auraStacking.ts +497 -0
  1029. package/src/engine/spells/cmangos-special-cases-combustion.ts +13 -0
  1030. package/src/engine/spells/cmangos-special-cases-s18.ts +445 -0
  1031. package/src/engine/spells/cmangos-special-cases-s19.ts +324 -0
  1032. package/src/engine/spells/cmangos-special-cases-s20.ts +385 -0
  1033. package/src/engine/spells/cmangos-special-cases-s21.ts +116 -0
  1034. package/src/engine/spells/cmangos-special-cases-s22.ts +24 -0
  1035. package/src/engine/spells/cmangos-special-cases-s23.ts +52 -0
  1036. package/src/engine/spells/cmangos-special-cases.ts +1570 -0
  1037. package/src/engine/spells/conditions.ts +718 -0
  1038. package/src/engine/spells/effects.ts +203 -0
  1039. package/src/engine/spells/fixtures/oracle-effects-lock9a.json +1 -0
  1040. package/src/engine/spells/immunity.ts +942 -0
  1041. package/src/engine/spells/intentEffects.ts +351 -0
  1042. package/src/engine/spells/interpreter/GAPS.md +21 -0
  1043. package/src/engine/spells/interpreter/amount.ts +109 -0
  1044. package/src/engine/spells/interpreter/class-scripts-kit.ts +478 -0
  1045. package/src/engine/spells/interpreter/diff.ts +246 -0
  1046. package/src/engine/spells/interpreter/duration.ts +90 -0
  1047. package/src/engine/spells/interpreter/form-attrs.json +1 -0
  1048. package/src/engine/spells/interpreter/form-attrs.ts +95 -0
  1049. package/src/engine/spells/interpreter/handlers/aura-remove.ts +113 -0
  1050. package/src/engine/spells/interpreter/handlers/auras-control.ts +1547 -0
  1051. package/src/engine/spells/interpreter/handlers/auras-core.ts +1583 -0
  1052. package/src/engine/spells/interpreter/handlers/auras-misc.ts +1774 -0
  1053. package/src/engine/spells/interpreter/handlers/effect-disenchant.ts +17 -0
  1054. package/src/engine/spells/interpreter/handlers/effect-skinning.ts +20 -0
  1055. package/src/engine/spells/interpreter/handlers/effects-action-utility.ts +1455 -0
  1056. package/src/engine/spells/interpreter/handlers/effects-core.ts +432 -0
  1057. package/src/engine/spells/interpreter/handlers/effects-remainder.ts +105 -0
  1058. package/src/engine/spells/interpreter/handlers/effects-summon-item.ts +687 -0
  1059. package/src/engine/spells/interpreter/handlers/mod-increase-energy-percent.ts +36 -0
  1060. package/src/engine/spells/interpreter/handlers/mod-increase-energy.ts +36 -0
  1061. package/src/engine/spells/interpreter/handlers/periodic-trigger-zero.ts +327 -0
  1062. package/src/engine/spells/interpreter/handlers/procs.ts +131 -0
  1063. package/src/engine/spells/interpreter/handlers/registry.ts +975 -0
  1064. package/src/engine/spells/interpreter/handlers/shared.ts +667 -0
  1065. package/src/engine/spells/interpreter/handlers.ts +9 -0
  1066. package/src/engine/spells/interpreter/hunter-kit.ts +202 -0
  1067. package/src/engine/spells/interpreter/index.ts +525 -0
  1068. package/src/engine/spells/interpreter/item-triggers.ts +1121 -0
  1069. package/src/engine/spells/interpreter/lift-consume-energy.ts +37 -0
  1070. package/src/engine/spells/interpreter/mage-portals.ts +37 -0
  1071. package/src/engine/spells/interpreter/materialize-charm.ts +88 -0
  1072. package/src/engine/spells/interpreter/materialize-dispel-mechanic.ts +25 -0
  1073. package/src/engine/spells/interpreter/materialize-rating-buffs.ts +81 -0
  1074. package/src/engine/spells/interpreter/materialize-speed.ts +109 -0
  1075. package/src/engine/spells/interpreter/materialize-spell-magnet.ts +22 -0
  1076. package/src/engine/spells/interpreter/opcodes.ts +789 -0
  1077. package/src/engine/spells/interpreter/paladin-kit.ts +95 -0
  1078. package/src/engine/spells/interpreter/paladin-seals.ts +499 -0
  1079. package/src/engine/spells/interpreter/priest-warlock-kit.ts +135 -0
  1080. package/src/engine/spells/interpreter/proc.ts +443 -0
  1081. package/src/engine/spells/interpreter/registry/helpers.ts +16 -0
  1082. package/src/engine/spells/interpreter/registry/index.ts +216 -0
  1083. package/src/engine/spells/interpreter/registry/packs/class-abilities.ts +304 -0
  1084. package/src/engine/spells/interpreter/registry/packs/dire-maul.ts +44 -0
  1085. package/src/engine/spells/interpreter/registry/packs/dummy-script-tail.ts +1065 -0
  1086. package/src/engine/spells/interpreter/registry/packs/ex-wave-m.ts +70 -0
  1087. package/src/engine/spells/interpreter/registry/packs/life-tap.ts +107 -0
  1088. package/src/engine/spells/interpreter/registry/packs/paladin.ts +246 -0
  1089. package/src/engine/spells/interpreter/registry/packs/raid-script-summons.ts +26 -0
  1090. package/src/engine/spells/interpreter/registry/packs/raid-threat-wipe.ts +22 -0
  1091. package/src/engine/spells/interpreter/registry/packs/shaman.ts +214 -0
  1092. package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-a.ts +269 -0
  1093. package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-b.ts +370 -0
  1094. package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-c.ts +172 -0
  1095. package/src/engine/spells/interpreter/registry/packs/si4-dummy-hunter.ts +60 -0
  1096. package/src/engine/spells/interpreter/registry/packs/si4-dummy-warrior.ts +31 -0
  1097. package/src/engine/spells/interpreter/registry/packs/si4-script-generic-a.ts +114 -0
  1098. package/src/engine/spells/interpreter/registry/packs/si4-script-generic-b.ts +249 -0
  1099. package/src/engine/spells/interpreter/registry/packs/si4-script-generic-c.ts +233 -0
  1100. package/src/engine/spells/interpreter/registry/types.ts +48 -0
  1101. package/src/engine/spells/interpreter/talent-scripted.ts +329 -0
  1102. package/src/engine/spells/interpreter/targeting.ts +90 -0
  1103. package/src/engine/spells/interpreter/types-intent.ts +1501 -0
  1104. package/src/engine/spells/interpreter/types.ts +419 -0
  1105. package/src/engine/spells/interpreter/warrior-druid-kit.ts +314 -0
  1106. package/src/engine/spells/interpreter/weapon-imbue-kit.ts +384 -0
  1107. package/src/engine/spells/interrupt.ts +217 -0
  1108. package/src/engine/spells/meleeAttackReset.ts +60 -0
  1109. package/src/engine/spells/oocTriggerSpell.ts +49 -0
  1110. package/src/engine/spells/overrideClassScripts.ts +319 -0
  1111. package/src/engine/spells/parryHaste.ts +20 -0
  1112. package/src/engine/spells/procs.ts +300 -0
  1113. package/src/engine/spells/recovery.ts +203 -0
  1114. package/src/engine/spells/scriptTargetDest.ts +76 -0
  1115. package/src/engine/spells/scriptTargets.ts +85 -0
  1116. package/src/engine/spells/singleTargetAura.ts +82 -0
  1117. package/src/engine/spells/specialCasePayloads.ts +57 -0
  1118. package/src/engine/spells/spellAttributes.ts +249 -0
  1119. package/src/engine/spells/spellBonus.ts +143 -0
  1120. package/src/engine/spells/spellModIntents.ts +419 -0
  1121. package/src/engine/spells/spellMods.ts +283 -0
  1122. package/src/engine/spells/spellRadius.ts +15 -0
  1123. package/src/engine/spells/trapObjects.ts +26 -0
  1124. package/src/json.d.ts +7 -0
  1125. package/src/persistence/memoryRepository.ts +23 -0
  1126. package/src/persistence/saveBackups.ts +162 -0
  1127. package/src/persistence/saveFile.ts +68 -0
  1128. package/src/persistence/saveRepository.ts +11 -0
  1129. package/src/persistence/saveShape.ts +668 -0
  1130. package/src/progression/leveling.ts +89 -0
  1131. package/src/progression/rest.ts +1434 -0
  1132. package/src/progression/training.ts +226 -0
  1133. package/src/progression/xpGain.ts +369 -0
  1134. package/src/quests/quests.ts +1580 -0
  1135. package/src/world/aoePull.ts +198 -0
  1136. package/src/world/areaTableDbc.ts +39 -0
  1137. package/src/world/areaTableMapIdPairs.ts +141 -0
  1138. package/src/world/bakeSlug.ts +43 -0
  1139. package/src/world/continuousDungeon.ts +1250 -0
  1140. package/src/world/dungeonBakeProjection.ts +190 -0
  1141. package/src/world/dungeonMapDensity.ts +342 -0
  1142. package/src/world/dungeonMapProjection.ts +135 -0
  1143. package/src/world/dungeonNav.ts +904 -0
  1144. package/src/world/dungeonNpcSim.ts +147 -0
  1145. package/src/world/dungeonObjectives.ts +234 -0
  1146. package/src/world/dungeonPreset.ts +35 -0
  1147. package/src/world/dungeonRoute.ts +311 -0
  1148. package/src/world/dungeonRouteGeometry.ts +732 -0
  1149. package/src/world/dungeonRouteOrder.ts +1037 -0
  1150. package/src/world/dungeonWingRooms.ts +426 -0
  1151. package/src/world/escort.ts +296 -0
  1152. package/src/world/goPoolRoll.ts +75 -0
  1153. package/src/world/instanceObjects.ts +785 -0
  1154. package/src/world/instances.ts +1572 -0
  1155. package/src/world/meetingStones.ts +56 -0
  1156. package/src/world/navmeshFloors.ts +145 -0
  1157. package/src/world/nearGrid.ts +167 -0
  1158. package/src/world/packBudget.ts +613 -0
  1159. package/src/world/rareRoll.ts +335 -0
  1160. package/src/world/routeAuthoring.ts +132 -0
  1161. package/src/world/routeEdit.ts +209 -0
  1162. package/src/world/world.ts +812 -0
  1163. package/src/world/zoneHazards.ts +47 -0
@@ -0,0 +1,1559 @@
1
+ // Character stat selectors: base/effective stats, health/power, armor, AP, resists, weapon profiles.
2
+ // Extracted from character.ts (Wave 1 S3 barrel split). Pure move — no behavior change.
3
+
4
+ import type { DataStore } from '../../data/dataStore.ts';
5
+ import type {
6
+ PrimaryStats, SpellDef, PowerType, EquipSlot, ItemDef, EnchantDef, School, Stat, RandomPropertyDef,
7
+ SpellEffect, ShapeshiftFormId,
8
+ } from '../../data/schema.ts';
9
+ import { RACE, CREATURE_TYPE } from '../../data/schema.ts';
10
+ import type { WeaponProfile } from '../../engine/combat.ts';
11
+ import type { Character, ActiveAura, EquippedValue } from './character.ts';
12
+ import { equippedEntry, equippedInstance } from '../items/itemInstance.ts';
13
+ import {
14
+ staminaToHealth,
15
+ intellectToMana,
16
+ meleeAttackPowerFromStats,
17
+ rangedAttackPowerFromStats,
18
+ shieldBlockValue,
19
+ unitModValue,
20
+ totalAttackPower,
21
+ armorFromAgiAndGear,
22
+ formHasAgiAttackBonus,
23
+ } from '../../engine/combat/formulas.ts';
24
+ import { RAGE_MAX, ENERGY_MAX, classPowerType } from '../core/ids.ts';
25
+ import {
26
+ formPowerType, isFormSpell, isNeedCastSpellAtFormApply, isStealthShapeshift, passiveAppliesInForm,
27
+ formCombatPackage, formArmorDelta,
28
+ } from './forms.ts';
29
+ import { racialSpells } from './races.ts';
30
+ import { spellLearnDependents } from './combatSkills.ts';
31
+ import { isBroken } from '../items/durability.ts';
32
+ import { gearEnchantGrant, type GearEnchantGrant } from '../professions/gearEnchant.ts';
33
+ import { talentStatFlat, talentStatPct, talentResistMods, SCHOOL_MASK_BIT } from './characterTalents.ts';
34
+ import { tempEnchantAttackPower } from '../professions/characterEnchants.ts';
35
+ import { spellExclusionGroup, exclusionIsPerCaster } from '../professions/characterEnchants.ts';
36
+ import { hasShieldEquipped } from './characterRatings.ts';
37
+ import type { SpellBook } from './spellbook.ts';
38
+ /** SpellAuraDefines.h:247 SPELL_AURA_OVERRIDE_CLASS_SCRIPTS. */
39
+ const SPELL_AURA_OVERRIDE_CLASS_SCRIPTS = 112;
40
+ import {
41
+ resolveCombatSpell,
42
+ resolveEquipBuffMods,
43
+ } from '../../engine/spells/interpreter/item-triggers.ts';
44
+ import {
45
+ auraHolderCharges, auraStackIdentity, existingStackIsStronger, stackIdentitiesExclude,
46
+ } from '../../engine/spells/auraStacking.ts';
47
+
48
+ const ZERO: PrimaryStats = { str: 0, agi: 0, sta: 0, int: 0, spi: 0 };
49
+
50
+ // The item defs currently equipped, in no particular order. Broken gear (0 durability) is skipped
51
+ // too, so it contributes no stats, armor or damage until repaired.
52
+ export function equippedItems(char: Character, ds: DataStore): ItemDef[] {
53
+ const out: ItemDef[] = [];
54
+ for (const [slot, v] of Object.entries(char.equipped) as [EquipSlot, EquippedValue | undefined][]) {
55
+ const entry = equippedEntry(v);
56
+ if (entry == null) continue;
57
+ const it = ds.item(entry);
58
+ if (it && !isBroken(char, slot, it)) out.push(it);
59
+ }
60
+ return out;
61
+ }
62
+
63
+ // Count non-broken equipped pieces per setId (same source as combat set bonuses). Shared by
64
+ // activeSetBonusSpells and item tooltips so piece progress never diverges.
65
+ export function setPieceCounts(char: Character, ds: DataStore): Map<number, number> {
66
+ const counts = new Map<number, number>();
67
+ for (const it of equippedItems(char, ds)) {
68
+ if (it.setId != null) {
69
+ counts.set(it.setId, (counts.get(it.setId) ?? 0) + 1);
70
+ }
71
+ }
72
+ return counts;
73
+ }
74
+
75
+ /** Returns the set-bonus spell ids that are active given current equipped counts (per ItemSetDef). */
76
+ export function activeSetBonusSpells(char: Character, ds: DataStore): number[] {
77
+ const counts = setPieceCounts(char, ds);
78
+ const out: number[] = [];
79
+ for (const [sid, cnt] of counts.entries()) {
80
+ const set = ds.itemSet ? ds.itemSet(sid) : undefined;
81
+ if (!set || !set.bonuses) continue;
82
+ for (const b of set.bonuses) {
83
+ if (cnt >= b.pieces && b.spell && !out.includes(b.spell)) out.push(b.spell);
84
+ }
85
+ }
86
+ return out;
87
+ }
88
+
89
+ // Active spend resource for the character. Form-aware for Druid (cat→energy, bear→rage); other classes stay
90
+ // class-static (warrior rage, rogue energy, casters mana).
91
+ export function powerType(char: Character): PowerType {
92
+ const form = (char.form ?? 0) | 0;
93
+ // FORM_STEALTH is a stance bit only — class power type (rogue energy) stays active.
94
+ if (form && !isStealthShapeshift(form)) return formPowerType(form);
95
+ return classPowerType(char.cls);
96
+ }
97
+
98
+ /** Base (create) mana for percentage form costs — player_classlevelstats.basemana. */
99
+ export function createMana(char: Character, ds: DataStore): number {
100
+ return ds.classLevel(char.cls, char.level)?.baseMana ?? 0;
101
+ }
102
+
103
+ // Base (create) health for POWER_HEALTH percentage costs — player_classlevelstats.basehp.
104
+ // Cite: Player.cpp SetCreateHealth; Spell.cpp CalculatePowerCost POWER_HEALTH (Bloodrage 20%).
105
+ export function createHealth(char: Character, ds: DataStore): number {
106
+ return ds.classLevel(char.cls, char.level)?.baseHp ?? 0;
107
+ }
108
+
109
+ // Current mana for display / form costs. When active power is mana, that's `power`; when shifted onto
110
+ // rage/energy, the parallel `mana` field holds it.
111
+ export function currentMana(char: Character): number {
112
+ if (powerType(char) === 'mana') return char.power;
113
+ return char.mana ?? 0;
114
+ }
115
+
116
+ // Max mana from intellect + flat max_mana aura buffs (SPELL_AURA_MOD_INCREASE_ENERGY / Flask of Distilled
117
+ // Wisdom). Cite: SpellAuras.cpp HandleAuraModIncreaseEnergy TOTAL_VALUE on UNIT_MOD_MANA.
118
+ export function maxMana(char: Character, ds: DataStore): number {
119
+ const cl = ds.classLevel(char.cls, char.level);
120
+ const base = cl?.baseMana ?? 0;
121
+ // Cite: StatSystem.cpp:193 — ignore classes without mana (create_power > 0).
122
+ if (base <= 0) return 0;
123
+ let mana = intellectToMana(base, effectiveStats(char, ds).int);
124
+ // SIE equip_spell kind mana (Enchant Chest - Minor Mana +5, …). Cite: aura 35; KI-551.
125
+ mana += equippedEnchantGrantAmount(char, ds, 'mana');
126
+ const now = Date.now();
127
+ for (const id of getActiveBuffIds(char, now, ds)) {
128
+ for (const eff of ds.spell(id)?.effects ?? []) {
129
+ if (eff.kind === 'buff' && eff.mod === 'max_mana') mana += eff.amount || 0;
130
+ }
131
+ }
132
+ return mana;
133
+ }
134
+
135
+ export function baseStats(char: Character, ds: DataStore): PrimaryStats {
136
+ return ds.levelStats(char.race, char.cls, char.level) ?? ZERO;
137
+ }
138
+
139
+ // Equipped, non-broken items paired with their profession enchant and random property. The one
140
+ // place gear stats are gathered, so both bonuses ride alongside the base item everywhere.
141
+ function equippedWithEnchant(char: Character, ds: DataStore): {
142
+ item: ItemDef;
143
+ enchant?: EnchantDef;
144
+ // The enchant resolved through BOTH tables, profession formula or SpellItemEnchantment.
145
+ // `enchant` above resolves only the profession side, so an armor kit or arcanum was invisible.
146
+ grant?: GearEnchantGrant;
147
+ randomProp?: RandomPropertyDef;
148
+ }[] {
149
+ const out: {
150
+ item: ItemDef;
151
+ enchant?: EnchantDef;
152
+ grant?: GearEnchantGrant;
153
+ randomProp?: RandomPropertyDef;
154
+ }[] = [];
155
+ for (const [slot, v] of Object.entries(char.equipped ?? {}) as [EquipSlot, EquippedValue | undefined][]) {
156
+ const entry = equippedEntry(v);
157
+ if (entry == null) continue;
158
+ const item = ds.item(entry);
159
+ if (!item || isBroken(char, slot, item)) continue; // broken gear (enchant + random prop) is inert
160
+ // Resolve random property from the equipped instance only (bare entry has no roll).
161
+ let randomProp: RandomPropertyDef | undefined;
162
+ const inst = equippedInstance(v);
163
+ const rpId = inst?.randomPropertyId;
164
+ if (rpId != null && rpId > 0) {
165
+ randomProp = ds.itemRandomProperty(rpId);
166
+ }
167
+ // Instance first (LOCK 2), then legacy slot map for bare entries.
168
+ const eid = inst?.permEnchantId ?? char.enchants?.[slot];
169
+ out.push({
170
+ item,
171
+ enchant: eid != null ? ds.enchant(eid) : undefined,
172
+ grant: eid != null ? gearEnchantGrant(eid, ds) : undefined,
173
+ randomProp,
174
+ });
175
+ }
176
+ return out;
177
+ }
178
+
179
+ /** Fold pre-resolved RandomPropertyDef.stats into a Stat map (MVP path — §7 / §5 P3). */
180
+ function addRandomPropStats(
181
+ out: Partial<Record<Stat, number>>,
182
+ prop: RandomPropertyDef | undefined,
183
+ ): void {
184
+ if (!prop?.stats) return;
185
+ for (const [k, v] of Object.entries(prop.stats)) {
186
+ if (typeof v !== 'number' || !Number.isFinite(v)) continue;
187
+ const stat = k as Stat;
188
+ out[stat] = (out[stat] ?? 0) + v;
189
+ }
190
+ }
191
+
192
+ /** Primary MOD_STAT buff keys — bake already folds these into prop.stats for animal suffixes. */
193
+ const PRIMARY_BUFF_MODS = new Set([
194
+ 'strength', 'agility', 'stamina', 'intellect', 'spirit',
195
+ 'str', 'agi', 'sta', 'int', 'spi',
196
+ ]);
197
+
198
+ /** Effects for a random-prop equip spell; SI-resolve when bake left effects[] hollow. */
199
+ function randomPropEquipSpellEffects(
200
+ ds: DataStore,
201
+ spellId: number,
202
+ casterLevel?: number,
203
+ ): SpellEffect[] {
204
+ return resolvedEffectsForSpell(ds.spell(spellId), casterLevel);
205
+ }
206
+
207
+ // Type-3 EQUIP_SPELL bonuses a random prop left on equipSpellIds only (of Power, of Healing,
208
+ // resists). Primaries stay on prop.stats so "of the Boar" is not double-counted.
209
+ function addRandomPropEquipSpellStats(
210
+ out: Partial<Record<Stat, number>>,
211
+ prop: RandomPropertyDef | undefined,
212
+ ds: DataStore,
213
+ casterLevel?: number,
214
+ ): void {
215
+ if (!prop?.equipSpellIds?.length) return;
216
+ for (const sid of prop.equipSpellIds) {
217
+ for (const e of randomPropEquipSpellEffects(ds, sid, casterLevel)) {
218
+ if (e.kind === 'buff' && e.mod) {
219
+ if (PRIMARY_BUFF_MODS.has(e.mod)) continue;
220
+ const amount = Number(e.amount);
221
+ if (!Number.isFinite(amount) || amount === 0) continue;
222
+ const stat = EQUIP_MOD_TO_STAT[e.mod];
223
+ if (stat) out[stat] = (out[stat] ?? 0) + amount;
224
+ } else if (e.kind === 'resist') {
225
+ const amount = Number(e.amount);
226
+ if (!Number.isFinite(amount) || amount === 0) continue;
227
+ const stat = RESIST_SCHOOL_TO_STAT[e.school];
228
+ if (stat) out[stat] = (out[stat] ?? 0) + amount;
229
+ }
230
+ }
231
+ }
232
+ }
233
+
234
+ // Sum one buff mod from random-prop type-3 equip spells on worn gear. Per spell take the max
235
+ // amount for `mod`: Attack Power 02 maps auras 99 and 124 to attack_power and summing doubles.
236
+ function randomPropEquipSpellModAmount(
237
+ char: Character,
238
+ ds: DataStore,
239
+ mod: string,
240
+ ): number {
241
+ let total = 0;
242
+ for (const { randomProp } of equippedWithEnchant(char, ds)) {
243
+ if (!randomProp?.equipSpellIds?.length) continue;
244
+ for (const sid of randomProp.equipSpellIds) {
245
+ let best = 0;
246
+ for (const e of randomPropEquipSpellEffects(ds, sid, char.level)) {
247
+ if (e.kind !== 'buff' || e.mod !== mod) continue;
248
+ const amount = Number(e.amount);
249
+ if (Number.isFinite(amount) && amount !== 0) best = Math.max(best, amount);
250
+ }
251
+ total += best;
252
+ }
253
+ }
254
+ return total;
255
+ }
256
+
257
+ // FI3 equipMods keys → paperdoll Stat. equipMods are BuffMod strings from mapSpellEffects.
258
+ // attack_power and armor are not Stat — they fold in via attackPower / effectiveArmor.
259
+ const EQUIP_MOD_TO_STAT: Readonly<Record<string, Stat>> = {
260
+ spell_power: 'spellPower',
261
+ mp5: 'mp5',
262
+ haste: 'haste',
263
+ dodge: 'dodge',
264
+ parry: 'parry',
265
+ // SPELL_AURA_MOD_BLOCK/CRIT/HIT_PERCENT + MOD_SKILL defense — equip passives.
266
+ // Cite: map-spell-effects auras 51/52/54/30; characterRatings critChance/hitChance/blockChance/defenseSkill.
267
+ block: 'block',
268
+ crit: 'crit',
269
+ hit: 'hit',
270
+ defense: 'defense',
271
+ // Aura 55 / 57 — distinct from melee hit/crit. Hollow Equip: census → spell_hit on 7 items.
272
+ spell_hit: 'spellHit',
273
+ spell_crit: 'spellCrit',
274
+ // MOD_STAT primaries (rare on equip auras; BuffMod full names)
275
+ strength: 'str',
276
+ agility: 'agi',
277
+ stamina: 'sta',
278
+ intellect: 'int',
279
+ spirit: 'spi',
280
+ // One-school MOD_DAMAGE_DONE — own Stat, not folded into spellPower.
281
+ // Cite: Unit.cpp SpellBaseDamageBonusDone ~7129 (mask ∩ school).
282
+ spell_damage_holy: 'spellHoly',
283
+ spell_damage_fire: 'spellFire',
284
+ spell_damage_nature: 'spellNature',
285
+ spell_damage_frost: 'spellFrost',
286
+ spell_damage_shadow: 'spellShadow',
287
+ spell_damage_arcane: 'spellArcane',
288
+ };
289
+
290
+ /** Non-Stat equip buff mods folded by attackPower / effectiveArmor / healingPower / blockValue. */
291
+ const EQUIP_MOD_NON_STAT = new Set([
292
+ 'attack_power',
293
+ 'armor',
294
+ 'healing_done',
295
+ 'block_value',
296
+ ]);
297
+
298
+ // Resistance school → the Stat key the paperdoll reads. `armor` is deliberately absent: an armor grant is
299
+ // added to the armor total, not to a Stat (see equippedStatBonuses).
300
+ const RESIST_SCHOOL_TO_STAT: Readonly<Record<string, Stat | undefined>> = {
301
+ holy: 'resHoly',
302
+ fire: 'resFire',
303
+ nature: 'resNature',
304
+ frost: 'resFrost',
305
+ shadow: 'resShadow',
306
+ arcane: 'resArcane',
307
+ };
308
+
309
+ /** One-school +spell damage → paperdoll Stat. Physical is attack_power, not a Stat. */
310
+ export const SPELL_SCHOOL_TO_STAT: Readonly<Record<string, Stat | undefined>> = {
311
+ holy: 'spellHoly',
312
+ fire: 'spellFire',
313
+ nature: 'spellNature',
314
+ frost: 'spellFrost',
315
+ shadow: 'spellShadow',
316
+ arcane: 'spellArcane',
317
+ };
318
+
319
+ /** Fold ItemDef.equipMods into a Stat map (skips attack_power / armor / unknown mods). */
320
+ function addEquipModsToStats(
321
+ out: Partial<Record<Stat, number>>,
322
+ item: ItemDef,
323
+ ): void {
324
+ for (const m of item.equipMods ?? []) {
325
+ if (!m || typeof m.mod !== 'string') continue;
326
+ const amount = Number(m.amount);
327
+ if (!Number.isFinite(amount) || amount === 0) continue;
328
+ const stat = EQUIP_MOD_TO_STAT[m.mod];
329
+ if (!stat) continue;
330
+ out[stat] = (out[stat] ?? 0) + amount;
331
+ }
332
+ }
333
+
334
+ // Hollow Equip: lines — the bake left equipMods empty and interpreter buff mods fill the gap.
335
+ // Skip mods already on equipMods so a partial bake does not double with resolve.
336
+ function addResolvedEquipBuffModsToStats(
337
+ out: Partial<Record<Stat, number>>,
338
+ item: ItemDef,
339
+ ds: DataStore,
340
+ casterLevel: number,
341
+ ): void {
342
+ const spells = item.equipSpells;
343
+ if (!spells?.length) return;
344
+ const have = new Set<string>();
345
+ for (const m of item.equipMods ?? []) {
346
+ if (m && typeof m.mod === 'string') have.add(m.mod);
347
+ }
348
+ const mods = resolveEquipBuffMods(spells, (id) => ds.spell(id), {
349
+ casterLevel,
350
+ unitTargetIsPlayer: true,
351
+ });
352
+ for (const m of mods) {
353
+ if (!m?.mod || have.has(m.mod)) continue;
354
+ const amount = Number(m.amount);
355
+ if (!Number.isFinite(amount) || amount === 0) continue;
356
+ const stat = EQUIP_MOD_TO_STAT[m.mod];
357
+ if (!stat) continue;
358
+ out[stat] = (out[stat] ?? 0) + amount;
359
+ }
360
+ }
361
+
362
+ // Sum one equipMods key across equipped non-broken gear, for the non-Stat paperdoll fields.
363
+ // Also folds random-prop type-3 equip spells, and resolves hollow items with empty equipMods.
364
+ function equippedEquipModAmount(char: Character, ds: DataStore, mod: string): number {
365
+ let total = 0;
366
+ for (const { item } of equippedWithEnchant(char, ds)) {
367
+ const baked = item.equipMods ?? [];
368
+ let fromBake = 0;
369
+ for (const m of baked) {
370
+ if (!m || m.mod !== mod) continue;
371
+ const amount = Number(m.amount);
372
+ if (!Number.isFinite(amount) || amount === 0) continue;
373
+ fromBake += amount;
374
+ }
375
+ total += fromBake;
376
+ // Interpreter fill when bake omitted this mod (hollow Equip: / partial bake).
377
+ if (fromBake === 0 && item.equipSpells?.length && EQUIP_MOD_NON_STAT.has(mod)) {
378
+ const mods = resolveEquipBuffMods(item.equipSpells, (id) => ds.spell(id), {
379
+ casterLevel: char.level,
380
+ unitTargetIsPlayer: true,
381
+ });
382
+ for (const m of mods) {
383
+ if (!m || m.mod !== mod) continue;
384
+ const amount = Number(m.amount);
385
+ if (!Number.isFinite(amount) || amount === 0) continue;
386
+ total += amount;
387
+ }
388
+ }
389
+ }
390
+ total += randomPropEquipSpellModAmount(char, ds, mod);
391
+ return total;
392
+ }
393
+
394
+ // Map non-stat SIE EnchantGrant kinds into paperdoll Stat keys. haste_pct takes the max: one
395
+ // enchant often stamps melee and ranged at the same amount, so gloves Minor Haste stays +1.
396
+ function addEnchantGrantsToStats(
397
+ out: Partial<Record<Stat, number>>,
398
+ grants: readonly import('../../data/schema.ts').EnchantGrant[] | undefined,
399
+ ): void {
400
+ if (!grants?.length) return;
401
+ let hastePct = 0;
402
+ for (const g of grants) {
403
+ switch (g.kind) {
404
+ case 'spell_power': {
405
+ // EnchantGrant.school is set only for a single magic bit (physical skipped at port).
406
+ // Cite: item-enchants-port.mjs aura 13; Unit.cpp SpellBaseDamageBonusDone mask ∩ school.
407
+ const schoolStat = g.school ? SPELL_SCHOOL_TO_STAT[g.school] : undefined;
408
+ if (schoolStat) out[schoolStat] = (out[schoolStat] ?? 0) + g.amount;
409
+ else out.spellPower = (out.spellPower ?? 0) + g.amount;
410
+ break;
411
+ }
412
+ case 'mp5':
413
+ out.mp5 = (out.mp5 ?? 0) + g.amount;
414
+ break;
415
+ case 'haste_pct':
416
+ hastePct = Math.max(hastePct, g.amount);
417
+ break;
418
+ case 'dodge_pct':
419
+ out.dodge = (out.dodge ?? 0) + g.amount;
420
+ break;
421
+ case 'crit_pct':
422
+ out.crit = (out.crit ?? 0) + g.amount;
423
+ break;
424
+ case 'spell_crit_pct':
425
+ out.spellCrit = (out.spellCrit ?? 0) + g.amount;
426
+ break;
427
+ case 'hit_pct':
428
+ out.hit = (out.hit ?? 0) + g.amount;
429
+ break;
430
+ case 'spell_hit_pct':
431
+ out.spellHit = (out.spellHit ?? 0) + g.amount;
432
+ break;
433
+ case 'block_pct':
434
+ out.block = (out.block ?? 0) + g.amount;
435
+ break;
436
+ default:
437
+ break;
438
+ }
439
+ }
440
+ if (hastePct) out.haste = (out.haste ?? 0) + hastePct;
441
+ }
442
+
443
+ /** Sum equip_spell grant amounts of one kind across equipped permanent enchants (SIE). */
444
+ export function equippedEnchantGrantAmount(
445
+ char: Character,
446
+ ds: DataStore,
447
+ kind: import('../../data/schema.ts').EnchantGrant['kind'],
448
+ ): number {
449
+ let total = 0;
450
+ for (const { grant } of equippedWithEnchant(char, ds)) {
451
+ if (!grant?.grants?.length) continue;
452
+ for (const g of grant.grants) {
453
+ if (g.kind !== kind) continue;
454
+ if ('amount' in g && typeof g.amount === 'number') total += g.amount;
455
+ }
456
+ }
457
+ return total;
458
+ }
459
+
460
+ // Speed a body carries with no buff running: the boots enchant (SIE 911 Minor Speed, +8 aura 31)
461
+ // and every spent talent rank whose aura is a speed one — Pursuit of Justice 26022/26023 (+4/+8
462
+ // run AND mounted), Feline Swiftness 17002/24866 (+15/+30, Stances 1 so cat form only).
463
+ //
464
+ // One row per source, never a sum. UpdateSpeed takes the MAX of aura 31, so adding these together
465
+ // would invent a stack the client never had. Cite: Unit.cpp:8352 GetMaxPositiveAuraModifier;
466
+ // item-enchants-port.mjs aura 31; Spell.cpp CheckShapeshift for the Stances gate.
467
+ export function permanentSpeedAuras(
468
+ char: Character,
469
+ ds: DataStore,
470
+ ): Array<{ aura: number; pct: number }> {
471
+ const out: Array<{ aura: number; pct: number }> = [];
472
+ for (const { grant } of equippedWithEnchant(char, ds)) {
473
+ for (const g of grant?.grants ?? []) {
474
+ if (g.kind === 'move_speed_pct' && g.amount > 0) out.push({ aura: 31, pct: g.amount });
475
+ if (g.kind === 'mount_speed_pct' && g.amount > 0) out.push({ aura: 130, pct: g.amount });
476
+ }
477
+ }
478
+ const form = ((char.form ?? 0) | 0) as ShapeshiftFormId;
479
+ for (const id of spentTalentRankSpellIds(char, ds)) {
480
+ const raw = ds.spell(id);
481
+ if (!raw) continue;
482
+ // A form-gated rank only counts in that form: Feline Swiftness is Stances 1 (cat).
483
+ if (!passiveAppliesInForm(raw, form)) continue;
484
+ for (const e of resolveCombatSpell(raw, { casterLevel: char.level }).effects ?? []) {
485
+ if (e.kind !== 'speed' || e.hostile === true) continue;
486
+ if ((e.durationMs ?? 0) !== 0 || e.pct <= 0) continue;
487
+ out.push({ aura: e.aura, pct: e.pct });
488
+ }
489
+ }
490
+ return out;
491
+ }
492
+
493
+ // MOD_STEALTH_LEVEL (154) from spent talent ranks — Feral Instinct is Stances 145, cat/bear/dire only.
494
+ // Cite: SpellAuras.cpp HandleModStealthLevel; spell_template 16947-16951.
495
+ export function talentStealthLevelBonus(char: Character, ds: DataStore): number {
496
+ const form = ((char.form ?? 0) | 0) as ShapeshiftFormId;
497
+ let n = 0;
498
+ for (const id of spentTalentRankSpellIds(char, ds)) {
499
+ const raw = ds.spell(id);
500
+ if (!raw || !passiveAppliesInForm(raw, form)) continue;
501
+ for (const e of resolvedEffectsForSpell(raw, char.level)) {
502
+ if (e.kind === 'stealth_level') n += e.amount | 0;
503
+ }
504
+ }
505
+ return n;
506
+ }
507
+
508
+ /** All stat bonuses (primary + secondary ratings/resists) from items + profession enchants + random props + equipMods. */
509
+ export function equippedStatBonuses(char: Character, ds: DataStore): Partial<Record<Stat, number>> {
510
+ const out: Partial<Record<Stat, number>> = {};
511
+ for (const { item, enchant, grant, randomProp } of equippedWithEnchant(char, ds)) {
512
+ for (const s of item.stats ?? []) out[s.stat] = (out[s.stat] ?? 0) + s.value;
513
+ // `grant` already contains the profession formula's own stats[], so prefer it and fall back to
514
+ // `enchant` only when it did not resolve. Adding both would double every profession enchant.
515
+ const enchStats = grant ? grant.stats : (enchant?.stats ?? []);
516
+ for (const s of enchStats) out[s.stat] = (out[s.stat] ?? 0) + s.value;
517
+ // Resistances an item enchant grants (fire/frost/…) — unreachable through EnchantDef.
518
+ for (const [school, amount] of Object.entries(grant?.resists ?? {})) {
519
+ const stat = RESIST_SCHOOL_TO_STAT[school];
520
+ if (stat && amount) out[stat] = (out[stat] ?? 0) + amount;
521
+ }
522
+ // SIE equip_spell non-stat grants (SP / mp5 / haste / dodge / …). KI-590.
523
+ addEnchantGrantsToStats(out, grant?.grants);
524
+ addRandomPropStats(out, randomProp);
525
+ // Type-3 random-prop equip spells left only on equipSpellIds (AP/SP/resists/…).
526
+ addRandomPropEquipSpellStats(out, randomProp, ds, char.level);
527
+ // FI3: equip-spell auras baked as equipMods (spell_power, mp5, dodge, …). KI-192.
528
+ addEquipModsToStats(out, item);
529
+ // OPEN 197: hollow Equip: lines — resolve when bake left equipMods empty / partial.
530
+ addResolvedEquipBuffModsToStats(out, item, ds, char.level);
531
+ }
532
+ return out;
533
+ }
534
+
535
+ /** Primary-stat bonuses (delegates to full aggregator for DRYness). */
536
+ export function equippedStatBonus(char: Character, ds: DataStore): PrimaryStats {
537
+ const all = equippedStatBonuses(char, ds);
538
+ return {
539
+ str: all.str ?? 0,
540
+ agi: all.agi ?? 0,
541
+ sta: all.sta ?? 0,
542
+ int: all.int ?? 0,
543
+ spi: all.spi ?? 0,
544
+ };
545
+ }
546
+
547
+ // Pet-repertoire party auras (Blood Pact, Furious Howl) contribute only while a living pet whose
548
+ // family kit has the spell is out. Player party buffs are never in petSpells and pass through.
549
+ function petPartyAuraContributes(char: Character, spellId: number, sp: SpellDef, ds: DataStore): boolean {
550
+ const isParty = sp.effects.some(
551
+ (e) => e.kind === 'buff' && e.applyTo === 'party' && !e.hostile,
552
+ );
553
+ if (!isParty) return true;
554
+ const rep = char.petSpells ?? [];
555
+ const taught = char.pet?.taughtSpells ?? [];
556
+ const inPetRepertoire =
557
+ rep.includes(spellId)
558
+ || taught.includes(spellId)
559
+ || rep.some((id) => ds.spell(id)?.name === sp.name)
560
+ || taught.some((id) => ds.spell(id)?.name === sp.name);
561
+ // Not a pet ability (Battle Shout etc.) — always contributes while on the sheet.
562
+ if (!inPetRepertoire) return true;
563
+ const pet = char.pet;
564
+ if (!pet || pet.health <= 0) return false;
565
+ const cdef = ds.creature(pet.creature);
566
+ if (!cdef) return false;
567
+ for (const id of cdef.petSpells ?? []) {
568
+ if (id === spellId) return true;
569
+ if (ds.spell(id)?.name === sp.name) return true;
570
+ }
571
+ return false;
572
+ }
573
+
574
+ // Effective primaries via UNIT_MOD layering: create from player_levelstats, TOTAL_VALUE from gear
575
+ // and flats, TOTAL_PCT from talents and all_stats_pct. Cite: Unit.cpp GetTotalStatValue.
576
+ export function effectiveStats(char: Character, ds: DataStore): PrimaryStats {
577
+ const create = baseStats(char, ds);
578
+ const e = equippedStatBonus(char, ds);
579
+ const now = Date.now();
580
+ const auraBoost: PrimaryStats = { str: 0, agi: 0, sta: 0, int: 0, spi: 0 };
581
+ // SPELL_AURA_MOD_TOTAL_STAT_PERCENTAGE (−1 = all): whole percent points on TOTAL_PCT.
582
+ // Cite: schema BuffMod 'all_stats_pct'; Blessing of Kings +10.
583
+ let auraAllStatsPct = 0;
584
+ // The per-stat twin (misc 0..4) — Demonic Embrace stamina, Spirit Tap spirit, Heart of the Wild
585
+ // intellect. Cite: schema BuffMod '<stat>_pct'; SpellAuras.cpp:3537-3568.
586
+ const auraStatPct: PrimaryStats = { str: 0, agi: 0, sta: 0, int: 0, spi: 0 };
587
+ const foldStatMod = (mod: string, amt: number): void => {
588
+ if (mod === 'strength') auraBoost.str += amt;
589
+ else if (mod === 'agility') auraBoost.agi += amt;
590
+ else if (mod === 'stamina') auraBoost.sta += amt;
591
+ else if (mod === 'intellect') auraBoost.int += amt;
592
+ else if (mod === 'spirit') auraBoost.spi += amt;
593
+ else if (mod === 'all_stats') { auraBoost.str += amt; auraBoost.agi += amt; auraBoost.sta += amt; auraBoost.int += amt; auraBoost.spi += amt; }
594
+ else if (mod === 'all_stats_pct') auraAllStatsPct += amt;
595
+ else if (mod === 'strength_pct') auraStatPct.str += amt;
596
+ else if (mod === 'agility_pct') auraStatPct.agi += amt;
597
+ else if (mod === 'stamina_pct') auraStatPct.sta += amt;
598
+ else if (mod === 'intellect_pct') auraStatPct.int += amt;
599
+ else if (mod === 'spirit_pct') auraStatPct.spi += amt;
600
+ };
601
+ // Racial passives are auras in C++ but never enter activeAuras: Expansive Mind +5% int (20591),
602
+ // The Human Spirit +5% spirit (20598). Cite: SpellAuras.cpp:3537 HandleModTotalPercentStat.
603
+ for (const eff of permanentRacialEffects(char, ds)) {
604
+ if (eff.kind === 'buff') foldStatMod(eff.mod, eff.amount || 0);
605
+ }
606
+ for (const id of getActiveBuffIds(char, now, ds)) {
607
+ const raw = ds.spell(id);
608
+ if (raw && !petPartyAuraContributes(char, id, raw, ds)) continue;
609
+ // Per-level well-fed (24870 0.25/level) is omitted from the bake. Cite: Object.cpp:2926-2934.
610
+ const fx = raw
611
+ ? (resolveCombatSpell(raw, {
612
+ casterLevel: char.level,
613
+ spellLevel: raw.spellLevel ?? 0,
614
+ spellBaseLevel: raw.baseLevel ?? 0,
615
+ spellMaxLevel: raw.maxLevel ?? 0,
616
+ }).effects ?? [])
617
+ : [];
618
+ for (const eff of fx) {
619
+ if (eff.kind === 'buff') foldStatMod(eff.mod, eff.amount || 0);
620
+ }
621
+ }
622
+ const tFlat = talentStatFlat(char, ds);
623
+ const tPct = talentStatPct(char, ds);
624
+ // unitModValue: ((BASE_VALUE + create) * BASE_PCT + TOTAL_VALUE) * TOTAL_PCT
625
+ const total = (key: keyof PrimaryStats): number => {
626
+ const totalValue = e[key] + auraBoost[key] + tFlat[key];
627
+ const totalPct = 1 + (tPct[key] || 0) / 100 + auraAllStatsPct / 100 + (auraStatPct[key] || 0) / 100;
628
+ return Math.round(unitModValue(0, create[key], 1, totalValue, totalPct));
629
+ };
630
+ return {
631
+ str: total('str'),
632
+ agi: total('agi'),
633
+ sta: total('sta'),
634
+ int: total('int'),
635
+ spi: total('spi'),
636
+ };
637
+ }
638
+
639
+ // Max health from stamina plus flat max_health aura buffs (MOD_INCREASE_HEALTH, Flask of the
640
+ // Titans). Cite: SpellAuras.cpp HandleAuraModIncreaseHealth TOTAL_VALUE on UNIT_MOD_HEALTH.
641
+ export function maxHealth(char: Character, ds: DataStore): number {
642
+ const cl = ds.classLevel(char.cls, char.level);
643
+ let hp = staminaToHealth(cl?.baseHp ?? 1, effectiveStats(char, ds).sta);
644
+ // SIE equip_spell kind health (Enchant Bracer - Minor Health +5, …). Cite: aura 34; KI-551.
645
+ hp += equippedEnchantGrantAmount(char, ds, 'health');
646
+ const now = Date.now();
647
+ for (const id of getActiveBuffIds(char, now, ds)) {
648
+ for (const eff of ds.spell(id)?.effects ?? []) {
649
+ if (eff.kind === 'buff' && eff.mod === 'max_health') hp += eff.amount || 0;
650
+ }
651
+ }
652
+ const form = ((char.form ?? 0) | 0) as ShapeshiftFormId;
653
+ hp += formCombatPackage(form).healthFlat;
654
+ // UNIT_MOD_HEALTH TOTAL_PCT last, one multiply per aura — Tauren Endurance 20550 (aura 133).
655
+ // Cite: SpellAuras.cpp:3740-3745 HandleAuraModIncreaseHealthPercent; StatSystem.cpp:174-183.
656
+ let pctMult = 1;
657
+ for (const eff of permanentRacialEffects(char, ds)) {
658
+ if (eff.kind === 'buff' && eff.mod === 'max_health_pct') pctMult *= (100 + (eff.amount || 0)) / 100;
659
+ }
660
+ return Math.round(hp * pctMult);
661
+ }
662
+
663
+ export function maxPower(char: Character, ds: DataStore): number {
664
+ const pt = powerType(char);
665
+ if (pt === 'rage') return RAGE_MAX;
666
+ // Energy is a fixed pool (POWER_ENERGY_DEFAULT = 100), not derived from intellect/basemana.
667
+ // Rogue player_classlevelstats.basemana is 0 at all levels — do not invent a mana bar.
668
+ if (pt === 'energy') return ENERGY_MAX;
669
+ return maxMana(char, ds);
670
+ }
671
+
672
+ // Is there a real class/level stats row behind maxHealth / maxMana? Without one `baseHp ?? 1`
673
+ // makes the max 1 at every level, so a clamp would pin fixture characters to 1 HP.
674
+ export function hasResourceBaseline(char: Character, ds: DataStore): boolean {
675
+ return !!ds.classLevel(char.cls, char.level);
676
+ }
677
+
678
+ // After gear or stat changes that lower a max pool, clamp current health, power and form mana.
679
+ // Cite: Unit.cpp SetMaxHealth / SetMaxPower — if (val < health) SetHealth(val).
680
+ export function clampResourcePools(char: Character, ds: DataStore): Character {
681
+ // No class/level row -> maxHealth degenerates to 1. Leave the pools alone.
682
+ if (!hasResourceBaseline(char, ds)) return char;
683
+ const mh = maxHealth(char, ds);
684
+ const mp = maxPower(char, ds);
685
+ const health = Math.min(char.health, mh);
686
+ const power = Math.min(char.power, mp);
687
+ // Parallel mana while shapeshifted (druid) is still int-derived and must clamp too.
688
+ const maxM = maxMana(char, ds);
689
+ const mana = char.mana != null ? Math.min(char.mana, maxM) : char.mana;
690
+ if (health === char.health && power === char.power && mana === char.mana) return char;
691
+ return { ...char, health, power, ...(mana !== char.mana ? { mana } : {}) };
692
+ }
693
+
694
+ // Currently active buff spell ids. Prefer `activeAuras` — when it is tracked, even empty, do NOT
695
+ // re-union legacy `activeBuffs`, or timed buffs come back after expiresAt (KI-145).
696
+ export function getActiveBuffIds(char: Character, now: number = Date.now(), ds?: DataStore): number[] {
697
+ // Source of truth: non-expired auras when the aura list is present.
698
+ // `?? []` would treat undefined as empty and drop legitimate legacy-only fixtures.
699
+ const auras = char.activeAuras;
700
+ const ids: number[] = auras !== undefined
701
+ ? auras
702
+ .filter((a) => a.expiresAt == null || a.expiresAt > now)
703
+ .map((a) => a.spellId)
704
+ : (char.activeBuffs ?? []);
705
+ // Dedup while preserving order. If ds, also collapse same-name (rank) dups.
706
+ const seen = new Set<string>();
707
+ const out: number[] = [];
708
+ for (const id of ids) {
709
+ let key = String(id);
710
+ if (ds) {
711
+ const sp = ds.spell(id);
712
+ if (sp?.name) key = sp.name;
713
+ }
714
+ if (!seen.has(key)) { seen.add(key); out.push(id); }
715
+ }
716
+ return out;
717
+ }
718
+
719
+ /** Prune expired auras in place (mutates a copy of the array). Returns cleaned list. */
720
+ export function pruneExpiredAuras(
721
+ auras: readonly ActiveAura[] | undefined,
722
+ now: number = Date.now(),
723
+ ): ActiveAura[] {
724
+ if (!auras) return [];
725
+ return auras.filter((a) => a.expiresAt == null || a.expiresAt > now);
726
+ }
727
+
728
+ // Apply or refresh an aura from a spell, setting expiresAt when durationMs > 0 and filling
729
+ // charges from procCharges. Enforces SpellGroup exclusivity and one shapeshift form at a time.
730
+ export function applyAura(
731
+ char: Character,
732
+ spellId: number,
733
+ durationMs?: number,
734
+ now: number = Date.now(),
735
+ ds?: DataStore,
736
+ opts?: { charges?: number; fromCaster?: string },
737
+ ): Character {
738
+ const auras = pruneExpiredAuras(char.activeAuras, now).slice();
739
+ const expiresAt = (durationMs && durationMs > 0) ? (now + durationMs) : undefined;
740
+
741
+ // Remove the same spell id (refresh) and any other rank of the SAME spell name, so a new rank of
742
+ // Fortitude or Blessing of Might replaces the old one instead of stacking in the buff bar.
743
+ let filtered = auras.filter((a) => a.spellId !== spellId);
744
+ if (ds) {
745
+ const nw = ds.spell(spellId);
746
+ if (nw && nw.name) {
747
+ filtered = filtered.filter((a) => {
748
+ const old = ds.spell(a.spellId);
749
+ return !old || old.name !== nw.name;
750
+ });
751
+ }
752
+ // SpellGroup EXCLUSIVE: drop other auras in the group (Mage Armors 13, flasks 4). Never add
753
+ // battle/guardian elixir exclusivity — that is TBC+, not 1.12. A UNIQUE_PER_CASTER group (7
754
+ // Blessing, 8 Aura, 10 Aspect, 12 Curse) drops only what THIS caster left, so two paladins
755
+ // leave two Blessings. Cite: Unit.cpp:4973 personal; :5021 RemoveAurasByCasterSpell.
756
+ const group = spellExclusionGroup(nw);
757
+ if (group != null) {
758
+ const perCaster = exclusionIsPerCaster(nw);
759
+ const from = opts?.fromCaster;
760
+ filtered = filtered.filter((a) => {
761
+ if (spellExclusionGroup(ds.spell(a.spellId)) !== group) return true;
762
+ return perCaster && (a.fromCaster ?? undefined) !== from;
763
+ });
764
+ }
765
+ // The pairs no spell_group covers: same class spell family and a shared aura, so the AoE and
766
+ // single-target versions replace each other. Cite: SpellStacking.cpp:135,352.
767
+ const ident = auraStackIdentity(nw);
768
+ if (ident) {
769
+ const siblings = filtered
770
+ .map((a) => auraStackIdentity(ds.spell(a.spellId)))
771
+ .filter((s) => stackIdentitiesExclude(ident, s));
772
+ // A stronger sibling refuses the apply outright, so Gift of the Wild rank 1 cannot downgrade
773
+ // Mark of the Wild rank 7. Cite: Unit.cpp:5013-5015; SpellMgr.h:1566.
774
+ if (siblings.some((s) => existingStackIsStronger(ident, s))) return char;
775
+ filtered = filtered.filter(
776
+ (a) => !stackIdentitiesExclude(ident, auraStackIdentity(ds.spell(a.spellId))),
777
+ );
778
+ }
779
+ // Shapeshift forms/stances: only one form aura (HandleAuraModShapeshift is modal).
780
+ if (nw && isFormSpell(nw)) {
781
+ filtered = filtered.filter((a) => {
782
+ const old = ds.spell(a.spellId);
783
+ return !old || !isFormSpell(old);
784
+ });
785
+ }
786
+ }
787
+ let charges = (opts?.charges != null && opts.charges > 0) ? opts.charges : undefined;
788
+ if (charges == null && ds) {
789
+ const n = auraHolderCharges(ds.spell(spellId));
790
+ if (n > 0) charges = n;
791
+ }
792
+ filtered.push({
793
+ spellId,
794
+ expiresAt,
795
+ ...(charges != null ? { charges } : {}),
796
+ ...(opts?.fromCaster != null ? { fromCaster: opts.fromCaster } : {}),
797
+ });
798
+
799
+ return {
800
+ ...char,
801
+ activeAuras: filtered,
802
+ // keep legacy in sync for now
803
+ activeBuffs: getActiveBuffIds({ ...char, activeAuras: filtered }, now, ds),
804
+ };
805
+ }
806
+
807
+ /** Sum a persistent-buff modifier ('armor' | 'attack_power') across active buffs/auras. */
808
+ export function buffMod(char: Character, ds: DataStore, mod: 'armor' | 'attack_power' | 'ranged_attack_power' | 'attack_power_pct'): number {
809
+ let total = 0;
810
+ for (const id of getActiveBuffIds(char, undefined, ds)) {
811
+ for (const eff of ds.spell(id)?.effects ?? []) {
812
+ if (eff.kind === 'buff' && eff.mod === mod) total += eff.amount;
813
+ }
814
+ }
815
+ return total;
816
+ }
817
+
818
+ // BASE bucket of UNIT_MOD_ARMOR: item, enchant and equipMod armor times every BASE_PCT aura (142).
819
+ // agi×2, the intellect term and flat armor buffs are TOTAL_VALUE and deliberately not here.
820
+ export function baseArmorValue(char: Character, ds: DataStore): number {
821
+ let gear = 0;
822
+ for (const { item, enchant, grant, randomProp } of equippedWithEnchant(char, ds)) {
823
+ // `grant` already includes the profession formula's armor, so prefer it over `enchant`
824
+ // rather than adding both — this is where an armor kit's +24 finally lands.
825
+ const enchArmor = grant ? grant.armor : (enchant?.armor ?? 0);
826
+ gear += (item.armor ?? 0) + enchArmor + (randomProp?.armor ?? 0);
827
+ }
828
+ // FI3 equipMods armor + hollow resolveEquipBuffMods — additive with item.armor.
829
+ gear += equippedEquipModAmount(char, ds, 'armor');
830
+ return gear * passiveBaseArmorPctMult(char, ds) * activeBaseArmorPctMult(char, ds);
831
+ }
832
+
833
+ // Effective armor = total AGI × 2 + base armor + armor buffs (no double agi×2). source: StatSystem.cpp
834
+ // UpdateArmor; Player.cpp InitStatsForLevel SetArmor(createAgi*2)
835
+ export function effectiveArmor(char: Character, ds: DataStore): number {
836
+ const stats = effectiveStats(char, ds);
837
+ // MOD_RESISTANCE_OF_STAT_PERCENT (182): armor also gains a percent of INTELLECT (Arcane
838
+ // Resilience 50%). SetArmor takes an int32, so the fraction truncates. Cite: StatSystem.cpp:125.
839
+ const fromInt = Math.trunc(stats.int * (armorFromIntellectPct(char, ds) / 100));
840
+ const base = baseArmorValue(char, ds);
841
+ const form = ((char.form ?? 0) | 0) as ShapeshiftFormId;
842
+ return armorFromAgiAndGear(stats.agi, base + buffMod(char, ds, 'armor')) + fromInt + formArmorDelta(base, form);
843
+ }
844
+
845
+ // Melee attack power: the class formula as BASE_VALUE plus buff TOTAL_VALUE via totalAttackPower.
846
+ // The form AGI bonus comes off SpellShapeshiftForm.dbc flags1, only Cat in 1.12.1.
847
+ export function attackPower(char: Character, ds: DataStore): number {
848
+ // Rockbiter TYPE_EQUIP_SPELL AP lands via the equip-spell aura in buffMod; tempEnchantAttackPower
849
+ // only covers cache-only rows (no equipSpellId). Cite: SpellAuras.cpp HandleAuraModAttackPower; KI-1039.
850
+ const st = effectiveStats(char, ds);
851
+ const form = ((char.form ?? 0) | 0) as ShapeshiftFormId;
852
+ const formAgi = formHasAgiAttackBonus(form);
853
+ const base = meleeAttackPowerFromStats(char.cls, char.level, st.str, st.agi, formAgi);
854
+ // Buff taxonomy uses spell effect mod 'attack_power' (Battle Shout etc.).
855
+ // Gear flat AP lives on ItemDef.equipMods (FI3), not Stat — fold here. KI-192.
856
+ const gearAp = equippedEquipModAmount(char, ds, 'attack_power')
857
+ + equippedEnchantGrantAmount(char, ds, 'attack_power');
858
+ const pkg = formCombatPackage(form);
859
+ let formAp = pkg.attackPowerFlat;
860
+ if (pkg.attackPowerFlat > 0) {
861
+ const psRank = (char.talents?.[803] as number) | 0;
862
+ const amt = psRank === 3 ? 150 : psRank === 2 ? 100 : psRank === 1 ? 50 : 0;
863
+ if (amt > 0) formAp += Math.trunc((Math.max(0, char.level | 0) * amt) / 100);
864
+ }
865
+ const apFlat = buffMod(char, ds, 'attack_power') + tempEnchantAttackPower(char) + gearAp + formAp;
866
+ // SPELL_AURA_MOD_ATTACK_POWER_PCT (166) lands on the TOTAL_PCT slot, after the flats.
867
+ // Cite: SpellAuras.cpp:4009 HandleStatModifier(UNIT_MOD_ATTACK_POWER, TOTAL_PCT).
868
+ const apPct = 1 + buffMod(char, ds, 'attack_power_pct') / 100;
869
+ return totalAttackPower(base, apFlat, 1, apPct);
870
+ }
871
+
872
+ // Ranged attack power (hunter/rogue/warrior paths; casters default agi-10). // source:
873
+ // StatSystem.cpp:Player::UpdateAttackPowerAndDamage (ranged==true)
874
+ export function rangedAttackPower(char: Character, ds: DataStore): number {
875
+ const st = effectiveStats(char, ds);
876
+ const base = rangedAttackPowerFromStats(char.cls, char.level, st.agi, (char.form ?? 0) | 0);
877
+ // Aura 124 is its own mod now (Aspect of the Hawk, Trueshot Aura); it used to bake as the melee
878
+ // `attack_power` and this line summed nothing, so a hunter's own aspect bought 0 ranged ap.
879
+ const apFlat = buffMod(char, ds, 'ranged_attack_power')
880
+ + equippedEquipModAmount(char, ds, 'ranged_attack_power')
881
+ + equippedEnchantGrantAmount(char, ds, 'ranged_attack_power');
882
+ return totalAttackPower(base, apFlat, 1, 1);
883
+ }
884
+
885
+ // Shield block value, the flat absorb amount, separate from block chance. The aura flat is
886
+ // equipMods block_value (aura 158) plus item stats. Cite: Player.cpp GetShieldBlockValue.
887
+ export function blockValue(char: Character, ds: DataStore): number {
888
+ if (!hasShieldEquipped(char, ds)) return 0;
889
+ // The shield's own item_template.block is FLAT_MOD too, and it is the dominant term at 60
890
+ // (Grand Marshal's Aegis 55 vs str 200 → 9). Cite: Player.cpp:7086-7087 _ApplyItemBonuses.
891
+ const ohEntry = equippedEntry(char.equipped.offhand);
892
+ const shieldBlock = (ohEntry != null ? ds.item(ohEntry)?.block : undefined) ?? 0;
893
+ const auraFlat = shieldBlock
894
+ + equippedEquipModAmount(char, ds, 'block_value')
895
+ + equippedEnchantGrantAmount(char, ds, 'block_value');
896
+ // PCT_MOD from passive aura 150 (Shield Specialization). Cite: Player.cpp:4877.
897
+ return shieldBlockValue(effectiveStats(char, ds).str, auraFlat, passiveBlockValuePctMult(char, ds));
898
+ }
899
+
900
+ // Flat +healing done from equip-spell auras (SPELL_AURA_MOD_HEALING_DONE 135). Classic separates
901
+ // this from spell damage. Cite: SpellAuras.cpp:3528 HandleModHealingDone.
902
+ export function healingPower(char: Character, ds: DataStore): number {
903
+ // Item equipMods + SIE healing_power grants (Enchant Weapon - Healing Power, …). KI-590.
904
+ return equippedEquipModAmount(char, ds, 'healing_done')
905
+ + equippedEnchantGrantAmount(char, ds, 'healing_power');
906
+ }
907
+
908
+ // ---- Creature-type "versus" + spell penetration (equip / OOC buff consumers) ----
909
+ // Cite: Unit.cpp MeleeDamageBonusDone :7471; GetResistancePenetration :3890; Unit.h:1432.
910
+
911
+ /** One versus-bonus row: amount applies when (mask & targetTypeMask) != 0. */
912
+ export type VersusBonus = { creatureTypeMask: number; amount: number };
913
+
914
+ // Target creature type → CMaNGOS type mask bit. Cite: Unit.h GetCreatureTypeMask: type>=1 ? (1<<(type-1)) :
915
+ // 0.
916
+ export function creatureTypeToMask(creatureType: number): number {
917
+ return creatureType >= 1 ? (1 << (creatureType - 1)) : 0;
918
+ }
919
+
920
+ // Sum versus amounts whose mask overlaps the target's type mask. Cite: Unit.cpp
921
+ // GetTotalAuraModifierByMiscMask :4527–4541 (miscvalue & misc_mask).
922
+ export function sumVersusForCreatureType(
923
+ bonuses: readonly VersusBonus[] | undefined,
924
+ creatureType: number,
925
+ ): number {
926
+ if (!bonuses?.length) return 0;
927
+ const typeMask = creatureTypeToMask(creatureType);
928
+ if (!typeMask) return 0;
929
+ let sum = 0;
930
+ for (const b of bonuses) {
931
+ if (((b.creatureTypeMask ?? 0) | 0) & typeMask) sum += b.amount;
932
+ }
933
+ return sum;
934
+ }
935
+
936
+ /** Resolve equip-spell / OOC buff effects (bake or SI5 interpret). */
937
+ function resolvedEffectsForSpell(raw: SpellDef | undefined | null, casterLevel?: number): SpellEffect[] {
938
+ if (!raw) return [];
939
+ if (raw.effects?.length) return raw.effects;
940
+ return resolveCombatSpell(raw, casterLevel != null ? { casterLevel } : {}).effects ?? [];
941
+ }
942
+
943
+ // Permanent buff effects of the race's own passives, for selectors that read no aura list. Timed and
944
+ // activated racials (Blood Fury, Berserking) are excluded — same guards as forEachPassiveBuffEffect.
945
+ export function permanentRacialEffects(char: Character, ds: DataStore): SpellEffect[] {
946
+ const out: SpellEffect[] = [];
947
+ for (const s of racialSpells(ds, char.race)) {
948
+ if (!s.passive || s.stances) continue;
949
+ for (const eff of resolvedEffectsForSpell(s, char.level)) {
950
+ if (eff.kind !== 'buff' || eff.hostile === true) continue;
951
+ if ((eff.durationMs ?? 0) > 0) continue;
952
+ out.push(eff);
953
+ }
954
+ }
955
+ return out;
956
+ }
957
+
958
+ // Walk equipSpells, racial passives and active OOC buffs, yielding every buff effect matching
959
+ // `mod` — for mask-bearing auras that cannot collapse into paperdoll equipMods.
960
+ function collectBuffEffectsByMod(
961
+ char: Character,
962
+ ds: DataStore,
963
+ mod: string,
964
+ includeTalents = false,
965
+ ): SpellEffect[] {
966
+ const out: SpellEffect[] = [];
967
+ const seen = new Set<number>();
968
+ const take = (spellId: number): void => {
969
+ const id = spellId | 0;
970
+ if (!id || seen.has(id)) return;
971
+ seen.add(id);
972
+ const raw = ds.spell(id);
973
+ let found = false;
974
+ for (const e of resolvedEffectsForSpell(raw, char.level)) {
975
+ if (e.kind === 'buff' && e.mod === mod) { out.push(e); found = true; }
976
+ }
977
+ if (found || !raw?.rawEffects?.length) return;
978
+ // A PARTIALLY baked spell is the trap: the ETL mapper and the interpreter are two producers, so
979
+ // a row the ETL never learned reads as no such effect. Ask the interpreter before answering no.
980
+ for (const e of resolveCombatSpell(raw, { casterLevel: char.level }).effects ?? []) {
981
+ if (e.kind === 'buff' && e.mod === mod) out.push(e);
982
+ }
983
+ };
984
+ for (const item of equippedItems(char, ds)) {
985
+ for (const sid of item.equipSpells ?? []) take(sid);
986
+ }
987
+ // Random-prop PROP slots: type-3 equip spells (beast slaying, versus AP, …).
988
+ for (const { randomProp } of equippedWithEnchant(char, ds)) {
989
+ for (const sid of randomProp?.equipSpellIds ?? []) take(sid);
990
+ }
991
+ // Permanent racial passives (Beast Slaying, …) — not equip and not activeAuras.
992
+ for (const s of racialSpells(ds, char.race)) take(s.id);
993
+ for (const id of getActiveBuffIds(char, undefined, ds)) take(id);
994
+ // Spent talent ranks are permanent passives too but are in neither equipSpells nor activeAuras.
995
+ // Opt-in per mod: any mod characterTalents.ts already sums would double-apply here.
996
+ if (includeTalents) for (const id of spentTalentRankSpellIds(char, ds)) take(id);
997
+ return out;
998
+ }
999
+
1000
+ // SPELL_AURA_OVERRIDE_CLASS_SCRIPTS (112) misc values the sheet carries. A spent talent is an aura
1001
+ // in C++ but neither a buff nor a spellbook row here, so `buffActive` alone finds nothing.
1002
+ export function overrideClassScriptIds(
1003
+ char: Character,
1004
+ ds: DataStore,
1005
+ sb?: SpellBook | null,
1006
+ ): number[] {
1007
+ const out = new Set<number>();
1008
+ const take = (spellId: number): void => {
1009
+ const raw = ds.spell(spellId | 0);
1010
+ for (const e of raw?.rawEffects ?? []) {
1011
+ if (((e as { auraName?: number }).auraName ?? 0) !== SPELL_AURA_OVERRIDE_CLASS_SCRIPTS) continue;
1012
+ const misc = ((e as { miscValue?: number }).miscValue ?? 0) | 0;
1013
+ if (misc) out.add(misc);
1014
+ }
1015
+ };
1016
+ for (const id of spentTalentRankSpellIds(char, ds)) take(id);
1017
+ for (const id of sb?.known ?? []) if (ds.spell(id)?.passive) take(id);
1018
+ return [...out];
1019
+ }
1020
+
1021
+ // Rank spell id per talent point spent, plus its spell_learn_spell dependents (24866 -> 24864, the
1022
+ // dodge half of Feline Swiftness). Cite: Player.cpp:3412 addSpell SpellLearnSpellMapBounds.
1023
+ export function spentTalentRankSpellIds(char: Character, ds: DataStore): number[] {
1024
+ const out: number[] = [];
1025
+ const meta = ds.combatSkillMeta?.();
1026
+ for (const [tidStr, rank] of Object.entries(char.talents ?? {})) {
1027
+ const tid = Number(tidStr);
1028
+ const r = (rank as number) | 0;
1029
+ if (!tid || r <= 0) continue;
1030
+ const tal = ds.talent(tid);
1031
+ if (!tal?.ranks?.length) continue;
1032
+ const spellId = tal.ranks[Math.min(r, tal.ranks.length) - 1]?.spellId | 0;
1033
+ if (!spellId) continue;
1034
+ out.push(spellId);
1035
+ for (const dep of spellLearnDependents(spellId, meta)) if (dep && !out.includes(dep)) out.push(dep);
1036
+ }
1037
+ return out;
1038
+ }
1039
+
1040
+ // Every permanent passive the character owns — racials, known passives, spent talent ranks — as ONE
1041
+ // list for every GetTotalAuraModifier fold. Per-source scans are how 59 talents shipped dead.
1042
+ function passiveSpells(
1043
+ char: Character,
1044
+ ds: DataStore,
1045
+ knownSpellIds?: readonly number[],
1046
+ ): SpellDef[] {
1047
+ const seen = new Set<number>();
1048
+ const out: SpellDef[] = [];
1049
+ const take = (id: number): void => {
1050
+ if (!id || seen.has(id)) return;
1051
+ seen.add(id);
1052
+ const raw = ds.spell(id);
1053
+ // A Stances-gated passive is live only inside its form (Sharpened Claws, Defiance), never dropped.
1054
+ // The form-apply half (24864) is DO_NOT_DISPLAY rather than PASSIVE, so ask the form gate too.
1055
+ if (!raw) return;
1056
+ const form = ((char.form ?? 0) | 0) as ShapeshiftFormId;
1057
+ if (!raw.passive && !isNeedCastSpellAtFormApply(raw, form)) return;
1058
+ if (!passiveAppliesInForm(raw, form)) return;
1059
+ out.push(raw);
1060
+ };
1061
+ for (const s of racialSpells(ds, char.race)) take(s.id);
1062
+ if (knownSpellIds) for (const id of knownSpellIds) take(id | 0);
1063
+ for (const id of spentTalentRankSpellIds(char, ds)) take(id);
1064
+ return out;
1065
+ }
1066
+
1067
+ /** Permanent (never timed — those seed combat via liveBuffs) buff effects of `mod` across passives. */
1068
+ function forEachPassiveBuffEffect(
1069
+ char: Character,
1070
+ ds: DataStore,
1071
+ mod: string,
1072
+ knownSpellIds: readonly number[] | undefined,
1073
+ cb: (amount: number) => void,
1074
+ ): void {
1075
+ for (const raw of passiveSpells(char, ds, knownSpellIds)) {
1076
+ let found = false;
1077
+ const fold = (effs: readonly SpellEffect[] | undefined): void => {
1078
+ for (const e of effs ?? []) {
1079
+ if (e.kind !== 'buff' || e.mod !== mod || e.hostile === true) continue;
1080
+ if ((e.durationMs ?? 0) > 0) continue;
1081
+ const amt = Number(e.amount);
1082
+ if (!Number.isFinite(amt) || !amt) continue;
1083
+ found = true;
1084
+ cb(amt);
1085
+ }
1086
+ };
1087
+ fold(raw.effects);
1088
+ // Partially baked rows drop effects the ETL never wrote — ask the interpreter too.
1089
+ // Cite: collectBuffEffectsByMod trap above (Heightened Senses).
1090
+ if (!found && raw.rawEffects?.length) {
1091
+ fold(resolveCombatSpell(raw, { casterLevel: char.level, unitTargetIsPlayer: true }).effects);
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ // GetTotalAuraModifier(auraType) over permanent passives, in percentage points. Cite:
1097
+ // StatSystem.cpp:397/:454/:490 Update{Block,Parry,Dodge}Percentage — talents are auras like any other.
1098
+ export function passiveBuffModPoints(
1099
+ char: Character,
1100
+ ds: DataStore,
1101
+ mod: string,
1102
+ knownSpellIds?: readonly number[],
1103
+ ): number {
1104
+ let pts = 0;
1105
+ forEachPassiveBuffEffect(char, ds, mod, knownSpellIds, (amt) => { pts += amt; });
1106
+ return pts;
1107
+ }
1108
+
1109
+ // SHIELD_BLOCK_VALUE PCT_MOD from passive aura 150 rows, multiplicative per aura.
1110
+ // Cite: Player.cpp:4877 GetShieldBlockValue; SpellAuras.cpp:4428 HandleShieldBlockValue.
1111
+ export function passiveBlockValuePctMult(
1112
+ char: Character,
1113
+ ds: DataStore,
1114
+ knownSpellIds?: readonly number[],
1115
+ ): number {
1116
+ let mult = 1;
1117
+ forEachPassiveBuffEffect(char, ds, 'block_value_pct', knownSpellIds, (amt) => {
1118
+ mult *= (100 + amt) / 100;
1119
+ });
1120
+ return mult;
1121
+ }
1122
+
1123
+ /** SpellAuraDefines.h:277 SPELL_AURA_MOD_BASE_RESISTANCE_PCT (Toughness / Thick Hide). */
1124
+ const SPELL_AURA_MOD_BASE_RESISTANCE_PCT = 142;
1125
+
1126
+ // UNIT_MOD_ARMOR BASE_PCT product over `spells`, off the resolved `armor_base_pct` surface. Aura
1127
+ // 142 is a PCT_MOD, so the auras multiply rather than sum. Cite: SpellAuras.cpp:3425.
1128
+ function baseArmorPctMultOf(
1129
+ char: Character,
1130
+ ds: DataStore,
1131
+ spells: Iterable<SpellDef>,
1132
+ ): number {
1133
+ let mult = 1;
1134
+ for (const raw of spells) {
1135
+ // 48 spells in the whole game carry aura 142 — gate on that before resolving anything.
1136
+ if (!raw.rawEffects?.some((r) => ((r.auraName ?? 0) | 0) === SPELL_AURA_MOD_BASE_RESISTANCE_PCT)) continue;
1137
+ const baked = (raw.effects ?? []).filter((e) => e.kind === 'buff' && e.mod === 'armor_base_pct');
1138
+ // Partially baked rows (every talent rank: effects[] empty) — ask the interpreter.
1139
+ const effs = baked.length
1140
+ ? baked
1141
+ : resolveCombatSpell(raw, { casterLevel: char.level, unitTargetIsPlayer: true }).effects;
1142
+ for (const e of effs) {
1143
+ if (e.kind !== 'buff' || e.mod !== 'armor_base_pct' || e.hostile === true) continue;
1144
+ const amt = Number(e.amount);
1145
+ if (Number.isFinite(amt) && amt) mult *= (100 + amt) / 100;
1146
+ }
1147
+ }
1148
+ return mult;
1149
+ }
1150
+
1151
+ /** UNIT_MOD_ARMOR BASE_PCT from passive aura 142 rows (Toughness, Thick Hide). */
1152
+ export function passiveBaseArmorPctMult(
1153
+ char: Character,
1154
+ ds: DataStore,
1155
+ knownSpellIds?: readonly number[],
1156
+ ): number {
1157
+ return baseArmorPctMultOf(char, ds, passiveSpells(char, ds, knownSpellIds));
1158
+ }
1159
+
1160
+ // UNIT_MOD_ARMOR BASE_PCT from TIMED aura 142 rows (Lay on Hands 20233, pet armor 27208).
1161
+ // Passives are excluded — passiveBaseArmorPctMult owns those, and both multiply the same base.
1162
+ export function activeBaseArmorPctMult(char: Character, ds: DataStore): number {
1163
+ const spells: SpellDef[] = [];
1164
+ for (const id of getActiveBuffIds(char, undefined, ds)) {
1165
+ const raw = ds.spell(id);
1166
+ if (raw && !raw.passive) spells.push(raw);
1167
+ }
1168
+ return baseArmorPctMultOf(char, ds, spells);
1169
+ }
1170
+
1171
+ /** MOD_MELEE_ATTACK_POWER_VERSUS (102) rows from equip + OOC buffs. */
1172
+ export function attackPowerVersusBonuses(char: Character, ds: DataStore): VersusBonus[] {
1173
+ const out: VersusBonus[] = [];
1174
+ for (const e of collectBuffEffectsByMod(char, ds, 'attack_power_versus')) {
1175
+ if (e.kind !== 'buff') continue;
1176
+ const mask = (e.creatureTypeMask ?? 0) | 0;
1177
+ const amount = Number(e.amount);
1178
+ if (!mask || !Number.isFinite(amount) || !amount) continue;
1179
+ out.push({ creatureTypeMask: mask, amount });
1180
+ }
1181
+ return out;
1182
+ }
1183
+
1184
+ /** MOD_RANGED_ATTACK_POWER_VERSUS (131) rows from equip + OOC buffs. */
1185
+ export function rangedAttackPowerVersusBonuses(char: Character, ds: DataStore): VersusBonus[] {
1186
+ const out: VersusBonus[] = [];
1187
+ for (const e of collectBuffEffectsByMod(char, ds, 'ranged_attack_power_versus')) {
1188
+ if (e.kind !== 'buff') continue;
1189
+ const mask = (e.creatureTypeMask ?? 0) | 0;
1190
+ const amount = Number(e.amount);
1191
+ if (!mask || !Number.isFinite(amount) || !amount) continue;
1192
+ out.push({ creatureTypeMask: mask, amount });
1193
+ }
1194
+ return out;
1195
+ }
1196
+
1197
+ /** MOD_FLAT_SPELL_DAMAGE_VERSUS (180) rows from equip + OOC buffs. */
1198
+ export function spellDamageVersusBonuses(char: Character, ds: DataStore): VersusBonus[] {
1199
+ const out: VersusBonus[] = [];
1200
+ for (const e of collectBuffEffectsByMod(char, ds, 'spell_damage_versus')) {
1201
+ if (e.kind !== 'buff') continue;
1202
+ const mask = (e.creatureTypeMask ?? 0) | 0;
1203
+ const amount = Number(e.amount);
1204
+ if (!mask || !Number.isFinite(amount) || !amount) continue;
1205
+ out.push({ creatureTypeMask: mask, amount });
1206
+ }
1207
+ return out;
1208
+ }
1209
+
1210
+ /** MOD_DAMAGE_DONE_VERSUS (168) percent rows from equip + OOC buffs. */
1211
+ export function damageDoneVersusPctBonuses(char: Character, ds: DataStore): VersusBonus[] {
1212
+ const out: VersusBonus[] = [];
1213
+ for (const e of collectBuffEffectsByMod(char, ds, 'damage_done_versus_pct', true)) {
1214
+ if (e.kind !== 'buff') continue;
1215
+ const mask = (e.creatureTypeMask ?? 0) | 0;
1216
+ const amount = Number(e.amount);
1217
+ if (!mask || !Number.isFinite(amount) || !amount) continue;
1218
+ out.push({ creatureTypeMask: mask, amount });
1219
+ }
1220
+ return out;
1221
+ }
1222
+
1223
+ // MOD_CRIT_PERCENT_VERSUS (169) rows — whole percent added to the CRIT MULTIPLIER when the target
1224
+ // creature type matches. Monster and Humanoid Slaying are the only vanilla sources, both talents.
1225
+ export function critDamageVersusPctBonuses(char: Character, ds: DataStore): VersusBonus[] {
1226
+ const out: VersusBonus[] = [];
1227
+ for (const e of collectBuffEffectsByMod(char, ds, 'crit_damage_versus_pct', true)) {
1228
+ if (e.kind !== 'buff') continue;
1229
+ const mask = (e.creatureTypeMask ?? 0) | 0;
1230
+ const amount = Number(e.amount);
1231
+ if (!mask || !Number.isFinite(amount) || !amount) continue;
1232
+ out.push({ creatureTypeMask: mask, amount });
1233
+ }
1234
+ return out;
1235
+ }
1236
+
1237
+ // SPELL_AURA_MOD_OFFHAND_DAMAGE_PCT (122) total, whole percent. Cite: SpellAuras.cpp:4175-4184
1238
+ // UNIT_MOD_DAMAGE_OFFHAND TOTAL_PCT.
1239
+ export function offhandDamagePctBonus(char: Character, ds: DataStore): number {
1240
+ let sum = 0;
1241
+ for (const e of collectBuffEffectsByMod(char, ds, 'offhand_damage_pct', true)) {
1242
+ if (e.kind !== 'buff') continue;
1243
+ const amount = Number(e.amount);
1244
+ if (Number.isFinite(amount)) sum += amount;
1245
+ }
1246
+ return sum;
1247
+ }
1248
+
1249
+ // SPELL_AURA_MOD_RESISTANCE_OF_STAT_PERCENT (182) total, whole percent of INTELLECT into armor. Cite:
1250
+ // StatSystem.cpp:125-138 Player::UpdateArmor.
1251
+ export function armorFromIntellectPct(char: Character, ds: DataStore): number {
1252
+ let sum = 0;
1253
+ for (const e of collectBuffEffectsByMod(char, ds, 'armor_from_intellect_pct', true)) {
1254
+ if (e.kind !== 'buff') continue;
1255
+ const amount = Number(e.amount);
1256
+ if (Number.isFinite(amount)) sum += amount;
1257
+ }
1258
+ return sum;
1259
+ }
1260
+
1261
+ // Victim-side attacker-chance auras (185-188), summed as signed percentage points. A defensive
1262
+ // talent ships a negative amount and the attacker's roll subtracts it. Cite: Unit.cpp:3525.
1263
+ export function attackerChanceBonus(
1264
+ char: Character,
1265
+ ds: DataStore,
1266
+ mod: 'attacker_melee_crit' | 'attacker_ranged_crit' | 'attacker_ranged_hit',
1267
+ ): number {
1268
+ let sum = 0;
1269
+ for (const e of collectBuffEffectsByMod(char, ds, mod, true)) {
1270
+ if (e.kind !== 'buff') continue;
1271
+ const amount = Number(e.amount);
1272
+ if (Number.isFinite(amount)) sum += amount;
1273
+ }
1274
+ return sum;
1275
+ }
1276
+
1277
+ // MOD_ATTACKER_SPELL_HIT_CHANCE (186) per school. The aura is mask-bearing, so a Heightened
1278
+ // Senses row (mask 126, every magic school) only touches magic spells. Cite: Unit.cpp:3873.
1279
+ export function attackerSpellHitBySchool(
1280
+ char: Character,
1281
+ ds: DataStore,
1282
+ ): Partial<Record<School, number>> {
1283
+ const out: Partial<Record<School, number>> = {};
1284
+ for (const e of collectBuffEffectsByMod(char, ds, 'attacker_spell_hit', true)) {
1285
+ if (e.kind !== 'buff') continue;
1286
+ const mask = ((e as { schoolMask?: number }).schoolMask ?? 0) | 0;
1287
+ const amount = Number(e.amount);
1288
+ if (!mask || !Number.isFinite(amount) || !amount) continue;
1289
+ for (const [sch, bit] of Object.entries(SCHOOL_MASK_BIT) as [School, number][]) {
1290
+ if (mask & bit) out[sch] = (out[sch] ?? 0) + amount;
1291
+ }
1292
+ }
1293
+ return out;
1294
+ }
1295
+
1296
+ // SPELL_AURA_MOD_DAMAGE_DONE_CREATURE (59) flats from permanent enchant grants (Beastslayer / Elemental
1297
+ // Slayer). Cite: Unit.cpp:7465 MeleeDamageBonusDone DoneFlat; :7061 SpellDamageBonusDone; KI-1043.
1298
+ export function damageDoneCreatureBonuses(char: Character, ds: DataStore): VersusBonus[] {
1299
+ const out: VersusBonus[] = [];
1300
+ for (const { grant } of equippedWithEnchant(char, ds)) {
1301
+ if (!grant?.grants?.length) continue;
1302
+ for (const g of grant.grants) {
1303
+ if (g.kind !== 'damage_done_creature') continue;
1304
+ const mask = (g.creatureTypeMask ?? 0) | 0;
1305
+ const amount = Number(g.amount);
1306
+ if (!mask || !Number.isFinite(amount) || !amount) continue;
1307
+ out.push({ creatureTypeMask: mask, amount });
1308
+ }
1309
+ }
1310
+ return out;
1311
+ }
1312
+
1313
+ // Permanent SIE TYPE_COMBAT_SPELL → gearProcs (CastItemCombatSpell). PPM/chance from the grant only — never
1314
+ // invented. Cite: Player.cpp:7403–7447; KI-1044.
1315
+ export function permanentEnchantCombatProcs(
1316
+ char: Character,
1317
+ ds: DataStore,
1318
+ ): Array<{ spell: number; trigger: number; chancePct?: number; ppm?: number }> {
1319
+ const out: Array<{ spell: number; trigger: number; chancePct?: number; ppm?: number }> = [];
1320
+ const seen = new Set<number>();
1321
+ for (const { grant } of equippedWithEnchant(char, ds)) {
1322
+ const c = grant?.combat;
1323
+ if (!c?.spellId || seen.has(c.spellId)) continue;
1324
+ seen.add(c.spellId);
1325
+ const row: { spell: number; trigger: number; chancePct?: number; ppm?: number } = {
1326
+ spell: c.spellId,
1327
+ trigger: 2, // ITEM_SPELLTRIGGER_CHANCE_ON_HIT
1328
+ };
1329
+ if (c.chancePct != null && c.chancePct > 0) row.chancePct = c.chancePct;
1330
+ if (c.ppm != null && c.ppm > 0) row.ppm = c.ppm;
1331
+ out.push(row);
1332
+ }
1333
+ return out;
1334
+ }
1335
+
1336
+ // Permanent SIE equip_spell proc_spell (chest Absorption) → startProcTriggers rows. Holder =
1337
+ // equip_spell.spellId; trigger = grant.spellId. Cite: aura 42; KI-1045.
1338
+ export function permanentEnchantProcTriggers(
1339
+ char: Character,
1340
+ ds: DataStore,
1341
+ ): Array<{
1342
+ spellId: number;
1343
+ name: string;
1344
+ triggerSpellId: number;
1345
+ remainingMs: number;
1346
+ charges?: number;
1347
+ cooldownMs?: number;
1348
+ procFlags?: number;
1349
+ procPpm?: number | null;
1350
+ procChance?: number | null;
1351
+ }> {
1352
+ const out: Array<{
1353
+ spellId: number;
1354
+ name: string;
1355
+ triggerSpellId: number;
1356
+ remainingMs: number;
1357
+ charges?: number;
1358
+ cooldownMs?: number;
1359
+ procFlags?: number;
1360
+ procPpm?: number | null;
1361
+ procChance?: number | null;
1362
+ }> = [];
1363
+ const seen = new Set<number>();
1364
+ for (const [slot, v] of Object.entries(char.equipped ?? {}) as [EquipSlot, EquippedValue | undefined][]) {
1365
+ const entry = equippedEntry(v);
1366
+ if (entry == null) continue;
1367
+ const item = ds.item(entry);
1368
+ if (!item || isBroken(char, slot, item)) continue;
1369
+ const inst = equippedInstance(v);
1370
+ const eid = inst?.permEnchantId ?? char.enchants?.[slot];
1371
+ if (eid == null) continue;
1372
+ const formula = ds.enchant(eid);
1373
+ const sieId = formula?.enchantId ?? eid;
1374
+ const sie = ds.itemEnchant(sieId);
1375
+ if (!sie?.effects?.length) continue;
1376
+ for (const eff of sie.effects) {
1377
+ if (eff.type !== 'equip_spell' || !eff.spellId) continue;
1378
+ for (const g of eff.grants ?? []) {
1379
+ if (g.kind !== 'proc_spell' || !g.spellId) continue;
1380
+ if (seen.has(eff.spellId)) continue;
1381
+ seen.add(eff.spellId);
1382
+ const raw = ds.spell(eff.spellId);
1383
+ const sp = raw ? resolveCombatSpell(raw, { casterLevel: char.level }) : undefined;
1384
+ const pe = sp?.effects?.find((e) => e.kind === 'proc_trigger_spell');
1385
+ const triggerSpellId =
1386
+ (pe && pe.kind === 'proc_trigger_spell' ? pe.triggerSpellId : 0) || g.spellId;
1387
+ if (!triggerSpellId) continue;
1388
+ const procFlags = sp?.procFlags ?? sp?.spellProcEvent?.procFlags;
1389
+ const procPpm = sp?.spellProcEvent?.ppmRate ?? null;
1390
+ let procChance: number | null = null;
1391
+ if (g.chancePct != null && g.chancePct > 0) {
1392
+ procChance = Math.min(1, g.chancePct / 100);
1393
+ } else if (sp?.spellProcEvent?.customChance != null && sp.spellProcEvent.customChance > 0) {
1394
+ const c = sp.spellProcEvent.customChance;
1395
+ procChance = c > 1 ? Math.min(1, c / 100) : Math.min(1, c);
1396
+ } else if (sp?.procChance != null && sp.procChance > 0 && sp.procChance <= 100) {
1397
+ procChance = Math.min(1, sp.procChance / 100);
1398
+ }
1399
+ out.push({
1400
+ spellId: eff.spellId,
1401
+ name: sp?.name ?? raw?.name ?? `enchant-proc ${eff.spellId}`,
1402
+ triggerSpellId,
1403
+ // Permanent while equipped — fight clock, not wall clock.
1404
+ remainingMs: 24 * 60 * 60 * 1000,
1405
+ charges: pe && pe.kind === 'proc_trigger_spell' ? pe.charges : sp?.procCharges,
1406
+ cooldownMs:
1407
+ (pe && pe.kind === 'proc_trigger_spell' ? pe.cooldownMs : undefined)
1408
+ ?? sp?.spellProcEvent?.cooldownMs,
1409
+ ...(procFlags != null && procFlags !== 0 ? { procFlags } : {}),
1410
+ ...(procPpm != null && procPpm > 0 ? { procPpm } : {}),
1411
+ ...(procChance != null ? { procChance } : {}),
1412
+ });
1413
+ }
1414
+ }
1415
+ }
1416
+ return out;
1417
+ }
1418
+
1419
+ // MOD_TARGET_RESISTANCE (123) from equip + OOC buffs → positive penetration per school. DBC amount is
1420
+ // typically negative; pen = −amount. Cite: Unit.cpp:3892–3894.
1421
+ export function equipResistancePenetration(
1422
+ char: Character,
1423
+ ds: DataStore,
1424
+ ): Partial<Record<School, number>> {
1425
+ const pen: Partial<Record<School, number>> = {};
1426
+ for (const e of collectBuffEffectsByMod(char, ds, 'target_resistance')) {
1427
+ if (e.kind !== 'buff') continue;
1428
+ const schoolMask = (e.schoolMask ?? 0) | 0;
1429
+ const amount = Number(e.amount);
1430
+ if (!schoolMask || !Number.isFinite(amount) || !amount) continue;
1431
+ const p = -amount; // sign reverse — aura is "mod target resistance"
1432
+ if (!p) continue;
1433
+ for (const [sch, bit] of Object.entries(SCHOOL_MASK_BIT) as [School, number][]) {
1434
+ if (schoolMask & bit) pen[sch] = (pen[sch] ?? 0) + p;
1435
+ }
1436
+ }
1437
+ return pen;
1438
+ }
1439
+
1440
+ /** Merge two partial school maps (additive). */
1441
+ export function mergeSchoolAmounts(
1442
+ a: Partial<Record<School, number>> | undefined,
1443
+ b: Partial<Record<School, number>> | undefined,
1444
+ ): Partial<Record<School, number>> | undefined {
1445
+ if (!a && !b) return undefined;
1446
+ const out: Partial<Record<School, number>> = { ...(a ?? {}) };
1447
+ for (const [sch, amt] of Object.entries(b ?? {}) as [School, number][]) {
1448
+ out[sch] = (out[sch] ?? 0) + amt;
1449
+ }
1450
+ return out;
1451
+ }
1452
+
1453
+ // Per-school magic resistance from racial passives plus active resistance buffs, data-driven off
1454
+ // `resist` effects. Lowers incoming magic damage and the chance a crowd control lands.
1455
+ export function resistances(char: Character, ds: DataStore): Partial<Record<School, number>> {
1456
+ const out: Partial<Record<School, number>> = {};
1457
+ const add = (effs: SpellDef['effects'] | undefined): void => {
1458
+ for (const e of effs ?? []) if (e.kind === 'resist') out[e.school] = (out[e.school] ?? 0) + e.amount;
1459
+ };
1460
+ for (const s of racialSpells(ds, char.race)) add(s.effects);
1461
+ for (const id of getActiveBuffIds(char, undefined, ds)) add(ds.spell(id)?.effects);
1462
+ // Talent resist mods (e.g. +fire resist talents) — additive with racials/buffs.
1463
+ const tRes = talentResistMods(char, ds);
1464
+ for (const [sch, amt] of Object.entries(tRes) as [School, number][]) {
1465
+ out[sch] = (out[sch] ?? 0) + amt;
1466
+ }
1467
+ // Gear item/enchant resistances (Item 5 itemization: resFire etc on stats[])
1468
+ const gear = equippedStatBonuses(char, ds);
1469
+ const resMap: Array<[keyof Partial<Record<Stat, number>>, School]> = [
1470
+ ['resHoly', 'holy'], ['resFire', 'fire'], ['resNature', 'nature'],
1471
+ ['resFrost', 'frost'], ['resShadow', 'shadow'], ['resArcane', 'arcane'],
1472
+ ];
1473
+ for (const [k, sch] of resMap) {
1474
+ const v = gear[k];
1475
+ if (v) out[sch] = (out[sch] ?? 0) + v;
1476
+ }
1477
+ return out;
1478
+ }
1479
+
1480
+ // SPELL_AURA_MOD_MECHANIC_RESISTANCE totals by mechanic id (Hardiness stun=12 → +25). Racials + active
1481
+ // buffs; resolveCombatSpell fills hollow racials. Cite: Unit.cpp:4016; KI-823.
1482
+ export function mechanicResistPct(
1483
+ char: Character,
1484
+ ds: DataStore,
1485
+ knownSpellIds?: readonly number[],
1486
+ ): Partial<Record<number, number>> {
1487
+ const out: Partial<Record<number, number>> = {};
1488
+ const add = (raw: SpellDef | undefined | null): void => {
1489
+ if (!raw) return;
1490
+ const sp = resolveCombatSpell(raw, { unitTargetIsPlayer: true });
1491
+ for (const e of sp.effects ?? []) {
1492
+ if (e.kind !== 'mod_mechanic_resistance') continue;
1493
+ const mid = e.mechanic | 0;
1494
+ const amt = e.amount | 0;
1495
+ if (!mid || !amt) continue;
1496
+ out[mid] = (out[mid] ?? 0) + amt;
1497
+ }
1498
+ };
1499
+ for (const s of racialSpells(ds, char.race)) add(s);
1500
+ if (knownSpellIds) {
1501
+ for (const id of knownSpellIds) add(ds.spell(id));
1502
+ }
1503
+ // Spent talent ranks (Iron Will, Surefooted) are permanent passives that sit in no spellbook.
1504
+ // Passive-gated: a rank that IS the active (Deterrence) applies on cast instead.
1505
+ for (const id of spentTalentRankSpellIds(char, ds)) {
1506
+ const raw = ds.spell(id);
1507
+ if (raw?.passive && passiveAppliesInForm(raw, ((char.form ?? 0) | 0) as ShapeshiftFormId)) add(raw);
1508
+ }
1509
+ for (const id of getActiveBuffIds(char, undefined, ds)) add(ds.spell(id));
1510
+ return out;
1511
+ }
1512
+
1513
+ /** CMaNGOS creature-type for CC immunity checks: an Undead hero counts as Undead, everyone else Humanoid. */
1514
+ export function combatantCreatureType(char: Character): number {
1515
+ return char.race === RACE.UNDEAD ? CREATURE_TYPE.UNDEAD : CREATURE_TYPE.HUMANOID;
1516
+ }
1517
+
1518
+ export function weaponProfile(char: Character, ds: DataStore): WeaponProfile {
1519
+ const mh = equippedEntry(char.equipped.mainhand);
1520
+ const it = mh != null ? ds.item(mh) : undefined;
1521
+ // A broken weapon deals no weapon damage — you fight unarmed until it's repaired.
1522
+ if (it?.weapon && !isBroken(char, 'mainhand', it)) {
1523
+ return { dmgMin: it.weapon.dmgMin, dmgMax: it.weapon.dmgMax, speedMs: it.weapon.delayMs, school: it.weapon.school, twoHand: it.slot === 'twohand' };
1524
+ }
1525
+ return { dmgMin: 1, dmgMax: 2, speedMs: 2000, school: 'physical' }; // unarmed (Unit.h BASE_MIN/MAXDAMAGE + 2s)
1526
+ }
1527
+
1528
+ // Off-hand weapon combat profile when the off-hand holds a weapon. Null when empty, a shield,
1529
+ // broken, or a non-weapon. Cite: Player::hasOffhandWeaponForAttack / GetWeaponForAttack.
1530
+ export function offhandWeaponProfile(char: Character, ds: DataStore): WeaponProfile | null {
1531
+ const oh = equippedEntry(char.equipped.offhand);
1532
+ if (oh == null) return null;
1533
+ const it = ds.item(oh);
1534
+ if (!it?.weapon || isBroken(char, 'offhand', it)) return null;
1535
+ // Shields are offhand armor with no weapon; holdables without weapon stay null above.
1536
+ return {
1537
+ dmgMin: it.weapon.dmgMin,
1538
+ dmgMax: it.weapon.dmgMax,
1539
+ speedMs: it.weapon.delayMs,
1540
+ school: it.weapon.school,
1541
+ };
1542
+ }
1543
+
1544
+ // True when dual-wielding: off-hand is a weapon (not a shield). Drives +19% miss and OH autos. source:
1545
+ // Unit.cpp hasOffhandWeaponForAttack; CalculateEffectiveMissChance dual-wield branch
1546
+ export function isDualWielding(char: Character, ds: DataStore): boolean {
1547
+ return offhandWeaponProfile(char, ds) != null;
1548
+ }
1549
+
1550
+ // The equipped ranged weapon (wand, bow, gun) as a combat profile, or null when empty or broken.
1551
+ // Powers Shoot and Auto Shot; the main-hand melee auto-attack is unaffected.
1552
+ export function rangedProfile(char: Character, ds: DataStore): WeaponProfile | null {
1553
+ const r = equippedEntry(char.equipped.ranged);
1554
+ const it = r != null ? ds.item(r) : undefined;
1555
+ if (it?.weapon && !isBroken(char, 'ranged', it)) {
1556
+ return { dmgMin: it.weapon.dmgMin, dmgMax: it.weapon.dmgMax, speedMs: it.weapon.delayMs, school: it.weapon.school };
1557
+ }
1558
+ return null;
1559
+ }