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,1545 @@
1
+ // Quest log plus the pure CanTakeQuest and Satisfy* gates. Cite: Player.cpp CanTakeQuest ~12198,
2
+ // SatisfyQuest* ~12776–13103; QuestHandler.cpp getDialogStatus ~628.
3
+ import type { ItemDef, QuestDef } from '../../data/schema.ts';
4
+ import type { Inventory } from '../items/inventory.ts';
5
+ import { countItem } from '../items/inventory.ts';
6
+ // QI5 cutover — interpreter CanCompleteQuest (lazy-safe static imports; live.ts does not import questlog).
7
+ import { canCompleteQuest } from './interpreter/canComplete.ts';
8
+ import {
9
+ shouldUseQuestInterpreter,
10
+ statusFromQuestState,
11
+ } from './interpreter/live.ts';
12
+ import { QUEST_SPECIAL_FLAG_REPEATABLE } from './interpreter/flags.ts';
13
+
14
+ export type QuestStatus = 'active' | 'complete' | 'turnedIn' | 'failed';
15
+
16
+ // QuestSpecialFlags bit 2: an exploration areatrigger or scripted event credit. Escort quests bake
17
+ // it with empty targets, so without requiring m_explored they free-complete on accept (KI-082).
18
+ export const QUEST_SPECIAL_FLAG_EXPLORATION_OR_EVENT = 2;
19
+
20
+ export interface QuestState {
21
+ entry: number;
22
+ status: QuestStatus;
23
+ kills: Record<string, number>; // creatureEntry (as string) -> count
24
+ objects: Record<string, number>; // objectEntry (as string) -> count (GameObject / chest / interact for Wave 3)
25
+ // Spell-cast objective progress (ReqSpellCast* / spellCastTargets). spell id (string) → casts. Optional
26
+ // on pre-FO3 saves → treat as {}. Cite: Player.cpp CastedCreatureOrGO; FO3.
27
+ spells?: Record<string, number>;
28
+ // AreaExplored or event objective met, for the EXPLORATION_OR_EVENT flag. Optional on pre-H2
29
+ // saves, where it reads false. Cite: Player.cpp AreaExploredOrEventHappens; CanCompleteQuest.
30
+ explored?: boolean;
31
+ // Source-item use-on-target progress: item entry to use count. Optional on old saves. Presence
32
+ // alone does not complete a pure sourceItemTargets quest — see requiresSourceUseCredit.
33
+ uses?: Record<string, number>;
34
+ // Talk-to-NPC or deliver-arrival objective met. Optional on old saves. Gates isTalkToNpcQuest
35
+ // and isDeliverQuest. Cite: Player.cpp TalkedToCreature; KI-845.
36
+ talked?: boolean;
37
+ /** Remaining time in ms for timed quests. Missing/undefined implies no timer. */
38
+ timerMs?: number;
39
+ }
40
+ export interface QuestLog {
41
+ quests: QuestState[];
42
+ }
43
+
44
+ // Optional CMaNGOS quest template fields not yet baked on every QuestDef. Safe to pass as QuestDef &
45
+ // QuestGatingFields; absent fields are ignored. Cite: QuestDef.h / quest_template columns.
46
+ export interface QuestGatingFields {
47
+ /** MaxLevel; default unlimited when absent (SatisfyQuestLevel). */
48
+ maxLevel?: number;
49
+ /** ExclusiveGroup; >0 = one-from-all while sibling active/complete. */
50
+ exclusiveGroup?: number;
51
+ /** Other quest entries sharing exclusiveGroup (caller-supplied; no ObjectMgr). */
52
+ exclusiveGroupMembers?: number[];
53
+ requiredSkill?: number;
54
+ requiredSkillValue?: number;
55
+ requiredMinRepFaction?: number;
56
+ requiredMinRepValue?: number;
57
+ requiredMaxRepFaction?: number;
58
+ requiredMaxRepValue?: number;
59
+ /** Race mask (RequiredRaces); 0/absent = any. */
60
+ reqRaceMask?: number;
61
+ /** QuestFlags / special flags bitfield if needed later. */
62
+ specialFlags?: number;
63
+ /** RewMoneyMaxLevel — used by progression/xpGain.questXpValue, not gating. */
64
+ rewMoneyMaxLevel?: number;
65
+ /** When true, quest is disabled (IsActive false). */
66
+ inactive?: boolean;
67
+ }
68
+
69
+ export type GatedQuest = QuestDef & QuestGatingFields;
70
+
71
+ // Pure player view for CanTakeQuest. Built by the engine from Character + QuestLog + skills/rep. Cite:
72
+ // Player.cpp CanTakeQuest reads level/class/race/skills/rep/quest status.
73
+ export interface QuestPlayerSnapshot {
74
+ level: number;
75
+ classId: number;
76
+ /** Race id 1..8; used only when quest.reqRaceMask is set. */
77
+ raceId?: number;
78
+ /** skillId → current value; missing skill ⇒ 0. */
79
+ skillValue?: (skillId: number) => number;
80
+ /** Absolute standing for faction (sparse + base resolved by caller). */
81
+ standingFor?: (factionId: number) => number;
82
+ // Quest log / history status. undefined or 'none' = never taken (QUEST_STATUS_NONE). turnedIn ≈ rewarded.
83
+ questStatus?: (entry: number) => QuestStatus | 'none' | undefined;
84
+ /** True if player already has a timed quest (SatisfyQuestTimed). */
85
+ hasActiveTimedQuest?: boolean;
86
+ /** Weekly quest already done this reset (SatisfyQuestWeek). Default allow. */
87
+ weeklyQuestDone?: (entry: number) => boolean;
88
+ /** Condition evaluation for QuestDef.reqCondition. When omitted, defaults to true. */
89
+ conditionEval?: (conditionId: number) => boolean;
90
+ }
91
+
92
+ /** QuestHandler.cpp / QuestDef.h dialog status integers (Classic). */
93
+ export const DIALOG_STATUS = {
94
+ NONE: 0,
95
+ UNAVAILABLE: 1,
96
+ CHAT: 2,
97
+ INCOMPLETE: 3,
98
+ REWARD_REP: 4,
99
+ AVAILABLE: 5,
100
+ REWARD_OLD: 6,
101
+ REWARD2: 7,
102
+ } as const;
103
+ export type DialogStatusCode = (typeof DIALOG_STATUS)[keyof typeof DIALOG_STATUS];
104
+
105
+ /** Classic race id → bitmask (1 << (race-1)), matching getRaceMask(). */
106
+ export function raceMaskFor(raceId: number): number {
107
+ if (raceId < 1 || raceId > 31) return 0;
108
+ return 1 << (raceId - 1);
109
+ }
110
+
111
+ function statusOf(snap: QuestPlayerSnapshot, entry: number): QuestStatus | 'none' {
112
+ const s = snap.questStatus?.(entry);
113
+ return s === undefined ? 'none' : s;
114
+ }
115
+
116
+ // Not already on the quest (any non-NONE). RewardQuest returns a REPEATABLE to QUEST_STATUS_NONE
117
+ // with m_rewarded set, so `turnedIn` never blocks re-take. Cite: Player.cpp:13034, :12611.
118
+ export function satisfyQuestStatus(
119
+ snap: QuestPlayerSnapshot,
120
+ questEntry: number,
121
+ repeatable = false,
122
+ ): boolean {
123
+ const s = statusOf(snap, questEntry);
124
+ if (repeatable && s === 'turnedIn') return true;
125
+ // Cite: Player.cpp CanTakeQuest ~12200 - FAILED quests must be abandoned before re-accept.
126
+ // Phonecraft forces the user (or autopilot) to explicitly abandon a failed quest.
127
+ return s === 'none';
128
+ }
129
+
130
+ /** SatisfyQuestLevel: MinLevel ≤ level ≤ MaxLevel (max absent ⇒ no upper bound). */
131
+ export function satisfyQuestLevel(snap: QuestPlayerSnapshot, quest: GatedQuest): boolean {
132
+ if (snap.level < quest.minLevel) return false;
133
+ if (quest.maxLevel != null && snap.level > quest.maxLevel) return false;
134
+ return true;
135
+ }
136
+
137
+ /** Classic class id → bitmask bit (1 << (classId-1)), matching getClassMask(). */
138
+ export function classMaskFor(classId: number): number {
139
+ if (classId < 1 || classId > 31) return 0;
140
+ return 1 << (classId - 1);
141
+ }
142
+
143
+ // SatisfyQuestClass: a multi-bit reqClassMask tests (mask & classBit) != 0, a single reqClass
144
+ // tests equality, and neither means any class. Cite: Player.cpp SatisfyQuestClass.
145
+ export function satisfyQuestClass(snap: QuestPlayerSnapshot, quest: GatedQuest): boolean {
146
+ const mask = quest.reqClassMask ?? 0;
147
+ if (mask !== 0) {
148
+ return (mask & classMaskFor(snap.classId)) !== 0;
149
+ }
150
+ if (quest.reqClass == null || quest.reqClass === 0) return true;
151
+ return snap.classId === quest.reqClass;
152
+ }
153
+
154
+ /** SatisfyQuestRace when reqRaceMask present; else true. */
155
+ export function satisfyQuestRace(snap: QuestPlayerSnapshot, quest: GatedQuest): boolean {
156
+ const mask = quest.reqRaceMask;
157
+ if (mask == null || mask === 0) return true;
158
+ if (snap.raceId == null) return false;
159
+ return (mask & raceMaskFor(snap.raceId)) !== 0;
160
+ }
161
+
162
+ /** SatisfyQuestSkill: skill value ≥ RequiredSkillValue when skill set. */
163
+ export function satisfyQuestSkill(snap: QuestPlayerSnapshot, quest: GatedQuest): boolean {
164
+ const skill = quest.requiredSkill ?? 0;
165
+ if (skill === 0) return true;
166
+ const need = quest.requiredSkillValue ?? 0;
167
+ const have = snap.skillValue?.(skill) ?? 0;
168
+ return have >= need;
169
+ }
170
+
171
+ // Optional lookup / flags for SatisfyQuestPreviousQuest. Cite: Player.cpp SatisfyQuestPreviousQuest
172
+ // ~12839–12936.
173
+ export interface SatisfyPrevQuestOpts {
174
+ // When prevQuest is set but the prerequisite row was pruned from the bake, treat as met — matching
175
+ // quests.prereqMet.
176
+ prevQuestMissingFromData?: boolean;
177
+ // Resolve another quest's def, for the exclusive-group-all check on the PREVIOUS quest. Without
178
+ // it only the single prevQuest-rewarded check runs. Cite: Player.cpp:12857–12890.
179
+ questDefFor?: (entry: number) => GatedQuest | undefined;
180
+ }
181
+
182
+ // SatisfyQuestPreviousQuest: a positive prevQuest must be turned in, and when that prev has an
183
+ // each-from-all exclusive group, every other baked member must be too. Cite: Player.cpp:12857.
184
+ export function satisfyQuestPreviousQuest(
185
+ snap: QuestPlayerSnapshot,
186
+ quest: GatedQuest,
187
+ opts?: SatisfyPrevQuestOpts,
188
+ ): boolean {
189
+ if (quest.prevQuest == null || quest.prevQuest === 0) return true;
190
+ if (opts?.prevQuestMissingFromData) return true;
191
+ const prevId = quest.prevQuest;
192
+ if (statusOf(snap, prevId) !== 'turnedIn') return false;
193
+
194
+ // each-from-all exclusive group on the previous quest (ExclusiveGroup < 0)
195
+ const prevDef = opts?.questDefFor?.(prevId);
196
+ const group = prevDef?.exclusiveGroup ?? 0;
197
+ if (group >= 0) return true; // no group, or one-from-all: rewarded prev is enough
198
+
199
+ const members = prevDef?.exclusiveGroupMembers ?? [];
200
+ for (const id of members) {
201
+ if (id === prevId) continue;
202
+ // Unbaked sibling: never force a softlock (same as prevQuestMissingFromData).
203
+ if (opts?.questDefFor && !opts.questDefFor(id)) continue;
204
+ if (statusOf(snap, id) !== 'turnedIn') return false;
205
+ }
206
+ return true;
207
+ }
208
+
209
+ // SatisfyQuestExclusiveGroup: if ExclusiveGroup > 0, no other member may be INCOMPLETE or COMPLETE (active
210
+ // log). Rewarded/turnedIn siblings do not block. Cite: Player.cpp:13062–13102.
211
+ export function satisfyQuestExclusiveGroup(snap: QuestPlayerSnapshot, quest: GatedQuest): boolean {
212
+ const group = quest.exclusiveGroup ?? 0;
213
+ if (group <= 0) return true;
214
+ const members = quest.exclusiveGroupMembers ?? [];
215
+ for (const id of members) {
216
+ if (id === quest.entry) continue;
217
+ const st = statusOf(snap, id);
218
+ if (st === 'active' || st === 'complete') return false;
219
+ }
220
+ return true;
221
+ }
222
+
223
+ // SatisfyQuestTimed: no second timed quest while one is active. Timed means LimitTime != 0 and
224
+ // nothing else — reading DB SpecialFlags bit 0 made 613 repeatable quests mutually exclusive.
225
+ export function satisfyQuestTimed(snap: QuestPlayerSnapshot, quest: GatedQuest): boolean {
226
+ if (!(quest.limitTime != null && quest.limitTime > 0)) return true;
227
+ return !snap.hasActiveTimedQuest;
228
+ }
229
+
230
+ /** SatisfyQuestWeek: default allow when no weekly tracker provided. */
231
+ export function satisfyQuestWeek(snap: QuestPlayerSnapshot, questEntry: number): boolean {
232
+ if (!snap.weeklyQuestDone) return true;
233
+ return !snap.weeklyQuestDone(questEntry);
234
+ }
235
+
236
+ // SatisfyQuestReqCondition: evaluate quest.reqCondition through snap.conditionEval, defaulting to
237
+ // true when no condition engine is attached. Cite: Player.cpp:12200–12206.
238
+ export function satisfyQuestReqCondition(snap: QuestPlayerSnapshot, quest: GatedQuest): boolean {
239
+ if (!quest.reqCondition) return true;
240
+ if (!snap.conditionEval) return true;
241
+ return snap.conditionEval(quest.reqCondition);
242
+ }
243
+
244
+ // CanSeeStartQuest subset — the always-on gates deciding whether a quest is offerable at all.
245
+ // Level is deliberately excluded: it decides AVAILABLE vs UNAVAILABLE, not visibility.
246
+ export function canSeeQuest(
247
+ snap: QuestPlayerSnapshot,
248
+ quest: GatedQuest,
249
+ opts?: SatisfyPrevQuestOpts,
250
+ ): boolean {
251
+ if (quest.inactive) return false;
252
+ return (
253
+ satisfyQuestClass(snap, quest)
254
+ && satisfyQuestRace(snap, quest)
255
+ && satisfyQuestSkill(snap, quest)
256
+ && satisfyQuestExclusiveGroup(snap, quest)
257
+ && satisfyQuestPreviousQuest(snap, quest, opts)
258
+ && satisfyQuestWeek(snap, quest.entry)
259
+ && satisfyQuestReqCondition(snap, quest)
260
+ );
261
+ }
262
+
263
+ // Player::CanTakeQuest for the baked fields: status, exclusiveGroup, class, race, level, skill,
264
+ // reputation, previous quest, timed, week, reqCondition and active. Cite: Player.cpp:12198–12206.
265
+ export function canTakeQuest(
266
+ snap: QuestPlayerSnapshot,
267
+ quest: GatedQuest,
268
+ opts?: SatisfyPrevQuestOpts,
269
+ ): boolean {
270
+ if (quest.inactive) return false;
271
+ if (!satisfyQuestStatus(snap, quest.entry, isRepeatableQuest(quest))) return false;
272
+ if (!satisfyQuestExclusiveGroup(snap, quest)) return false;
273
+ if (!satisfyQuestClass(snap, quest)) return false;
274
+ if (!satisfyQuestRace(snap, quest)) return false;
275
+ if (!satisfyQuestLevel(snap, quest)) return false;
276
+ if (!satisfyQuestSkill(snap, quest)) return false;
277
+ // Reputation: reuse satisfyQuestReputation (Player.cpp:13011)
278
+ if (snap.standingFor) {
279
+ if (!satisfyQuestReputation({
280
+ minFaction: quest.requiredMinRepFaction,
281
+ minValue: quest.requiredMinRepValue,
282
+ maxFaction: quest.requiredMaxRepFaction,
283
+ maxValue: quest.requiredMaxRepValue,
284
+ standingFor: snap.standingFor,
285
+ })) return false;
286
+ } else if (
287
+ (quest.requiredMinRepFaction != null && quest.requiredMinRepValue != null)
288
+ || (quest.requiredMaxRepFaction != null && quest.requiredMaxRepValue != null)
289
+ ) {
290
+ // Bounds exist but no standing provider → cannot satisfy (strict)
291
+ return false;
292
+ }
293
+ if (!satisfyQuestPreviousQuest(snap, quest, opts)) return false;
294
+ if (!satisfyQuestTimed(snap, quest)) return false;
295
+ if (!satisfyQuestWeek(snap, quest.entry)) return false;
296
+ if (!satisfyQuestReqCondition(snap, quest)) return false;
297
+ return true;
298
+ }
299
+
300
+ // Per-quest dialog marker. `role` is 'finisher' for an involved relation and 'giver' for a start
301
+ // one; lowLevelHideDiff defaults to 5, and a negative value disables the gray demotion.
302
+ export function dialogStatusForQuest(
303
+ snap: QuestPlayerSnapshot,
304
+ quest: GatedQuest,
305
+ role: 'giver' | 'finisher',
306
+ opts?: { lowLevelHideDiff?: number; isRepeatable?: boolean; isAutoComplete?: boolean },
307
+ ): DialogStatusCode {
308
+ if (quest.inactive) return DIALOG_STATUS.NONE;
309
+ const st = statusOf(snap, quest.entry);
310
+ const auto = opts?.isAutoComplete ?? false;
311
+ const rep = opts?.isRepeatable ?? false;
312
+
313
+ if (role === 'finisher') {
314
+ if (st === 'complete') {
315
+ // COMPLETE && !rewarded
316
+ return rep && !auto ? DIALOG_STATUS.REWARD_REP : DIALOG_STATUS.REWARD2;
317
+ }
318
+ if (auto && canTakeQuest(snap, quest)) {
319
+ return rep ? DIALOG_STATUS.REWARD_REP : DIALOG_STATUS.AVAILABLE;
320
+ }
321
+ if (st === 'active') return DIALOG_STATUS.INCOMPLETE;
322
+ return DIALOG_STATUS.NONE;
323
+ }
324
+
325
+ // giver path: status NONE && can see start (CanSeeStartQuest = the level-independent gates).
326
+ // A rewarded repeatable reads NONE in core, so it keeps its marker. Cite: Player.cpp:12611.
327
+ if (st !== 'none' && !(rep && st === 'turnedIn')) return DIALOG_STATUS.NONE;
328
+ if (!canSeeQuest(snap, quest)) return DIALOG_STATUS.NONE;
329
+
330
+ if (!satisfyQuestLevel(snap, quest)) return DIALOG_STATUS.UNAVAILABLE;
331
+
332
+ if (auto) return DIALOG_STATUS.REWARD_REP;
333
+
334
+ const lowDiff = opts?.lowLevelHideDiff ?? 5;
335
+ const qLevel = quest.questLevel > 0 ? quest.questLevel : snap.level;
336
+ if (lowDiff < 0 || snap.level <= qLevel + lowDiff) return DIALOG_STATUS.AVAILABLE;
337
+ return DIALOG_STATUS.CHAT;
338
+ }
339
+
340
+ // Aggregate dialog status for a giver with multiple start/finish quest ids (max wins). Cite:
341
+ // QuestHandler.cpp getDialogStatus — higher numeric status wins.
342
+ export function getDialogStatusForGiver(
343
+ snap: QuestPlayerSnapshot,
344
+ relations: { entry: number; role: 'giver' | 'finisher'; quest: GatedQuest }[],
345
+ defStatus: DialogStatusCode = DIALOG_STATUS.NONE,
346
+ ): DialogStatusCode {
347
+ let dialogStatus: DialogStatusCode = defStatus;
348
+ for (const rel of relations) {
349
+ const next = dialogStatusForQuest(snap, rel.quest, rel.role);
350
+ if (next > dialogStatus) dialogStatus = next;
351
+ }
352
+ return dialogStatus;
353
+ }
354
+
355
+ /** Build snapshot questStatus from a QuestLog (pure helper for callers/tests). */
356
+ export function questStatusFromLog(log: QuestLog): (entry: number) => QuestStatus | 'none' {
357
+ return (entry: number) => getQuest(log, entry)?.status ?? 'none';
358
+ }
359
+
360
+ // ---- FO2: GameObject quest-giver accept / turn-in offer --------------------
361
+ // The relations reverse-bake to questsStarted / questsEnded, so this only resolves WHICH quests.
362
+
363
+ /** Resolved accept / turn-in lists for a GO with reverse quest links. */
364
+ export interface GoQuestGiverOffer {
365
+ /** Quest entries the player can take from this GO (not in log, passes canTakeQuest). */
366
+ acceptable: number[];
367
+ /** Complete quests ready to hand in at this GO. */
368
+ turnInReady: number[];
369
+ /** Active incomplete quests this GO would finish (in-progress marker). */
370
+ incomplete: number[];
371
+ }
372
+
373
+ // FO2 pure offer list for a GameObject giver: acceptable uses canTakeQuest and turn-in needs both
374
+ // complete status and questComplete. It invents no zone spawns — the caller must have the GO.
375
+ export function goQuestGiverOffer(
376
+ go: { questsStarted?: number[]; questsEnded?: number[] },
377
+ log: QuestLog,
378
+ snap: QuestPlayerSnapshot,
379
+ questOf: (entry: number) => QuestDef | undefined,
380
+ inv: Inventory,
381
+ opts?: SatisfyPrevQuestOpts & QuestCompleteOpts & {
382
+ /** Fixture override when reverse links are omitted on the GO def. */
383
+ fallbackStarted?: number[];
384
+ fallbackEnded?: number[];
385
+ },
386
+ ): GoQuestGiverOffer {
387
+ const started = (go.questsStarted?.length ? go.questsStarted : opts?.fallbackStarted ?? [])
388
+ .filter((id) => id > 0);
389
+ const ended = (go.questsEnded?.length ? go.questsEnded : opts?.fallbackEnded ?? [])
390
+ .filter((id) => id > 0);
391
+
392
+ const acceptable: number[] = [];
393
+ for (const id of started) {
394
+ const q = questOf(id);
395
+ if (!q) continue;
396
+ const prevMissing = !!(q.prevQuest && opts?.questDefFor && !opts.questDefFor(q.prevQuest));
397
+ if (!canTakeQuest(snap, q, {
398
+ ...opts,
399
+ prevQuestMissingFromData: opts?.prevQuestMissingFromData ?? prevMissing,
400
+ })) {
401
+ continue;
402
+ }
403
+ acceptable.push(id);
404
+ }
405
+
406
+ const turnInReady: number[] = [];
407
+ const incomplete: number[] = [];
408
+ for (const id of ended) {
409
+ const q = questOf(id);
410
+ if (!q) continue;
411
+ const st = getQuest(log, id);
412
+ if (!st || st.status === 'turnedIn') continue;
413
+ if (st.status === 'complete' || questComplete(q, st, inv, opts)) {
414
+ turnInReady.push(id);
415
+ } else if (st.status === 'active' || st.status === 'failed') {
416
+ incomplete.push(id);
417
+ }
418
+ }
419
+
420
+ return { acceptable, turnInReady, incomplete };
421
+ }
422
+
423
+ // Dialog-status marker for a GO row, from the same lists goQuestGiverOffer uses. Precedence
424
+ // matches getDialogStatusForGiver: reward beats available beats incomplete.
425
+ export type GoDialogMarker = 'reward' | 'available' | 'incomplete';
426
+
427
+ export function goDialogMarkerFromOffer(offer: GoQuestGiverOffer): GoDialogMarker | null {
428
+ if (offer.turnInReady.length > 0) return 'reward';
429
+ if (offer.acceptable.length > 0) return 'available';
430
+ if (offer.incomplete.length > 0) return 'incomplete';
431
+ return null;
432
+ }
433
+
434
+ // Slots your quest log can hold at once (the classic 1.12 cap). Only quests you're still carrying — active
435
+ // or complete — occupy a slot; turnedIn entries are history and don't.
436
+ export const MAX_QUEST_SLOTS = 20;
437
+
438
+ export function emptyQuestLog(): QuestLog {
439
+ return { quests: [] };
440
+ }
441
+
442
+ export function getQuest(log: QuestLog, entry: number): QuestState | undefined {
443
+ return log.quests.find((q) => q.entry === entry);
444
+ }
445
+
446
+ /** Slots currently in use: quests you're carrying (active or complete), turnedIn excluded. */
447
+ export function activeQuestCount(log: QuestLog): number {
448
+ return log.quests.reduce((n, q) => (q.status === 'turnedIn' ? n : n + 1), 0);
449
+ }
450
+
451
+ /** Whether there's room in the log to accept another quest. */
452
+ export function hasQuestSlot(log: QuestLog): boolean {
453
+ return activeQuestCount(log) < MAX_QUEST_SLOTS;
454
+ }
455
+
456
+ function clone(log: QuestLog): QuestLog {
457
+ return {
458
+ quests: log.quests.map((q) => ({
459
+ ...q,
460
+ kills: { ...q.kills },
461
+ objects: { ...(q.objects || {}) },
462
+ spells: { ...(q.spells || {}) },
463
+ uses: { ...(q.uses || {}) },
464
+ // explored / talked are scalars on the spread; undefined stays undefined for old states
465
+ })),
466
+ };
467
+ }
468
+
469
+ // Whether completion requires AreaExploredOrEventHappens: true when exploreTriggers are baked or
470
+ // SpecialFlags carries EXPLORATION_OR_EVENT. Cite: Player.cpp:12276.
471
+ export function requiresEventCredit(quest: QuestDef): boolean {
472
+ if ((quest.exploreTriggers?.length ?? 0) > 0) return true;
473
+ return ((quest.specialFlags ?? 0) & QUEST_SPECIAL_FLAG_EXPLORATION_OR_EVENT) !== 0;
474
+ }
475
+
476
+ // Event / escort / exploration quests that need AreaExploredOrEventHappens credit. Alias of
477
+ // requiresEventCredit for FO6 disposition naming.
478
+ export function isEventOrEscortQuest(quest: QuestDef): boolean {
479
+ return requiresEventCredit(quest);
480
+ }
481
+
482
+ // True when the quest has no kill, item, object, sourceItem or spellCast trackable. Creature
483
+ // SPEAKTO slots from rawObjectives count as trackable even when slim killTargets were cleared.
484
+ function hasEmptyTrackables(quest: QuestDef): boolean {
485
+ return (
486
+ creatureCreditTargets(quest).length === 0 &&
487
+ (quest.itemTargets?.length ?? 0) === 0 &&
488
+ (quest.objectTargets?.length ?? 0) === 0 &&
489
+ (quest.sourceItemTargets?.length ?? 0) === 0 &&
490
+ !(quest.spellCastTargets || []).some((t) => (t.count || 0) > 0)
491
+ );
492
+ }
493
+
494
+ // True when the quest ends at a GameObject — an examined crate, a wrecked boat. Zone presence must
495
+ // NOT free-credit talked; only recordGoEnderInteract does. Cite: KI-507, KI-508.
496
+ export function hasGoEnder(quest: QuestDef): boolean {
497
+ if (quest.goEnder != null && quest.goEnder > 0) return true;
498
+ return (quest.goEnderEntries ?? []).some((e) => e > 0);
499
+ }
500
+
501
+ /** The GameObject that ends this quest: the scalar goEnder, else the first goEnderEntries row. */
502
+ export function goEnderEntryOf(quest: QuestDef): number | undefined {
503
+ if ((quest.goEnder ?? 0) > 0) return quest.goEnder;
504
+ return (quest.goEnderEntries ?? []).find((e) => e > 0);
505
+ }
506
+
507
+ // FO4 talk-to-NPC candidate, with no baked talkToNpc field to read: empty trackables, not an event
508
+ // or escort, not pure-rep, and carrying an ender or starter identity.
509
+ export function isTalkToNpcQuest(quest: QuestDef): boolean {
510
+ if (!hasEmptyTrackables(quest)) return false;
511
+ if (requiresEventCredit(quest)) return false;
512
+ // Pure-rep stays gated by standingFor; do not force a talk bit on rep-only quests.
513
+ if (quest.repObjective && (quest.repObjective.value || 0) > 0) return false;
514
+ const ender = (quest.enderName ?? '').trim();
515
+ const starter = (quest.starterName ?? '').trim();
516
+ return !!(ender || starter);
517
+ }
518
+
519
+ // Empty flag-2 quests whose CMaNGOS completion is a gossip SCRIPT_COMMAND_QUEST_EXPLORED rather
520
+ // than an escort, fight or use. talkToNpc is the product proxy for that gossip chain.
521
+ const EVENT_TALK_FIGHT_ESCORT_RE =
522
+ /\b(?:escort|protect|defend|defeat|kill|slay|ambush|face |fight|subdue|save \d+|bring forth)\b/i;
523
+ const EVENT_TALK_OBJECTIVE_RE =
524
+ /\b(?:listen (?:as|to)\b|tells? (?:his|her|their) story|recant the history|heard your words|seek out the .+? and learn|learn what .{0,80}(?:share|teach|lore it keeps)|speak(?:ing)? (?:with|to)\b)\b/i;
525
+
526
+ export function isEventTalkQuest(quest: QuestDef): boolean {
527
+ if (!hasEmptyTrackables(quest)) return false;
528
+ if (!requiresEventCredit(quest)) return false;
529
+ if ((quest.exploreTriggers?.length ?? 0) > 0) return false;
530
+ if (hasGoEnder(quest)) return false;
531
+ if (quest.repObjective && (quest.repObjective.value || 0) > 0) return false;
532
+ const text = `${quest.objectivesText ?? ''} ${quest.detailsText ?? ''}`;
533
+ if (EVENT_TALK_FIGHT_ESCORT_RE.test(text)) return false;
534
+ return EVENT_TALK_OBJECTIVE_RE.test(text);
535
+ }
536
+
537
+ // Talk or deliver-arrival met: a talk-to-NPC quest needs state.talked, and so does a deliver quest
538
+ // with baked enderZones. Without enderZones, items alone suffice. Cite: Player.cpp TalkedToCreature.
539
+ export function talkMet(state: QuestState, quest: QuestDef): boolean {
540
+ if (isTalkToNpcQuest(quest)) return state.talked === true;
541
+ if (isDeliverQuest(quest)) {
542
+ const enders = quest.enderZones ?? [];
543
+ if (enders.length === 0) return true;
544
+ return state.talked === true;
545
+ }
546
+ return true;
547
+ }
548
+
549
+ // Whether pure sourceItemTargets need recorded use credit rather than mere presence: yes when there
550
+ // are no kill, item or object targets. With normal itemTargets alongside, presence is enough.
551
+ export function requiresSourceUseCredit(quest: QuestDef): boolean {
552
+ const sources = quest.sourceItemTargets || [];
553
+ if (sources.length === 0) return false;
554
+ return (
555
+ (quest.killTargets?.length ?? 0) === 0 &&
556
+ (quest.itemTargets?.length ?? 0) === 0 &&
557
+ (quest.objectTargets?.length ?? 0) === 0
558
+ );
559
+ }
560
+
561
+ /** Source-item use counts met (only when requiresSourceUseCredit; otherwise always true). */
562
+ export function sourceUsesMet(state: QuestState, quest: QuestDef): boolean {
563
+ if (!requiresSourceUseCredit(quest)) return true;
564
+ const uses = state.uses || {};
565
+ return (quest.sourceItemTargets || []).every(
566
+ (it) => (uses[String(it.item)] ?? 0) >= it.count,
567
+ );
568
+ }
569
+
570
+ // FO6 degraded-escort candidate: a pure escort or event with empty trackables and no baked
571
+ // exploreTriggers, about 113 live quests. finishEscort is the player-invokable end condition.
572
+ export function isDegradedEscortCandidate(quest: QuestDef): boolean {
573
+ if (!requiresEventCredit(quest)) return false;
574
+ if ((quest.exploreTriggers?.length ?? 0) > 0) return false;
575
+ return hasEmptyTrackables(quest);
576
+ }
577
+
578
+ // Explicit finishEscort may credit this quest: empty escorts and empty explore-trigger quests, the
579
+ // same degraded end condition. Mixed event-plus-kill waits for eventCreditIsTheRemainingWork.
580
+ export function canDegradedAutoFinish(quest: QuestDef): boolean {
581
+ if (!requiresEventCredit(quest)) return false;
582
+ return hasEmptyTrackables(quest);
583
+ }
584
+
585
+ // The event bit is the only remaining work: escorts, empty explore, or a mixed quest whose kill,
586
+ // item, object and use objectives are already met. Talk-gated event quests stay on the speak path.
587
+ export function eventCreditIsTheRemainingWork(
588
+ quest: QuestDef,
589
+ qs: QuestState,
590
+ inv: Inventory,
591
+ ): boolean {
592
+ if (qs.explored === true) return false;
593
+ if (isEventTalkQuest(quest)) return false;
594
+ if (!killsMet(qs, quest) || !itemsMet(quest, inv) || !objectsMet(qs, quest)) return false;
595
+ if (!sourcesMet(quest, inv) || !sourceUsesMet(qs, quest) || !spellsMet(qs, quest)) return false;
596
+ if (canDegradedAutoFinish(quest)) return true;
597
+ return (quest.exploreTriggers?.length ?? 0) > 0;
598
+ }
599
+
600
+ // Item-started quest (item_template.startquest): the item IS the giver, so accept never free-grants
601
+ // it. Detected from baked startedByItem, else a pre-FQ6 heuristic on srcItem and the placeholder name.
602
+ export function isItemStartedQuest(quest: QuestDef): boolean {
603
+ if (quest.startedByItem != null && quest.startedByItem > 0) return true;
604
+
605
+ const src = quest.srcItem;
606
+ if (!src?.item) return false;
607
+ const srcIsObjective = (quest.itemTargets ?? []).some((t) => t.item === src.item);
608
+ if (!srcIsObjective) return false;
609
+ // GO starter relation is a real world starter (not item-start).
610
+ if (quest.goStarter != null && quest.goStarter > 0) return false;
611
+ if ((quest.goStarterEntries ?? []).some((e) => e > 0)) return false;
612
+ const name = (quest.starterName ?? '').trim();
613
+ if (!name || name === 'A quest giver' || name === 'Unknown') return true;
614
+ // Some extracts label item-starts as "item: <Name>" (e.g. Glowing Shard).
615
+ if (/^item\s*:/i.test(name)) return true;
616
+ return false;
617
+ }
618
+
619
+ // Standalone GO-started SrcItem==ReqItem find: skip zone-list grant (KI-587). C++ GiveQuestSourceItemIfNeed
620
+ // always grants (Player.cpp:13163); chain follow-ups (PrevQuestId) are that grant (KI-922).
621
+ export function isWorldLootSrcItemObjective(quest: QuestDef): boolean {
622
+ const src = quest.srcItem;
623
+ if (!src?.item) return false;
624
+ const srcIsObjective = (quest.itemTargets ?? []).some((t) => t.item === src.item);
625
+ if (!srcIsObjective) return false;
626
+ const hasGo =
627
+ (quest.goStarter != null && quest.goStarter > 0)
628
+ || (quest.goStarterEntries ?? []).some((e) => e > 0);
629
+ if (!hasGo) return false;
630
+ // PrevQuestId: chain follow-up offered after the previous turn-in — grant.
631
+ if ((quest.prevQuest ?? 0) > 0) return false;
632
+ return true;
633
+ }
634
+
635
+ // Whether acceptQuest should top up the SrcItem, which CMaNGOS always attempts and which is a no-op
636
+ // when the count is met. False only for a standalone GO find away from the GO (KI-587).
637
+ export function shouldGrantSrcItemOnAccept(
638
+ quest: QuestDef,
639
+ opts?: { atGo?: boolean },
640
+ ): boolean {
641
+ if (!quest.srcItem?.item) return false;
642
+ // GiveQuestSourceItemIfNeed runs on AcceptQuest at the giver, so a list-accept away from the GO
643
+ // must not invent standalone plant or crate loot (KI-587). At the GO it must grant.
644
+ if (isWorldLootSrcItemObjective(quest) && !opts?.atGo) return false;
645
+ return true;
646
+ }
647
+
648
+ /**
649
+ * Player.cpp:13710 HasQuestForItem — incomplete quest still short of ReqItem/ReqSource.
650
+ * `stackOf` supplies item_template.Stackable, the cap a ReqSourceCount of 0 uses (Player.cpp:13760).
651
+ */
652
+ export function hasQuestForItem(
653
+ log: QuestLog,
654
+ questDef: (entry: number) => QuestDef | undefined,
655
+ inv: Inventory,
656
+ itemId: number,
657
+ stackOf?: (item: number) => number | undefined,
658
+ ): boolean {
659
+ if (itemId <= 0) return false;
660
+ const have = countItem(inv, itemId);
661
+ for (const q of log.quests) {
662
+ if (q.status !== 'active') continue;
663
+ const def = questDef(q.entry);
664
+ if (!def) continue;
665
+ for (const t of def.itemTargets ?? []) {
666
+ if (t.item === itemId && have < t.count) return true;
667
+ }
668
+ if (!sourceDropItemEntries(def).includes(itemId)) continue;
669
+ const count = (def.sourceItemTargets ?? []).find((t) => t.item === itemId)?.count ?? 0;
670
+ const need = count > 0 ? count : Math.max(1, stackOf?.(itemId) ?? 1);
671
+ if (have < need) return true;
672
+ }
673
+ return false;
674
+ }
675
+
676
+ // Item entry the player must already hold to accept an item-started quest. Prefer baked startedByItem; else
677
+ // srcItem (classic delivery pattern).
678
+ export function itemStartRequiredItem(quest: QuestDef): number | undefined {
679
+ if (!isItemStartedQuest(quest)) return undefined;
680
+ if (quest.startedByItem != null && quest.startedByItem > 0) return quest.startedByItem;
681
+ return quest.srcItem?.item;
682
+ }
683
+
684
+ // Item-started accept destroys the starting item when it is neither a ReqItem objective nor the
685
+ // SrcItem — 3181 destroys 10000, while 832 keeps the Eye 4903. Cite: Player.cpp:12470–12488.
686
+ export function shouldDestroyStartItemOnAccept(quest: QuestDef): boolean {
687
+ if (!isItemStartedQuest(quest)) return false;
688
+ const startId = itemStartRequiredItem(quest);
689
+ if (startId == null || startId <= 0) return false;
690
+ if ((quest.itemTargets ?? []).some((t) => t.item === startId)) return false;
691
+ if (quest.srcItem?.item === startId) return false;
692
+ return true;
693
+ }
694
+
695
+ // Item-start accept gate: the start item must already be in bags, since the item IS the giver.
696
+ // Shared with the autopilot so the planner cannot offer an accept the engine will refuse.
697
+ export function itemStartGateMet(quest: QuestDef, inventory: Inventory): boolean {
698
+ const startItemId = itemStartRequiredItem(quest);
699
+ if (startItemId == null) return true;
700
+ const needStart = quest.srcItem?.item === startItemId && quest.srcItem.count > 0 ? quest.srcItem.count : 1;
701
+ return countItem(inventory, startItemId) >= needStart;
702
+ }
703
+
704
+ // Creature objective slots that track progress in QuestState.kills. Prefer slim killTargets; when
705
+ // empty, open kill keys for rawObjectives SPEAKTO slots so TalkedToCreature can still complete.
706
+ export function creatureCreditTargets(
707
+ quest: QuestDef,
708
+ ): { creatureEntry: number; count: number }[] {
709
+ const slim = quest.killTargets ?? [];
710
+ if (slim.length > 0) return slim;
711
+ const raw = quest.rawObjectives;
712
+ if (!raw) return [];
713
+ const out: { creatureEntry: number; count: number }[] = [];
714
+ const n = Math.min(
715
+ raw.reqCreatureOrGoId?.length ?? 0,
716
+ raw.reqCreatureOrGoCount?.length ?? 0,
717
+ raw.reqSpell?.length ?? 0,
718
+ );
719
+ for (let i = 0; i < n; i++) {
720
+ const id = raw.reqCreatureOrGoId[i] | 0;
721
+ const count = raw.reqCreatureOrGoCount[i] | 0;
722
+ const spell = raw.reqSpell[i] | 0;
723
+ if (id > 0 && spell === 0 && count > 0) {
724
+ out.push({ creatureEntry: id, count });
725
+ }
726
+ }
727
+ return out;
728
+ }
729
+
730
+ // A fresh QUEST_STATUS_INCOMPLETE row: every counter at zero and the event/talk latches down.
731
+ // Cite: Player.cpp:12405 AddQuest.
732
+ export function freshQuestState(quest: QuestDef): QuestState {
733
+ const kills: Record<string, number> = {};
734
+ for (const kt of creatureCreditTargets(quest)) kills[String(kt.creatureEntry)] = 0;
735
+ const objects: Record<string, number> = {};
736
+ for (const ot of (quest.objectTargets || [])) objects[String(ot.objectEntry)] = 0;
737
+ const spells: Record<string, number> = {};
738
+ for (const st of (quest.spellCastTargets || [])) {
739
+ if (st.spell) spells[String(st.spell)] = 0;
740
+ }
741
+ const uses: Record<string, number> = {};
742
+ if (requiresSourceUseCredit(quest)) {
743
+ for (const st of quest.sourceItemTargets || []) {
744
+ if (st.item) uses[String(st.item)] = 0;
745
+ }
746
+ }
747
+ // Event/explore quests start with explored=false so empty objectives (escorts) and granted tools (Taming
748
+ // Rod as itemTarget) cannot self-satisfy before event credit. Cite: Player.cpp:12405; FQ1 / FQ2.
749
+ const needsEvent = requiresEventCredit(quest);
750
+ // FO4 talk + QI2.5 deliver: start talked=false so accept+srcItem cannot free-complete (KI-845).
751
+ const needsTalk = isTalkToNpcQuest(quest) || isDeliverQuest(quest);
752
+ return {
753
+ entry: quest.entry,
754
+ status: 'active',
755
+ kills,
756
+ objects,
757
+ spells,
758
+ ...(Object.keys(uses).length ? { uses } : {}),
759
+ ...(needsEvent ? { explored: false } : {}),
760
+ ...(needsTalk ? { talked: false } : {}),
761
+ ...(quest.limitTime ? { timerMs: quest.limitTime * 1000 } : {}),
762
+ };
763
+ }
764
+
765
+ // Rounds of a REPEATABLE the bags already pay for, counting the one in the log. 0 for anything the
766
+ // bags cannot settle (a kill, an escort, a talk). Cite: Player.cpp:12546 DestroyItemCount.
767
+ export function questRepeatsAvailable(
768
+ quest: QuestDef,
769
+ inv: Inventory,
770
+ zoneId?: string,
771
+ ): number {
772
+ if (!isRepeatableQuest(quest)) return 0;
773
+ const targets = (quest.itemTargets ?? []).filter((t) => t.item > 0 && t.count > 0);
774
+ if (!targets.length) return 0;
775
+ if (requiresEventCredit(quest) || isTalkToNpcQuest(quest)) return 0;
776
+ if (creatureCreditTargets(quest).length > 0) return 0;
777
+ if ((quest.objectTargets ?? []).length > 0) return 0;
778
+ if ((quest.spellCastTargets ?? []).length > 0) return 0;
779
+ // A deliver's next round is credited by STANDING at the ender, so away from that zone the rounds
780
+ // are travel, not a press. Cite: recordSameZoneDeliver; deliverArrivalPending.
781
+ if (isDeliverQuest(quest)) {
782
+ const enders = quest.enderZones ?? [];
783
+ if (enders.length > 0 && (!zoneId || !enders.includes(zoneId))) return 0;
784
+ }
785
+ let rounds = Number.MAX_SAFE_INTEGER;
786
+ for (const t of targets) {
787
+ rounds = Math.min(rounds, Math.floor(countItem(inv, t.item) / t.count));
788
+ if (rounds <= 0) return 0;
789
+ }
790
+ return rounds;
791
+ }
792
+
793
+ // A rewarded REPEATABLE is re-taken over its own row: core holds one QuestStatusData per quest and
794
+ // AddQuest overwrites its counters, keeping m_rewarded. Cite: Player.cpp:12405; Player.cpp:12611.
795
+ export function accept(log: QuestLog, quest: QuestDef): QuestLog {
796
+ const held = getQuest(log, quest.entry);
797
+ if (held && !(held.status === 'turnedIn' && isRepeatableQuest(quest))) return log;
798
+ const out = clone(log);
799
+ const row = freshQuestState(quest);
800
+ const at = out.quests.findIndex((x) => x.entry === quest.entry);
801
+ if (at >= 0) out.quests[at] = row;
802
+ else out.quests.push(row);
803
+ return out;
804
+ }
805
+
806
+ // AreaExploredOrEventHappens proxy: mark explore satisfied for active quests whose exploreTriggers
807
+ // name this zone. A trigger with no zone is ignored — it cannot be attributed without geometry.
808
+ export function recordExplore(
809
+ log: QuestLog,
810
+ zoneId: string,
811
+ questOf: (entry: number) => QuestDef | undefined,
812
+ ): QuestLog {
813
+ let touched = false;
814
+ const out = clone(log);
815
+ for (const q of out.quests) {
816
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
817
+ if (q.explored) continue;
818
+ const quest = questOf(q.entry);
819
+ const triggers = quest?.exploreTriggers;
820
+ if (!triggers?.length) continue;
821
+ // Zone match is the Phonecraft proxy for entering the areatrigger volume.
822
+ if (!triggers.some((t) => t.zone === zoneId)) continue;
823
+ q.explored = true;
824
+ touched = true;
825
+ }
826
+ return touched ? out : log;
827
+ }
828
+
829
+ // AreaExploredOrEventHappens by quest id, the script and QUEST_COMPLETE spell path used by the
830
+ // hunter taming spells. Cite: SpellEffects.cpp EffectQuestComplete; Player.cpp:13335-13357.
831
+ export function recordEventHappens(log: QuestLog, questId: number): QuestLog {
832
+ if (!questId) return log;
833
+ const q = getQuest(log, questId);
834
+ if (!q || q.status === 'turnedIn' || q.status === 'failed' || q.explored) return log;
835
+ const out = clone(log);
836
+ const st = out.quests.find((x) => x.entry === questId);
837
+ if (!st || st.status === 'turnedIn' || st.status === 'failed' || st.explored) return log;
838
+ st.explored = true;
839
+ return out;
840
+ }
841
+
842
+ // Explore or event objective met: with no exploreTriggers and no EXPLORATION flag it is always met,
843
+ // otherwise state.explored is required. Cite: Player.cpp CanCompleteQuest ~12276.
844
+ export function exploreMet(state: QuestState, quest: QuestDef): boolean {
845
+ if (!requiresEventCredit(quest)) return true;
846
+ // CQ6 scripted-kill events: a flag-2 quest with REAL kill objectives but no baked exploreTriggers
847
+ // has the kill script AS the event, so meeting the kills satisfies the credit.
848
+ if ((quest.exploreTriggers?.length ?? 0) === 0
849
+ && (quest.killTargets?.length ?? 0) > 0 && killsMet(state, quest)) return true;
850
+ // Same class for GO object objectives: flag-2 with real objectTargets and no exploreTriggers means
851
+ // the GO Use path IS the event, as in 924 Demon Seed. Cite: Player.cpp CanCompleteQuest ~12276.
852
+ if ((quest.exploreTriggers?.length ?? 0) === 0
853
+ && (quest.objectTargets?.length ?? 0) > 0 && objectsMet(state, quest)) return true;
854
+ return state.explored === true;
855
+ }
856
+
857
+ // Creature credit objectives met (from tracked kills). Uses creatureCreditTargets so
858
+ // SPEAKTO/TalkedToCreature slots (rawObjectives) still gate when slim killTargets is empty.
859
+ export function killsMet(state: QuestState, quest: QuestDef): boolean {
860
+ return creatureCreditTargets(quest).every(
861
+ (kt) => (state.kills[String(kt.creatureEntry)] ?? 0) >= kt.count,
862
+ );
863
+ }
864
+ /** Item objectives met (checked live against the inventory). */
865
+ export function itemsMet(quest: QuestDef, inv: Inventory): boolean {
866
+ return quest.itemTargets.every((it) => countItem(inv, it.item) >= it.count);
867
+ }
868
+ // Source-item objectives met. CanCompleteQuest does NOT check ReqSourceId — it drives drop
869
+ // eligibility, not turn-in. Only a pure use-on-target source needs the item in bags (KI-586).
870
+ export function sourcesMet(quest: QuestDef, inv: Inventory): boolean {
871
+ if (!requiresSourceUseCredit(quest)) return true;
872
+ return (quest.sourceItemTargets || []).every((it) => countItem(inv, it.item) >= it.count);
873
+ }
874
+ // Every ReqSourceId this quest permits as a drop, including slots the slim sourceItemTargets drops
875
+ // because ReqSourceCount is 0 (quest 905's Sunscale Feather). Count 0 is not an objective count: it
876
+ // allows the drop up to the item's stack size. Cite: Player.cpp:13741-13761 HasQuestForItem.
877
+ export function sourceDropItemEntries(quest: QuestDef): number[] {
878
+ const out: number[] = [];
879
+ for (const t of quest.sourceItemTargets ?? []) if (t.item > 0) out.push(t.item);
880
+ for (const id of quest.rawObjectives?.reqSourceId ?? []) {
881
+ const item = id | 0;
882
+ if (item > 0 && !out.includes(item)) out.push(item);
883
+ }
884
+ return out;
885
+ }
886
+ // Item entries any not-yet-turned-in quest still cares about. Bulk junk-selling must never vendor
887
+ // these: a few vanilla collect quests ask for grey drops, and selling one reverts the objective.
888
+ export function questNeededItems(log: QuestLog, questDef: (entry: number) => QuestDef | undefined): Set<number> {
889
+ const needed = new Set<number>();
890
+ for (const q of log.quests) {
891
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
892
+ const def = questDef(q.entry);
893
+ if (!def) continue;
894
+ for (const t of def.itemTargets) needed.add(t.item);
895
+ for (const item of sourceDropItemEntries(def)) needed.add(item);
896
+ if (def.srcItem?.item) needed.add(def.srcItem.item);
897
+ if (def.startedByItem) needed.add(def.startedByItem);
898
+ }
899
+ return needed;
900
+ }
901
+
902
+ /** Cite: ItemPrototype.h ITEM_CLASS_QUEST; item_template.bonding 4 = quest. */
903
+ const ITEM_CLASS_QUEST = 12;
904
+ const BINDING_QUEST = 4;
905
+
906
+ export interface LeftoverQuestItem {
907
+ slot: number;
908
+ item: number;
909
+ count: number;
910
+ quests: Array<{ entry: number; title: string }>;
911
+ }
912
+
913
+ function isQuestResidueItem(it: ItemDef): boolean {
914
+ return !!(it.questItem || it.itemClass === ITEM_CLASS_QUEST || it.binding === BINDING_QUEST);
915
+ }
916
+
917
+ function questUsedItem(def: QuestDef, item: number): { source: boolean; start: boolean; objective: boolean } {
918
+ return {
919
+ source: def.srcItem?.item === item || sourceDropItemEntries(def).includes(item),
920
+ start: def.startedByItem === item,
921
+ objective: (def.itemTargets ?? []).some((t) => t.item === item),
922
+ };
923
+ }
924
+
925
+ // SpecialFlags bit 0. Cite: QuestDef.h:147 QUEST_SPECIAL_FLAG_REPEATABLE.
926
+ export function isRepeatableQuest(def: Pick<QuestDef, 'specialFlags'>): boolean {
927
+ return ((def.specialFlags ?? 0) & QUEST_SPECIAL_FLAG_REPEATABLE) !== 0;
928
+ }
929
+
930
+ function addQuestItemIds(def: QuestDef, into: Set<number>): void {
931
+ if (def.srcItem?.item) into.add(def.srcItem.item);
932
+ if (def.startedByItem) into.add(def.startedByItem);
933
+ for (const t of def.itemTargets ?? []) if (t.item > 0) into.add(t.item);
934
+ for (const item of sourceDropItemEntries(def)) into.add(item);
935
+ }
936
+
937
+ // Bag stacks that belonged to a turned-in quest and no remaining log entry still needs.
938
+ // Class-0 leftovers of a "buy beer / linen" objective stay — those are still drinks and cloth.
939
+ // SrcItem / source-use tools and class-12 quest objects are the residue that floods bags.
940
+ // Repeatable quests (SpecialFlags REPEATABLE) keep their items — the next turn-in still wants them.
941
+ export function leftoverQuestItems(
942
+ log: QuestLog,
943
+ inventory: Inventory,
944
+ questDef: (entry: number) => QuestDef | undefined,
945
+ itemDef: (entry: number) => ItemDef | undefined,
946
+ allQuests: Iterable<QuestDef>,
947
+ ): LeftoverQuestItem[] {
948
+ const needed = questNeededItems(log, questDef);
949
+ const repeatableItems = new Set<number>();
950
+ for (const def of allQuests) {
951
+ if (!isRepeatableQuest(def)) continue;
952
+ addQuestItemIds(def, repeatableItems);
953
+ }
954
+ const fromFinished = new Map<number, { quests: Array<{ entry: number; title: string }>; source: boolean; start: boolean }>();
955
+ for (const q of log.quests) {
956
+ if (q.status !== 'turnedIn') continue;
957
+ const def = questDef(q.entry);
958
+ if (!def || isRepeatableQuest(def)) continue;
959
+ const mark = (item: number): void => {
960
+ if (!(item > 0)) return;
961
+ if (repeatableItems.has(item)) return;
962
+ const used = questUsedItem(def, item);
963
+ const cur = fromFinished.get(item) ?? { quests: [], source: false, start: false };
964
+ if (!cur.quests.some((x) => x.entry === def.entry)) {
965
+ cur.quests.push({ entry: def.entry, title: def.title });
966
+ }
967
+ cur.source = cur.source || used.source;
968
+ cur.start = cur.start || used.start;
969
+ fromFinished.set(item, cur);
970
+ };
971
+ if (def.srcItem?.item) mark(def.srcItem.item);
972
+ if (def.startedByItem) mark(def.startedByItem);
973
+ for (const t of def.itemTargets ?? []) mark(t.item);
974
+ for (const item of sourceDropItemEntries(def)) mark(item);
975
+ }
976
+
977
+ const out: LeftoverQuestItem[] = [];
978
+ inventory.stacks.forEach((stk, slot) => {
979
+ if (!stk || stk.count <= 0) return;
980
+ if (needed.has(stk.item)) return;
981
+ if (repeatableItems.has(stk.item)) return;
982
+ const it = itemDef(stk.item);
983
+ if (!it) return;
984
+ const start = it.startQuest ?? 0;
985
+ if (start > 0) {
986
+ const startDef = questDef(start);
987
+ if (startDef && isRepeatableQuest(startDef)) return;
988
+ const qs = getQuest(log, start);
989
+ if (!qs || qs.status !== 'turnedIn') return;
990
+ }
991
+ const hit = fromFinished.get(stk.item);
992
+ if (!hit || hit.quests.length === 0) return;
993
+ if (!isQuestResidueItem(it) && !hit.source && !hit.start && start <= 0) return;
994
+ out.push({ slot, item: stk.item, count: stk.count, quests: hit.quests });
995
+ });
996
+ return out;
997
+ }
998
+
999
+ /** Object (GameObject/chest) objectives met. */
1000
+ export function objectsMet(state: QuestState, quest: QuestDef): boolean {
1001
+ const objs = state.objects || {};
1002
+ return (quest.objectTargets || []).every((ot) => (objs[String(ot.objectEntry)] ?? 0) >= ot.count);
1003
+ }
1004
+
1005
+ // Sum spellCastTargets counts per spell id (duplicate slots share progress). Cite: QuestDef.h
1006
+ // ReqSpellCast[i] + ReqCreatureOrGOCount[i]; FO3 consumer.
1007
+ export function spellCastNeed(quest: QuestDef): Map<number, number> {
1008
+ const needed = new Map<number, number>();
1009
+ for (const t of quest.spellCastTargets || []) {
1010
+ if (!t.spell || (t.count || 0) <= 0) continue;
1011
+ needed.set(t.spell, (needed.get(t.spell) ?? 0) + t.count);
1012
+ }
1013
+ return needed;
1014
+ }
1015
+
1016
+ /** Spell-cast objectives met (tracked casts, capped per spell id). */
1017
+ export function spellsMet(state: QuestState, quest: QuestDef): boolean {
1018
+ const casts = state.spells || {};
1019
+ for (const [spell, count] of spellCastNeed(quest)) {
1020
+ if ((casts[String(spell)] ?? 0) < count) return false;
1021
+ }
1022
+ return true;
1023
+ }
1024
+
1025
+ export interface QuestCompleteOpts {
1026
+ // Absolute standing for a faction id. When given, a repObjective above 0 requires standing at
1027
+ // least that value; when omitted, pure-rep quests stay incomplete and mixed ones ignore rep.
1028
+ standingFor?: (factionId: number) => number;
1029
+ // QI5: `false` forces pre-QI slim body (oracle / debug); `true` forces interpreter. Default: interpreter
1030
+ // when `rawObjectives` present (`PHONECRAFT_QI_INTERPRETER=0` off).
1031
+ useInterpreter?: boolean;
1032
+ /** Absolute copper; used when RewOrReqMoney < 0 (Player.cpp:12282–12286). */
1033
+ money?: number;
1034
+ rewOrReqMoney?: number;
1035
+ canTake?: boolean;
1036
+ autoRewardedSatisfied?: boolean;
1037
+ }
1038
+
1039
+ // The pre-QI5 slim completion body, still diffed against by the LOCK-9 dual oracle. Live completion
1040
+ // routes through the QI2 interpreter when rawObjectives are baked.
1041
+ export function questCompleteSlim(
1042
+ quest: QuestDef,
1043
+ state: QuestState,
1044
+ inv: Inventory,
1045
+ opts?: QuestCompleteOpts,
1046
+ ): boolean {
1047
+ const k = killsMet(state, quest);
1048
+ const i = itemsMet(quest, inv);
1049
+ const s = sourcesMet(quest, inv);
1050
+ const o = objectsMet(state, quest);
1051
+ const sp = spellsMet(state, quest);
1052
+ const su = sourceUsesMet(state, quest);
1053
+ if (!(k && i && s && o && sp && su)) return false;
1054
+ // AreaExplored: require exploreTriggers satisfied when present (mixed or pure explore).
1055
+ // Cite: ObjectMgr force-sets EXPLORATION_OR_EVENT; AreaExploredOrEventHappens.
1056
+ if (!exploreMet(state, quest)) return false;
1057
+ // FO4: empty talk-style quests require explicit talk credit (not accept free-complete).
1058
+ if (!talkMet(state, quest)) return false;
1059
+ // Degrade gracefully: a timed quest surfaces its limit but never blocks completion, spell casts
1060
+ // are tracked via spellsMet, and reputation is enforced only when standingFor is supplied.
1061
+ const spellTargetCount = (quest.spellCastTargets || []).filter((t) => (t.count || 0) > 0).length;
1062
+ const trackableCount =
1063
+ creatureCreditTargets(quest).length +
1064
+ (quest.itemTargets?.length || 0) +
1065
+ ((quest.sourceItemTargets || []).length) +
1066
+ ((quest.objectTargets || []).length) +
1067
+ spellTargetCount;
1068
+ const hasExplore = (quest.exploreTriggers?.length || 0) > 0;
1069
+ const hasRep = !!(quest.repObjective && (quest.repObjective.value || 0) > 0);
1070
+
1071
+ if (hasRep && opts?.standingFor) {
1072
+ const need = quest.repObjective!.value || 0;
1073
+ if (opts.standingFor(quest.repObjective!.faction) < need) return false;
1074
+ // Standing met: pure-rep (and any already-met spell trackables) can complete.
1075
+ return true;
1076
+ }
1077
+
1078
+ // Pure untracked-objective quests — rep only, no explore — must not read as complete. Spell casts
1079
+ // and exploration are already gated above, and talk candidates by talkMet.
1080
+ const needsEvent = requiresEventCredit(quest);
1081
+ if (trackableCount === 0 && hasRep && !hasExplore && !needsEvent) return false;
1082
+ return true;
1083
+ }
1084
+
1085
+ // Live quest completion. With the interpreter gate on and rawObjectives present it runs
1086
+ // CanCompleteQuest then the FO4 talk and source gates; otherwise the slim body.
1087
+ export function questComplete(
1088
+ quest: QuestDef,
1089
+ state: QuestState,
1090
+ inv: Inventory,
1091
+ opts?: QuestCompleteOpts,
1092
+ ): boolean {
1093
+ if (shouldUseQuestInterpreter(quest, opts)) {
1094
+ const st = statusFromQuestState(quest, state, inv);
1095
+ if (
1096
+ !canCompleteQuest(quest, st, {
1097
+ standingFor: opts?.standingFor,
1098
+ money: opts?.money,
1099
+ rewOrReqMoney: opts?.rewOrReqMoney,
1100
+ canTake: opts?.canTake,
1101
+ autoRewardedSatisfied: opts?.autoRewardedSatisfied,
1102
+ })
1103
+ ) {
1104
+ return false;
1105
+ }
1106
+ // FO4 zone-granular gates with no CMaNGOS m_* counterpart.
1107
+ if (!talkMet(state, quest)) return false;
1108
+ if (!sourceUsesMet(state, quest)) return false;
1109
+ if (!sourcesMet(quest, inv)) return false;
1110
+ return true;
1111
+ }
1112
+ return questCompleteSlim(quest, state, inv, opts);
1113
+ }
1114
+
1115
+ // SatisfyQuestReputation-style accept gate: min requires standing at or above the value and max
1116
+ // requires it strictly below. Absent bounds are ignored, and the fields are not baked yet.
1117
+ export function satisfyQuestReputation(opts: {
1118
+ minFaction?: number;
1119
+ minValue?: number;
1120
+ maxFaction?: number;
1121
+ maxValue?: number;
1122
+ standingFor: (factionId: number) => number;
1123
+ }): boolean {
1124
+ if (opts.minFaction != null && opts.minValue != null) {
1125
+ if (opts.standingFor(opts.minFaction) < opts.minValue) return false;
1126
+ }
1127
+ if (opts.maxFaction != null && opts.maxValue != null) {
1128
+ if (opts.standingFor(opts.maxFaction) >= opts.maxValue) return false;
1129
+ }
1130
+ return true;
1131
+ }
1132
+
1133
+ // Record a creature credit — KilledMonsterCredit or TalkedToCreature — against every active quest
1134
+ // tracking that entry. It does not change status; callers run questComplete afterwards.
1135
+ export function recordKill(log: QuestLog, creatureEntry: number, questOf: (entry: number) => QuestDef | undefined): QuestLog {
1136
+ const out = clone(log);
1137
+ for (const q of out.quests) {
1138
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
1139
+ const key = String(creatureEntry);
1140
+ if (!(key in q.kills)) continue;
1141
+ const quest = questOf(q.entry);
1142
+ if (!quest) continue;
1143
+ const target = creatureCreditTargets(quest).find((kt) => kt.creatureEntry === creatureEntry);
1144
+ if (target) q.kills[key] = Math.min(target.count, (q.kills[key] ?? 0) + 1);
1145
+ }
1146
+ return out;
1147
+ }
1148
+
1149
+ // Required spell for a GO objectTarget slot, from rawObjectives when baked. 0 is a plain Use.
1150
+ // Cite: Player.cpp CastedCreatureOrGO :13558–13560, where ReqSpell must match the spell id.
1151
+ export function reqSpellForObjectTarget(
1152
+ quest: QuestDef,
1153
+ objectEntry: number,
1154
+ ): number {
1155
+ const raw = quest.rawObjectives;
1156
+ if (raw) {
1157
+ for (let j = 0; j < (raw.reqCreatureOrGoId?.length ?? 0); j++) {
1158
+ const id = raw.reqCreatureOrGoId[j] ?? 0;
1159
+ if (id === -objectEntry) return (raw.reqSpell?.[j] ?? 0) | 0;
1160
+ }
1161
+ }
1162
+ // Slim fixtures without raw: treat as plain interact (spell 0).
1163
+ return 0;
1164
+ }
1165
+
1166
+ // Record an object interaction against every active quest tracking it, on the plain Use path only:
1167
+ // slots whose ReqSpell is above 0 need recordCastedCreatureOrGO. Cite: GameObject.cpp:1635.
1168
+ export function recordObject(log: QuestLog, objectEntry: number, questOf: (entry: number) => QuestDef | undefined): QuestLog {
1169
+ const out = clone(log);
1170
+ for (const q of out.quests) {
1171
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
1172
+ const key = String(objectEntry);
1173
+ if (!q.objects) q.objects = {};
1174
+ const quest = questOf(q.entry);
1175
+ const target = quest?.objectTargets?.find((ot) => ot.objectEntry === objectEntry);
1176
+ if (!target) continue;
1177
+ // Cast-on-GO objectives (ReqSpell > 0) are not credited by bare Use.
1178
+ if (quest && reqSpellForObjectTarget(quest, objectEntry) > 0) continue;
1179
+ if (!(key in q.objects)) q.objects[key] = 0; // init for legacy saves or new objective
1180
+ q.objects[key] = Math.min(target.count, (q.objects[key] ?? 0) + 1);
1181
+ }
1182
+ return out;
1183
+ }
1184
+
1185
+ // CastedCreatureOrGO slim bridge: credit GO progress when the spell matches that slot's ReqSpell,
1186
+ // and advance spellCastTargets. A spellId of 0 matches only ReqSpell 0 — prefer recordObject.
1187
+ export function recordCastedCreatureOrGO(
1188
+ log: QuestLog,
1189
+ goEntry: number,
1190
+ spellId: number,
1191
+ questOf: (entry: number) => QuestDef | undefined,
1192
+ ): QuestLog {
1193
+ if (!goEntry) return log;
1194
+ let out = clone(log);
1195
+ let touched = false;
1196
+ for (const q of out.quests) {
1197
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
1198
+ const quest = questOf(q.entry);
1199
+ if (!quest) continue;
1200
+ const target = quest.objectTargets?.find((ot) => ot.objectEntry === goEntry);
1201
+ if (!target) continue;
1202
+ const needSpell = reqSpellForObjectTarget(quest, goEntry);
1203
+ // Player.cpp:13558 — skip wrong spell casts.
1204
+ if (needSpell !== spellId) continue;
1205
+ if (!q.objects) q.objects = {};
1206
+ const key = String(goEntry);
1207
+ if (!(key in q.objects)) q.objects[key] = 0;
1208
+ const next = Math.min(target.count, (q.objects[key] ?? 0) + 1);
1209
+ if (next !== q.objects[key]) {
1210
+ q.objects[key] = next;
1211
+ touched = true;
1212
+ }
1213
+ }
1214
+ // Also advance spellCastTargets when the cast carries a real spell id.
1215
+ if (spellId > 0) {
1216
+ const afterSpell = recordSpellCast(out, spellId, questOf);
1217
+ if (afterSpell !== out) {
1218
+ out = afterSpell;
1219
+ touched = true;
1220
+ }
1221
+ }
1222
+ return touched ? out : log;
1223
+ }
1224
+
1225
+ // Record a successful cast of `spellId` against every active quest with a matching
1226
+ // spellCastTargets entry. Progress is per spell id, and duplicate slots sum their counts.
1227
+ export function recordSpellCast(
1228
+ log: QuestLog,
1229
+ spellId: number,
1230
+ questOf: (entry: number) => QuestDef | undefined,
1231
+ ): QuestLog {
1232
+ if (!spellId) return log;
1233
+ const out = clone(log);
1234
+ for (const q of out.quests) {
1235
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
1236
+ const quest = questOf(q.entry);
1237
+ if (!quest) continue;
1238
+ const need = spellCastNeed(quest).get(spellId);
1239
+ if (need == null || need <= 0) continue;
1240
+ if (!q.spells) q.spells = {};
1241
+ const key = String(spellId);
1242
+ if (!(key in q.spells)) q.spells[key] = 0;
1243
+ q.spells[key] = Math.min(need, (q.spells[key] ?? 0) + 1);
1244
+ }
1245
+ return out;
1246
+ }
1247
+
1248
+ // FO4: record using a source item on a target, crediting active quests that list the entry in
1249
+ // sourceItemTargets, capped at the required count. It does not consume inventory.
1250
+ export function recordSourceItemUse(
1251
+ log: QuestLog,
1252
+ itemEntry: number,
1253
+ questOf: (entry: number) => QuestDef | undefined,
1254
+ count = 1,
1255
+ ): QuestLog {
1256
+ if (!itemEntry || count <= 0) return log;
1257
+ const out = clone(log);
1258
+ let touched = false;
1259
+ for (const q of out.quests) {
1260
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
1261
+ const quest = questOf(q.entry);
1262
+ if (!quest) continue;
1263
+ const target = (quest.sourceItemTargets || []).find((t) => t.item === itemEntry);
1264
+ if (!target || target.count <= 0) continue;
1265
+ if (!q.uses) q.uses = {};
1266
+ const key = String(itemEntry);
1267
+ if (!(key in q.uses)) q.uses[key] = 0;
1268
+ const next = Math.min(target.count, (q.uses[key] ?? 0) + count);
1269
+ if (next !== q.uses[key]) {
1270
+ q.uses[key] = next;
1271
+ touched = true;
1272
+ }
1273
+ }
1274
+ return touched ? out : log;
1275
+ }
1276
+
1277
+ // FO4: match NPC identity to a talk-candidate quest's ender/starter name. creatureEntry resolves via
1278
+ // optional name lookup (bake has names, not ender entries).
1279
+ export function npcMatchesTalkObjective(
1280
+ quest: QuestDef,
1281
+ opts: { npcName?: string; creatureEntry?: number },
1282
+ creatureNameOf?: (entry: number) => string | undefined,
1283
+ ): boolean {
1284
+ const names = new Set(
1285
+ [quest.enderName, quest.starterName]
1286
+ .map((n) => (n ?? '').trim())
1287
+ .filter(Boolean),
1288
+ );
1289
+ if (names.size === 0) return false;
1290
+ if (opts.npcName) {
1291
+ const n = opts.npcName.trim();
1292
+ if (n && names.has(n)) return true;
1293
+ }
1294
+ if (opts.creatureEntry != null && creatureNameOf) {
1295
+ const cn = (creatureNameOf(opts.creatureEntry) ?? '').trim();
1296
+ if (cn && names.has(cn)) return true;
1297
+ }
1298
+ return false;
1299
+ }
1300
+
1301
+ // FO4 talk credit, preferring an ender match and accepting a starter. A talkGo quest never takes
1302
+ // NPC-talk credit; an event-talk quest sets m_explored when the spoken NPC matches.
1303
+ export function recordTalkToNpc(
1304
+ log: QuestLog,
1305
+ questOf: (entry: number) => QuestDef | undefined,
1306
+ opts: { npcName?: string; creatureEntry?: number },
1307
+ creatureNameOf?: (entry: number) => string | undefined,
1308
+ ): QuestLog {
1309
+ const out = clone(log);
1310
+ let touched = false;
1311
+ for (const q of out.quests) {
1312
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
1313
+ const quest = questOf(q.entry);
1314
+ if (!quest) continue;
1315
+ // GO ender examine is the objective — talking to the starter (or any NPC) is not.
1316
+ if (hasGoEnder(quest)) continue;
1317
+
1318
+ // FO4: empty non-event talk candidates → talked bit.
1319
+ if (isTalkToNpcQuest(quest)) {
1320
+ if (q.talked) continue;
1321
+ if (!npcMatchesTalkObjective(quest, opts, creatureNameOf)) continue;
1322
+ q.talked = true;
1323
+ touched = true;
1324
+ continue;
1325
+ }
1326
+
1327
+ // Empty SF2 gossip-story / listen: QUEST_EXPLORED → m_explored (not talked).
1328
+ if (isEventTalkQuest(quest)) {
1329
+ if (q.explored) continue;
1330
+ if (!npcMatchesEventTalkObjective(quest, opts, creatureNameOf)) continue;
1331
+ q.explored = true;
1332
+ touched = true;
1333
+ }
1334
+ }
1335
+ return touched ? out : log;
1336
+ }
1337
+
1338
+ // Event-talk NPC match: the ender or starter, or a name appearing in the objectives or details —
1339
+ // the Great Bear Spirit is not the questrelation ender. Cite: dbscripts_on_gossip 3884.
1340
+ export function npcMatchesEventTalkObjective(
1341
+ quest: QuestDef,
1342
+ opts: { npcName?: string; creatureEntry?: number },
1343
+ creatureNameOf?: (entry: number) => string | undefined,
1344
+ ): boolean {
1345
+ if (npcMatchesTalkObjective(quest, opts, creatureNameOf)) return true;
1346
+ let name = (opts.npcName ?? '').trim();
1347
+ if (!name && opts.creatureEntry != null && creatureNameOf) {
1348
+ name = (creatureNameOf(opts.creatureEntry) ?? '').trim();
1349
+ }
1350
+ if (!name) return false;
1351
+ const text = `${quest.objectivesText ?? ''} ${quest.detailsText ?? ''}`;
1352
+ return text.toLowerCase().includes(name.toLowerCase());
1353
+ }
1354
+
1355
+ // Credit talk, deliver or event when the player uses a GameObject baked as an ender. For an
1356
+ // empty-objective examine quest the GO Use IS the talk proxy, and for a flag-2 quest the credit.
1357
+ export function recordGoEnderInteract(
1358
+ log: QuestLog,
1359
+ objectEntry: number,
1360
+ questOf: (entry: number) => QuestDef | undefined,
1361
+ inv: Inventory,
1362
+ opts?: {
1363
+ goName?: string;
1364
+ /** gameobject_involvedrelation reverse list on the GO def. */
1365
+ questsEnded?: readonly number[];
1366
+ },
1367
+ ): QuestLog {
1368
+ if (!objectEntry) return log;
1369
+ const out = clone(log);
1370
+ let touched = false;
1371
+ const endedSet = opts?.questsEnded?.length
1372
+ ? new Set(opts.questsEnded.filter((id) => id > 0))
1373
+ : null;
1374
+ const goName = (opts?.goName ?? '').trim();
1375
+ for (const q of out.quests) {
1376
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
1377
+ const quest = questOf(q.entry);
1378
+ if (!quest) continue;
1379
+ const isEnder =
1380
+ quest.goEnder === objectEntry ||
1381
+ (quest.goEnderEntries ?? []).includes(objectEntry) ||
1382
+ (endedSet?.has(q.entry) ?? false) ||
1383
+ (!!goName && hasEmptyTrackables(quest) && (quest.enderName ?? '').trim() === goName);
1384
+ if (!isEnder) continue;
1385
+
1386
+ // FO4 talk-to examine (empty trackables, not event/escort, not pure-rep).
1387
+ if (isTalkToNpcQuest(quest)) {
1388
+ if (!q.talked) {
1389
+ q.talked = true;
1390
+ touched = true;
1391
+ }
1392
+ continue;
1393
+ }
1394
+
1395
+ // talkGo + EXPLORATION_OR_EVENT: GO ender interact grants m_explored (event proxy).
1396
+ // Pure-rep stays gated by standingFor — do not free-complete rep-only on GO use.
1397
+ if (
1398
+ requiresEventCredit(quest) &&
1399
+ hasEmptyTrackables(quest) &&
1400
+ !(quest.repObjective && (quest.repObjective.value || 0) > 0)
1401
+ ) {
1402
+ if (!q.explored) {
1403
+ q.explored = true;
1404
+ touched = true;
1405
+ }
1406
+ continue;
1407
+ }
1408
+
1409
+ // DELIVER-with-items: arrival credit only when carried items meet the objective.
1410
+ if (isDeliverQuest(quest) && itemsMet(quest, inv)) {
1411
+ if (!q.talked) {
1412
+ q.talked = true;
1413
+ touched = true;
1414
+ }
1415
+ }
1416
+ }
1417
+ return touched ? out : log;
1418
+ }
1419
+
1420
+ // QI2.5 deliver class: item objectives, no kill, object or spell trackables, not an event or
1421
+ // escort and not a pure talk-to-NPC. The typical "Report to" or gather-and-hand-in.
1422
+ export function isDeliverQuest(quest: QuestDef): boolean {
1423
+ if (isTalkToNpcQuest(quest)) return false;
1424
+ if (requiresEventCredit(quest)) return false;
1425
+ if ((quest.killTargets?.length ?? 0) > 0) return false;
1426
+ if ((quest.objectTargets?.length ?? 0) > 0) return false;
1427
+ if ((quest.spellCastTargets ?? []).some((t) => (t.count || 0) > 0)) return false;
1428
+ return (quest.itemTargets?.length ?? 0) > 0;
1429
+ }
1430
+
1431
+ // A deliver arrival not yet credited: the items are in the bags and the player is standing in an
1432
+ // ender zone. Only a zone TRANSITION ever set `talked`, which stranded a same-zone finish.
1433
+ export function deliverArrivalPending(
1434
+ quest: QuestDef,
1435
+ state: QuestState,
1436
+ inv: Inventory,
1437
+ zoneId: string | undefined,
1438
+ ): boolean {
1439
+ if (state.talked === true || state.status === 'turnedIn' || state.status === 'failed') return false;
1440
+ if (!zoneId || !isDeliverQuest(quest)) return false;
1441
+ if (!(quest.enderZones ?? []).includes(zoneId)) return false;
1442
+ return itemsMet(quest, inv);
1443
+ }
1444
+
1445
+ export function recordSameZoneTalkOrDeliver(
1446
+ log: QuestLog,
1447
+ zoneId: string,
1448
+ questOf: (entry: number) => QuestDef | undefined,
1449
+ inv: Inventory,
1450
+ ): QuestLog {
1451
+ if (!zoneId) return log;
1452
+ const out = clone(log);
1453
+ let touched = false;
1454
+ for (const q of out.quests) {
1455
+ if (q.status === 'turnedIn' || q.status === 'failed' || q.talked) continue;
1456
+ const quest = questOf(q.entry);
1457
+ if (!quest) continue;
1458
+ const enders = quest.enderZones ?? [];
1459
+ if (!enders.includes(zoneId)) continue;
1460
+
1461
+ if (isTalkToNpcQuest(quest)) {
1462
+ // GO ender: must examine the object — being anywhere in the ender zone is not enough.
1463
+ if (hasGoEnder(quest)) continue;
1464
+ q.talked = true;
1465
+ touched = true;
1466
+ continue;
1467
+ }
1468
+ if (isDeliverQuest(quest) && itemsMet(quest, inv)) {
1469
+ // Deliver in ender zone with carried items → TalkedToCreature proxy (KI-845).
1470
+ // talkMet gates questComplete; goEnder delivers still prefer GO interact for turn-in UI.
1471
+ q.talked = true;
1472
+ touched = true;
1473
+ }
1474
+ }
1475
+ return touched ? out : log;
1476
+ }
1477
+
1478
+ // Deliver-only ender-zone credit, so gathering in the zone completes without a leave-and-reenter.
1479
+ // It does NOT credit a pure talk quest, which still needs a speak or a zone enter.
1480
+ export function recordSameZoneDeliver(
1481
+ log: QuestLog,
1482
+ zoneId: string,
1483
+ questOf: (entry: number) => QuestDef | undefined,
1484
+ inv: Inventory,
1485
+ ): QuestLog {
1486
+ if (!zoneId) return log;
1487
+ const out = clone(log);
1488
+ let touched = false;
1489
+ for (const q of out.quests) {
1490
+ if (q.status === 'turnedIn' || q.status === 'failed' || q.talked) continue;
1491
+ const quest = questOf(q.entry);
1492
+ if (!quest || !isDeliverQuest(quest)) continue;
1493
+ const enders = quest.enderZones ?? [];
1494
+ if (!enders.includes(zoneId)) continue;
1495
+ if (!itemsMet(quest, inv)) continue;
1496
+ q.talked = true;
1497
+ touched = true;
1498
+ }
1499
+ return touched ? out : log;
1500
+ }
1501
+
1502
+ /** Recompute complete/active for every non-turned-in quest (kills + live item counts + optional rep). */
1503
+ export function refreshCompletion(
1504
+ log: QuestLog,
1505
+ questOf: (entry: number) => QuestDef | undefined,
1506
+ inv: Inventory,
1507
+ opts?: QuestCompleteOpts,
1508
+ ): QuestLog {
1509
+ const out = clone(log);
1510
+ for (const q of out.quests) {
1511
+ if (q.status === 'turnedIn' || q.status === 'failed') continue;
1512
+ const quest = questOf(q.entry);
1513
+ if (!quest) continue;
1514
+ q.status = questComplete(quest, q, inv, opts) ? 'complete' : 'active';
1515
+ }
1516
+ return out;
1517
+ }
1518
+
1519
+ export function markTurnedIn(log: QuestLog, entry: number): QuestLog {
1520
+ const out = clone(log);
1521
+ const q = out.quests.find((x) => x.entry === entry);
1522
+ if (q) q.status = 'turnedIn';
1523
+ return out;
1524
+ }
1525
+
1526
+ // Fail an incomplete quest (TERMINATE_COND failQuest). Only INCOMPLETE → FAILED. Cite:
1527
+ // Player.cpp:12730–12761 FailQuest; ScriptMgr.cpp:2732 QUEST_STATUS_INCOMPLETE.
1528
+ export function fail(log: QuestLog, entry: number): QuestLog {
1529
+ const q = getQuest(log, entry);
1530
+ if (!q || q.status !== 'active') return log;
1531
+ const out = clone(log);
1532
+ const row = out.quests.find((x) => x.entry === entry);
1533
+ if (row) row.status = 'failed';
1534
+ return out;
1535
+ }
1536
+
1537
+ // Abandon a quest: drop it from the log entirely, freeing the slot and making it offerable again,
1538
+ // with kill progress lost. A turned-in quest is never abandoned — chains gate on it.
1539
+ export function abandon(log: QuestLog, entry: number): QuestLog {
1540
+ const q = getQuest(log, entry);
1541
+ if (!q || q.status === 'turnedIn') return log;
1542
+ const out = clone(log);
1543
+ out.quests = out.quests.filter((x) => x.entry !== entry);
1544
+ return out;
1545
+ }