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,1572 @@
1
+ // Instanced-dungeon rules (Phase 5), pure and deterministic. Given an InstanceDef
2
+ // (static data) and an InstanceRun (per-character progress) it answers: what can I
3
+ // fight next, is the dungeon cleared, am I locked out? No DOM, no clock — any "now"
4
+ // is injected by the caller (the UI boundary), so this stays testable and the
5
+ // arch.test purity guard is satisfied.
6
+ //
7
+ // D-4 event primitives (flags, gates, wave/phase progress) live here as pure
8
+ // helpers; game.ts is the wiring layer only.
9
+
10
+ import type { DataStore } from '../data/dataStore.ts';
11
+ import type {
12
+ InstanceDef,
13
+ EncounterDef,
14
+ EncounterEvent,
15
+ EncounterPackMember,
16
+ } from '../data/schema.ts';
17
+ import {
18
+ checkAreaTriggerLock,
19
+ findEntranceTeleport,
20
+ type AreaTriggerTeleportRow,
21
+ } from '../domain/world/areaTriggerLocks.ts';
22
+ import type { PullFusion, RouteDensity } from './dungeonRoute.ts';
23
+ import {
24
+ DEFAULT_PULL_FUSION,
25
+ DEFAULT_ROUTE_DENSITY,
26
+ selectRouteEncounters,
27
+ } from './dungeonRoute.ts';
28
+ import type { CreatureLookup, SpellLookup, PackBudgetPreset } from './packBudget.ts';
29
+ import {
30
+ expandRouteByPackBudget,
31
+ packBudgetPresetFor,
32
+ } from './packBudget.ts';
33
+ import {
34
+ filterRouteByPoolRoll,
35
+ orderRouteByGeometry,
36
+ routeItemSources,
37
+ } from './dungeonRouteOrder.ts';
38
+ import type { NavFloorDoc, NavGrid } from './dungeonNav.ts';
39
+ import { dungeonGameObjectsForMap } from '../data/dungeonGameObjects.ts';
40
+ import {
41
+ STRATH_LIVE_POSTBOX,
42
+ postboxOrigin,
43
+ } from '../data/dungeons/stratholme-live.ts';
44
+ import type { DungeonPoolRoll, PoolMapSpawn } from './rareRoll.ts';
45
+ import { INSTANCE_DATA } from '../domain/scriptdev/types.ts';
46
+ import {
47
+ applyMajordomoSpawnOnPlayerEnter,
48
+ applyMoltenCoreRuneSpecial,
49
+ moltenCoreRuneField,
50
+ } from '../domain/scriptdev/moltenCore.ts';
51
+
52
+ /** Per-character progress through one active dungeon run. */
53
+ export interface InstanceRun {
54
+ dungeonId: string;
55
+ defeated: string[]; // encounter ids cleared so far this run
56
+ /**
57
+ * Free-walk floor progress: spawn guids already killed. `defeated` tracks ENCOUNTER ids,
58
+ * which the continuous floor does not use — so without this every mob you killed came back
59
+ * alive the moment the controller re-mounted ("Continue run" rebuilds the floor from the
60
+ * spawn bake). Optional: absent ≡ nothing cleared, so old saves stay valid.
61
+ */
62
+ floorClearedGuids?: number[];
63
+ /**
64
+ * Guids that won dungeon `pool_creature` rolls at first mount. Restored on re-mount so
65
+ * rares / substitutes do not re-roll mid-run. Absent ≡ not rolled yet (legacy) — mount
66
+ * rolls once and stamps this. Empty array = map had no relevant pools (or all failed).
67
+ * Cite: MapPersistentStateMgr.cpp InitPools ~198-207; pools.json member chances only.
68
+ */
69
+ floorPoolSpawnedGuids?: number[];
70
+ /**
71
+ * Guids that won the map's GAMEOBJECT pool rolls at first mount — the herb, vein, crate and
72
+ * chest positions that hold an object this run. Losers are candidate spots the DB allows and
73
+ * nothing stands on. Absent ≡ not rolled yet (legacy save): every candidate stays, which is
74
+ * what every surface did before the pool bake. Cite: PoolManager.cpp:1087 SpawnPool.
75
+ */
76
+ floorPoolSpawnedGoGuids?: number[];
77
+ // Where the party stands on the free-walk floor. Absent ≡ start at the instance entrance. Restored on
78
+ // re-mount so a Continue does not teleport you back to the door.
79
+ floorPartyPos?: { x: number; y: number; z?: number };
80
+ /**
81
+ * Deaths this run has taken, for Alterac Valley's reinforcement race — one death is one
82
+ * reinforcement (BattleGroundAV.cpp:38-45 HandleKillPlayer). Counted in liveFloorRecovery's
83
+ * `chargeDeaths`, which is already the once-per-death moment. Optional: absent ≡ 0, so every
84
+ * existing save stays valid and every non-AV run simply never writes it.
85
+ */
86
+ avDeaths?: number;
87
+ /**
88
+ * Alterac Valley's capture board — node id -> owner/state/assault clock, mine id -> owner, and
89
+ * the mines' banked reinforcement income. State, not a read, because a node has an assault
90
+ * clock; see src/domain/bg/avInstanceNodes.ts. Optional throughout: absent ≡ the DB's own
91
+ * starting board, so every existing save stays valid and no non-AV run ever writes it.
92
+ */
93
+ avNodes?: Record<string, { o: 'alliance' | 'horde' | null; s: 'owned' | 'assaulted'; m?: number }>;
94
+ avMines?: Record<string, 'alliance' | 'horde'>;
95
+ avMineTickAtMs?: number;
96
+ avMineIncome?: number;
97
+ /**
98
+ * Instance flags set by boss-death `setsFlag`, object-use gates, HP phases, etc.
99
+ * Optional for save back-compat (absent ≡ []). Survives mid-run save without a
100
+ * SAVE_VERSION bump (migrate treats missing as empty).
101
+ */
102
+ flags?: string[];
103
+ /**
104
+ * SPELL_AURA_FORCE_REACTION (139) map — Faction.dbc id → ReputationRank. Lives on the run
105
+ * because the crown is stripped on leaving the instance. Optional (absent ≡ none).
106
+ * Cite: instance_dire_maul.cpp:75-79; domain/creature/forcedReaction.ts.
107
+ */
108
+ forcedReactions?: import('../domain/creature/forcedReaction.ts').ForcedReactionMap;
109
+ /**
110
+ * ScriptDev numeric instance data store (TYPE_* → NOT_STARTED/IN_PROGRESS/…).
111
+ * Written by GO-use / boss SetData paths. Optional — absent ≡ empty (all NOT_STARTED).
112
+ * Cite: sc_instance.h; stratholme.h TYPE_*; host handleGoUse / applyInstanceSetData.
113
+ */
114
+ scriptInstanceData?: Record<number, number>;
115
+ // Stratholme postbox open counter (m_postboxesUsed). After the 2nd IN_PROGRESS write to TYPE_POSTMASTER
116
+ // the store promotes to SPECIAL. Cite: stratholme.cpp:463-471 TYPE_POSTMASTER SetData.
117
+ postboxesUsed?: number;
118
+ /**
119
+ * Gnomeregan bomb faces (`instance_gnomeregan::m_asBombFaces`). On the RUN because the C++
120
+ * keeps it on the instance, not on the boss AI: `DoActivateBombFace` is reached from the boss
121
+ * spell script and `DoDeactivateBombFace` from `GOUse_go_gnomeface_button`, and both go through
122
+ * the instance. It lived on the UI-layer dungeon ctx here, so a click that was not a hand click
123
+ * could not see it. Absent = no run has engaged him (all faces off).
124
+ * Cite: instance_gnomeregan.cpp:250-274; gnomeregan.h:47-51 sBombFace; boss_thermaplugg.cpp:263-281.
125
+ */
126
+ thermapluggFaces?: import('../data/dungeons/gnomeregan.ts').ThermapluggFightState;
127
+ // Multi-step objectGate progress: setsFlag → successful use count toward needUses. Optional — absent ≡
128
+ // {}. Survives mid-run save without SAVE_VERSION bump.
129
+ objectGateUses?: Record<string, number>;
130
+ // Multi-entry objectGate: setsFlag → goEntries already counted (each entry ≤1). Optional — absent ≡ {}.
131
+ // Used when gate.goEntries has length > 1.
132
+ objectGateUsedEntries?: Record<string, number[]>;
133
+ /**
134
+ * ScriptDev summon_count queue from GO-use (e.g. 3× Undead Postman per postbox).
135
+ * Optional coords = authored GO origin for floor inject; encounterId pins the
136
+ * summoned-body guid (Malown → postmaster-malown). Absent ≡ none pending.
137
+ * Cite: stratholmeScripts.cpp:110,118-122; docs/dungeons/malown/README.md.
138
+ */
139
+ scriptPendingSummons?: Array<{
140
+ creatureEntry: number;
141
+ count: number;
142
+ x?: number;
143
+ y?: number;
144
+ z?: number;
145
+ /** Floor guid = summonedBodyGuid(encounterId) for the first body of this row. */
146
+ encounterId?: string;
147
+ }>;
148
+ /**
149
+ * Path-dependent encounter origins stamped at gate complete (Strat Malown 3rd box).
150
+ * Optional — absent ≡ use EncounterDef.origin. Survives mid-run save without bump.
151
+ * Cite: stratholmeScripts.cpp:108-110; docs/dungeons/malown/README.md.
152
+ */
153
+ encounterOrigins?: Record<string, { x: number; y: number; z?: number }>;
154
+ // DR route density for this run (PACK-COMBAT-VISION §8b). Absent ≡ `bosses` for new runs; legacy mid-run
155
+ // saves without the field still use full def order when `routeEncounterIds` is also absent.
156
+ routeDensity?: RouteDensity;
157
+ // DR pull fusion for this run. Absent ≡ `single`. Rest pacing stays GameState.squadStrategy /
158
+ // SQUAD_PACING (careful|aggressive|sip).
159
+ pullFusion?: PullFusion;
160
+ /**
161
+ * Materialized ordered encounter ids for this run's route (enterInstance).
162
+ * When set, unlock / next / complete drive off this list; non-route packs are
163
+ * never required. Absent ≡ full `def.encounters` order (legacy saves).
164
+ */
165
+ routeEncounterIds?: string[];
166
+ /**
167
+ * Budget-split route atoms (COMBAT-INTERACTION-V2 §Pack-strength budget).
168
+ * Materialized at enter when pack-budget context is supplied. Synthetic
169
+ * sub-pull ids (e.g. `pack__budget2`) resolve from here, not the static def.
170
+ * Absent ≡ lookup encounters from InstanceDef only (legacy / no budget).
171
+ */
172
+ routeEncounters?: EncounterDef[];
173
+ /** Pack-budget preset stamped at enter (optional; for UI / score tags). */
174
+ packBudgetPreset?: PackBudgetPreset;
175
+ }
176
+
177
+ /** dungeonId -> epoch ms at which a timed lockout expires (5-mans never add one). */
178
+ export type LockoutMap = Record<string, number>;
179
+
180
+ export interface EnterRunOpts {
181
+ routeDensity?: RouteDensity;
182
+ pullFusion?: PullFusion;
183
+ /** Precomputed route; when omitted, density is applied against a def if provided. */
184
+ routeEncounterIds?: string[];
185
+ /** Full encounter defs for budget-split atoms (same order as routeEncounterIds). */
186
+ routeEncounters?: EncounterDef[];
187
+ packBudgetPreset?: PackBudgetPreset;
188
+ }
189
+
190
+ /** Begin a fresh run of a dungeon (no progress yet). */
191
+ export function enterRun(dungeonId: string, opts?: EnterRunOpts): InstanceRun {
192
+ const routeDensity = opts?.routeDensity ?? DEFAULT_ROUTE_DENSITY;
193
+ const pullFusion = opts?.pullFusion ?? DEFAULT_PULL_FUSION;
194
+ const run: InstanceRun = {
195
+ dungeonId,
196
+ defeated: [],
197
+ flags: [],
198
+ routeDensity,
199
+ pullFusion,
200
+ };
201
+ if (opts?.routeEncounterIds?.length) {
202
+ run.routeEncounterIds = [...opts.routeEncounterIds];
203
+ }
204
+ if (opts?.routeEncounters?.length) {
205
+ run.routeEncounters = opts.routeEncounters.map((e) => ({ ...e }));
206
+ }
207
+ if (opts?.packBudgetPreset) {
208
+ run.packBudgetPreset = opts.packBudgetPreset;
209
+ }
210
+ return run;
211
+ }
212
+
213
+ export interface EnterRunWithRouteOpts {
214
+ routeDensity?: RouteDensity;
215
+ pullFusion?: PullFusion;
216
+ // When set with creature/spell lookups, over-budget trash packs are split into sequential sub-pulls
217
+ // (COMBAT-INTERACTION-V2 §Pack-strength budget). Omitted → density filter only (tests / legacy).
218
+ packBudget?: {
219
+ partyLevel: number;
220
+ creatureOf: CreatureLookup;
221
+ spellOf: SpellLookup;
222
+ /** Override preset; default derived from density + fusion. */
223
+ preset?: PackBudgetPreset;
224
+ };
225
+ /**
226
+ * Nav grid / floorplan for geometric route order (entrance-first, A* distance).
227
+ * When omitted, uses the instance nav registry (primed when a floorplan loads).
228
+ * Without any grid the orderer still applies flag/boss constraints and Euclidean
229
+ * nearest-neighbour as a last-resort rank — better than declaration order, but
230
+ * wall-aware only once a floorplan is registered.
231
+ */
232
+ navGrid?: NavGrid | null;
233
+ floorplan?: NavFloorDoc | null;
234
+ // Skip geometric reorder (tests that assert declaration / density relative order). Production enter
235
+ // always orders geometrically.
236
+ skipGeometricOrder?: boolean;
237
+ /** Bags at enter — a carried 13523 sends the tour to Kirtonos, not Reliquary. */
238
+ hasItem?: (itemEntry: number) => boolean;
239
+ // True when the party can loot this inside-key NOW (quest conditions). 13523 is only farmable after 5384
240
+ // is rewarded; otherwise skip Kirtonos.
241
+ itemObtainable?: (itemEntry: number) => boolean;
242
+ // Once-per-run pool roll (liveDungeonController.ts:252-291). When set with mapSpawns,
243
+ // orderRouteByGeometry strips pool-absent rares + multi-slot dups.
244
+ poolRoll?: DungeonPoolRoll | null;
245
+ mapSpawns?: readonly PoolMapSpawn[];
246
+ /** Persist winners on the run (MapPersistentStateMgr InitPools). */
247
+ floorPoolSpawnedGuids?: readonly number[];
248
+ /** Same, for the map's gameobject pools (goPoolRoll.rollDungeonGoPools). */
249
+ floorPoolSpawnedGoGuids?: readonly number[];
250
+ /** Alternate world door; route orders from that areatrigger dest. */
251
+ entranceTriggerId?: number;
252
+ }
253
+
254
+ /**
255
+ * Begin a run and materialize the density-filtered route from `def`.
256
+ * Preferred entry from game.enterInstance.
257
+ * Optional pack-budget context splits over-strong trash (never nerfs stats).
258
+ * Route ids are ordered geometrically (nav-graph from entrance), not def order.
259
+ */
260
+ export function enterRunWithRoute(
261
+ def: InstanceDef,
262
+ opts?: EnterRunWithRouteOpts,
263
+ ): InstanceRun {
264
+ const routeDensity = opts?.routeDensity ?? DEFAULT_ROUTE_DENSITY;
265
+ const pullFusion = opts?.pullFusion ?? DEFAULT_PULL_FUSION;
266
+ let route = selectRouteEncounters(def, routeDensity);
267
+ let packBudgetPreset: PackBudgetPreset | undefined;
268
+ if (opts?.packBudget) {
269
+ packBudgetPreset =
270
+ opts.packBudget.preset
271
+ ?? packBudgetPresetFor(routeDensity, pullFusion);
272
+ // def.maxPlayers picks the table: a 40-man keeps the legacy raid budget, a 5/10-man gets
273
+ // the one measured against probe-dungeon-completion at band (packBudget.ts).
274
+ route = expandRouteByPackBudget(
275
+ route,
276
+ opts.packBudget.partyLevel,
277
+ opts.packBudget.creatureOf,
278
+ opts.packBudget.spellOf,
279
+ packBudgetPreset,
280
+ def.maxPlayers,
281
+ );
282
+ }
283
+ // Geometric order: declaration order was the player-visible scatter bug.
284
+ // Pool strip + multi-slot rare collapse ride the same path as the live floor
285
+ // (liveDungeonController.ts:271 stripAbsentPoolEncountersFromRoute).
286
+ if (!opts?.skipGeometricOrder) {
287
+ route = orderRouteByGeometry(def, route, {
288
+ grid: opts?.navGrid,
289
+ floorplan: opts?.floorplan,
290
+ hasItem: opts?.hasItem,
291
+ itemObtainable: opts?.itemObtainable,
292
+ poolRoll: opts?.poolRoll,
293
+ mapSpawns: opts?.mapSpawns,
294
+ entranceTriggerId: opts?.entranceTriggerId,
295
+ });
296
+ } else if (opts?.mapSpawns) {
297
+ route = filterRouteByPoolRoll(route, opts.poolRoll, opts.mapSpawns);
298
+ }
299
+ const run = enterRun(def.id, {
300
+ routeDensity,
301
+ pullFusion,
302
+ routeEncounterIds: route.map((e) => e.id),
303
+ ...(opts?.packBudget
304
+ ? { routeEncounters: route, packBudgetPreset }
305
+ : {}),
306
+ });
307
+ if (opts?.floorPoolSpawnedGuids) {
308
+ run.floorPoolSpawnedGuids = [...opts.floorPoolSpawnedGuids];
309
+ } else if (opts?.poolRoll) {
310
+ run.floorPoolSpawnedGuids = [...opts.poolRoll.spawnedGuids];
311
+ }
312
+ if (opts?.floorPoolSpawnedGoGuids) {
313
+ run.floorPoolSpawnedGoGuids = [...opts.floorPoolSpawnedGoGuids];
314
+ }
315
+ // molten_core.cpp:62-65 OnPlayerEnter → DoSpawnMajordomoIfCan(true).
316
+ return applyMajordomoSpawnOnPlayerEnter(run);
317
+ }
318
+
319
+ /**
320
+ * Re-select and re-order the route once the nav bake lands.
321
+ *
322
+ * Selection itself is nav-free now that density is `bosses`/`full` — the corridor spine
323
+ * that needed the bake was deleted 2026-08-17. What still needs it is `orderRouteByGeometry`:
324
+ * at enter time it ranks stops without a mesh, so the itinerary can start in walking order
325
+ * and settle once the bake is in.
326
+ *
327
+ * Fresh runs only, and never over budget-split atoms: `routeEncounters` was materialized
328
+ * from the route this would replace, so in practice this is the legacy-save path —
329
+ * `enterInstance` always passes a packBudget and `reapplyGeometricRouteOrder` handles the rest.
330
+ */
331
+ /**
332
+ * Bring an IN-FLIGHT run route in line with its density.
333
+ *
334
+ * `reapplyRouteSelection` below returns early on `run.routeEncounters`, and EVERY live run has
335
+ * that field (enterInstance always passes packBudget), so a run entered before a density change
336
+ * keeps its old stop list for the rest of its life. `state.instance` is persisted, so that means
337
+ * across reloads too: a blackrock-depths run entered under the old `normal` default still walks
338
+ * its 235 trash stops after the default moved to `bosses`, and the autopilot still names them.
339
+ *
340
+ * Cleared stops are KEPT so isCleared / isComplete / routeBossProgress stay coherent; only
341
+ * UNCLEARED stops the current density would not select are dropped. Budget-split atoms
342
+ * (`<id>__budgetN`, packBudget.ts:526) resolve to their base encounter before the test.
343
+ */
344
+ export function pruneRouteToDensity(def: InstanceDef, run: InstanceRun): InstanceRun {
345
+ const ids = run.routeEncounterIds;
346
+ if (!ids?.length) return run;
347
+ const density = run.routeDensity ?? DEFAULT_ROUTE_DENSITY;
348
+ const keep = new Set(selectRouteEncounters(def, density).map((e) => e.id));
349
+ const baseOf = (id: string): string => id.split('__budget')[0] ?? id;
350
+ const next = ids.filter((id) => isCleared(run, id) || keep.has(baseOf(id)));
351
+ if (next.length === ids.length) return run;
352
+ const out: InstanceRun = { ...run, routeEncounterIds: next };
353
+ if (run.routeEncounters?.length) {
354
+ const kept = new Set(next);
355
+ out.routeEncounters = run.routeEncounters.filter((e) => kept.has(e.id));
356
+ }
357
+ return out;
358
+ }
359
+
360
+ export function reapplyRouteSelection(
361
+ def: InstanceDef,
362
+ run: InstanceRun,
363
+ opts?: {
364
+ navGrid?: NavGrid | null;
365
+ floorplan?: NavFloorDoc | null;
366
+ hasItem?: (itemEntry: number) => boolean;
367
+ itemObtainable?: (itemEntry: number) => boolean;
368
+ },
369
+ ): InstanceRun {
370
+ if (run.defeated.length > 0 || (run.flags?.length ?? 0) > 0) return run;
371
+ if (run.routeEncounters?.length) return run;
372
+ const density = run.routeDensity ?? DEFAULT_ROUTE_DENSITY;
373
+ const ids = orderRouteByGeometry(def, selectRouteEncounters(def, density), {
374
+ grid: opts?.navGrid,
375
+ floorplan: opts?.floorplan,
376
+ hasItem: opts?.hasItem,
377
+ itemObtainable: opts?.itemObtainable,
378
+ }).map((e) => e.id);
379
+ const prev = run.routeEncounterIds ?? [];
380
+ if (prev.length === ids.length && prev.every((id, i) => id === ids[i])) return run;
381
+ return { ...run, routeEncounterIds: ids };
382
+ }
383
+
384
+ // Re-apply geometric route order on a fresh run (defeated empty) once a floorplan nav grid becomes
385
+ // available. No-op when progress has started.
386
+ export function reapplyGeometricRouteOrder(
387
+ def: InstanceDef,
388
+ run: InstanceRun,
389
+ opts?: {
390
+ navGrid?: NavGrid | null;
391
+ floorplan?: NavFloorDoc | null;
392
+ hasItem?: (itemEntry: number) => boolean;
393
+ itemObtainable?: (itemEntry: number) => boolean;
394
+ },
395
+ ): InstanceRun {
396
+ if (run.defeated.length > 0) return run;
397
+ // Resolve current route atoms (budget splits live on run.routeEncounters).
398
+ let route: EncounterDef[];
399
+ if (run.routeEncounters?.length) {
400
+ const byId = new Map(run.routeEncounters.map((e) => [e.id, e]));
401
+ const ids = run.routeEncounterIds ?? run.routeEncounters.map((e) => e.id);
402
+ route = [];
403
+ for (const id of ids) {
404
+ const e = byId.get(id);
405
+ if (e) route.push(e);
406
+ }
407
+ } else {
408
+ const ids = run.routeEncounterIds ?? def.encounters.map((e) => e.id);
409
+ route = [];
410
+ for (const id of ids) {
411
+ const e = encounterOf(def, id, run);
412
+ if (e) route.push(e);
413
+ }
414
+ }
415
+ const ordered = orderRouteByGeometry(def, route, {
416
+ grid: opts?.navGrid,
417
+ floorplan: opts?.floorplan,
418
+ hasItem: opts?.hasItem,
419
+ itemObtainable: opts?.itemObtainable,
420
+ });
421
+ const ids = ordered.map((e) => e.id);
422
+ const prev = run.routeEncounterIds ?? [];
423
+ if (
424
+ prev.length === ids.length
425
+ && prev.every((id, i) => id === ids[i])
426
+ && (!run.routeEncounters?.length
427
+ || ordered.every((e, i) => !e.optional || run.routeEncounters?.[i]?.optional === e.optional))
428
+ ) {
429
+ return run;
430
+ }
431
+ const next: InstanceRun = {
432
+ ...run,
433
+ routeEncounterIds: ids,
434
+ };
435
+ if (run.routeEncounters?.length) {
436
+ next.routeEncounters = ordered;
437
+ }
438
+ return next;
439
+ }
440
+
441
+ /** Wipe progress for a fresh attempt — keeps density / fusion / route list. */
442
+ export function resetRun(run: InstanceRun): InstanceRun {
443
+ return {
444
+ dungeonId: run.dungeonId,
445
+ defeated: [],
446
+ flags: [],
447
+ ...(run.routeDensity != null ? { routeDensity: run.routeDensity } : {}),
448
+ ...(run.pullFusion != null ? { pullFusion: run.pullFusion } : {}),
449
+ ...(run.routeEncounterIds?.length
450
+ ? { routeEncounterIds: [...run.routeEncounterIds] }
451
+ : {}),
452
+ ...(run.routeEncounters?.length
453
+ ? { routeEncounters: run.routeEncounters.map((e) => ({ ...e })) }
454
+ : {}),
455
+ ...(run.packBudgetPreset != null ? { packBudgetPreset: run.packBudgetPreset } : {}),
456
+ };
457
+ // objectGateUses / objectGateUsedEntries intentionally dropped (fresh attempt).
458
+ }
459
+
460
+ // Ordered encounter ids for progress checks. Prefers the run's materialized route; falls back to full def
461
+ // order (legacy saves without DR fields).
462
+ export function routeEncounterIdList(def: InstanceDef, run: InstanceRun): string[] {
463
+ if (run.routeEncounterIds?.length) return run.routeEncounterIds;
464
+ return def.encounters.map((e) => e.id);
465
+ }
466
+
467
+ /** Encounter defs on the active route (lookup from full def; preserves route order). */
468
+ export function routeEncounterList(def: InstanceDef, run: InstanceRun): EncounterDef[] {
469
+ // Prefer budget-materialized atoms (may include synthetic sub-pull ids).
470
+ if (run.routeEncounters?.length) {
471
+ const byId = new Map(run.routeEncounters.map((e) => [e.id, e]));
472
+ const out: EncounterDef[] = [];
473
+ for (const id of routeEncounterIdList(def, run)) {
474
+ const e = byId.get(id) ?? encounterOf(def, id);
475
+ if (e) out.push(e);
476
+ }
477
+ return out;
478
+ }
479
+ const out: EncounterDef[] = [];
480
+ for (const id of routeEncounterIdList(def, run)) {
481
+ const e = encounterOf(def, id, run);
482
+ if (e) out.push(e);
483
+ }
484
+ return out;
485
+ }
486
+
487
+ // Resolve an encounter by id. When `run` carries budget-split `routeEncounters`, those take precedence
488
+ // (synthetic `__budgetN` atoms are not on InstanceDef).
489
+ export function encounterOf(
490
+ def: InstanceDef,
491
+ encounterId: string,
492
+ run?: InstanceRun | null,
493
+ ): EncounterDef | undefined {
494
+ if (run?.routeEncounters?.length) {
495
+ const fromRun = run.routeEncounters.find((e) => e.id === encounterId);
496
+ if (fromRun) return fromRun;
497
+ }
498
+ return def.encounters.find((e) => e.id === encounterId);
499
+ }
500
+
501
+ export function isCleared(run: InstanceRun, encounterId: string): boolean {
502
+ return run.defeated.includes(encounterId);
503
+ }
504
+
505
+ // ---- flags -----------------------------------------------------------------
506
+
507
+ /** Flags present on this run (empty when unset). */
508
+ export function runFlags(run: InstanceRun): readonly string[] {
509
+ return run.flags ?? [];
510
+ }
511
+
512
+ export function hasFlag(run: InstanceRun, flag: string): boolean {
513
+ return (run.flags ?? []).includes(flag);
514
+ }
515
+
516
+ /** Add a flag (pure; idempotent — same reference when already present). */
517
+ export function setFlag(run: InstanceRun, flag: string): InstanceRun {
518
+ if ((run.flags ?? []).includes(flag)) return run;
519
+ return { ...run, flags: [...(run.flags ?? []), flag] };
520
+ }
521
+
522
+ /** `requiresFlag` entries on an encounter (empty when none). */
523
+ export function requiredFlags(enc: EncounterDef): string[] {
524
+ const out: string[] = [];
525
+ for (const ev of enc.events ?? []) {
526
+ if (ev.kind === 'requiresFlag') out.push(ev.flag);
527
+ }
528
+ return out;
529
+ }
530
+
531
+ /** `setsFlag` entries applied when the encounter fully clears. */
532
+ export function flagsSetOnClear(enc: EncounterDef): string[] {
533
+ const out: string[] = [];
534
+ for (const ev of enc.events ?? []) {
535
+ if (ev.kind === 'setsFlag') out.push(ev.flag);
536
+ }
537
+ return out;
538
+ }
539
+
540
+ /**
541
+ * An encounter is engageable once every `requiresFlag` on it is satisfied. Encounters
542
+ * not on the run's route are never unlocked.
543
+ *
544
+ * THE ROUTE ORDER IS AN ITINERARY, NOT A LOCK. This used to also require every
545
+ * non-optional route stop listed before it to be cleared, and that prefix was invented
546
+ * scaffolding — vanilla gates a dungeon with doors, keys and script flags, never with
547
+ * "kill the pack in front first". Three measurements against it:
548
+ *
549
+ * - blackrock-depths 2026-08-17 (probe-run-trace, band L55, 300 s at 8x): the route was
550
+ * 257 stops, so boss 4 sat behind 235 trash prerequisites. 3/22 bosses, 8/257 stops,
551
+ * and the autopilot's target at the end was `230-3fe13518` — a trash pack, because
552
+ * `nextAutoBossId` is forced to walk to "the first uncleared non-optional predecessor".
553
+ * - scholomance (dungeonRouteOrder.ts:531): one mis-ordered key gate "blocked all 49
554
+ * stops behind it: 9/58 cleared, 0/13 bosses".
555
+ * - UBRS / SFK (liveAutopilot.ts:472): the prefix read as sealed for nearby trash, so
556
+ * 8-10 of ~10 packs never streamed and chips never painted.
557
+ *
558
+ * The order still decides what the party WALKS to (`nextEncounter`, `routeEncounterList`,
559
+ * `nextAutoBossId`); it no longer decides what it may pull once it is standing there.
560
+ */
561
+ export function isUnlocked(def: InstanceDef, run: InstanceRun, encounterId: string): boolean {
562
+ const seq = routeEncounterIdList(def, run);
563
+ if (!seq.includes(encounterId)) return false;
564
+ const enc = encounterOf(def, encounterId, run);
565
+ if (!enc) return false;
566
+ for (const flag of requiredFlags(enc)) {
567
+ if (!hasFlag(run, flag)) return false;
568
+ }
569
+ return true;
570
+ }
571
+
572
+ /**
573
+ * Optional route encounter that drops an inside-key for a missing gate flag.
574
+ * Same "sealed boss is not a destination — walk to what opens it" rule as
575
+ * liveAutopilot, for keys that only drop from optional pulls (DMN Inner Door
576
+ * key 18268 ← Guard Mol'dar). Kromcrush hosts the GO but is not the dropper.
577
+ */
578
+ function optionalKeyOpenerForFlags(
579
+ def: InstanceDef,
580
+ run: InstanceRun,
581
+ seq: readonly string[],
582
+ missingFlags: readonly string[],
583
+ ): EncounterDef | undefined {
584
+ if (missingFlags.length === 0) return undefined;
585
+ const need = new Set(missingFlags);
586
+ const items = new Set<number>();
587
+ for (const other of def.encounters) {
588
+ for (const gate of objectGateEvents(other)) {
589
+ if (!need.has(gate.setsFlag) || gate.requiredItem == null) continue;
590
+ items.add(gate.requiredItem);
591
+ }
592
+ }
593
+ for (const item of items) {
594
+ const sources = routeItemSources(def.id, item);
595
+ // Empty ⇒ outside key; autoAdvanceRouteGates bypasses those — not a dest.
596
+ if (sources.length === 0) continue;
597
+ for (const id of seq) {
598
+ if (!sources.includes(id)) continue;
599
+ if (isCleared(run, id) || !isUnlocked(def, run, id)) continue;
600
+ const enc = encounterOf(def, id, run);
601
+ // Only the optional exception — non-optional sources are primary dests above.
602
+ if (!enc || enc.optional !== true) continue;
603
+ return enc;
604
+ }
605
+ }
606
+ return undefined;
607
+ }
608
+
609
+ /**
610
+ * The next engageable uncleared **non-optional** route encounter, or undefined
611
+ * if none remain (route complete, only optionals left, or remaining pulls are
612
+ * flag-gated). Matches `isComplete` and liveAutopilot destination skips — an
613
+ * optional boss is skippable, not deleted; `beginEncounter(def, optionalId)`
614
+ * still works when the player pulls it by hand.
615
+ *
616
+ * Exception: when a required encounter is sealed on a flag whose objectGate key
617
+ * drops only from an optional encounter still on the route, dest that opener
618
+ * (e.g. Guard Mol'dar for Gordok Inner Door key 18268) so the door can fire
619
+ * without pulling unrelated optionals like Captain Kromcrush.
620
+ */
621
+ export function nextEncounter(def: InstanceDef, run: InstanceRun): EncounterDef | undefined {
622
+ const seq = routeEncounterIdList(def, run);
623
+ for (const id of seq) {
624
+ if (isCleared(run, id) || !isUnlocked(def, run, id)) continue;
625
+ const enc = encounterOf(def, id, run);
626
+ // Optional tribute / skippable rares are not auto-run destinations.
627
+ if (enc?.optional === true) continue;
628
+ return enc;
629
+ }
630
+ // No unlocked required stop — look for an optional key-dropper that opens one.
631
+ for (const id of seq) {
632
+ if (isCleared(run, id)) continue;
633
+ const enc = encounterOf(def, id, run);
634
+ if (!enc || enc.optional === true) continue;
635
+ const missing = requiredFlags(enc).filter((f) => !hasFlag(run, f));
636
+ const opener = optionalKeyOpenerForFlags(def, run, seq, missing);
637
+ if (opener) return opener;
638
+ }
639
+ return undefined;
640
+ }
641
+
642
+ /** True once every non-optional route encounter has been cleared this run. */
643
+ export function isComplete(def: InstanceDef, run: InstanceRun): boolean {
644
+ return routeEncounterIdList(def, run).every((id) => {
645
+ if (isCleared(run, id)) return true;
646
+ return encounterOf(def, id, run)?.optional === true;
647
+ });
648
+ }
649
+
650
+ /** Record an encounter as defeated (returns a new run; idempotent). */
651
+ export function markDefeated(run: InstanceRun, encounterId: string): InstanceRun {
652
+ if (run.defeated.includes(encounterId)) return run;
653
+ return { ...run, defeated: [...run.defeated, encounterId] };
654
+ }
655
+
656
+ /**
657
+ * Mark an encounter cleared and apply its `setsFlag` events (and optional extra
658
+ * flags, e.g. HP phase markers). Pure; idempotent for the defeat list.
659
+ *
660
+ * Object gates are **not** applied here — use `useObjectGate` (manual, item
661
+ * checked) or `autoAdvanceRouteGates` (auto-run, item-bypassed + orphan flags).
662
+ */
663
+ export function applyEncounterCleared(
664
+ run: InstanceRun,
665
+ enc: EncounterDef,
666
+ extraFlags?: readonly string[],
667
+ ): InstanceRun {
668
+ let next = markDefeated(run, enc.id);
669
+ for (const f of flagsSetOnClear(enc)) next = setFlag(next, f);
670
+ if (extraFlags) {
671
+ for (const f of extraFlags) next = setFlag(next, f);
672
+ }
673
+ return next;
674
+ }
675
+
676
+ /** How many bosses this dungeon has, and how many are down — for a progress hint. */
677
+ export function bossProgress(def: InstanceDef, run: InstanceRun): { down: number; total: number } {
678
+ const bosses = def.encounters.filter((e) => e.kind === 'boss');
679
+ return { down: bosses.filter((e) => isCleared(run, e.id)).length, total: bosses.length };
680
+ }
681
+
682
+ // Boss progress on the **active route only** (density-filtered). Use for run scoring / "route cleared" UI
683
+ // so Normal/Speedrun aren't graded against Full.
684
+ export function routeBossProgress(def: InstanceDef, run: InstanceRun): { down: number; total: number } {
685
+ // Optional tribute guards do not count toward "route cleared" progress.
686
+ const bosses = routeEncounterList(def, run).filter((e) => e.kind === 'boss' && !e.optional);
687
+ return { down: bosses.filter((e) => isCleared(run, e.id)).length, total: bosses.length };
688
+ }
689
+
690
+ // ---- event DSL: waves / death-summon / HP thresholds / object gates --------
691
+
692
+ /** First `waves` event on the encounter, if any. */
693
+ export function wavesEvent(
694
+ enc: EncounterDef,
695
+ ): Extract<EncounterEvent, { kind: 'waves' }> | undefined {
696
+ return (enc.events ?? []).find((e): e is Extract<EncounterEvent, { kind: 'waves' }> => e.kind === 'waves');
697
+ }
698
+
699
+ /**
700
+ * Waves this encounter can run ON THE FLOOR: authored `waves` whose primary
701
+ * appears on the final wave (Ring / Doom'rel / Grubbis) OR on the first wave
702
+ * with a later follow-up pack (SFK Fenrus → voidwalkers after death). Flattened
703
+ * to entry/count for the live controller sequencer.
704
+ * Cite: instance_shadowfang_keep.cpp:184-191 TYPE_FENRUS DONE → Arugal + voidwalkers;
705
+ * shadowfang_keep.cpp:443-460 ARCHMAGE_VOIDWALKERS summon NPC_VOIDWALKER×4.
706
+ * Null when there are no waves or neither accepted shape matches.
707
+ */
708
+ export function floorGauntletWaves(
709
+ enc: EncounterDef,
710
+ ): { entry: number; count: number }[][] | null {
711
+ const w = wavesEvent(enc);
712
+ if (!w?.waves.length) return null;
713
+ const primary = enc.creatureEntry;
714
+ if (!(primary > 0)) return null;
715
+ const first = w.waves[0]!.pack ?? [];
716
+ const last = w.waves[w.waves.length - 1]!.pack ?? [];
717
+ const lastHas = last.some((m) => m.creatureEntry === primary);
718
+ const firstHas = first.some((m) => m.creatureEntry === primary);
719
+ if (!lastHas && !firstHas) return null;
720
+ return w.waves.map((wave) =>
721
+ (wave.pack ?? []).map((m) => ({ entry: m.creatureEntry, count: m.count })),
722
+ );
723
+ }
724
+
725
+ /** Ordered `onDeathSummon` events (phase-chain after primary). */
726
+ export function onDeathSummons(
727
+ enc: EncounterDef,
728
+ ): Extract<EncounterEvent, { kind: 'onDeathSummon' }>[] {
729
+ return (enc.events ?? []).filter(
730
+ (e): e is Extract<EncounterEvent, { kind: 'onDeathSummon' }> => e.kind === 'onDeathSummon',
731
+ );
732
+ }
733
+
734
+ /** Ordered `hpThreshold` events (stable authored order). */
735
+ export function hpThresholdEvents(
736
+ enc: EncounterDef,
737
+ ): Extract<EncounterEvent, { kind: 'hpThreshold' }>[] {
738
+ return (enc.events ?? []).filter(
739
+ (e): e is Extract<EncounterEvent, { kind: 'hpThreshold' }> => e.kind === 'hpThreshold',
740
+ );
741
+ }
742
+
743
+ // Object-gate events on this encounter (and, when scanning a dungeon, callers iterate all encounters).
744
+ // `once` defaults to true.
745
+ export function objectGateEvents(
746
+ enc: EncounterDef,
747
+ ): Extract<EncounterEvent, { kind: 'objectGate' }>[] {
748
+ return (enc.events ?? []).filter(
749
+ (e): e is Extract<EncounterEvent, { kind: 'objectGate' }> => e.kind === 'objectGate',
750
+ );
751
+ }
752
+
753
+ /** Entries that satisfy an objectGate (`goEntries` or sole `goEntry`). */
754
+ export function objectGateAcceptedEntries(
755
+ gate: Extract<EncounterEvent, { kind: 'objectGate' }>,
756
+ ): number[] {
757
+ if (gate.goEntries?.length) return [...gate.goEntries];
758
+ return [gate.goEntry];
759
+ }
760
+
761
+ /** Uses required before setsFlag (default 1). */
762
+ export function objectGateNeedUses(
763
+ gate: Extract<EncounterEvent, { kind: 'objectGate' }>,
764
+ ): number {
765
+ const n = gate.needUses ?? 1;
766
+ return n > 0 ? n : 1;
767
+ }
768
+
769
+ /** Find the first objectGate in the instance matching `goEntry` (primary or goEntries). */
770
+ export function findObjectGate(
771
+ def: InstanceDef,
772
+ goEntry: number,
773
+ ): { encounter: EncounterDef; gate: Extract<EncounterEvent, { kind: 'objectGate' }> } | undefined {
774
+ for (const enc of def.encounters) {
775
+ for (const g of objectGateEvents(enc)) {
776
+ if (g.goEntry === goEntry || g.goEntries?.includes(goEntry)) {
777
+ return { encounter: enc, gate: g };
778
+ }
779
+ }
780
+ }
781
+ return undefined;
782
+ }
783
+
784
+ /**
785
+ * How many auto-chained phases this encounter has (including the initial phase 0).
786
+ * - waves: waves.length
787
+ * - onDeathSummon: 1 + summons.length
788
+ * - hpThreshold with spawn: 1 (primary) + number of thresholds that have spawn
789
+ * - otherwise: 1
790
+ *
791
+ * When both waves and onDeathSummon are authored, waves win (mutually exclusive
792
+ * authoring intent for Wave 1).
793
+ */
794
+ export function encounterPhaseCount(enc: EncounterDef): number {
795
+ const waves = wavesEvent(enc);
796
+ if (waves && waves.waves.length > 0) return waves.waves.length;
797
+
798
+ const summons = onDeathSummons(enc);
799
+ const hpSpawns = hpThresholdEvents(enc).filter((t) => (t.spawn?.length ?? 0) > 0);
800
+ // Primary + each death-summon + each HP-spawn phase (HP spawns chain after summons).
801
+ return 1 + summons.length + hpSpawns.length;
802
+ }
803
+
804
+ /**
805
+ * Uniform CSE roll over survey/DB candidates only.
806
+ * CMaNGOS pattern: Creature::ResetEntry / GetRandomCreatureEntry (creature_spawn_entry).
807
+ * `rng` must return [0, 1) — inject mulberry32 / LOCK 4 (never Math.random / wall clock).
808
+ */
809
+ export function pickPoolCandidate(candidates: readonly number[], rng: () => number): number {
810
+ if (candidates.length === 0) {
811
+ throw new Error('pickPoolCandidate: empty candidates');
812
+ }
813
+ if (candidates.length === 1) return candidates[0]!;
814
+ const u = rng();
815
+ const idx = Math.min(candidates.length - 1, Math.floor(u * candidates.length));
816
+ return candidates[idx]!;
817
+ }
818
+
819
+ // Resolve CSE placeholders on one pack row to a concrete creatureEntry. Non-pool rows pass through. count
820
+ // preserved.
821
+ export function materializePackMember(
822
+ row: EncounterPackMember,
823
+ rng: () => number,
824
+ ): EncounterPackMember {
825
+ if (!row.poolCandidates?.length) {
826
+ return { creatureEntry: row.creatureEntry, count: row.count };
827
+ }
828
+ // One CSE slot per guid: count must be 1; still materialize count times if mis-authored.
829
+ if (row.count <= 1) {
830
+ return { creatureEntry: pickPoolCandidate(row.poolCandidates, rng), count: 1 };
831
+ }
832
+ // Degenerate: expand multi-count CSE into one roll each (should not be authored).
833
+ return {
834
+ creatureEntry: pickPoolCandidate(row.poolCandidates, rng),
835
+ count: row.count,
836
+ };
837
+ }
838
+
839
+ // Materialize CSE on an encounter (primary poolCandidates + pack rows). Returns a concrete pull safe for
840
+ // combat / kill credit. Injected RNG only.
841
+ export function materializeEncounterCse(
842
+ enc: EncounterDef,
843
+ rng: () => number,
844
+ ): EncounterDef {
845
+ const next: EncounterDef = {
846
+ ...enc,
847
+ creatureEntry: enc.poolCandidates?.length
848
+ ? pickPoolCandidate(enc.poolCandidates, rng)
849
+ : enc.creatureEntry,
850
+ };
851
+ delete next.poolCandidates;
852
+ if (enc.pack?.length) {
853
+ next.pack = enc.pack.map((row) => materializePackMember(row, rng));
854
+ }
855
+ return next;
856
+ }
857
+
858
+ /**
859
+ * Resolved pull for one phase of a multi-phase encounter.
860
+ * Returns null when `phaseIndex` is out of range.
861
+ *
862
+ * Phase layout:
863
+ * waves: phase i → waves[i].pack (first row is primary)
864
+ * else: phase 0 → primary + pack
865
+ * next phases → each onDeathSummon in order
866
+ * then each hpThreshold with spawn (pack becomes the pull)
867
+ *
868
+ * Optional `rng`: when provided, CSE poolCandidates are rolled to concrete entries
869
+ * (run/fight start — CMaNGOS ResetEntry). Without rng, placeholders remain
870
+ * (first candidate) for pure graph inspection.
871
+ */
872
+ export function resolveEncounterPhase(
873
+ enc: EncounterDef,
874
+ phaseIndex: number,
875
+ rng?: () => number,
876
+ ): {
877
+ creatureEntry: number;
878
+ bossSpells?: number[];
879
+ pack?: EncounterPackMember[];
880
+ } | null {
881
+ if (phaseIndex < 0) return null;
882
+
883
+ const base = rng ? materializeEncounterCse(enc, rng) : enc;
884
+
885
+ const waves = wavesEvent(base);
886
+ if (waves && waves.waves.length > 0) {
887
+ const w = waves.waves[phaseIndex];
888
+ if (!w) return null;
889
+ const pack = rng
890
+ ? w.pack.map((row) => materializePackMember(row, rng))
891
+ : w.pack;
892
+ return packToPull(pack);
893
+ }
894
+
895
+ if (phaseIndex === 0) {
896
+ return {
897
+ creatureEntry: base.creatureEntry,
898
+ bossSpells: base.bossSpells,
899
+ pack: base.pack,
900
+ };
901
+ }
902
+
903
+ const summons = onDeathSummons(base);
904
+ if (phaseIndex <= summons.length) {
905
+ const s = summons[phaseIndex - 1]!;
906
+ return { creatureEntry: s.creatureEntry, bossSpells: s.bossSpells };
907
+ }
908
+
909
+ const hpSpawns = hpThresholdEvents(base).filter((t) => (t.spawn?.length ?? 0) > 0);
910
+ const hi = phaseIndex - 1 - summons.length;
911
+ const thr = hpSpawns[hi];
912
+ if (!thr?.spawn) return null;
913
+ const spawn = rng
914
+ ? thr.spawn.map((row) => materializePackMember(row, rng))
915
+ : thr.spawn;
916
+ return packToPull(spawn);
917
+ }
918
+
919
+ /** Expand a pack table into primary + remaining pack members (first count>0 row wins as primary). */
920
+ export function packToPull(
921
+ pack: EncounterPackMember[],
922
+ ): { creatureEntry: number; pack?: EncounterPackMember[] } | null {
923
+ const expanded: EncounterPackMember[] = [];
924
+ for (const row of pack) {
925
+ if (row.count <= 0) continue;
926
+ // Drop poolCandidates from combat pull (callers should materialize first).
927
+ expanded.push({ creatureEntry: row.creatureEntry, count: row.count });
928
+ }
929
+ if (expanded.length === 0) return null;
930
+ const head = expanded[0]!;
931
+ const rest: EncounterPackMember[] = [];
932
+ if (head.count > 1) rest.push({ creatureEntry: head.creatureEntry, count: head.count - 1 });
933
+ for (let i = 1; i < expanded.length; i++) rest.push(expanded[i]!);
934
+ return {
935
+ creatureEntry: head.creatureEntry,
936
+ pack: rest.length ? rest : undefined,
937
+ };
938
+ }
939
+
940
+ /**
941
+ * Which HP thresholds fire at the given primary HP percent (0–100).
942
+ * `fired` is the set of threshold keys already consumed (`once` default true).
943
+ * Key format: `hp:<pct>` or `hp:<pct>:phase:<n>` when phase is authored.
944
+ */
945
+ export function hpThresholdKey(t: Extract<EncounterEvent, { kind: 'hpThreshold' }>): string {
946
+ return t.phase != null ? `hp:${t.pct}:phase:${t.phase}` : `hp:${t.pct}`;
947
+ }
948
+
949
+ export function thresholdsAtHp(
950
+ thresholds: readonly Extract<EncounterEvent, { kind: 'hpThreshold' }>[],
951
+ hpPct: number,
952
+ fired: ReadonlySet<string> | readonly string[],
953
+ ): Extract<EncounterEvent, { kind: 'hpThreshold' }>[] {
954
+ const done = fired instanceof Set ? fired : new Set(fired);
955
+ const out: Extract<EncounterEvent, { kind: 'hpThreshold' }>[] = [];
956
+ for (const t of thresholds) {
957
+ if (hpPct > t.pct) continue;
958
+ const key = hpThresholdKey(t);
959
+ const once = t.once !== false;
960
+ if (once && done.has(key)) continue;
961
+ out.push(t);
962
+ }
963
+ return out;
964
+ }
965
+
966
+ // Flags to set when the primary has been reduced to `hpPct` (and/or killed at 0). Includes `phase:<n>` for
967
+ // authored phase numbers and the stable `hpThresholdKey`.
968
+ export function flagsForHpThresholds(
969
+ thresholds: readonly Extract<EncounterEvent, { kind: 'hpThreshold' }>[],
970
+ hpPct: number,
971
+ already: ReadonlySet<string> | readonly string[],
972
+ ): string[] {
973
+ const fired = thresholdsAtHp(thresholds, hpPct, already);
974
+ const out: string[] = [];
975
+ for (const t of fired) {
976
+ out.push(hpThresholdKey(t));
977
+ if (t.phase != null) out.push(`phase:${t.phase}`);
978
+ }
979
+ return out;
980
+ }
981
+
982
+ // Result of attempting to use an instance object (GO entry). Pure: does not touch inventory (caller checks
983
+ // item presence and spends `consumeItem`).
984
+ export type UseObjectResult =
985
+ | {
986
+ ok: true;
987
+ run: InstanceRun;
988
+ flag: string;
989
+ already: boolean;
990
+ /** Uses counted toward needUses after this call (0 when already). */
991
+ uses: number;
992
+ needUses: number;
993
+ /** True when this call set (or already had) setsFlag. */
994
+ completed: boolean;
995
+ /** Entry to destroy one of (successful use, spellcharges_1 < 0). Cite: Spell.cpp:4209 TakeCastItem. */
996
+ consumeItem?: number;
997
+ }
998
+ | { ok: false; reason: string };
999
+
1000
+ /**
1001
+ * Apply an object-use gate. `hasItem` is true when the optional required item is
1002
+ * available (caller owns inventory). When `once` (default true) and the flag is
1003
+ * already set, returns ok with `already: true` and an unchanged run (no-op).
1004
+ *
1005
+ * Multi-step: increments objectGateUses[setsFlag]; sets flag only when uses ≥ needUses
1006
+ * (default 1). Multi-entry gates (goEntries length > 1) count each entry at most once.
1007
+ * Each successful use reports `consumeItem` independently (ST 4× Hakkari Blood).
1008
+ */
1009
+ export function useObjectGate(
1010
+ run: InstanceRun,
1011
+ gate: Extract<EncounterEvent, { kind: 'objectGate' }>,
1012
+ hasItem: boolean,
1013
+ opts?: { goEntry?: number; /** ItemDef.charges for gate.requiredItem (0 = keep). */ itemCharges?: number },
1014
+ ): UseObjectResult {
1015
+ const need = objectGateNeedUses(gate);
1016
+ const allowed = objectGateAcceptedEntries(gate);
1017
+ const usedEntry = opts?.goEntry ?? gate.goEntry;
1018
+ if (!allowed.includes(usedEntry)) {
1019
+ return { ok: false, reason: 'Nothing happens' };
1020
+ }
1021
+ // already before the item check: a consumed key must not turn a no-op into "you lack".
1022
+ const once = gate.once !== false;
1023
+ if (once && hasFlag(run, gate.setsFlag)) {
1024
+ return {
1025
+ ok: true,
1026
+ run,
1027
+ flag: gate.setsFlag,
1028
+ already: true,
1029
+ uses: need,
1030
+ needUses: need,
1031
+ completed: true,
1032
+ };
1033
+ }
1034
+
1035
+ const multiEntry = allowed.length > 1;
1036
+ const prevUsed = run.objectGateUsedEntries?.[gate.setsFlag] ?? [];
1037
+ if (multiEntry && prevUsed.includes(usedEntry)) {
1038
+ const uses = run.objectGateUses?.[gate.setsFlag] ?? prevUsed.length;
1039
+ return {
1040
+ ok: true,
1041
+ run,
1042
+ flag: gate.setsFlag,
1043
+ already: true,
1044
+ uses,
1045
+ needUses: need,
1046
+ completed: hasFlag(run, gate.setsFlag),
1047
+ };
1048
+ }
1049
+
1050
+ if (gate.requiredItem != null && !hasItem) {
1051
+ return { ok: false, reason: 'You lack the required item' };
1052
+ }
1053
+
1054
+ let next: InstanceRun = run;
1055
+ if (multiEntry) {
1056
+ const usedEntries = [...prevUsed, usedEntry];
1057
+ next = {
1058
+ ...next,
1059
+ objectGateUsedEntries: {
1060
+ ...(next.objectGateUsedEntries ?? {}),
1061
+ [gate.setsFlag]: usedEntries,
1062
+ },
1063
+ };
1064
+ }
1065
+ const prevCount = next.objectGateUses?.[gate.setsFlag] ?? 0;
1066
+ const count = prevCount + 1;
1067
+ next = {
1068
+ ...next,
1069
+ objectGateUses: { ...(next.objectGateUses ?? {}), [gate.setsFlag]: count },
1070
+ };
1071
+ if (count >= need) {
1072
+ next = setFlag(next, gate.setsFlag);
1073
+ // Strat postboxes: Malown spawns at the box that completed needUses=3.
1074
+ // Cite: stratholmeScripts.cpp:108-110; docs/dungeons/malown/README.md.
1075
+ if (gate.setsFlag === STRATH_LIVE_POSTBOX.setsFlag) {
1076
+ const origin = postboxOrigin(usedEntry);
1077
+ if (origin) {
1078
+ next = {
1079
+ ...next,
1080
+ encounterOrigins: {
1081
+ ...(next.encounterOrigins ?? {}),
1082
+ [STRATH_LIVE_POSTBOX.encId]: { x: origin.x, y: origin.y, z: origin.z },
1083
+ },
1084
+ };
1085
+ }
1086
+ }
1087
+ }
1088
+ // TakeCastItem destroys only when SpellCharges < 0; bake stamps 0 and omits −1.
1089
+ const consumeItem =
1090
+ gate.requiredItem != null && opts?.itemCharges !== 0 ? gate.requiredItem : undefined;
1091
+ return {
1092
+ ok: true,
1093
+ run: next,
1094
+ flag: gate.setsFlag,
1095
+ already: false,
1096
+ uses: count,
1097
+ needUses: need,
1098
+ completed: count >= need,
1099
+ ...(consumeItem != null ? { consumeItem } : {}),
1100
+ };
1101
+ }
1102
+
1103
+ /** Flags an encounter sets on clear (setsFlag events + hosted objectGate.setsFlag). */
1104
+ export function flagsSetByEncounter(enc: EncounterDef): string[] {
1105
+ const out: string[] = [...flagsSetOnClear(enc)];
1106
+ for (const gate of objectGateEvents(enc)) {
1107
+ if (!out.includes(gate.setsFlag)) out.push(gate.setsFlag);
1108
+ }
1109
+ return out;
1110
+ }
1111
+
1112
+ /**
1113
+ * Auto-run helper: for every uncleared encounter that is sequentially reachable
1114
+ * but missing flags, try matching objectGates anywhere in the instance.
1115
+ *
1116
+ * `hasItem` gates real inventory for UI-driven use. Pass
1117
+ * `opts.ignoreRequiredItem: true` on squad/live/headless auto-runs so OUTSIDE key
1118
+ * items (Seal of Ascension, Staff of Prehistoria, …) do not soft-lock the route.
1119
+ * Keys that drop inside this instance stay honest via `opts.keyDropsInside`.
1120
+ * Manual object-use still enforces items via `useObjectGate`.
1121
+ */
1122
+ export function autoFireObjectGates(
1123
+ def: InstanceDef,
1124
+ run: InstanceRun,
1125
+ hasItem: (itemEntry: number) => boolean,
1126
+ opts?: {
1127
+ ignoreRequiredItem?: boolean;
1128
+ /** True when the key drops from creatures/GOs in this instance (plan 4d). */
1129
+ keyDropsInside?: (itemEntry: number) => boolean;
1130
+ },
1131
+ ): { run: InstanceRun; fired: number; consumedItems: number[] } {
1132
+ let next = run;
1133
+ let fired = 0;
1134
+ const consumedItems: number[] = [];
1135
+ const seq = routeEncounterIdList(def, run);
1136
+ const ignoreItem = opts?.ignoreRequiredItem === true;
1137
+ const dropsInside = opts?.keyDropsInside;
1138
+ // Iterate until no new gates fire (flags can unlock further gated uses).
1139
+ for (let guard = 0; guard < 32; guard++) {
1140
+ let progressed = false;
1141
+ for (const encId of seq) {
1142
+ const enc = encounterOf(def, encId, next);
1143
+ if (!enc) continue;
1144
+ if (isCleared(next, enc.id)) continue;
1145
+ // Sequential **route** prefix must be clear before we auto-open gates.
1146
+ // Optional tribute guards do not block gate firing for later pulls.
1147
+ const idx = seq.indexOf(enc.id);
1148
+ let seqOk = true;
1149
+ for (let i = 0; i < idx; i++) {
1150
+ const prev = encounterOf(def, seq[i]!, next);
1151
+ if (prev?.optional) continue;
1152
+ if (!isCleared(next, seq[i]!)) {
1153
+ seqOk = false;
1154
+ break;
1155
+ }
1156
+ }
1157
+ if (!seqOk) continue;
1158
+
1159
+ const missing = requiredFlags(enc).filter((f) => !hasFlag(next, f));
1160
+ if (missing.length === 0) continue;
1161
+
1162
+ // Search whole instance for objectGates that set a missing flag.
1163
+ for (const other of def.encounters) {
1164
+ for (const gate of objectGateEvents(other)) {
1165
+ if (!missing.includes(gate.setsFlag)) continue;
1166
+ const req = gate.requiredItem;
1167
+ const honest = req != null && dropsInside?.(req) === true;
1168
+ const owned = req == null || hasItem(req);
1169
+ const okItem = owned || (ignoreItem && !honest);
1170
+ // Multi-step: walk accepted entries (or repeat sole entry needUses times).
1171
+ const allowed = objectGateAcceptedEntries(gate);
1172
+ const need = objectGateNeedUses(gate);
1173
+ if (allowed.length > 1) {
1174
+ for (const entry of allowed) {
1175
+ if (hasFlag(next, gate.setsFlag)) break;
1176
+ const field = moltenCoreRuneField(entry);
1177
+ // molten_coreScripts.cpp:47-52 GetData != DONE → no SetData SPECIAL.
1178
+ if (
1179
+ field != null
1180
+ && (next.scriptInstanceData?.[field] ?? INSTANCE_DATA.NOT_STARTED)
1181
+ !== INSTANCE_DATA.DONE
1182
+ ) {
1183
+ continue;
1184
+ }
1185
+ const r = useObjectGate(next, gate, okItem, { goEntry: entry });
1186
+ if (r.ok && !r.already) {
1187
+ next = r.run;
1188
+ if (field != null) next = applyMoltenCoreRuneSpecial(next, field);
1189
+ fired++;
1190
+ progressed = true;
1191
+ // Spend only when the party actually held the item (not outside-key bypass).
1192
+ if (r.consumeItem != null && owned) consumedItems.push(r.consumeItem);
1193
+ }
1194
+ }
1195
+ } else {
1196
+ for (let i = 0; i < need; i++) {
1197
+ if (hasFlag(next, gate.setsFlag)) break;
1198
+ const entry = allowed[0]!;
1199
+ const field = moltenCoreRuneField(entry);
1200
+ if (
1201
+ field != null
1202
+ && (next.scriptInstanceData?.[field] ?? INSTANCE_DATA.NOT_STARTED)
1203
+ !== INSTANCE_DATA.DONE
1204
+ ) {
1205
+ break;
1206
+ }
1207
+ const r = useObjectGate(next, gate, okItem, { goEntry: entry });
1208
+ if (r.ok && !r.already) {
1209
+ next = r.run;
1210
+ if (field != null) next = applyMoltenCoreRuneSpecial(next, field);
1211
+ fired++;
1212
+ progressed = true;
1213
+ if (r.consumeItem != null && owned) consumedItems.push(r.consumeItem);
1214
+ } else {
1215
+ break;
1216
+ }
1217
+ }
1218
+ }
1219
+ }
1220
+ }
1221
+ }
1222
+ if (!progressed) break;
1223
+ }
1224
+ return { run: next, fired, consumedItems };
1225
+ }
1226
+
1227
+ /**
1228
+ * When route density skips the packs that would `setsFlag` a gate (e.g. SFK
1229
+ * top-floor worgs, Strat courtyard band, UBRS rune rooms), auto-set those flags
1230
+ * so the run does not soft-lock on Normal/Speedrun.
1231
+ *
1232
+ * Density orphans are `setsFlag`-on-clear only. Never stamp a flag that any
1233
+ * `objectGate.setsFlag` produces — key doors stay on autoFire honesty + manual use
1234
+ * (Deadmines iron-clad / Defias Gunpowder 5397).
1235
+ */
1236
+ export function autoSatisfyOrphanRouteFlags(
1237
+ def: InstanceDef,
1238
+ run: InstanceRun,
1239
+ ): { run: InstanceRun; fired: number } {
1240
+ let next = run;
1241
+ let fired = 0;
1242
+ const seq = routeEncounterIdList(def, run);
1243
+
1244
+ const objectGateFlags = new Set<string>();
1245
+ for (const enc of def.encounters) {
1246
+ for (const gate of objectGateEvents(enc)) objectGateFlags.add(gate.setsFlag);
1247
+ }
1248
+
1249
+ for (let guard = 0; guard < 32; guard++) {
1250
+ let progressed = false;
1251
+ for (const encId of seq) {
1252
+ const enc = encounterOf(def, encId, next);
1253
+ if (!enc || isCleared(next, enc.id)) continue;
1254
+ const idx = seq.indexOf(enc.id);
1255
+ let seqOk = true;
1256
+ for (let i = 0; i < idx; i++) {
1257
+ // Optional stops are skippable, not prerequisites — the same read `diagnose` and
1258
+ // `nextEncounter` take. Counting them here meant one un-pulled optional boss held the
1259
+ // stamp forever, so `wc-disciple-ritual` (no setter anywhere) read as an authoring gap.
1260
+ if (encounterOf(def, seq[i]!, next)?.optional) continue;
1261
+ if (!isCleared(next, seq[i]!)) {
1262
+ seqOk = false;
1263
+ break;
1264
+ }
1265
+ }
1266
+ if (!seqOk) continue;
1267
+
1268
+ for (const flag of requiredFlags(enc)) {
1269
+ if (hasFlag(next, flag)) continue;
1270
+ // Key / GO doors: never density-orphan-stamp (KI-940).
1271
+ if (objectGateFlags.has(flag)) continue;
1272
+
1273
+ // Route encounters that set this flag on clear (setsFlag events only).
1274
+ const setters: string[] = [];
1275
+ for (const sid of seq) {
1276
+ const sEnc = encounterOf(def, sid, next);
1277
+ if (!sEnc) continue;
1278
+ if (flagsSetOnClear(sEnc).includes(flag)) setters.push(sid);
1279
+ }
1280
+
1281
+ // Orphan: nothing on this route sets the flag (density skipped them).
1282
+ // Or all setters already cleared but flag still missing (authoring gap).
1283
+ const orphan =
1284
+ setters.length === 0
1285
+ || setters.every((sid) => isCleared(next, sid));
1286
+ if (!orphan) continue;
1287
+
1288
+ next = setFlag(next, flag);
1289
+ fired++;
1290
+ progressed = true;
1291
+ }
1292
+ }
1293
+ if (!progressed) break;
1294
+ }
1295
+ return { run: next, fired };
1296
+ }
1297
+
1298
+ // Full auto-run unlock pass: object gates (outside keys bypassed; in-instance keys honest) + orphan density
1299
+ // flags. Call before every `nextEncounter` in live / headless / runFullDungeon.
1300
+ export function autoAdvanceRouteGates(
1301
+ def: InstanceDef,
1302
+ run: InstanceRun,
1303
+ hasItem: (itemEntry: number) => boolean = () => false,
1304
+ opts?: { keyDropsInside?: (itemEntry: number) => boolean },
1305
+ ): { run: InstanceRun; fired: number; consumedItems: number[] } {
1306
+ let next = run;
1307
+ let fired = 0;
1308
+ const consumedItems: number[] = [];
1309
+ for (let guard = 0; guard < 8; guard++) {
1310
+ const a = autoFireObjectGates(def, next, hasItem, {
1311
+ ignoreRequiredItem: true,
1312
+ keyDropsInside: opts?.keyDropsInside,
1313
+ });
1314
+ next = a.run;
1315
+ fired += a.fired;
1316
+ consumedItems.push(...a.consumedItems);
1317
+ const b = autoSatisfyOrphanRouteFlags(def, next);
1318
+ next = b.run;
1319
+ fired += b.fired;
1320
+ if (a.fired + b.fired === 0) break;
1321
+ }
1322
+ return { run: next, fired, consumedItems };
1323
+ }
1324
+
1325
+ type LootRow = { item: number; conditionId?: number };
1326
+
1327
+ /**
1328
+ * True when `itemEntry` appears on a creature or GO loot table for this instance def.
1329
+ * Used to keep auto-run honest for keys that drop inside (Gordok Courtyard/Inner keys).
1330
+ *
1331
+ * Sources: encounter creature loot, objectGate GO lootIds, `def.lootObjects`, and
1332
+ * floor GOs from `dungeonGameObjectsForMap(def.mapId)` (Defias Gunpowder GO 17155).
1333
+ *
1334
+ * `rowAllowed` filters condition-gated rows (13523 / condition 213 QUESTREWARDED 5384).
1335
+ * Omit it to ask "does any row exist"; pass lootDropAllowed to ask "can THIS party loot it".
1336
+ */
1337
+ export function itemDropsInsideInstance(
1338
+ def: InstanceDef,
1339
+ itemEntry: number,
1340
+ lootTable: (lootId: number | string) => readonly LootRow[],
1341
+ goLootId?: (goEntry: number) => number | undefined,
1342
+ rowAllowed?: (row: LootRow) => boolean,
1343
+ ): boolean {
1344
+ const seen = new Set<number>();
1345
+ const seenGoLoot = new Set<number>();
1346
+ const hits = (rows: readonly LootRow[]): boolean =>
1347
+ rows.some((r) => r.item === itemEntry && (rowAllowed ? rowAllowed(r) : true));
1348
+ const checkCreature = (entry: number): boolean => {
1349
+ if (seen.has(entry)) return false;
1350
+ seen.add(entry);
1351
+ return hits(lootTable(entry));
1352
+ };
1353
+ const checkGoLoot = (lid: number | undefined): boolean => {
1354
+ if (lid == null || !(lid > 0) || seenGoLoot.has(lid)) return false;
1355
+ seenGoLoot.add(lid);
1356
+ return hits(lootTable(`go:${lid}`));
1357
+ };
1358
+ for (const enc of def.encounters) {
1359
+ if (checkCreature(enc.creatureEntry)) return true;
1360
+ for (const p of enc.pack ?? []) {
1361
+ if (checkCreature(p.creatureEntry)) return true;
1362
+ }
1363
+ for (const ev of enc.events ?? []) {
1364
+ if (ev.kind !== 'objectGate') continue;
1365
+ if (checkGoLoot(goLootId?.(ev.goEntry))) return true;
1366
+ }
1367
+ }
1368
+ for (const lo of def.lootObjects ?? []) {
1369
+ if (checkGoLoot(goLootId?.(lo.goEntry))) return true;
1370
+ }
1371
+ // Floor chests / powder piles are not on encounters or lootObjects (KI-940).
1372
+ for (const go of dungeonGameObjectsForMap(def.mapId)) {
1373
+ if (checkGoLoot(go.lootId)) return true;
1374
+ }
1375
+ return false;
1376
+ }
1377
+
1378
+ // ---- instance save (5-mans: 30 min; raids: until lockout / reset_delay) ------
1379
+
1380
+ /**
1381
+ * How long a normal (5-man) instance keeps its state after you walk out.
1382
+ * Cite: MapPersistentStateMgr.h:41 `#define NORMAL_INSTANCE_RESET_TIME 30 * MINUTE`.
1383
+ * Raid/heroic saves are NOT this macro — they use `def.lockoutMs` (instance_template
1384
+ * reset_delay); do not reuse this constant for them.
1385
+ */
1386
+ export const NORMAL_INSTANCE_RESET_TIME_MS = 30 * 60 * 1000;
1387
+
1388
+ /** A walked-out-of run, held until `expiresAtMs` (30 min or lockout). Cite: MapPersistentState unload-or-reset. */
1389
+ export interface InstanceSave {
1390
+ run: InstanceRun;
1391
+ expiresAtMs: number;
1392
+ }
1393
+
1394
+ /**
1395
+ * dungeonId -> held instance state. A map, not one slot: saves are per-instance in
1396
+ * CMaNGOS (`MapPersistentStateManager` keyed by instance id), so entering Deadmines
1397
+ * must not reset your Wailing Caverns clear. Raids land here too — the lockout is
1398
+ * the bind clock, this map is the instance itself.
1399
+ */
1400
+ export type InstanceSaveMap = Record<string, InstanceSave>;
1401
+
1402
+ /** Does this run hold progress worth keeping across a step outside? */
1403
+ function runHasProgress(run: InstanceRun): boolean {
1404
+ return run.defeated.length > 0
1405
+ || (run.floorClearedGuids?.length ?? 0) > 0
1406
+ || (run.flags?.length ?? 0) > 0;
1407
+ }
1408
+
1409
+ /** Drop saves whose `expiresAtMs` has passed — those instances reset. */
1410
+ export function pruneInstanceSaves(saves: InstanceSaveMap, nowMs: number): InstanceSaveMap {
1411
+ const out: InstanceSaveMap = {};
1412
+ for (const [id, s] of Object.entries(saves)) {
1413
+ if (s.expiresAtMs > nowMs) out[id] = s;
1414
+ }
1415
+ return out;
1416
+ }
1417
+
1418
+ /** 5-man: 30 min. Raid: existing lockout, else now + reset_delay. Cite: MapPersistentStateMgr.cpp:348-352. */
1419
+ function saveExpiresAtMs(def: InstanceDef, nowMs: number, lockouts?: LockoutMap): number {
1420
+ if (def.lockoutMs > 0) {
1421
+ const until = lockouts?.[def.id];
1422
+ if (until != null && until > nowMs) return until;
1423
+ return nowMs + def.lockoutMs;
1424
+ }
1425
+ return nowMs + NORMAL_INSTANCE_RESET_TIME_MS;
1426
+ }
1427
+
1428
+ // File the run on leaving, pruning expired ones. No progress → drop. 5-mans expire in 30 min; raids expire
1429
+ // at the lockout (reset_delay), so a mid-raid leave is not a reset.
1430
+ export function saveRunOnLeave(
1431
+ saves: InstanceSaveMap,
1432
+ run: InstanceRun,
1433
+ def: InstanceDef,
1434
+ nowMs: number,
1435
+ lockouts?: LockoutMap,
1436
+ ): InstanceSaveMap {
1437
+ const pruned = pruneInstanceSaves(saves, nowMs);
1438
+ if (!runHasProgress(run)) {
1439
+ delete pruned[def.id];
1440
+ return pruned;
1441
+ }
1442
+ return { ...pruned, [def.id]: { run, expiresAtMs: saveExpiresAtMs(def, nowMs, lockouts) } };
1443
+ }
1444
+
1445
+ // The saved run to re-enter with, or null when there is none for this dungeon or `expiresAtMs` has passed
1446
+ // (the instance reset while you were out).
1447
+ export function restoreSavedRun(
1448
+ saves: InstanceSaveMap | undefined,
1449
+ dungeonId: string,
1450
+ nowMs: number,
1451
+ ): InstanceRun | null {
1452
+ const save = saves?.[dungeonId];
1453
+ if (!save || save.expiresAtMs <= nowMs) return null;
1454
+ if (save.run.dungeonId !== dungeonId) return null;
1455
+ // molten_core.cpp:62-65 OnPlayerEnter on a saved instance.
1456
+ return applyMajordomoSpawnOnPlayerEnter(save.run);
1457
+ }
1458
+
1459
+ // ---- lockouts (raid/heroic; 5-mans use lockoutMs 0 and never lock) ----------
1460
+
1461
+ export function isLocked(lockouts: LockoutMap, dungeonId: string, nowMs: number): boolean {
1462
+ const until = lockouts[dungeonId];
1463
+ return until != null && until > nowMs;
1464
+ }
1465
+
1466
+ export function lockRemainingMs(lockouts: LockoutMap, dungeonId: string, nowMs: number): number {
1467
+ const until = lockouts[dungeonId];
1468
+ return until != null && until > nowMs ? until - nowMs : 0;
1469
+ }
1470
+
1471
+ /** Save the player to a dungeon on completion (no-op for freely-resettable 5-mans). */
1472
+ export function applyLockout(lockouts: LockoutMap, def: InstanceDef, nowMs: number): LockoutMap {
1473
+ if (def.lockoutMs <= 0) return lockouts;
1474
+ return { ...lockouts, [def.id]: nowMs + def.lockoutMs };
1475
+ }
1476
+
1477
+ // ---- portals ----------------------------------------------------------------
1478
+
1479
+ /** Dungeons whose entrance portal is in the given world zone. */
1480
+ export function instancePortals(ds: DataStore, zoneId: string): InstanceDef[] {
1481
+ return ds.instances().filter((d) => d.portalZone === zoneId);
1482
+ }
1483
+
1484
+ export interface EnterCheck {
1485
+ ok: boolean;
1486
+ reason?: string;
1487
+ }
1488
+
1489
+ /**
1490
+ * Optional bag for areatrigger_teleport lock chain (GetAreaTriggerLockStatus).
1491
+ * When `teleports` is provided and an entrance row matches `def.portalZone`,
1492
+ * the trigger's requiredLevel/item/quest gates replace authored minLevel.
1493
+ * When no matching row (or teleports omitted), authored `def.minLevel` remains.
1494
+ *
1495
+ * Wiring (game.ts enterInstance — do not implement here):
1496
+ * canEnter(def, level, lockouts, nowMs, {
1497
+ * teleports: b.areatriggerTeleports,
1498
+ * ownedItems: inventory item entry set,
1499
+ * completedQuests: turned-in quest id set,
1500
+ * nameFragment: optional dungeon name hint,
1501
+ * })
1502
+ */
1503
+ export interface CanEnterContext {
1504
+ /** Baked areatrigger_teleport rows (DataBundle.areatriggerTeleports). */
1505
+ teleports?: readonly AreaTriggerTeleportRow[];
1506
+ /** Narrow entrance match by name substring (e.g. "Deadmines", "Onyxia"). */
1507
+ nameFragment?: string;
1508
+ ownedItems?: ReadonlySet<number> | readonly number[];
1509
+ hasItem?: (entry: number) => boolean;
1510
+ completedQuests?: ReadonlySet<number> | readonly number[];
1511
+ hasQuestRewarded?: (questId: number) => boolean;
1512
+ /** Filed run(s). Lockout + a live save is re-entry of YOUR instance, not a bar. */
1513
+ instanceSaves?: InstanceSaveMap;
1514
+ }
1515
+
1516
+ /**
1517
+ * Whether a character may enter a dungeon right now.
1518
+ * Lockout without a filed save refuses a *fresh* raid; lockout + save allows
1519
+ * re-entry of that same instance. Level/item/quest: sourced lock chain when a
1520
+ * matching entrance teleport exists; otherwise authored `def.minLevel`.
1521
+ */
1522
+ export function canEnter(
1523
+ def: InstanceDef,
1524
+ level: number,
1525
+ lockouts: LockoutMap,
1526
+ nowMs: number,
1527
+ ctx?: CanEnterContext,
1528
+ ): EnterCheck {
1529
+ // Sourced lock chain when teleports supplied and an entrance matches portalZone.
1530
+ // requiredLevel on the trigger replaces authored minLevel when present;
1531
+ // if the trigger has no requiredLevel, fall back to def.minLevel.
1532
+ if (ctx?.teleports) {
1533
+ const entrance = def.entranceTriggerId != null
1534
+ ? ctx.teleports.find((t) => t.id === def.entranceTriggerId)
1535
+ : findEntranceTeleport(def.portalZone, ctx.teleports, ctx.nameFragment);
1536
+ if (entrance) {
1537
+ const teleport = {
1538
+ requiredLevel:
1539
+ (entrance.requiredLevel ?? 0) > 0 ? entrance.requiredLevel : def.minLevel,
1540
+ requiredItem: entrance.requiredItem,
1541
+ requiredItem2: entrance.requiredItem2,
1542
+ requiredQuest: entrance.requiredQuest,
1543
+ statusFailedText: entrance.statusFailedText,
1544
+ };
1545
+ const lock = checkAreaTriggerLock({
1546
+ level,
1547
+ ownedItems: ctx.ownedItems,
1548
+ hasItem: ctx.hasItem,
1549
+ completedQuests: ctx.completedQuests,
1550
+ hasQuestRewarded: ctx.hasQuestRewarded,
1551
+ teleport,
1552
+ });
1553
+ if (!lock.ok) return { ok: false, reason: lock.reason };
1554
+ } else if (level < def.minLevel) {
1555
+ // No matching trigger row → keep authored minLevel (jasperlode / unbaked)
1556
+ return { ok: false, reason: `Requires level ${def.minLevel}` };
1557
+ }
1558
+ } else if (level < def.minLevel) {
1559
+ return { ok: false, reason: `Requires level ${def.minLevel}` };
1560
+ }
1561
+
1562
+ // Perm bind is to YOUR instance. HAS_BIND only fires when the target is a *different*
1563
+ // instance. Cite: Player.cpp:19990-19994; Map.cpp:1797-1812.
1564
+ if (isLocked(lockouts, def.id, nowMs) && !restoreSavedRun(ctx?.instanceSaves, def.id, nowMs)) {
1565
+ return { ok: false, reason: 'You are saved to this instance' };
1566
+ }
1567
+ return { ok: true };
1568
+ }
1569
+
1570
+ /** Re-export for callers that only import world/instances. */
1571
+ export { findEntranceTeleport, checkAreaTriggerLock } from '../domain/world/areaTriggerLocks.ts';
1572
+ export type { AreaTriggerTeleportRow, AreaTriggerLockResult } from '../domain/world/areaTriggerLocks.ts';