azeroth-shell 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1163) hide show
  1. package/README.md +138 -2
  2. package/bin/azsh.mjs +19 -0
  3. package/docs/aifid/eventai-texts.json +4414 -0
  4. package/docs/dungeons/atlas/atlas-rows-dungeons.json +8989 -0
  5. package/docs/dungeons/survey/pack-id-migration.json +2418 -0
  6. package/package.json +35 -4
  7. package/src/autopilot/bossSkip.ts +26 -0
  8. package/src/autopilot/combatRotation.ts +824 -0
  9. package/src/autopilot/config.ts +533 -0
  10. package/src/autopilot/consumables.ts +58 -0
  11. package/src/autopilot/encounterStand.ts +121 -0
  12. package/src/autopilot/objectGatePlan.ts +53 -0
  13. package/src/autopilot/pathEngage.ts +229 -0
  14. package/src/autopilot/plan.ts +1643 -0
  15. package/src/autopilot/questPolicy.ts +274 -0
  16. package/src/autopilot/routeStep.ts +69 -0
  17. package/src/autopilot/sessionReport.ts +88 -0
  18. package/src/autopilot/topOffPower.ts +26 -0
  19. package/src/autopilot/view.ts +476 -0
  20. package/src/cli/bootCheck.mjs +34 -0
  21. package/src/cli/bootData.mjs +57 -0
  22. package/src/cli/combatLogFilter.ts +153 -0
  23. package/src/cli/combatLogFormat.ts +613 -0
  24. package/src/cli/fileRepository.mjs +105 -0
  25. package/src/cli/gzLoader.mjs +31 -0
  26. package/src/cli/liveGauntletEscort.ts +978 -0
  27. package/src/cli/liveStratUndead.ts +305 -0
  28. package/src/cli/macroHostCombat.ts +545 -0
  29. package/src/cli/macroHostWorld.ts +330 -0
  30. package/src/cli/prefs.mjs +31 -0
  31. package/src/cli/register.mjs +4 -0
  32. package/src/cli/repl.mjs +693 -0
  33. package/src/cli/rotationText.mjs +226 -0
  34. package/src/cli/squad.mjs +105 -0
  35. package/src/cli/term.mjs +144 -0
  36. package/src/cli/text.mjs +122 -0
  37. package/src/cli/tui.mjs +626 -0
  38. package/src/cli/tuiArena.mjs +109 -0
  39. package/src/cli/tuiAutopilotSettings.mjs +93 -0
  40. package/src/cli/tuiBags.mjs +180 -0
  41. package/src/cli/tuiBars.mjs +133 -0
  42. package/src/cli/tuiBg.mjs +243 -0
  43. package/src/cli/tuiBulk.mjs +162 -0
  44. package/src/cli/tuiCheck.mjs +1882 -0
  45. package/src/cli/tuiCity.mjs +396 -0
  46. package/src/cli/tuiCompanionKit.mjs +300 -0
  47. package/src/cli/tuiCompanions.mjs +471 -0
  48. package/src/cli/tuiDev.mjs +62 -0
  49. package/src/cli/tuiDungeon.mjs +606 -0
  50. package/src/cli/tuiEnchantTarget.mjs +120 -0
  51. package/src/cli/tuiFight.mjs +577 -0
  52. package/src/cli/tuiFightPanes.mjs +228 -0
  53. package/src/cli/tuiFloor.mjs +506 -0
  54. package/src/cli/tuiGear.mjs +158 -0
  55. package/src/cli/tuiHonor.mjs +74 -0
  56. package/src/cli/tuiLoot.mjs +97 -0
  57. package/src/cli/tuiMacros.mjs +144 -0
  58. package/src/cli/tuiOutfitter.mjs +115 -0
  59. package/src/cli/tuiPet.mjs +116 -0
  60. package/src/cli/tuiProfessions.mjs +800 -0
  61. package/src/cli/tuiRaid.mjs +127 -0
  62. package/src/cli/tuiRep.mjs +65 -0
  63. package/src/cli/tuiSaves.mjs +69 -0
  64. package/src/cli/tuiSearch.mjs +363 -0
  65. package/src/cli/tuiSettings.mjs +151 -0
  66. package/src/cli/tuiSimLab.mjs +80 -0
  67. package/src/cli/tuiTalents.mjs +60 -0
  68. package/src/cli/tuiViews.mjs +1178 -0
  69. package/src/cli/tuiWiki.mjs +181 -0
  70. package/src/cli/tuiWorld.mjs +361 -0
  71. package/src/cli/wikiIndex.ts +259 -0
  72. package/src/cli/wikiSources.ts +355 -0
  73. package/src/data/alteracValley.ts +176 -0
  74. package/src/data/appearanceOptions.ts +534 -0
  75. package/src/data/applyLootChanceSanity.ts +21 -0
  76. package/src/data/createFlavor.ts +96 -0
  77. package/src/data/customItems.ts +33 -0
  78. package/src/data/dataStore.ts +1904 -0
  79. package/src/data/dungeonGameObjects.ts +84 -0
  80. package/src/data/dungeonPlateCover.ts +66 -0
  81. package/src/data/dungeonSpawns.ts +294 -0
  82. package/src/data/dungeons/alterac-valley.ts +135 -0
  83. package/src/data/dungeons/apply-dungeon-rooms.ts +29 -0
  84. package/src/data/dungeons/apply-encounter-outdoors.ts +72 -0
  85. package/src/data/dungeons/apply-instance-geometry.ts +65 -0
  86. package/src/data/dungeons/apply-spawn-placement.ts +171 -0
  87. package/src/data/dungeons/blackfathom-deeps.ts +539 -0
  88. package/src/data/dungeons/blackrock-depths.ts +1410 -0
  89. package/src/data/dungeons/blackwing-lair.ts +129 -0
  90. package/src/data/dungeons/boss-eventai.ts +8 -0
  91. package/src/data/dungeons/deadmines.ts +410 -0
  92. package/src/data/dungeons/dire-maul-east.ts +980 -0
  93. package/src/data/dungeons/dire-maul-north.ts +661 -0
  94. package/src/data/dungeons/dire-maul-west.ts +785 -0
  95. package/src/data/dungeons/dungeon-rooms.json +5479 -0
  96. package/src/data/dungeons/encounter-outdoors.json +359 -0
  97. package/src/data/dungeons/eventai/blackfathom-deeps.ts +308 -0
  98. package/src/data/dungeons/eventai/blackrock-depths.ts +1257 -0
  99. package/src/data/dungeons/eventai/deadmines.ts +264 -0
  100. package/src/data/dungeons/eventai/dire-maul-east.ts +320 -0
  101. package/src/data/dungeons/eventai/gnomeregan.ts +213 -0
  102. package/src/data/dungeons/eventai/helpers.ts +79 -0
  103. package/src/data/dungeons/eventai/index.ts +133 -0
  104. package/src/data/dungeons/eventai/lbrs.ts +372 -0
  105. package/src/data/dungeons/eventai/maraudon.ts +323 -0
  106. package/src/data/dungeons/eventai/ragefire-chasm.ts +108 -0
  107. package/src/data/dungeons/eventai/razorfen-downs.ts +207 -0
  108. package/src/data/dungeons/eventai/razorfen-kraul.ts +326 -0
  109. package/src/data/dungeons/eventai/scholomance.ts +680 -0
  110. package/src/data/dungeons/eventai/shadowfang-keep.ts +230 -0
  111. package/src/data/dungeons/eventai/sm-cathedral.ts +75 -0
  112. package/src/data/dungeons/eventai/sm-graveyard.ts +150 -0
  113. package/src/data/dungeons/eventai/sm-library.ts +55 -0
  114. package/src/data/dungeons/eventai/stratholme-undead.ts +127 -0
  115. package/src/data/dungeons/eventai/sunken-temple.ts +582 -0
  116. package/src/data/dungeons/eventai/the-stockade.ts +228 -0
  117. package/src/data/dungeons/eventai/ubrs.ts +225 -0
  118. package/src/data/dungeons/eventai/uldaman.ts +355 -0
  119. package/src/data/dungeons/eventai/wailing-caverns.ts +396 -0
  120. package/src/data/dungeons/eventai/zul-farrak.ts +591 -0
  121. package/src/data/dungeons/gnomeregan.ts +909 -0
  122. package/src/data/dungeons/index.ts +126 -0
  123. package/src/data/dungeons/instance-geometry.json +5233 -0
  124. package/src/data/dungeons/key-sources.json +169 -0
  125. package/src/data/dungeons/lbrs.ts +996 -0
  126. package/src/data/dungeons/maraudon.ts +786 -0
  127. package/src/data/dungeons/molten-core.ts +194 -0
  128. package/src/data/dungeons/naxxramas.ts +173 -0
  129. package/src/data/dungeons/onyxias-lair.ts +71 -0
  130. package/src/data/dungeons/pack-from-clusters.ts +1256 -0
  131. package/src/data/dungeons/ragefire-chasm.ts +238 -0
  132. package/src/data/dungeons/razorfen-downs.ts +698 -0
  133. package/src/data/dungeons/razorfen-kraul.ts +766 -0
  134. package/src/data/dungeons/ruins-of-ahnqiraj.ts +121 -0
  135. package/src/data/dungeons/scholomance.ts +1076 -0
  136. package/src/data/dungeons/shadowfang-keep.ts +740 -0
  137. package/src/data/dungeons/sm-armory.ts +266 -0
  138. package/src/data/dungeons/sm-cathedral.ts +430 -0
  139. package/src/data/dungeons/sm-graveyard.ts +482 -0
  140. package/src/data/dungeons/sm-library.ts +337 -0
  141. package/src/data/dungeons/stratholme-live.ts +808 -0
  142. package/src/data/dungeons/stratholme-undead.ts +1164 -0
  143. package/src/data/dungeons/sunken-temple.ts +931 -0
  144. package/src/data/dungeons/temple-of-ahnqiraj.ts +119 -0
  145. package/src/data/dungeons/the-stockade.ts +282 -0
  146. package/src/data/dungeons/ubrs.ts +1286 -0
  147. package/src/data/dungeons/uldaman.ts +685 -0
  148. package/src/data/dungeons/wailing-caverns.ts +356 -0
  149. package/src/data/dungeons/zul-farrak.ts +686 -0
  150. package/src/data/dungeons/zul-gurub.ts +142 -0
  151. package/src/data/dungeons.ts +42 -0
  152. package/src/data/fixtures.ts +446 -0
  153. package/src/data/gameobject-data.json.gz +0 -0
  154. package/src/data/item-data.json.gz +0 -0
  155. package/src/data/loot-data.json.gz +0 -0
  156. package/src/data/maps/atlas-approach-maps.json +1425 -0
  157. package/src/data/maps/atlas-landmarks.json +1711 -0
  158. package/src/data/maps/dungeon-viewports.json +392 -0
  159. package/src/data/materializeModDamagePercentTaken.ts +70 -0
  160. package/src/data/materializeModHealthRegen.ts +89 -0
  161. package/src/data/materializeSpellReputation.ts +26 -0
  162. package/src/data/pageTexts.ts +19 -0
  163. package/src/data/profession-data.json.gz +0 -0
  164. package/src/data/raid-plans/blackfathom-deeps.json +86 -0
  165. package/src/data/raid-plans/blackrock-depths.json +419 -0
  166. package/src/data/raid-plans/blackwing-lair.json +328 -0
  167. package/src/data/raid-plans/deadmines.json +55 -0
  168. package/src/data/raid-plans/dire-maul-east.json +94 -0
  169. package/src/data/raid-plans/dire-maul-north.json +70 -0
  170. package/src/data/raid-plans/dire-maul-west.json +75 -0
  171. package/src/data/raid-plans/gnomeregan.json +131 -0
  172. package/src/data/raid-plans/lbrs.json +176 -0
  173. package/src/data/raid-plans/maraudon.json +102 -0
  174. package/src/data/raid-plans/molten-core.json +169 -0
  175. package/src/data/raid-plans/naxxramas.json +354 -0
  176. package/src/data/raid-plans/onyxias-lair.json +18 -0
  177. package/src/data/raid-plans/ragefire-chasm.json +30 -0
  178. package/src/data/raid-plans/razorfen-downs.json +75 -0
  179. package/src/data/raid-plans/razorfen-kraul.json +53 -0
  180. package/src/data/raid-plans/ruins-of-ahnqiraj.json +110 -0
  181. package/src/data/raid-plans/scholomance.json +170 -0
  182. package/src/data/raid-plans/shadowfang-keep.json +87 -0
  183. package/src/data/raid-plans/sm-armory.json +53 -0
  184. package/src/data/raid-plans/sm-cathedral.json +72 -0
  185. package/src/data/raid-plans/sm-graveyard.json +63 -0
  186. package/src/data/raid-plans/sm-library.json +23 -0
  187. package/src/data/raid-plans/stratholme-live.json +129 -0
  188. package/src/data/raid-plans/stratholme-undead.json +73 -0
  189. package/src/data/raid-plans/sunken-temple.json +159 -0
  190. package/src/data/raid-plans/temple-of-ahnqiraj.json +163 -0
  191. package/src/data/raid-plans/the-stockade.json +42 -0
  192. package/src/data/raid-plans/ubrs.json +53 -0
  193. package/src/data/raid-plans/uldaman.json +131 -0
  194. package/src/data/raid-plans/wailing-caverns.json +90 -0
  195. package/src/data/raid-plans/zul-farrak.json +156 -0
  196. package/src/data/raid-plans/zul-gurub.json +307 -0
  197. package/src/data/raidInstanceCreatures.mjs +473 -0
  198. package/src/data/rotations/index.ts +152 -0
  199. package/src/data/rotations/layout.mjs +72 -0
  200. package/src/data/rotations/pve/druid/balance.json +754 -0
  201. package/src/data/rotations/pve/druid/feral-tank.json +2448 -0
  202. package/src/data/rotations/pve/druid/feral.json +2660 -0
  203. package/src/data/rotations/pve/druid/restoration.json +424 -0
  204. package/src/data/rotations/pve/hunter/beast-mastery.json +1288 -0
  205. package/src/data/rotations/pve/hunter/marksmanship.json +1260 -0
  206. package/src/data/rotations/pve/hunter/survival.json +1373 -0
  207. package/src/data/rotations/pve/mage/arcane-leveling.json +1798 -0
  208. package/src/data/rotations/pve/mage/fire-leveling.json +2003 -0
  209. package/src/data/rotations/pve/mage/frost-leveling.json +1912 -0
  210. package/src/data/rotations/pve/paladin/holy.json +755 -0
  211. package/src/data/rotations/pve/paladin/protection.json +962 -0
  212. package/src/data/rotations/pve/paladin/retribution.json +924 -0
  213. package/src/data/rotations/pve/priest/discipline.json +772 -0
  214. package/src/data/rotations/pve/priest/holy.json +707 -0
  215. package/src/data/rotations/pve/priest/shadow.json +787 -0
  216. package/src/data/rotations/pve/rogue/assassination.json +2477 -0
  217. package/src/data/rotations/pve/rogue/combat.json +2483 -0
  218. package/src/data/rotations/pve/rogue/subtlety.json +2523 -0
  219. package/src/data/rotations/pve/shaman/elemental.json +1157 -0
  220. package/src/data/rotations/pve/shaman/enhancement.json +1085 -0
  221. package/src/data/rotations/pve/shaman/restoration.json +592 -0
  222. package/src/data/rotations/pve/warlock/affliction.json +1185 -0
  223. package/src/data/rotations/pve/warlock/demonology.json +1165 -0
  224. package/src/data/rotations/pve/warlock/destruction.json +1177 -0
  225. package/src/data/rotations/pve/warrior/arms.json +3020 -0
  226. package/src/data/rotations/pve/warrior/fury.json +2996 -0
  227. package/src/data/rotations/pve/warrior/protection.json +2509 -0
  228. package/src/data/rotations/pvp/druid/balance.json +593 -0
  229. package/src/data/rotations/pvp/druid/feral.json +3276 -0
  230. package/src/data/rotations/pvp/druid/restoration.json +662 -0
  231. package/src/data/rotations/pvp/hunter/beast-mastery.json +665 -0
  232. package/src/data/rotations/pvp/hunter/marksmanship.json +2406 -0
  233. package/src/data/rotations/pvp/hunter/survival.json +635 -0
  234. package/src/data/rotations/pvp/mage/arcane.json +574 -0
  235. package/src/data/rotations/pvp/mage/fire.json +570 -0
  236. package/src/data/rotations/pvp/mage/frost.json +4048 -0
  237. package/src/data/rotations/pvp/paladin/holy.json +1498 -0
  238. package/src/data/rotations/pvp/paladin/protection.json +567 -0
  239. package/src/data/rotations/pvp/paladin/retribution.json +1110 -0
  240. package/src/data/rotations/pvp/priest/discipline.json +1848 -0
  241. package/src/data/rotations/pvp/priest/holy.json +6340 -0
  242. package/src/data/rotations/pvp/priest/shadow.json +2329 -0
  243. package/src/data/rotations/pvp/rogue/assassination.json +1322 -0
  244. package/src/data/rotations/pvp/rogue/combat.json +5258 -0
  245. package/src/data/rotations/pvp/rogue/subtlety.json +586 -0
  246. package/src/data/rotations/pvp/shaman/elemental.json +1404 -0
  247. package/src/data/rotations/pvp/shaman/enhancement.json +1277 -0
  248. package/src/data/rotations/pvp/shaman/restoration.json +2490 -0
  249. package/src/data/rotations/pvp/warlock/affliction.json +1017 -0
  250. package/src/data/rotations/pvp/warlock/demonology.json +1503 -0
  251. package/src/data/rotations/pvp/warlock/destruction.json +638 -0
  252. package/src/data/rotations/pvp/warrior/arms.json +5665 -0
  253. package/src/data/rotations/pvp/warrior/fury.json +850 -0
  254. package/src/data/rotations/pvp/warrior/protection.json +777 -0
  255. package/src/data/schema/bundle.ts +511 -0
  256. package/src/data/schema/constants.ts +49 -0
  257. package/src/data/schema/creature.ts +454 -0
  258. package/src/data/schema/encounter.ts +155 -0
  259. package/src/data/schema/faction.ts +48 -0
  260. package/src/data/schema/item.ts +297 -0
  261. package/src/data/schema/itemEnchant.ts +105 -0
  262. package/src/data/schema/npc.ts +44 -0
  263. package/src/data/schema/primitives.ts +295 -0
  264. package/src/data/schema/profession.ts +226 -0
  265. package/src/data/schema/quest.ts +154 -0
  266. package/src/data/schema/spell.ts +1149 -0
  267. package/src/data/schema/talent.ts +74 -0
  268. package/src/data/schema/world.ts +124 -0
  269. package/src/data/schema.ts +19 -0
  270. package/src/data/skinNodeGapFill.ts +68 -0
  271. package/src/data/spell-data.json.gz +0 -0
  272. package/src/data/spell-script-targets.json +1 -0
  273. package/src/data/tables/ahItemValues.json.gz +0 -0
  274. package/src/data/tables/alteracValley.json.gz +0 -0
  275. package/src/data/tables/areatriggerTeleports.json.gz +0 -0
  276. package/src/data/tables/botNames.json.gz +0 -0
  277. package/src/data/tables/botPopulation.json.gz +0 -0
  278. package/src/data/tables/botSpecs.json.gz +0 -0
  279. package/src/data/tables/classLevelStats.json.gz +0 -0
  280. package/src/data/tables/combatConditions.json.gz +0 -0
  281. package/src/data/tables/combatSkillMeta.json.gz +0 -0
  282. package/src/data/tables/conditions.json.gz +0 -0
  283. package/src/data/tables/createInfo.json.gz +0 -0
  284. package/src/data/tables/creatureAggroGroups.json.gz +0 -0
  285. package/src/data/tables/creatureClassLevelStats.json.gz +0 -0
  286. package/src/data/tables/creaturePatrolPacks.json.gz +0 -0
  287. package/src/data/tables/creatureWeapons.json.gz +0 -0
  288. package/src/data/tables/creatures.json.gz +0 -0
  289. package/src/data/tables/dbScripts.json.gz +0 -0
  290. package/src/data/tables/dungeonAuthoredRoutes.json.gz +0 -0
  291. package/src/data/tables/dungeonCensus.json.gz +0 -0
  292. package/src/data/tables/dungeonGameObjects.json.gz +0 -0
  293. package/src/data/tables/dungeonPacks.json.gz +0 -0
  294. package/src/data/tables/dungeonPlateCover.json.gz +0 -0
  295. package/src/data/tables/dungeonSpawns.json.gz +0 -0
  296. package/src/data/tables/dungeonSurvey.json.gz +0 -0
  297. package/src/data/tables/durabilityCosts.json.gz +0 -0
  298. package/src/data/tables/durabilityQuality.json.gz +0 -0
  299. package/src/data/tables/enchantApplicators.json.gz +0 -0
  300. package/src/data/tables/entranceYards.json.gz +0 -0
  301. package/src/data/tables/explorationBasexp.json.gz +0 -0
  302. package/src/data/tables/factionTemplates.json.gz +0 -0
  303. package/src/data/tables/factions.json.gz +0 -0
  304. package/src/data/tables/gameEvents.json.gz +0 -0
  305. package/src/data/tables/gameObjects.json.gz +0 -0
  306. package/src/data/tables/gossipMenus.json.gz +0 -0
  307. package/src/data/tables/graveyards.json.gz +0 -0
  308. package/src/data/tables/instanceMaps.json.gz +0 -0
  309. package/src/data/tables/itemEnchantmentTemplates.json.gz +0 -0
  310. package/src/data/tables/itemEnchants.json.gz +0 -0
  311. package/src/data/tables/itemRandomProperties.json.gz +0 -0
  312. package/src/data/tables/itemSets.json.gz +0 -0
  313. package/src/data/tables/items.json.gz +0 -0
  314. package/src/data/tables/levelStats.json.gz +0 -0
  315. package/src/data/tables/locks.json.gz +0 -0
  316. package/src/data/tables/map-pin-overrides.json.gz +0 -0
  317. package/src/data/tables/nonCombatTrash.json.gz +0 -0
  318. package/src/data/tables/npcTexts.json.gz +0 -0
  319. package/src/data/tables/pageTexts.json.gz +0 -0
  320. package/src/data/tables/petAbilityTP.json.gz +0 -0
  321. package/src/data/tables/petGenericSpells.json.gz +0 -0
  322. package/src/data/tables/petLevelStats.json.gz +0 -0
  323. package/src/data/tables/playerbot-weightscales.json.gz +0 -0
  324. package/src/data/tables/pools.json.gz +0 -0
  325. package/src/data/tables/quests.json.gz +0 -0
  326. package/src/data/tables/races.json.gz +0 -0
  327. package/src/data/tables/reputationRanks.json.gz +0 -0
  328. package/src/data/tables/reputationRates.json.gz +0 -0
  329. package/src/data/tables/reputationSpillover.json.gz +0 -0
  330. package/src/data/tables/ridingSpells.json.gz +0 -0
  331. package/src/data/tables/scriptTexts.json.gz +0 -0
  332. package/src/data/tables/soundClipVolume.json.gz +0 -0
  333. package/src/data/tables/spellChain.json.gz +0 -0
  334. package/src/data/tables/spellTeleports.json.gz +0 -0
  335. package/src/data/tables/spells.json.gz +0 -0
  336. package/src/data/tables/talents.json.gz +0 -0
  337. package/src/data/tables/tavernTriggers.json.gz +0 -0
  338. package/src/data/tables/taxiNodes.json.gz +0 -0
  339. package/src/data/tables/taxiRoutes.json.gz +0 -0
  340. package/src/data/tables/trainers.json.gz +0 -0
  341. package/src/data/tables/transports.json.gz +0 -0
  342. package/src/data/tables/travelAnchors.json.gz +0 -0
  343. package/src/data/tables/travelEdges.json.gz +0 -0
  344. package/src/data/tables/unitConditions.json.gz +0 -0
  345. package/src/data/tables/vendors.json.gz +0 -0
  346. package/src/data/tables/weaponEnchantProcs.json.gz +0 -0
  347. package/src/data/tables/xp.json.gz +0 -0
  348. package/src/data/tables/zoneWeather.json.gz +0 -0
  349. package/src/data/tables/zones.json.gz +0 -0
  350. package/src/data/world-data.ts +56 -0
  351. package/src/data/zone-music-sets.json +2751 -0
  352. package/src/dev/etl/lib/loot-chance-sanity.mjs +180 -0
  353. package/src/domain/audioQuality.mjs +145 -0
  354. package/src/domain/bg/ab.ts +433 -0
  355. package/src/domain/bg/arenaComps.ts +644 -0
  356. package/src/domain/bg/arenaLab.ts +695 -0
  357. package/src/domain/bg/arenaPace.ts +31 -0
  358. package/src/domain/bg/arenaRating.ts +110 -0
  359. package/src/domain/bg/av.ts +758 -0
  360. package/src/domain/bg/avEnemyPush.ts +219 -0
  361. package/src/domain/bg/avField.ts +283 -0
  362. package/src/domain/bg/avInstanceNodes.ts +197 -0
  363. package/src/domain/bg/avInstanceScore.ts +123 -0
  364. package/src/domain/bg/avQuestItems.ts +55 -0
  365. package/src/domain/bg/avQuests.ts +210 -0
  366. package/src/domain/bg/avSession.ts +196 -0
  367. package/src/domain/bg/bgConsumes.ts +269 -0
  368. package/src/domain/bg/bgPrebuff.ts +153 -0
  369. package/src/domain/bg/bgRest.ts +91 -0
  370. package/src/domain/bg/bgRoster.ts +305 -0
  371. package/src/domain/bg/bgTier.ts +61 -0
  372. package/src/domain/bg/bgWorld.ts +862 -0
  373. package/src/domain/bg/match.ts +215 -0
  374. package/src/domain/bg/strategies.ts +552 -0
  375. package/src/domain/bg/types.ts +291 -0
  376. package/src/domain/bg/wsg.ts +264 -0
  377. package/src/domain/bots/actionProviderSpec.ts +38 -0
  378. package/src/domain/bots/autoConsumables.ts +285 -0
  379. package/src/domain/bots/betweenPullRest.ts +239 -0
  380. package/src/domain/bots/botGear.ts +248 -0
  381. package/src/domain/bots/botInventory.ts +486 -0
  382. package/src/domain/bots/botPet.ts +535 -0
  383. package/src/domain/bots/botPool.ts +291 -0
  384. package/src/domain/bots/botProfessions.ts +334 -0
  385. package/src/domain/bots/companionActionBar.ts +209 -0
  386. package/src/domain/bots/companionHireCost.ts +236 -0
  387. package/src/domain/bots/companionMissions.ts +582 -0
  388. package/src/domain/bots/companionPrep.ts +254 -0
  389. package/src/domain/bots/companionQuests.ts +270 -0
  390. package/src/domain/bots/companionRep.ts +75 -0
  391. package/src/domain/bots/companionRotation.ts +708 -0
  392. package/src/domain/bots/companionXp.ts +66 -0
  393. package/src/domain/bots/consumableShare.ts +811 -0
  394. package/src/domain/bots/dpsMate.ts +1072 -0
  395. package/src/domain/bots/groupCompReport.ts +132 -0
  396. package/src/domain/bots/healthstoneSupply.ts +198 -0
  397. package/src/domain/bots/outfitter.ts +1056 -0
  398. package/src/domain/bots/paladinPrePull.ts +231 -0
  399. package/src/domain/bots/partyRole.ts +40 -0
  400. package/src/domain/bots/playerbotEquipCache.ts +412 -0
  401. package/src/domain/bots/playerbotItemEligibility.ts +60 -0
  402. package/src/domain/bots/playerbotStatWeight.ts +853 -0
  403. package/src/domain/bots/playerbotUnavailableItems.ts +147 -0
  404. package/src/domain/bots/prePullBuffs.ts +219 -0
  405. package/src/domain/bots/prePullStealth.ts +82 -0
  406. package/src/domain/bots/runSupply.ts +251 -0
  407. package/src/domain/bots/runSupplyPrefs.ts +28 -0
  408. package/src/domain/bots/sendTradeGoods.ts +394 -0
  409. package/src/domain/bots/soulShardCap.ts +35 -0
  410. package/src/domain/bots/squad.ts +842 -0
  411. package/src/domain/bots/squadRun.ts +81 -0
  412. package/src/domain/character/actionbar.ts +346 -0
  413. package/src/domain/character/appearance.ts +165 -0
  414. package/src/domain/character/buildCombatant.ts +1726 -0
  415. package/src/domain/character/character.ts +356 -0
  416. package/src/domain/character/characterName.ts +43 -0
  417. package/src/domain/character/characterRatings.ts +424 -0
  418. package/src/domain/character/characterSpells.ts +282 -0
  419. package/src/domain/character/characterStats.ts +1559 -0
  420. package/src/domain/character/characterTalents.ts +398 -0
  421. package/src/domain/character/combatSkills.ts +994 -0
  422. package/src/domain/character/displayForm.ts +72 -0
  423. package/src/domain/character/formTalentAuras.ts +171 -0
  424. package/src/domain/character/forms.ts +499 -0
  425. package/src/domain/character/races.ts +40 -0
  426. package/src/domain/character/resurrectionSickness.ts +49 -0
  427. package/src/domain/character/spellbook.ts +169 -0
  428. package/src/domain/character/stanceBar.ts +354 -0
  429. package/src/domain/character/stancePrereq.ts +70 -0
  430. package/src/domain/character/talentBuilds.ts +63 -0
  431. package/src/domain/character/talentImport.ts +130 -0
  432. package/src/domain/character/talentLink.ts +123 -0
  433. package/src/domain/character/talents.ts +201 -0
  434. package/src/domain/core/conditionSnapshot.ts +92 -0
  435. package/src/domain/core/hover.ts +14 -0
  436. package/src/domain/core/ids.ts +58 -0
  437. package/src/domain/core/internalNameTags.ts +5 -0
  438. package/src/domain/core/runtimeDataPatches.ts +357 -0
  439. package/src/domain/creature/aiTargeting.ts +588 -0
  440. package/src/domain/creature/combatCondition.ts +157 -0
  441. package/src/domain/creature/creatureAggro.ts +804 -0
  442. package/src/domain/creature/creatureAi.ts +206 -0
  443. package/src/domain/creature/creatureAiConstants.ts +661 -0
  444. package/src/domain/creature/creatureCastList.ts +1007 -0
  445. package/src/domain/creature/creatureEventAi.ts +1409 -0
  446. package/src/domain/creature/creatureGroups.ts +144 -0
  447. package/src/domain/creature/creatureLinking.ts +333 -0
  448. package/src/domain/creature/creatureScaling.ts +320 -0
  449. package/src/domain/creature/creatureTrigger.ts +53 -0
  450. package/src/domain/creature/creatureWander.ts +74 -0
  451. package/src/domain/creature/eventAiTexts.ts +145 -0
  452. package/src/domain/creature/faction.ts +287 -0
  453. package/src/domain/creature/forcedReaction.ts +48 -0
  454. package/src/domain/creature/outdoorHuntable.ts +74 -0
  455. package/src/domain/creature/patrolPacks.ts +210 -0
  456. package/src/domain/creature/reputation.ts +967 -0
  457. package/src/domain/creature/unitFlags.ts +74 -0
  458. package/src/domain/creature/worldBosses.ts +393 -0
  459. package/src/domain/dungeonMusic.mjs +48 -0
  460. package/src/domain/economy/auction.ts +282 -0
  461. package/src/domain/economy/auctionBot.ts +1060 -0
  462. package/src/domain/economy/honorPrice.ts +100 -0
  463. package/src/domain/economy/mail.ts +958 -0
  464. package/src/domain/economy/money.ts +42 -0
  465. package/src/domain/economy/syntheticClassTrainers.ts +62 -0
  466. package/src/domain/economy/trading.ts +296 -0
  467. package/src/domain/economy/vendorSellAll.ts +252 -0
  468. package/src/domain/economy/vendorStock.ts +146 -0
  469. package/src/domain/items/ammo.ts +817 -0
  470. package/src/domain/items/bank.ts +162 -0
  471. package/src/domain/items/consumableFamily.ts +205 -0
  472. package/src/domain/items/craftUpgrades.ts +83 -0
  473. package/src/domain/items/createItem.ts +93 -0
  474. package/src/domain/items/durability.ts +581 -0
  475. package/src/domain/items/equipment.ts +441 -0
  476. package/src/domain/items/equipmentHelper.ts +767 -0
  477. package/src/domain/items/inventory.ts +561 -0
  478. package/src/domain/items/itemBonding.ts +93 -0
  479. package/src/domain/items/itemContainer.ts +245 -0
  480. package/src/domain/items/itemDisplayName.ts +33 -0
  481. package/src/domain/items/itemInstance.ts +181 -0
  482. package/src/domain/items/itemRandomEnchantOutcomes.ts +315 -0
  483. package/src/domain/items/itemRandomProps.ts +77 -0
  484. package/src/domain/items/itemRestrictions.ts +329 -0
  485. package/src/domain/items/itemTeaches.ts +82 -0
  486. package/src/domain/items/itemUseCooldown.ts +52 -0
  487. package/src/domain/items/itemUseTarget.ts +90 -0
  488. package/src/domain/items/junkCategories.ts +174 -0
  489. package/src/domain/items/junkFilter.ts +368 -0
  490. package/src/domain/items/keyring.ts +131 -0
  491. package/src/domain/items/loot.ts +221 -0
  492. package/src/domain/items/mounts.ts +190 -0
  493. package/src/domain/items/onUsePayload.ts +102 -0
  494. package/src/domain/items/pendingLoot.ts +107 -0
  495. package/src/domain/items/relic.ts +105 -0
  496. package/src/domain/items/rocketBoots.ts +36 -0
  497. package/src/domain/items/sortContainer.ts +139 -0
  498. package/src/domain/items/spareContainers.ts +71 -0
  499. package/src/domain/items/spellReagentIndex.ts +131 -0
  500. package/src/domain/items/staleConsumables.ts +66 -0
  501. package/src/domain/items/weaponSpender.ts +110 -0
  502. package/src/domain/listDungeonIds.mjs +28 -0
  503. package/src/domain/macro/compose.ts +239 -0
  504. package/src/domain/macro/conditions.ts +260 -0
  505. package/src/domain/macro/creatureTypes.ts +40 -0
  506. package/src/domain/macro/describe.ts +169 -0
  507. package/src/domain/macro/icon.ts +60 -0
  508. package/src/domain/macro/index.ts +20 -0
  509. package/src/domain/macro/normalize.ts +112 -0
  510. package/src/domain/macro/parse.ts +316 -0
  511. package/src/domain/macro/plan.ts +288 -0
  512. package/src/domain/macro/rotation.ts +590 -0
  513. package/src/domain/macro/rotationEnv.ts +173 -0
  514. package/src/domain/macro/slots.ts +101 -0
  515. package/src/domain/macro/snapshot.ts +114 -0
  516. package/src/domain/macro/templates.ts +446 -0
  517. package/src/domain/macro/types.ts +70 -0
  518. package/src/domain/macro/units.ts +93 -0
  519. package/src/domain/macro/weaponTypes.ts +83 -0
  520. package/src/domain/nav/cityTeleports.ts +10 -0
  521. package/src/domain/nav/mounting.ts +113 -0
  522. package/src/domain/nav/nav.ts +43 -0
  523. package/src/domain/nav/navCollision.ts +606 -0
  524. package/src/domain/nav/navOutdoors.ts +91 -0
  525. package/src/domain/nav/navRegistry.ts +179 -0
  526. package/src/domain/nav/navRoomGraph.ts +302 -0
  527. package/src/domain/nav/navTypes.ts +43 -0
  528. package/src/domain/nav/navmesh.ts +1715 -0
  529. package/src/domain/nav/speed.ts +200 -0
  530. package/src/domain/nav/storey.ts +10 -0
  531. package/src/domain/nav/travel.ts +725 -0
  532. package/src/domain/nav/travelTypes.ts +75 -0
  533. package/src/domain/pet/pet.ts +1150 -0
  534. package/src/domain/pet/petActionBar.ts +180 -0
  535. package/src/domain/pet/petSpellbook.ts +39 -0
  536. package/src/domain/pet/petStable.ts +81 -0
  537. package/src/domain/professions/autoDisenchant.ts +236 -0
  538. package/src/domain/professions/characterEnchants.ts +176 -0
  539. package/src/domain/professions/craftCooldown.ts +52 -0
  540. package/src/domain/professions/enchantApply.ts +62 -0
  541. package/src/domain/professions/enchantTargets.ts +114 -0
  542. package/src/domain/professions/enchanting.ts +236 -0
  543. package/src/domain/professions/gatherFilter.ts +149 -0
  544. package/src/domain/professions/gearEnchant.ts +137 -0
  545. package/src/domain/professions/heldTools.ts +29 -0
  546. package/src/domain/professions/learnAllRecipes.ts +146 -0
  547. package/src/domain/professions/miningHarvest.ts +69 -0
  548. package/src/domain/professions/partyDisenchant.ts +294 -0
  549. package/src/domain/professions/partyWork.ts +83 -0
  550. package/src/domain/professions/pickpocket.ts +172 -0
  551. package/src/domain/professions/poisons.ts +240 -0
  552. package/src/domain/professions/professions.ts +677 -0
  553. package/src/domain/professions/rareNodes.ts +45 -0
  554. package/src/domain/professions/skinLoot.ts +27 -0
  555. package/src/domain/professions/vendorReagents.ts +152 -0
  556. package/src/domain/professions/weaponImbueHand.ts +171 -0
  557. package/src/domain/questFarmFilter.mjs +42 -0
  558. package/src/domain/quests/doQuest.ts +1204 -0
  559. package/src/domain/quests/gossip.ts +308 -0
  560. package/src/domain/quests/gossipTalk.ts +298 -0
  561. package/src/domain/quests/interpreter/canComplete.ts +150 -0
  562. package/src/domain/quests/interpreter/claim.ts +137 -0
  563. package/src/domain/quests/interpreter/dbscript/handlers.ts +1371 -0
  564. package/src/domain/quests/interpreter/dbscript/handlersBatch.ts +1251 -0
  565. package/src/domain/quests/interpreter/dbscript/hooks.ts +331 -0
  566. package/src/domain/quests/interpreter/dbscript/index.ts +212 -0
  567. package/src/domain/quests/interpreter/dbscript/opcodes.ts +257 -0
  568. package/src/domain/quests/interpreter/dbscript/registry.ts +1194 -0
  569. package/src/domain/quests/interpreter/dbscript/registryAreatriggerScripts.ts +539 -0
  570. package/src/domain/quests/interpreter/dbscript/registryAshenvale.ts +553 -0
  571. package/src/domain/quests/interpreter/dbscript/registryAzshara.ts +272 -0
  572. package/src/domain/quests/interpreter/dbscript/registryDarkshore.ts +637 -0
  573. package/src/domain/quests/interpreter/dbscript/registryDesolace.ts +501 -0
  574. package/src/domain/quests/interpreter/dbscript/registryDustwallowMarsh.ts +735 -0
  575. package/src/domain/quests/interpreter/dbscript/registryFelwood.ts +493 -0
  576. package/src/domain/quests/interpreter/dbscript/registryFeralas.ts +485 -0
  577. package/src/domain/quests/interpreter/dbscript/registryGoScripts.ts +203 -0
  578. package/src/domain/quests/interpreter/dbscript/registryMoonglade.ts +288 -0
  579. package/src/domain/quests/interpreter/dbscript/registryNpcProfessions.ts +230 -0
  580. package/src/domain/quests/interpreter/dbscript/registryNpcsSpecial.ts +545 -0
  581. package/src/domain/quests/interpreter/dbscript/registryRazorfenDowns.ts +293 -0
  582. package/src/domain/quests/interpreter/dbscript/registryRazorfenKraul.ts +221 -0
  583. package/src/domain/quests/interpreter/dbscript/registryRedridge.ts +112 -0
  584. package/src/domain/quests/interpreter/dbscript/registrySearingGorge.ts +125 -0
  585. package/src/domain/quests/interpreter/dbscript/registrySiblings.ts +51 -0
  586. package/src/domain/quests/interpreter/dbscript/registrySilverpineForest.ts +172 -0
  587. package/src/domain/quests/interpreter/dbscript/registryStonetalonMountains.ts +180 -0
  588. package/src/domain/quests/interpreter/dbscript/registryStormwindCity.ts +369 -0
  589. package/src/domain/quests/interpreter/dbscript/registryStranglethornVale.ts +115 -0
  590. package/src/domain/quests/interpreter/dbscript/registryStratholme.ts +146 -0
  591. package/src/domain/quests/interpreter/dbscript/registrySunkenTemple.ts +149 -0
  592. package/src/domain/quests/interpreter/dbscript/registrySwampOfSorrows.ts +163 -0
  593. package/src/domain/quests/interpreter/dbscript/registryTanaris.ts +318 -0
  594. package/src/domain/quests/interpreter/dbscript/registryTeldrassil.ts +120 -0
  595. package/src/domain/quests/interpreter/dbscript/registryTheBarrens.ts +603 -0
  596. package/src/domain/quests/interpreter/dbscript/registryThousandNeedles.ts +499 -0
  597. package/src/domain/quests/interpreter/dbscript/registryTirisfalGlades.ts +222 -0
  598. package/src/domain/quests/interpreter/dbscript/registryUngoroCrater.ts +291 -0
  599. package/src/domain/quests/interpreter/dbscript/registryWarEffort.ts +773 -0
  600. package/src/domain/quests/interpreter/dbscript/registryWesternPlaguelands.ts +206 -0
  601. package/src/domain/quests/interpreter/dbscript/registryWestfall.ts +190 -0
  602. package/src/domain/quests/interpreter/dbscript/registryWetlands.ts +186 -0
  603. package/src/domain/quests/interpreter/dbscript/registryWinterspring.ts +235 -0
  604. package/src/domain/quests/interpreter/dbscript/types.ts +817 -0
  605. package/src/domain/quests/interpreter/dbscript/vm.ts +171 -0
  606. package/src/domain/quests/interpreter/diff.ts +349 -0
  607. package/src/domain/quests/interpreter/fixtures/oracle-questlog-qi2.json +2417 -0
  608. package/src/domain/quests/interpreter/flags.ts +101 -0
  609. package/src/domain/quests/interpreter/index.ts +113 -0
  610. package/src/domain/quests/interpreter/live.ts +178 -0
  611. package/src/domain/quests/interpreter/types.ts +81 -0
  612. package/src/domain/quests/interpreter/updaters.ts +412 -0
  613. package/src/domain/quests/questCorpseUse.ts +155 -0
  614. package/src/domain/quests/questDialog.ts +43 -0
  615. package/src/domain/quests/questFight.ts +34 -0
  616. package/src/domain/quests/questObjectiveLabel.ts +39 -0
  617. package/src/domain/quests/questRewardChoice.ts +85 -0
  618. package/src/domain/quests/questScriptCredit.ts +26 -0
  619. package/src/domain/quests/questScriptItemUse.ts +74 -0
  620. package/src/domain/quests/questTameRods.ts +79 -0
  621. package/src/domain/quests/questlog.ts +1545 -0
  622. package/src/domain/quests/talkDeliver.ts +170 -0
  623. package/src/domain/raid/globalActionBar.ts +195 -0
  624. package/src/domain/raid/instanceRoster.ts +194 -0
  625. package/src/domain/raid/partySummon.ts +198 -0
  626. package/src/domain/raid/partyVault.ts +320 -0
  627. package/src/domain/raid/raidBossAbilities.ts +268 -0
  628. package/src/domain/raid/raidComp.ts +335 -0
  629. package/src/domain/raid/raidCustomRows.ts +453 -0
  630. package/src/domain/raid/raidEncounterGear.ts +298 -0
  631. package/src/domain/raid/raidEncounterTactics.ts +412 -0
  632. package/src/domain/raid/raidPlan.ts +509 -0
  633. package/src/domain/raid/raidPlanEdits.ts +98 -0
  634. package/src/domain/raid/raidPlanPacks.ts +480 -0
  635. package/src/domain/raid/raidRecovery.ts +183 -0
  636. package/src/domain/raid/raidRosterTemplate.ts +138 -0
  637. package/src/domain/rotation/auraSide.ts +68 -0
  638. package/src/domain/rotation/build.ts +3031 -0
  639. package/src/domain/rotation/classes/druid.ts +58 -0
  640. package/src/domain/rotation/classes/hooks.ts +59 -0
  641. package/src/domain/rotation/classes/hunter.ts +156 -0
  642. package/src/domain/rotation/classes/index.ts +104 -0
  643. package/src/domain/rotation/classes/mage.ts +99 -0
  644. package/src/domain/rotation/classes/paladin.ts +296 -0
  645. package/src/domain/rotation/classes/priest.ts +94 -0
  646. package/src/domain/rotation/classes/pullOpener.ts +92 -0
  647. package/src/domain/rotation/classes/rogue.ts +69 -0
  648. package/src/domain/rotation/classes/shaman.ts +29 -0
  649. package/src/domain/rotation/classes/supportSpec.ts +94 -0
  650. package/src/domain/rotation/classes/warlock.ts +81 -0
  651. package/src/domain/rotation/classes/warrior.ts +402 -0
  652. package/src/domain/rotation/classify.ts +1381 -0
  653. package/src/domain/rotation/condMetricPicker.ts +379 -0
  654. package/src/domain/rotation/consumables.ts +242 -0
  655. package/src/domain/rotation/evaluate.ts +1115 -0
  656. package/src/domain/rotation/finisherLadder.ts +145 -0
  657. package/src/domain/rotation/invent.ts +938 -0
  658. package/src/domain/rotation/killOrder.ts +30 -0
  659. package/src/domain/rotation/openerForm.ts +22 -0
  660. package/src/domain/rotation/paladinBuffs.ts +568 -0
  661. package/src/domain/rotation/prebuff.ts +283 -0
  662. package/src/domain/rotation/raidRowTuning.ts +34 -0
  663. package/src/domain/rotation/rotation.ts +11 -0
  664. package/src/domain/rotation/rotationProfiles.ts +180 -0
  665. package/src/domain/rotation/rotationSuggestions.ts +851 -0
  666. package/src/domain/rotation/ruleAfterChain.ts +49 -0
  667. package/src/domain/rotation/spec.ts +70 -0
  668. package/src/domain/rotation/spellPicker.ts +140 -0
  669. package/src/domain/rotation/stand.ts +143 -0
  670. package/src/domain/rotation/types.ts +718 -0
  671. package/src/domain/run/battlegroundRun.ts +111 -0
  672. package/src/domain/run/canEnterContext.ts +32 -0
  673. package/src/domain/run/death.ts +561 -0
  674. package/src/domain/run/deathRecovery.ts +289 -0
  675. package/src/domain/run/duel.ts +141 -0
  676. package/src/domain/run/dungeonRun.ts +344 -0
  677. package/src/domain/run/floorRest.ts +159 -0
  678. package/src/domain/run/honor.ts +557 -0
  679. package/src/domain/run/honorWeek.ts +180 -0
  680. package/src/domain/run/whenMap.ts +977 -0
  681. package/src/domain/scriptdev/arathiHighlands.ts +445 -0
  682. package/src/domain/scriptdev/ashenvale.ts +635 -0
  683. package/src/domain/scriptdev/ashenvaleHelpers.ts +1215 -0
  684. package/src/domain/scriptdev/azshara.ts +892 -0
  685. package/src/domain/scriptdev/blackfathomDeeps.ts +212 -0
  686. package/src/domain/scriptdev/blackrockDepths.ts +1434 -0
  687. package/src/domain/scriptdev/blackrockSpire.ts +730 -0
  688. package/src/domain/scriptdev/blackwingLair.ts +521 -0
  689. package/src/domain/scriptdev/burningSteppes.ts +62 -0
  690. package/src/domain/scriptdev/classFilter.ts +43 -0
  691. package/src/domain/scriptdev/darkshore.ts +817 -0
  692. package/src/domain/scriptdev/darkshoreHelpers.ts +853 -0
  693. package/src/domain/scriptdev/deadmines.ts +331 -0
  694. package/src/domain/scriptdev/desolace.ts +1239 -0
  695. package/src/domain/scriptdev/direMaul.ts +859 -0
  696. package/src/domain/scriptdev/durotar.ts +532 -0
  697. package/src/domain/scriptdev/dustwallowMarsh.ts +758 -0
  698. package/src/domain/scriptdev/dustwallowMarshHelpers.ts +994 -0
  699. package/src/domain/scriptdev/easternPlaguelands.ts +21 -0
  700. package/src/domain/scriptdev/felwood.ts +1055 -0
  701. package/src/domain/scriptdev/feralas.ts +571 -0
  702. package/src/domain/scriptdev/feralasHelpers.ts +838 -0
  703. package/src/domain/scriptdev/gnomeregan.ts +466 -0
  704. package/src/domain/scriptdev/guards.ts +616 -0
  705. package/src/domain/scriptdev/hinterlands.ts +693 -0
  706. package/src/domain/scriptdev/host.ts +1134 -0
  707. package/src/domain/scriptdev/index.ts +123 -0
  708. package/src/domain/scriptdev/instanceRaids.ts +136 -0
  709. package/src/domain/scriptdev/itemScripts.ts +37 -0
  710. package/src/domain/scriptdev/lochModan.ts +283 -0
  711. package/src/domain/scriptdev/maraudon.ts +232 -0
  712. package/src/domain/scriptdev/moltenCore.ts +831 -0
  713. package/src/domain/scriptdev/moonglade.ts +659 -0
  714. package/src/domain/scriptdev/moongladeHelpers.ts +933 -0
  715. package/src/domain/scriptdev/naxxramas.ts +577 -0
  716. package/src/domain/scriptdev/naxxramasConstruct.ts +364 -0
  717. package/src/domain/scriptdev/naxxramasFrostwyrm.ts +378 -0
  718. package/src/domain/scriptdev/naxxramasMilitary.ts +276 -0
  719. package/src/domain/scriptdev/npcProfessions.ts +1025 -0
  720. package/src/domain/scriptdev/npcsSpecial.ts +1379 -0
  721. package/src/domain/scriptdev/onyxiasLair.ts +313 -0
  722. package/src/domain/scriptdev/orgrimmar.ts +147 -0
  723. package/src/domain/scriptdev/professionSpecConsumer.ts +481 -0
  724. package/src/domain/scriptdev/razorfenDowns.ts +468 -0
  725. package/src/domain/scriptdev/razorfenKraul.ts +410 -0
  726. package/src/domain/scriptdev/redridgeMountains.ts +163 -0
  727. package/src/domain/scriptdev/registry.ts +945 -0
  728. package/src/domain/scriptdev/ruinsOfAhnqiraj.ts +356 -0
  729. package/src/domain/scriptdev/scarletMonastery.ts +713 -0
  730. package/src/domain/scriptdev/scholomance.ts +682 -0
  731. package/src/domain/scriptdev/searingGorge.ts +295 -0
  732. package/src/domain/scriptdev/shadowfangKeep.ts +181 -0
  733. package/src/domain/scriptdev/silithus.ts +71 -0
  734. package/src/domain/scriptdev/silverpineForest.ts +662 -0
  735. package/src/domain/scriptdev/stonetalonMountains.ts +493 -0
  736. package/src/domain/scriptdev/stormwindCity.ts +1192 -0
  737. package/src/domain/scriptdev/stranglethornVale.ts +402 -0
  738. package/src/domain/scriptdev/stratholme.ts +650 -0
  739. package/src/domain/scriptdev/stratholmeScripts.ts +505 -0
  740. package/src/domain/scriptdev/sunkenTemple.ts +736 -0
  741. package/src/domain/scriptdev/swampOfSorrows.ts +22 -0
  742. package/src/domain/scriptdev/tanaris.ts +391 -0
  743. package/src/domain/scriptdev/tanarisHelpers.ts +661 -0
  744. package/src/domain/scriptdev/teldrassil.ts +143 -0
  745. package/src/domain/scriptdev/templeOfAhnqiraj.ts +1013 -0
  746. package/src/domain/scriptdev/theBarrens.ts +709 -0
  747. package/src/domain/scriptdev/theBarrensHelpers.ts +1026 -0
  748. package/src/domain/scriptdev/thousandNeedles.ts +426 -0
  749. package/src/domain/scriptdev/thousandNeedlesHelpers.ts +566 -0
  750. package/src/domain/scriptdev/tirisfalGlades.ts +533 -0
  751. package/src/domain/scriptdev/types.ts +1026 -0
  752. package/src/domain/scriptdev/uldaman.ts +437 -0
  753. package/src/domain/scriptdev/ungoroCrater.ts +549 -0
  754. package/src/domain/scriptdev/ungoroCraterHelpers.ts +1006 -0
  755. package/src/domain/scriptdev/wailingCaverns.ts +700 -0
  756. package/src/domain/scriptdev/warEffort.ts +371 -0
  757. package/src/domain/scriptdev/westernPlaguelands.ts +952 -0
  758. package/src/domain/scriptdev/westfall.ts +767 -0
  759. package/src/domain/scriptdev/wetlands.ts +660 -0
  760. package/src/domain/scriptdev/winterspring.ts +482 -0
  761. package/src/domain/scriptdev/winterspringHelpers.ts +801 -0
  762. package/src/domain/scriptdev/worldBosses.ts +605 -0
  763. package/src/domain/scriptdev/zulfarrak.ts +478 -0
  764. package/src/domain/scriptdev/zulgurub.ts +1152 -0
  765. package/src/domain/sim/dummySim.ts +408 -0
  766. package/src/domain/sim/targetDummy.ts +29 -0
  767. package/src/domain/social/guild.ts +148 -0
  768. package/src/domain/spells/areaAuraMembership.ts +93 -0
  769. package/src/domain/spells/breakableStunCc.ts +166 -0
  770. package/src/domain/spells/cancelAura.ts +80 -0
  771. package/src/domain/spells/diminishing.ts +167 -0
  772. package/src/domain/spells/dispel.ts +149 -0
  773. package/src/domain/spells/dispelMechanic.ts +105 -0
  774. package/src/domain/spells/equipSpells.ts +247 -0
  775. package/src/domain/spells/learnOnTargets.ts +260 -0
  776. package/src/domain/spells/mergeSchoolImmunityFromRaw.ts +66 -0
  777. package/src/domain/spells/resurrectSpell.ts +124 -0
  778. package/src/domain/spells/spellPushback.ts +39 -0
  779. package/src/domain/spells/spellReflect.ts +43 -0
  780. package/src/domain/spells/stealth.ts +183 -0
  781. package/src/domain/spells/totem.ts +394 -0
  782. package/src/domain/spells/worldCast.ts +187 -0
  783. package/src/domain/world/areaTriggerLocks.ts +177 -0
  784. package/src/domain/world/dungeonMusic.ts +108 -0
  785. package/src/domain/world/gameEvents.ts +119 -0
  786. package/src/domain/world/gameObjects.ts +1030 -0
  787. package/src/domain/world/goEventFire.ts +234 -0
  788. package/src/domain/world/goMoTransport.ts +74 -0
  789. package/src/domain/world/goTransport.ts +81 -0
  790. package/src/domain/world/hazards.ts +459 -0
  791. package/src/domain/world/openLock.ts +398 -0
  792. package/src/domain/world/pools.ts +470 -0
  793. package/src/domain/world/restArea.ts +37 -0
  794. package/src/domain/world/weather.ts +201 -0
  795. package/src/domain/zoneAmbience.mjs +87 -0
  796. package/src/domain/zoneMusic.mjs +62 -0
  797. package/src/domain/zoneMusicQueue.mjs +7 -0
  798. package/src/engine/combat/activeSeal.ts +39 -0
  799. package/src/engine/combat/aiTargetResolve.ts +64 -0
  800. package/src/engine/combat/aoeCast.ts +289 -0
  801. package/src/engine/combat/applyConsumeAura.ts +74 -0
  802. package/src/engine/combat/applyDemonicSacrifice.ts +70 -0
  803. package/src/engine/combat/applyDispelMechanic.ts +30 -0
  804. package/src/engine/combat/applyHealEffect.ts +301 -0
  805. package/src/engine/combat/applySchoolAbsorb.ts +114 -0
  806. package/src/engine/combat/applySpell.ts +3169 -0
  807. package/src/engine/combat/applySpellLate.ts +2026 -0
  808. package/src/engine/combat/applySpellLateBag.ts +144 -0
  809. package/src/engine/combat/areaAura.ts +283 -0
  810. package/src/engine/combat/attackTable.ts +508 -0
  811. package/src/engine/combat/battleRez.ts +87 -0
  812. package/src/engine/combat/castAction.ts +36 -0
  813. package/src/engine/combat/castTargetGates.ts +75 -0
  814. package/src/engine/combat/castableKit.ts +68 -0
  815. package/src/engine/combat/ccMirrorUpgrade.ts +39 -0
  816. package/src/engine/combat/chainFan.ts +210 -0
  817. package/src/engine/combat/channel.ts +423 -0
  818. package/src/engine/combat/chaseCloseness.ts +95 -0
  819. package/src/engine/combat/cmangosCastGates.ts +129 -0
  820. package/src/engine/combat/combatFlag.ts +77 -0
  821. package/src/engine/combat/combatGeometry.ts +1329 -0
  822. package/src/engine/combat/combustion.ts +68 -0
  823. package/src/engine/combat/context.ts +730 -0
  824. package/src/engine/combat/createSession.ts +1165 -0
  825. package/src/engine/combat/creatureLeash.ts +38 -0
  826. package/src/engine/combat/crowdControl.ts +1259 -0
  827. package/src/engine/combat/dispelDuty.ts +54 -0
  828. package/src/engine/combat/dispelReflect.ts +609 -0
  829. package/src/engine/combat/displaceFloor.ts +113 -0
  830. package/src/engine/combat/duelBounds.ts +47 -0
  831. package/src/engine/combat/equippedItemGate.ts +297 -0
  832. package/src/engine/combat/evadeLifecycle.ts +63 -0
  833. package/src/engine/combat/eventAi.ts +2220 -0
  834. package/src/engine/combat/facing.ts +74 -0
  835. package/src/engine/combat/fearFlee.ts +137 -0
  836. package/src/engine/combat/fighterFactory.ts +1144 -0
  837. package/src/engine/combat/forceDespawnAction.ts +51 -0
  838. package/src/engine/combat/formulas.ts +807 -0
  839. package/src/engine/combat/friendlyFan.ts +55 -0
  840. package/src/engine/combat/groundPoolTick.ts +210 -0
  841. package/src/engine/combat/hazards.ts +114 -0
  842. package/src/engine/combat/healTargets.ts +226 -0
  843. package/src/engine/combat/heartbeatResist.ts +49 -0
  844. package/src/engine/combat/hostileItemUse.ts +75 -0
  845. package/src/engine/combat/interruptAuras.ts +66 -0
  846. package/src/engine/combat/itemRescue.ts +49 -0
  847. package/src/engine/combat/itemUse.ts +260 -0
  848. package/src/engine/combat/kitById.ts +23 -0
  849. package/src/engine/combat/knockBack.ts +201 -0
  850. package/src/engine/combat/leap.ts +89 -0
  851. package/src/engine/combat/mitigation.ts +587 -0
  852. package/src/engine/combat/modTaunt.ts +43 -0
  853. package/src/engine/combat/motionClaim.ts +268 -0
  854. package/src/engine/combat/motionGate.ts +150 -0
  855. package/src/engine/combat/motionGenerator.ts +27 -0
  856. package/src/engine/combat/navSight.ts +138 -0
  857. package/src/engine/combat/perfSink.ts +28 -0
  858. package/src/engine/combat/periodicDue.ts +55 -0
  859. package/src/engine/combat/petAuras.ts +86 -0
  860. package/src/engine/combat/pickAction.ts +3449 -0
  861. package/src/engine/combat/powerForm.ts +735 -0
  862. package/src/engine/combat/powerPools.ts +49 -0
  863. package/src/engine/combat/predicates.ts +538 -0
  864. package/src/engine/combat/procsDamage.ts +1823 -0
  865. package/src/engine/combat/reactiveGates.ts +135 -0
  866. package/src/engine/combat/roles.ts +389 -0
  867. package/src/engine/combat/rotationSubject.ts +78 -0
  868. package/src/engine/combat/ruleAfter.ts +106 -0
  869. package/src/engine/combat/seedDisplayAuras.ts +203 -0
  870. package/src/engine/combat/seedOocEffects.ts +317 -0
  871. package/src/engine/combat/seedSchoolImmunities.ts +45 -0
  872. package/src/engine/combat/session-types.ts +458 -0
  873. package/src/engine/combat/sessionApi.ts +1785 -0
  874. package/src/engine/combat/sessionCharge.ts +124 -0
  875. package/src/engine/combat/sessionChase.ts +869 -0
  876. package/src/engine/combat/sessionDeaths.ts +469 -0
  877. package/src/engine/combat/sessionFocus.ts +506 -0
  878. package/src/engine/combat/sessionFollow.ts +167 -0
  879. package/src/engine/combat/sessionGeometry.ts +188 -0
  880. package/src/engine/combat/sessionHome.ts +75 -0
  881. package/src/engine/combat/sessionInject.ts +719 -0
  882. package/src/engine/combat/sessionMechanicMove.ts +555 -0
  883. package/src/engine/combat/sessionMotion.ts +1553 -0
  884. package/src/engine/combat/sessionMotionKit.ts +247 -0
  885. package/src/engine/combat/sessionMoveIntent.ts +49 -0
  886. package/src/engine/combat/sessionMoveOrder.ts +200 -0
  887. package/src/engine/combat/sessionPathTo.ts +657 -0
  888. package/src/engine/combat/sessionPools.ts +688 -0
  889. package/src/engine/combat/sessionSteer.ts +144 -0
  890. package/src/engine/combat/sessionStepBack.ts +151 -0
  891. package/src/engine/combat/sessionStepWrap.ts +116 -0
  892. package/src/engine/combat/sessionView.ts +442 -0
  893. package/src/engine/combat/simEffects.ts +32 -0
  894. package/src/engine/combat/simFacade.ts +620 -0
  895. package/src/engine/combat/simPump.ts +264 -0
  896. package/src/engine/combat/simSnapshot.ts +165 -0
  897. package/src/engine/combat/simThreadPool.ts +35 -0
  898. package/src/engine/combat/skeramImages.ts +57 -0
  899. package/src/engine/combat/slowUpdate.ts +178 -0
  900. package/src/engine/combat/snapToFloor.ts +42 -0
  901. package/src/engine/combat/speedAura.ts +29 -0
  902. package/src/engine/combat/spellReagents.ts +71 -0
  903. package/src/engine/combat/stealthDetect.ts +93 -0
  904. package/src/engine/combat/stepBackMotion.ts +171 -0
  905. package/src/engine/combat/stepSession.ts +3102 -0
  906. package/src/engine/combat/summonCast.ts +243 -0
  907. package/src/engine/combat/thaddiusPolarity.ts +97 -0
  908. package/src/engine/combat/threat.ts +388 -0
  909. package/src/engine/combat/threatOps.ts +272 -0
  910. package/src/engine/combat/tickWorkerBody.ts +211 -0
  911. package/src/engine/combat/totemAuras.ts +122 -0
  912. package/src/engine/combat/triggerPayload.ts +42 -0
  913. package/src/engine/combat/triggeredCompanion.ts +63 -0
  914. package/src/engine/combat/types.ts +1897 -0
  915. package/src/engine/combat/useObjectTrap.ts +140 -0
  916. package/src/engine/combat/walkSurface.ts +54 -0
  917. package/src/engine/combat/weapon.ts +1248 -0
  918. package/src/engine/combat/worldFrame.ts +43 -0
  919. package/src/engine/combat.solo-golden.json +2242 -0
  920. package/src/engine/combat.ts +84 -0
  921. package/src/engine/core/detMath.ts +289 -0
  922. package/src/engine/core/events.ts +357 -0
  923. package/src/engine/core/rng.ts +36 -0
  924. package/src/engine/game/actionBars.ts +124 -0
  925. package/src/engine/game/activateObjectFromSpell.ts +320 -0
  926. package/src/engine/game/applyActiveObjectFromDbScript.ts +98 -0
  927. package/src/engine/game/applyDeathAndSpellScripts.ts +268 -0
  928. package/src/engine/game/applyDoorFromDbScript.ts +388 -0
  929. package/src/engine/game/applyEscortFromDbScript.ts +290 -0
  930. package/src/engine/game/applyGoSpellcasterCast.ts +40 -0
  931. package/src/engine/game/applyHoverFromDbScript.ts +110 -0
  932. package/src/engine/game/applyOocResurrect.ts +71 -0
  933. package/src/engine/game/applySpellDurability.ts +115 -0
  934. package/src/engine/game/applyTailFromDbScript.ts +478 -0
  935. package/src/engine/game/applyTerminateFromDbScript.ts +95 -0
  936. package/src/engine/game/auctionMail.ts +269 -0
  937. package/src/engine/game/bagOverflow.ts +130 -0
  938. package/src/engine/game/bank.ts +110 -0
  939. package/src/engine/game/barAutofill.ts +26 -0
  940. package/src/engine/game/battleground.ts +369 -0
  941. package/src/engine/game/combatEnemySpec.ts +216 -0
  942. package/src/engine/game/combatObjectTraps.ts +167 -0
  943. package/src/engine/game/combatSeed.ts +320 -0
  944. package/src/engine/game/combatSession.ts +3241 -0
  945. package/src/engine/game/combatSessionClock.ts +62 -0
  946. package/src/engine/game/companionQuestActions.ts +242 -0
  947. package/src/engine/game/companionTraining.ts +40 -0
  948. package/src/engine/game/consumables.ts +624 -0
  949. package/src/engine/game/context.ts +53 -0
  950. package/src/engine/game/createHealthstone.ts +45 -0
  951. package/src/engine/game/deathFlow.ts +127 -0
  952. package/src/engine/game/devGrant.ts +117 -0
  953. package/src/engine/game/dynamicObject.ts +365 -0
  954. package/src/engine/game/enchantItem.ts +177 -0
  955. package/src/engine/game/equipmentHelper.ts +806 -0
  956. package/src/engine/game/factionState.ts +108 -0
  957. package/src/engine/game/garmentsOoc.ts +69 -0
  958. package/src/engine/game/globalActionBar.ts +179 -0
  959. package/src/engine/game/goInteractQuestCredit.ts +51 -0
  960. package/src/engine/game/goMoTransportUse.ts +61 -0
  961. package/src/engine/game/goTransportUse.ts +60 -0
  962. package/src/engine/game/healthstoneUpkeep.ts +59 -0
  963. package/src/engine/game/killSkin.ts +124 -0
  964. package/src/engine/game/learnFromItem.ts +118 -0
  965. package/src/engine/game/loadReconcile.ts +551 -0
  966. package/src/engine/game/loot.ts +294 -0
  967. package/src/engine/game/macros.ts +125 -0
  968. package/src/engine/game/mail.ts +293 -0
  969. package/src/engine/game/meetingStoneQueue.ts +75 -0
  970. package/src/engine/game/meetingStoneUse.ts +54 -0
  971. package/src/engine/game/newGameState.ts +363 -0
  972. package/src/engine/game/oocAuraInterrupt.ts +92 -0
  973. package/src/engine/game/oocCast.ts +241 -0
  974. package/src/engine/game/oocCastEffects.ts +803 -0
  975. package/src/engine/game/oocCastMethods.ts +1077 -0
  976. package/src/engine/game/openLockFromSpell.ts +174 -0
  977. package/src/engine/game/outdoorPvp.ts +187 -0
  978. package/src/engine/game/outfitter.ts +644 -0
  979. package/src/engine/game/paladinBoard.ts +393 -0
  980. package/src/engine/game/partyAuras.ts +121 -0
  981. package/src/engine/game/persistCombatAuras.ts +91 -0
  982. package/src/engine/game/pets.ts +459 -0
  983. package/src/engine/game/prePullHealthstones.ts +154 -0
  984. package/src/engine/game/professions.ts +2227 -0
  985. package/src/engine/game/pruneStaleRuntime.ts +39 -0
  986. package/src/engine/game/questFightCredit.ts +36 -0
  987. package/src/engine/game/questItemOnCorpse.ts +166 -0
  988. package/src/engine/game/questItemOnCreature.ts +103 -0
  989. package/src/engine/game/questLog.ts +879 -0
  990. package/src/engine/game/questSpellComplete.ts +21 -0
  991. package/src/engine/game/questTurnIn.ts +598 -0
  992. package/src/engine/game/raidParty.ts +32 -0
  993. package/src/engine/game/reagentBuyIn.ts +39 -0
  994. package/src/engine/game/regenRest.ts +186 -0
  995. package/src/engine/game/rotation.ts +425 -0
  996. package/src/engine/game/runFullDungeon.ts +611 -0
  997. package/src/engine/game/runSupply.ts +80 -0
  998. package/src/engine/game/sanitizeDriftedItems.ts +135 -0
  999. package/src/engine/game/scriptDevGoUse.ts +397 -0
  1000. package/src/engine/game/scriptDevInnkeeper.ts +150 -0
  1001. package/src/engine/game/scriptDevProfessionSpec.ts +99 -0
  1002. package/src/engine/game/sendEventFromSpell.ts +87 -0
  1003. package/src/engine/game/sortContainers.ts +85 -0
  1004. package/src/engine/game/spellReputation.ts +69 -0
  1005. package/src/engine/game/squad/squadDungeonSeating.ts +197 -0
  1006. package/src/engine/game/squadDungeon.ts +4222 -0
  1007. package/src/engine/game/squadQuestShare.ts +260 -0
  1008. package/src/engine/game/summonObjectSlot.ts +69 -0
  1009. package/src/engine/game/summonObjectWild.ts +73 -0
  1010. package/src/engine/game/summoningRitualUse.ts +141 -0
  1011. package/src/engine/game/talents.ts +356 -0
  1012. package/src/engine/game/tamingRodUse.ts +66 -0
  1013. package/src/engine/game/tempSpawnCreature.ts +136 -0
  1014. package/src/engine/game/tickDungeonPatrolScripts.ts +205 -0
  1015. package/src/engine/game/tickTimedQuests.ts +28 -0
  1016. package/src/engine/game/trade.ts +586 -0
  1017. package/src/engine/game/travel.ts +1155 -0
  1018. package/src/engine/game/types.ts +1855 -0
  1019. package/src/engine/game/vendorOffers.ts +244 -0
  1020. package/src/engine/game/weaponImbueItem.ts +281 -0
  1021. package/src/engine/game/worldSession.ts +183 -0
  1022. package/src/engine/game/worldState.ts +166 -0
  1023. package/src/engine/game.ts +968 -0
  1024. package/src/engine/spells/GAP.md +67 -0
  1025. package/src/engine/spells/absorbs.ts +376 -0
  1026. package/src/engine/spells/addTargetTrigger.ts +53 -0
  1027. package/src/engine/spells/auraPerSecondCost.ts +50 -0
  1028. package/src/engine/spells/auraStacking.ts +497 -0
  1029. package/src/engine/spells/cmangos-special-cases-combustion.ts +13 -0
  1030. package/src/engine/spells/cmangos-special-cases-s18.ts +445 -0
  1031. package/src/engine/spells/cmangos-special-cases-s19.ts +324 -0
  1032. package/src/engine/spells/cmangos-special-cases-s20.ts +385 -0
  1033. package/src/engine/spells/cmangos-special-cases-s21.ts +116 -0
  1034. package/src/engine/spells/cmangos-special-cases-s22.ts +24 -0
  1035. package/src/engine/spells/cmangos-special-cases-s23.ts +52 -0
  1036. package/src/engine/spells/cmangos-special-cases.ts +1570 -0
  1037. package/src/engine/spells/conditions.ts +718 -0
  1038. package/src/engine/spells/effects.ts +203 -0
  1039. package/src/engine/spells/fixtures/oracle-effects-lock9a.json +1 -0
  1040. package/src/engine/spells/immunity.ts +942 -0
  1041. package/src/engine/spells/intentEffects.ts +351 -0
  1042. package/src/engine/spells/interpreter/GAPS.md +21 -0
  1043. package/src/engine/spells/interpreter/amount.ts +109 -0
  1044. package/src/engine/spells/interpreter/class-scripts-kit.ts +478 -0
  1045. package/src/engine/spells/interpreter/diff.ts +246 -0
  1046. package/src/engine/spells/interpreter/duration.ts +90 -0
  1047. package/src/engine/spells/interpreter/form-attrs.json +1 -0
  1048. package/src/engine/spells/interpreter/form-attrs.ts +95 -0
  1049. package/src/engine/spells/interpreter/handlers/aura-remove.ts +113 -0
  1050. package/src/engine/spells/interpreter/handlers/auras-control.ts +1547 -0
  1051. package/src/engine/spells/interpreter/handlers/auras-core.ts +1583 -0
  1052. package/src/engine/spells/interpreter/handlers/auras-misc.ts +1774 -0
  1053. package/src/engine/spells/interpreter/handlers/effect-disenchant.ts +17 -0
  1054. package/src/engine/spells/interpreter/handlers/effect-skinning.ts +20 -0
  1055. package/src/engine/spells/interpreter/handlers/effects-action-utility.ts +1455 -0
  1056. package/src/engine/spells/interpreter/handlers/effects-core.ts +432 -0
  1057. package/src/engine/spells/interpreter/handlers/effects-remainder.ts +105 -0
  1058. package/src/engine/spells/interpreter/handlers/effects-summon-item.ts +687 -0
  1059. package/src/engine/spells/interpreter/handlers/mod-increase-energy-percent.ts +36 -0
  1060. package/src/engine/spells/interpreter/handlers/mod-increase-energy.ts +36 -0
  1061. package/src/engine/spells/interpreter/handlers/periodic-trigger-zero.ts +327 -0
  1062. package/src/engine/spells/interpreter/handlers/procs.ts +131 -0
  1063. package/src/engine/spells/interpreter/handlers/registry.ts +975 -0
  1064. package/src/engine/spells/interpreter/handlers/shared.ts +667 -0
  1065. package/src/engine/spells/interpreter/handlers.ts +9 -0
  1066. package/src/engine/spells/interpreter/hunter-kit.ts +202 -0
  1067. package/src/engine/spells/interpreter/index.ts +525 -0
  1068. package/src/engine/spells/interpreter/item-triggers.ts +1121 -0
  1069. package/src/engine/spells/interpreter/lift-consume-energy.ts +37 -0
  1070. package/src/engine/spells/interpreter/mage-portals.ts +37 -0
  1071. package/src/engine/spells/interpreter/materialize-charm.ts +88 -0
  1072. package/src/engine/spells/interpreter/materialize-dispel-mechanic.ts +25 -0
  1073. package/src/engine/spells/interpreter/materialize-rating-buffs.ts +81 -0
  1074. package/src/engine/spells/interpreter/materialize-speed.ts +109 -0
  1075. package/src/engine/spells/interpreter/materialize-spell-magnet.ts +22 -0
  1076. package/src/engine/spells/interpreter/opcodes.ts +789 -0
  1077. package/src/engine/spells/interpreter/paladin-kit.ts +95 -0
  1078. package/src/engine/spells/interpreter/paladin-seals.ts +499 -0
  1079. package/src/engine/spells/interpreter/priest-warlock-kit.ts +135 -0
  1080. package/src/engine/spells/interpreter/proc.ts +443 -0
  1081. package/src/engine/spells/interpreter/registry/helpers.ts +16 -0
  1082. package/src/engine/spells/interpreter/registry/index.ts +216 -0
  1083. package/src/engine/spells/interpreter/registry/packs/class-abilities.ts +304 -0
  1084. package/src/engine/spells/interpreter/registry/packs/dire-maul.ts +44 -0
  1085. package/src/engine/spells/interpreter/registry/packs/dummy-script-tail.ts +1065 -0
  1086. package/src/engine/spells/interpreter/registry/packs/ex-wave-m.ts +70 -0
  1087. package/src/engine/spells/interpreter/registry/packs/life-tap.ts +107 -0
  1088. package/src/engine/spells/interpreter/registry/packs/paladin.ts +246 -0
  1089. package/src/engine/spells/interpreter/registry/packs/raid-script-summons.ts +26 -0
  1090. package/src/engine/spells/interpreter/registry/packs/raid-threat-wipe.ts +22 -0
  1091. package/src/engine/spells/interpreter/registry/packs/shaman.ts +214 -0
  1092. package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-a.ts +269 -0
  1093. package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-b.ts +370 -0
  1094. package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-c.ts +172 -0
  1095. package/src/engine/spells/interpreter/registry/packs/si4-dummy-hunter.ts +60 -0
  1096. package/src/engine/spells/interpreter/registry/packs/si4-dummy-warrior.ts +31 -0
  1097. package/src/engine/spells/interpreter/registry/packs/si4-script-generic-a.ts +114 -0
  1098. package/src/engine/spells/interpreter/registry/packs/si4-script-generic-b.ts +249 -0
  1099. package/src/engine/spells/interpreter/registry/packs/si4-script-generic-c.ts +233 -0
  1100. package/src/engine/spells/interpreter/registry/types.ts +48 -0
  1101. package/src/engine/spells/interpreter/talent-scripted.ts +329 -0
  1102. package/src/engine/spells/interpreter/targeting.ts +90 -0
  1103. package/src/engine/spells/interpreter/types-intent.ts +1501 -0
  1104. package/src/engine/spells/interpreter/types.ts +419 -0
  1105. package/src/engine/spells/interpreter/warrior-druid-kit.ts +314 -0
  1106. package/src/engine/spells/interpreter/weapon-imbue-kit.ts +384 -0
  1107. package/src/engine/spells/interrupt.ts +217 -0
  1108. package/src/engine/spells/meleeAttackReset.ts +60 -0
  1109. package/src/engine/spells/oocTriggerSpell.ts +49 -0
  1110. package/src/engine/spells/overrideClassScripts.ts +319 -0
  1111. package/src/engine/spells/parryHaste.ts +20 -0
  1112. package/src/engine/spells/procs.ts +300 -0
  1113. package/src/engine/spells/recovery.ts +203 -0
  1114. package/src/engine/spells/scriptTargetDest.ts +76 -0
  1115. package/src/engine/spells/scriptTargets.ts +85 -0
  1116. package/src/engine/spells/singleTargetAura.ts +82 -0
  1117. package/src/engine/spells/specialCasePayloads.ts +57 -0
  1118. package/src/engine/spells/spellAttributes.ts +249 -0
  1119. package/src/engine/spells/spellBonus.ts +143 -0
  1120. package/src/engine/spells/spellModIntents.ts +419 -0
  1121. package/src/engine/spells/spellMods.ts +283 -0
  1122. package/src/engine/spells/spellRadius.ts +15 -0
  1123. package/src/engine/spells/trapObjects.ts +26 -0
  1124. package/src/json.d.ts +7 -0
  1125. package/src/persistence/memoryRepository.ts +23 -0
  1126. package/src/persistence/saveBackups.ts +162 -0
  1127. package/src/persistence/saveFile.ts +68 -0
  1128. package/src/persistence/saveRepository.ts +11 -0
  1129. package/src/persistence/saveShape.ts +668 -0
  1130. package/src/progression/leveling.ts +89 -0
  1131. package/src/progression/rest.ts +1434 -0
  1132. package/src/progression/training.ts +226 -0
  1133. package/src/progression/xpGain.ts +369 -0
  1134. package/src/quests/quests.ts +1580 -0
  1135. package/src/world/aoePull.ts +198 -0
  1136. package/src/world/areaTableDbc.ts +39 -0
  1137. package/src/world/areaTableMapIdPairs.ts +141 -0
  1138. package/src/world/bakeSlug.ts +43 -0
  1139. package/src/world/continuousDungeon.ts +1250 -0
  1140. package/src/world/dungeonBakeProjection.ts +190 -0
  1141. package/src/world/dungeonMapDensity.ts +342 -0
  1142. package/src/world/dungeonMapProjection.ts +135 -0
  1143. package/src/world/dungeonNav.ts +904 -0
  1144. package/src/world/dungeonNpcSim.ts +147 -0
  1145. package/src/world/dungeonObjectives.ts +234 -0
  1146. package/src/world/dungeonPreset.ts +35 -0
  1147. package/src/world/dungeonRoute.ts +311 -0
  1148. package/src/world/dungeonRouteGeometry.ts +732 -0
  1149. package/src/world/dungeonRouteOrder.ts +1037 -0
  1150. package/src/world/dungeonWingRooms.ts +426 -0
  1151. package/src/world/escort.ts +296 -0
  1152. package/src/world/goPoolRoll.ts +75 -0
  1153. package/src/world/instanceObjects.ts +785 -0
  1154. package/src/world/instances.ts +1572 -0
  1155. package/src/world/meetingStones.ts +56 -0
  1156. package/src/world/navmeshFloors.ts +145 -0
  1157. package/src/world/nearGrid.ts +167 -0
  1158. package/src/world/packBudget.ts +613 -0
  1159. package/src/world/rareRoll.ts +335 -0
  1160. package/src/world/routeAuthoring.ts +132 -0
  1161. package/src/world/routeEdit.ts +209 -0
  1162. package/src/world/world.ts +812 -0
  1163. package/src/world/zoneHazards.ts +47 -0
@@ -0,0 +1,1580 @@
1
+ import type { DataStore } from '../data/dataStore.ts';
2
+ import { firstDungeonGameObjectForEntry } from '../data/dungeonGameObjects.ts';
3
+ import type { Character } from '../domain/character/character.ts';
4
+ import type { QuestLog, QuestState, QuestPlayerSnapshot } from '../domain/quests/questlog.ts';
5
+ import {
6
+ getQuest,
7
+ questComplete,
8
+ canSeeQuest,
9
+ canTakeQuest,
10
+ questStatusFromLog,
11
+ isTalkToNpcQuest,
12
+ isEventTalkQuest,
13
+ isDeliverQuest,
14
+ talkMet,
15
+ itemsMet,
16
+ requiresEventCredit,
17
+ exploreMet,
18
+ accept,
19
+ shouldGrantSrcItemOnAccept,
20
+ isWorldLootSrcItemObjective,
21
+ isItemStartedQuest,
22
+ isRepeatableQuest,
23
+ itemStartGateMet,
24
+ canDegradedAutoFinish,
25
+ satisfyQuestClass,
26
+ satisfyQuestExclusiveGroup,
27
+ satisfyQuestLevel,
28
+ satisfyQuestPreviousQuest,
29
+ satisfyQuestRace,
30
+ satisfyQuestReputation,
31
+ satisfyQuestSkill,
32
+ satisfyQuestTimed,
33
+ hasGoEnder,
34
+ goEnderEntryOf,
35
+ creatureCreditTargets,
36
+ } from '../domain/quests/questlog.ts';
37
+ import { getStanding } from '../domain/creature/reputation.ts';
38
+ import type { Inventory } from '../domain/items/inventory.ts';
39
+ import { addItem, countItem } from '../domain/items/inventory.ts';
40
+ import type { ConditionDef, QuestDef } from '../data/schema.ts';
41
+ import { isConditionSatisfied, type ConditionSnapshot } from '../engine/spells/conditions.ts';
42
+ import { isConditionFulfilled, worldStateConditionFns } from '../engine/game/worldState.ts';
43
+ import {
44
+ canOpenLock,
45
+ requiredKeyItemEntry,
46
+ hardestSkillLockCase,
47
+ } from '../domain/world/openLock.ts';
48
+ import { FARM_LEVEL_HI_SLACK } from '../domain/questFarmFilter.mjs';
49
+ import { lootRowIsQuestItemSource } from '../domain/items/loot.ts';
50
+ import { creatureObjectiveLabel } from '../domain/quests/questObjectiveLabel.ts';
51
+ import { isScriptCreditOnlyTarget } from '../domain/quests/questScriptCredit.ts';
52
+ import {
53
+ corpseUseFarmEntries,
54
+ corpseUseFightEntries,
55
+ corpseUseSpellId,
56
+ corpseUseSummonedEntries,
57
+ corpseUseWorkRemaining,
58
+ } from '../domain/quests/questCorpseUse.ts';
59
+
60
+ /** Zones listing `creatureEntry` as huntable or on spatial spawns (Do Quest zonesHostingCreature). */
61
+ function spawnZonesForCreature(ds: DataStore, creatureEntry: number): string[] {
62
+ const cache = spawnZoneCache(ds);
63
+ const hit = cache.get(creatureEntry);
64
+ if (hit) return hit;
65
+ const out: string[] = [];
66
+ for (const z of ds.zones()) {
67
+ if ((z.huntable ?? []).includes(creatureEntry)) out.push(z.id);
68
+ else if ((z.spawns ?? []).some((s) => s.entry === creatureEntry)) out.push(z.id);
69
+ }
70
+ cache.set(creatureEntry, out);
71
+ return out;
72
+ }
73
+
74
+ // A per-store memo for the spawn-zone walk: one call visits all 46 zones and Do Quest asks per dropper
75
+ // per quest. Keyed on the world generation so a late attach drops it rather than freezing the answer.
76
+ const spawnZoneCaches = new WeakMap<DataStore, { gen: number; zones: Map<number, string[]> }>();
77
+
78
+ function spawnZoneCache(ds: DataStore): Map<number, string[]> {
79
+ const gen = ds.zoneWorldGen();
80
+ let slot = spawnZoneCaches.get(ds);
81
+ if (!slot || slot.gen !== gen) {
82
+ slot = { gen, zones: new Map() };
83
+ spawnZoneCaches.set(ds, slot);
84
+ }
85
+ return slot.zones;
86
+ }
87
+
88
+ /** Classic WoW difficulty colour of a target relative to the player, red = hardest. */
89
+ export type DifficultyTier = 'gray' | 'green' | 'yellow' | 'orange' | 'red';
90
+
91
+ // The level at (or below) which a target is "gray" — trivial, no XP — for a player of `playerLevel`. This
92
+ // is the classic 1.12 gray formula, used for both mobs and quests. Below level 6 nothing is gray.
93
+ function grayLevel(playerLevel: number): number {
94
+ if (playerLevel <= 5) return 0;
95
+ if (playerLevel <= 39) return playerLevel - 5 - Math.floor(playerLevel / 10);
96
+ if (playerLevel <= 59) return playerLevel - 1 - Math.floor(playerLevel / 5);
97
+ return playerLevel - 9;
98
+ }
99
+
100
+ // Difficulty colour of a `targetLevel` (quest or mob) for a given `playerLevel`, mirroring the WoW client:
101
+ // red (deadly, 5+ above) → orange → yellow (even, ±2) → green (below but not trivial) → gray (trivial).
102
+ export function difficultyTier(targetLevel: number, playerLevel: number): DifficultyTier {
103
+ const diff = targetLevel - playerLevel;
104
+ if (diff >= 5) return 'red';
105
+ if (diff >= 3) return 'orange';
106
+ if (diff >= -2) return 'yellow';
107
+ if (targetLevel > grayLevel(playerLevel)) return 'green';
108
+ return 'gray';
109
+ }
110
+
111
+ /** A quest offered in a zone, annotated for display (colour + accept eligibility). */
112
+ export interface OfferedQuest {
113
+ quest: QuestDef;
114
+ tier: DifficultyTier; // colour of the quest vs the player's level
115
+ canAccept: boolean; // player meets the quest's minLevel
116
+ reqLevel: number; // minLevel required to accept
117
+ }
118
+
119
+ function compareOffered(a: OfferedQuest, b: OfferedQuest, playerLevel: number): number {
120
+ // Acceptable quests first, then the ones closest to the player's level, so the most relevant surface
121
+ // at the top; then a low-to-high tiebreak, then required level, then title.
122
+ if (a.canAccept !== b.canAccept) return a.canAccept ? -1 : 1;
123
+ return (
124
+ Math.abs(a.quest.questLevel - playerLevel) - Math.abs(b.quest.questLevel - playerLevel) ||
125
+ a.quest.questLevel - b.quest.questLevel ||
126
+ a.reqLevel - b.reqLevel ||
127
+ a.quest.title.localeCompare(b.quest.title)
128
+ );
129
+ }
130
+
131
+ // Whether a quest's chain prerequisite is satisfied — it must have been TURNED IN. A prerequisite the
132
+ // bake pruned counts as satisfied, or a chain whose talk-only intro was dropped could never start.
133
+ export function prereqMet(ds: DataStore, quest: QuestDef, log: QuestLog): boolean {
134
+ if (!quest.prevQuest) return true;
135
+ if (!ds.quest(quest.prevQuest)) return true; // prerequisite pruned from the data → don't gate
136
+ return getQuest(log, quest.prevQuest)?.status === 'turnedIn';
137
+ }
138
+
139
+ // The pure player view for the offer gates. It must carry every field the accept path's snapshot does,
140
+ // or an offer passes the list's gate and fails the accept's — which is a button that says no.
141
+ function offerSnapshot(char: Character, log: QuestLog, ds?: DataStore): QuestPlayerSnapshot {
142
+ let condTable: Map<number, ConditionDef> | undefined;
143
+ return {
144
+ level: char.level,
145
+ classId: char.cls,
146
+ raceId: char.race,
147
+ questStatus: questStatusFromLog(log),
148
+ standingFor: ds
149
+ ? (factionId: number) => {
150
+ const f = ds.faction(factionId);
151
+ return f ? getStanding(char.reputations, f, char.race, char.cls) : 0;
152
+ }
153
+ : undefined,
154
+ skillValue: (skillId: number) => char.combatSkills?.[skillId]?.current ?? 0,
155
+ hasActiveTimedQuest: log.quests.some((qq) => {
156
+ if (qq.status === 'turnedIn' || qq.status === 'failed') return false;
157
+ const qd = ds?.quest(qq.entry);
158
+ return !!(qd?.limitTime && qd.limitTime > 0);
159
+ }),
160
+ conditionEval: ds ? (conditionId: number) => {
161
+ if (!condTable) {
162
+ condTable = new Map();
163
+ for (const c of ds.conditionsList()) condTable.set(c.id, c);
164
+ }
165
+ const cSnap: ConditionSnapshot = {
166
+ level: char.level,
167
+ raceMask: char.race ? 1 << (char.race - 1) : undefined,
168
+ classMask: char.cls ? 1 << (char.cls - 1) : undefined,
169
+ quests: Object.fromEntries(log.quests.map((q) => [
170
+ q.entry,
171
+ q.status === 'turnedIn' ? 'rewarded' : q.status === 'complete' ? 'complete' : 'taken',
172
+ ])),
173
+ };
174
+ return isConditionSatisfied(conditionId, cSnap, condTable).ok;
175
+ } : undefined,
176
+ };
177
+ }
178
+
179
+ // A log row that no longer blocks an offer: a rewarded REPEATABLE, which core has already put back to
180
+ // QUEST_STATUS_NONE. Cite: Player.cpp:12611 RewardQuest; questlog.satisfyQuestStatus.
181
+ export function logRowBlocksOffer(log: QuestLog, quest: QuestDef): boolean {
182
+ const held = getQuest(log, quest.entry);
183
+ if (!held) return false;
184
+ return !(held.status === 'turnedIn' && isRepeatableQuest(quest));
185
+ }
186
+
187
+ // Every quest a zone offers that is not already in the log and that this character may see, including
188
+ // ones their level refuses, so the UI can show the requirement. Item-starts and wrong-zone rows are out.
189
+ export function offeredQuests(ds: DataStore, zoneId: string, char: Character, log: QuestLog): OfferedQuest[] {
190
+ const zone = ds.zone(zoneId);
191
+ if (!zone) return [];
192
+ const snap = offerSnapshot(char, log, ds);
193
+ const out: OfferedQuest[] = [];
194
+ for (const qe of zone.quests) {
195
+ const q = ds.quest(qe);
196
+ if (!q) continue;
197
+ if (logRowBlocksOffer(log, q)) continue; // already active/complete/turned in
198
+ if (isItemStartedQuest(q)) continue; // the item is the giver — see bagQuestStarts
199
+ const starters = q.starterZones ?? [];
200
+ if (starters.length > 0 && !starters.includes(zoneId)) continue;
201
+ // prevQuest pruned from the bake (no row) must not gate — same as prereqMet / acceptQuest.
202
+ const prevQuestMissingFromData = !!(q.prevQuest && !ds.quest(q.prevQuest));
203
+ if (!canSeeQuest(snap, q, { prevQuestMissingFromData })) continue; // class/race/prev/skill/exclusive/week
204
+ const prevOpts = {
205
+ prevQuestMissingFromData,
206
+ questDefFor: (id: number) => ds.quest(id),
207
+ };
208
+ out.push({
209
+ quest: q,
210
+ tier: difficultyTier(q.questLevel, char.level),
211
+ // Full CanTakeQuest (level + rep + timed + exclusive) so Accept never soft-fails.
212
+ canAccept: canTakeQuest(snap, q, prevOpts),
213
+ reqLevel: q.minLevel,
214
+ });
215
+ }
216
+ return out.sort((a, b) => compareOffered(a, b, char.level));
217
+ }
218
+
219
+ /** The chain follow-up this zone offers now that `handedIn` is turned in, or null. Only an explicit
220
+ * link counts - NextQuestId, else an offer whose PrevQuestId is it. Cite: quest.ts:53-54. */
221
+ export function chainFollowUpOffer(
222
+ ds: DataStore,
223
+ char: Character,
224
+ log: QuestLog,
225
+ handedIn: number,
226
+ ): OfferedQuest | null {
227
+ const offers = offeredQuests(ds, char.zoneId, char, log).filter((o) => o.canAccept);
228
+ if (offers.length === 0) return null;
229
+ const next = ds.quest(handedIn)?.nextQuest ?? 0;
230
+ return (next ? offers.find((o) => o.quest.entry === next) : undefined)
231
+ ?? offers.find((o) => o.quest.prevQuest === handedIn)
232
+ ?? null;
233
+ }
234
+
235
+ /** An item-started offer, plus the bag item that is the giver. */
236
+ export interface BagQuestStart extends OfferedQuest {
237
+ /** item_template entry whose `startQuest` opened this offer. */
238
+ startItem: number;
239
+ }
240
+
241
+ // The item-started quests the bags can start right now: the ITEM is the giver, so the offer follows the
242
+ // bag. Of 208 baked item-starts, 30 appear in no zone list and the rest were gated to one zone.
243
+ export function bagQuestStarts(
244
+ ds: DataStore,
245
+ char: Character,
246
+ log: QuestLog,
247
+ inv: Inventory,
248
+ ): BagQuestStart[] {
249
+ // Candidates first: this runs on the world-view / menu-badge render path, and most bags
250
+ // hold no quest starter at all, so the snapshot is only worth building when one does.
251
+ const candidates: { startItem: number; quest: QuestDef }[] = [];
252
+ const seenQuest = new Set<number>();
253
+ for (const st of inv.stacks) {
254
+ const def = ds.item(st.item);
255
+ const qe = def?.startQuest ?? 0;
256
+ if (qe <= 0 || seenQuest.has(qe)) continue;
257
+ seenQuest.add(qe);
258
+ const q = ds.quest(qe);
259
+ if (!q) continue;
260
+ if (logRowBlocksOffer(log, q)) continue; // already active/complete/turned in
261
+ candidates.push({ startItem: st.item, quest: q });
262
+ }
263
+ if (!candidates.length) return [];
264
+ const snap = offerSnapshot(char, log, ds);
265
+ const out: BagQuestStart[] = [];
266
+ for (const { startItem, quest: q } of candidates) {
267
+ const prevQuestMissingFromData = !!(q.prevQuest && !ds.quest(q.prevQuest));
268
+ if (!canSeeQuest(snap, q, { prevQuestMissingFromData })) continue;
269
+ // The engine gates on the required start item, preferring the baked one. Two items can name the
270
+ // same quest, so holding the other one offered an accept that then refused.
271
+ if (!itemStartGateMet(q, inv)) continue;
272
+ out.push({
273
+ quest: q,
274
+ startItem,
275
+ tier: difficultyTier(q.questLevel, char.level),
276
+ canAccept: canTakeQuest(snap, q, {
277
+ prevQuestMissingFromData,
278
+ questDefFor: (id: number) => ds.quest(id),
279
+ }),
280
+ reqLevel: q.minLevel,
281
+ });
282
+ }
283
+ return out.sort((a, b) => compareOffered(a, b, char.level));
284
+ }
285
+
286
+ /** The quest a held item would start, when the bags can start it right now. */
287
+ export function bagQuestStartFor(
288
+ ds: DataStore,
289
+ char: Character,
290
+ log: QuestLog,
291
+ inv: Inventory,
292
+ itemEntry: number,
293
+ ): BagQuestStart | undefined {
294
+ return bagQuestStarts(ds, char, log, inv).find((o) => o.startItem === itemEntry);
295
+ }
296
+
297
+ /** What a quest-starting item is doing right now, or why it is doing nothing. */
298
+ export type BagQuestStartState =
299
+ | { kind: 'offer'; offer: BagQuestStart }
300
+ | { kind: 'blocked'; quest: QuestDef; reason: string }
301
+ | null;
302
+
303
+ // The state of a quest a bag item starts: the live offer, or the one gate holding it. Without this a
304
+ // gated starter is a dead item with no button and no explanation. The reasons follow the C++ order.
305
+ export function bagQuestStartState(
306
+ ds: DataStore,
307
+ char: Character,
308
+ log: QuestLog,
309
+ inv: Inventory,
310
+ itemEntry: number,
311
+ ): BagQuestStartState {
312
+ const qe = ds.item(itemEntry)?.startQuest ?? 0;
313
+ if (qe <= 0) return null;
314
+ const quest = ds.quest(qe);
315
+ if (!quest) return null;
316
+ const offer = bagQuestStarts(ds, char, log, inv).find((o) => o.startItem === itemEntry);
317
+ if (offer?.canAccept) return { kind: 'offer', offer };
318
+ const blocked = (reason: string): BagQuestStartState => ({ kind: 'blocked', quest, reason });
319
+ const st = getQuest(log, qe);
320
+ if (st) {
321
+ return blocked(st.status === 'turnedIn'
322
+ ? `You already finished "${quest.title}"`
323
+ : `"${quest.title}" is in your quest log`);
324
+ }
325
+ const snap = offerSnapshot(char, log, ds);
326
+ if (!satisfyQuestClass(snap, quest)) return blocked(`"${quest.title}" is not for your class`);
327
+ if (!satisfyQuestRace(snap, quest)) return blocked(`"${quest.title}" is not for your race`);
328
+ if (!satisfyQuestLevel(snap, quest)) return blocked(`"${quest.title}" requires level ${quest.minLevel}`);
329
+ if (!satisfyQuestSkill(snap, quest)) return blocked(`"${quest.title}" needs a higher skill`);
330
+ if (snap.standingFor && !satisfyQuestReputation({
331
+ minFaction: quest.requiredMinRepFaction,
332
+ minValue: quest.requiredMinRepValue,
333
+ maxFaction: quest.requiredMaxRepFaction,
334
+ maxValue: quest.requiredMaxRepValue,
335
+ standingFor: snap.standingFor,
336
+ })) {
337
+ return blocked(`Your reputation is wrong for "${quest.title}"`);
338
+ }
339
+ const prev = quest.prevQuest ? ds.quest(quest.prevQuest) : undefined;
340
+ if (prev && !satisfyQuestPreviousQuest(snap, quest, { questDefFor: (id: number) => ds.quest(id) })) {
341
+ return blocked(`Finish "${prev.title}" before "${quest.title}"`);
342
+ }
343
+ // INVALIDREASON_QUEST_ONLY_ONE_TIMED (Player.cpp:13051).
344
+ if (!satisfyQuestTimed(snap, quest)) return blocked(`Finish your timed quest before "${quest.title}"`);
345
+ if (!satisfyQuestExclusiveGroup(snap, quest)) return blocked(`Another quest in the same set as "${quest.title}" is in your log`);
346
+ if (!itemStartGateMet(quest, inv)) {
347
+ const need = ds.item(quest.startedByItem ?? quest.srcItem?.item ?? 0)?.name;
348
+ return blocked(need ? `"${quest.title}" starts from ${need}` : `You cannot start "${quest.title}" yet`);
349
+ }
350
+ return blocked(`You cannot start "${quest.title}" yet`);
351
+ }
352
+
353
+ /** Quests a zone offers that the character can accept right now. */
354
+ export function availableQuests(ds: DataStore, zoneId: string, char: Character, log: QuestLog): QuestDef[] {
355
+ return offeredQuests(ds, zoneId, char, log).filter((o) => o.canAccept).map((o) => o.quest);
356
+ }
357
+
358
+ // ---- QI3 acquisition graph (item→creature reverse index, zone routing) ----
359
+ // Cite: docs/archive/QUEST-INTERPRETER.md §5 QI3; extract.mjs droppersForItem; LootMgr conditionId.
360
+
361
+ /** Player::CanTakeQuest for CONDITION_QUESTAVAILABLE. Cite: Conditions.cpp:295-298. */
362
+ export function canTakeQuestForCondition(
363
+ ds: DataStore,
364
+ char: Pick<Character, 'level' | 'cls' | 'race'>,
365
+ log: QuestLog,
366
+ ): (questId: number) => boolean {
367
+ const questStatus = questStatusFromLog(log);
368
+ return (questId: number) => {
369
+ const q = ds.quest(questId);
370
+ if (!q) return false;
371
+ return canTakeQuest(
372
+ { level: char.level, classId: char.cls, raceId: char.race, questStatus },
373
+ q,
374
+ {
375
+ prevQuestMissingFromData: !!(q.prevQuest && !ds.quest(q.prevQuest)),
376
+ questDefFor: (id) => ds.quest(id),
377
+ },
378
+ );
379
+ };
380
+ }
381
+
382
+ /** Quest-state snapshot for loot conditionId evaluation (QUESTREWARDED / QUESTTAKEN / QUESTAVAILABLE). */
383
+ export function questConditionSnapshot(
384
+ log: QuestLog,
385
+ level?: number,
386
+ canTake?: (questId: number) => boolean,
387
+ worldStates?: Record<number, number>,
388
+ areaFlags?: number,
389
+ auras?: readonly number[],
390
+ ): ConditionSnapshot {
391
+ const quests: Record<string, 'none' | 'taken' | 'complete' | 'rewarded'> = {};
392
+ for (const q of log.quests) {
393
+ if (q.status === 'turnedIn') quests[String(q.entry)] = 'rewarded';
394
+ else if (q.status === 'complete') quests[String(q.entry)] = 'complete';
395
+ else quests[String(q.entry)] = 'taken';
396
+ }
397
+ const base = {
398
+ quests,
399
+ level,
400
+ // Cite: Conditions.cpp:254 pAreaEntry->flags. Absent bake → undefined (fail-closed).
401
+ areaFlags,
402
+ worldScript: isConditionFulfilled,
403
+ ...(worldStates ? worldStateConditionFns(worldStates) : {}),
404
+ ...(auras !== undefined ? { auras } : {}),
405
+ };
406
+ return canTake ? { ...base, canTakeQuest: canTake } : base;
407
+ }
408
+
409
+ function lootSnap(log?: QuestLog, level?: number, auras?: readonly number[]): ConditionSnapshot {
410
+ return questConditionSnapshot(log ?? { quests: [] }, level, undefined, undefined, undefined, auras);
411
+ }
412
+
413
+ function conditionsTable(ds: DataStore): Map<number, ConditionDef> {
414
+ const map = new Map<number, ConditionDef>();
415
+ for (const c of ds.conditionsList()) map.set(c.id, c);
416
+ return map;
417
+ }
418
+
419
+ // Whether a loot row's condition is satisfiable for planning. Fail-closed: no evaluator or an
420
+ // unsatisfied condition means not a usable dropper. Cite: LootMgr.cpp Process; Conditions.cpp Meets.
421
+ export function lootDropAllowed(
422
+ ds: DataStore,
423
+ conditionId: number | undefined,
424
+ snapshot: ConditionSnapshot,
425
+ ): boolean {
426
+ const cid = (conditionId ?? 0) | 0;
427
+ if (cid === 0) return true;
428
+ const table = conditionsTable(ds);
429
+ if (!table.has(cid)) return false;
430
+ return isConditionSatisfied(cid, snapshot, table).ok;
431
+ }
432
+
433
+ // The minimum free-drop chance to count as a farm source when better droppers exist. Below it is
434
+ // reverse-loot noise. A quest-gated row uses the absolute chance and is never scrubbed by this alone.
435
+ export const MIN_QUEST_DROPPER_CHANCE = 1;
436
+
437
+ const bestItemDropCache = new WeakMap<DataStore, Map<string, number>>();
438
+
439
+ function dropSnapKey(itemId: number, snapshot: ConditionSnapshot): string {
440
+ const auras = snapshot.auras;
441
+ let ak = '';
442
+ if (auras) {
443
+ const ids = [...auras].map((n) => n | 0).sort((a, b) => a - b);
444
+ ak = ids.join(',');
445
+ }
446
+ return `${itemId}:${snapshot.team ?? 0}:${ak}`;
447
+ }
448
+
449
+ // Best chance on ANY loot table for the item. A local-pool floor read "in this zone" and quest
450
+ // 4022 fought Dark Iron Taskmaster 0.02% over Frenzied Black Drake 96%.
451
+ export function bestItemDropChance(
452
+ ds: DataStore,
453
+ itemId: number,
454
+ snapshot: ConditionSnapshot,
455
+ ): number {
456
+ const key = dropSnapKey(itemId, snapshot);
457
+ let slot = bestItemDropCache.get(ds);
458
+ const hit = slot?.get(key);
459
+ if (hit != null) return hit;
460
+ let best = 0;
461
+ for (const lootId of ds.lootKeysForItem(itemId)) {
462
+ for (const d of ds.lootTable(lootId)) {
463
+ if (d.item !== itemId) continue;
464
+ if (!lootDropAllowed(ds, d.conditionId, snapshot)) continue;
465
+ const mag = d.chance < 0 ? -d.chance : d.chance;
466
+ const ch = (d.groupId ?? 0) !== 0 && mag === 0 ? 1 : mag;
467
+ if (ch > best) best = ch;
468
+ }
469
+ }
470
+ if (ds.lootAttached()) {
471
+ if (!slot) {
472
+ slot = new Map();
473
+ bestItemDropCache.set(ds, slot);
474
+ }
475
+ slot.set(key, best);
476
+ }
477
+ return best;
478
+ }
479
+
480
+ export function dropperNoiseFloor(
481
+ ds: DataStore,
482
+ itemId: number,
483
+ snapshot: ConditionSnapshot,
484
+ ): number {
485
+ return bestItemDropChance(ds, itemId, snapshot) >= MIN_QUEST_DROPPER_CHANCE ? MIN_QUEST_DROPPER_CHANCE : 0;
486
+ }
487
+
488
+ export function dropperNoiseFloorFor(
489
+ ds: DataStore,
490
+ itemId: number,
491
+ log?: QuestLog,
492
+ level?: number,
493
+ auras?: readonly number[],
494
+ ): number {
495
+ return dropperNoiseFloor(ds, itemId, lootSnap(log, level, auras));
496
+ }
497
+
498
+ /** Template level used for farm ranking (static, else scaling min). */
499
+ export function creatureFarmLevel(
500
+ cr: { level?: number; scaling?: { minLevel?: number } } | null | undefined,
501
+ ): number {
502
+ if (!cr) return 0;
503
+ return ((cr.level ?? 0) | 0) || ((cr.scaling?.minLevel ?? 0) | 0) || 0;
504
+ }
505
+
506
+ /** CREATURE_ELITE_* (`rank ?? scaling.rank`). Same read as dataStore spawnRankOf / questPolicy. */
507
+ export function creatureFarmRank(
508
+ cr: { rank?: number; scaling?: { rank?: number } } | null | undefined,
509
+ ): number {
510
+ if (!cr) return 0;
511
+ return (cr.rank ?? cr.scaling?.rank ?? 0) | 0;
512
+ }
513
+
514
+ // Item-farm ranking: non-elite before elite, then closer to the player, then at-or-below, then
515
+ // highest drop chance. Highest-drop-first pulled elites; lowest-level pulled Small Crag Boar.
516
+ export function compareQuestDroppers(
517
+ a: { level: number; chance: number; rank?: number },
518
+ b: { level: number; chance: number; rank?: number },
519
+ playerLevel: number,
520
+ ): number {
521
+ const aElite = (a.rank ?? 0) > 0 ? 1 : 0;
522
+ const bElite = (b.rank ?? 0) > 0 ? 1 : 0;
523
+ if (aElite !== bElite) return aElite - bElite;
524
+ if (!(playerLevel > 0)) {
525
+ return (b.chance - a.chance) || (a.level - b.level);
526
+ }
527
+ const aDist = Math.abs(a.level - playerLevel);
528
+ const bDist = Math.abs(b.level - playerLevel);
529
+ if (aDist !== bDist) return aDist - bDist;
530
+ const aOver = a.level > playerLevel ? 1 : 0;
531
+ const bOver = b.level > playerLevel ? 1 : 0;
532
+ if (aOver !== bOver) return aOver - bOver;
533
+ if (a.chance !== b.chance) return b.chance - a.chance;
534
+ return a.level - b.level;
535
+ }
536
+
537
+ // The best eligible loot chance for an item on this creature, 0 for none. An equal-chance group row
538
+ // scores as one so it beats a near-zero free row. Cite: domain/items/loot.ts.
539
+ export function lootChanceForItem(
540
+ ds: DataStore,
541
+ creatureEntry: number,
542
+ itemId: number,
543
+ log?: QuestLog,
544
+ level?: number,
545
+ auras?: readonly number[],
546
+ ): number {
547
+ return lootChanceWithSnap(ds, creatureEntry, itemId, lootSnap(log, level, auras));
548
+ }
549
+
550
+ /** Same, with the snapshot already built — droppersForItem scans 2,605 creatures per call. */
551
+ function lootChanceWithSnap(
552
+ ds: DataStore,
553
+ creatureEntry: number,
554
+ itemId: number,
555
+ snapshot: ConditionSnapshot,
556
+ ): number {
557
+ const cr = ds.creature(creatureEntry);
558
+ if (!cr) return 0;
559
+ let best = 0;
560
+ for (const d of ds.lootTable(cr.lootId)) {
561
+ if (d.item !== itemId) continue;
562
+ if (!lootDropAllowed(ds, d.conditionId, snapshot)) continue;
563
+ const mag = Math.abs(d.chance);
564
+ if ((d.groupId ?? 0) !== 0 && mag === 0) best = Math.max(best, 1);
565
+ else best = Math.max(best, mag);
566
+ }
567
+ return best;
568
+ }
569
+
570
+ // The huntable universe the dropper search walks, keyed the way the store answers: loot id to the
571
+ // creatures reading it. The old form visited all 2,605 creatures — 766k rows a call.
572
+ interface HuntableLootIndex {
573
+ byLootId: Map<number, number[]>;
574
+ farmLevel: Map<number, number>;
575
+ farmRank: Map<number, number>;
576
+ }
577
+ const huntableLootIndex = new WeakMap<DataStore, { gen: number; index: HuntableLootIndex }>();
578
+
579
+ function huntableDroppers(ds: DataStore): HuntableLootIndex {
580
+ const gen = ds.zoneWorldGen();
581
+ const hit = huntableLootIndex.get(ds);
582
+ if (hit && hit.gen === gen) return hit.index;
583
+ const byLootId = new Map<number, number[]>();
584
+ const farmLevel = new Map<number, number>();
585
+ const farmRank = new Map<number, number>();
586
+ for (const z of ds.zones()) {
587
+ for (const ce of z.huntable ?? []) {
588
+ if (farmLevel.has(ce)) continue;
589
+ const cr = ds.creature(ce);
590
+ if (!cr) continue;
591
+ farmLevel.set(ce, creatureFarmLevel(cr));
592
+ farmRank.set(ce, creatureFarmRank(cr));
593
+ // No lootId is no loot: lootChanceForItem read [] and scored 0 for these.
594
+ const lootId = cr.lootId ?? 0;
595
+ if (!lootId) continue;
596
+ let list = byLootId.get(lootId);
597
+ if (list === undefined) {
598
+ list = [];
599
+ byLootId.set(lootId, list);
600
+ }
601
+ list.push(ce);
602
+ }
603
+ }
604
+ const built = { byLootId, farmLevel, farmRank };
605
+ huntableLootIndex.set(ds, { gen, index: built });
606
+ return built;
607
+ }
608
+
609
+ /** Build the huntable map ahead of the first Quest Log paint (bootData's idle warm). */
610
+ export function warmDropperIndex(ds: DataStore): void {
611
+ huntableDroppers(ds);
612
+ }
613
+
614
+ /** Huntable creatures whose table can hold `itemId`. Deduped: one creature, one score. */
615
+ function huntableCandidates(ds: DataStore, itemId: number): Set<number> {
616
+ const index = huntableDroppers(ds);
617
+ const out = new Set<number>();
618
+ for (const lootId of ds.lootKeysForItem(itemId)) {
619
+ // A prefixed key (go:/item:/skinning:…) is never a creature lootId — normalizeLootKey
620
+ // leaves only those as strings, and this index holds creature.lootId numbers.
621
+ if (typeof lootId !== 'number') continue;
622
+ for (const ce of index.byLootId.get(lootId) ?? []) out.add(ce);
623
+ }
624
+ return out;
625
+ }
626
+
627
+ // The cache generation for the dropper search: the only log state a loot condition reads is each
628
+ // quest's status, so a status change is the whole invalidation.
629
+ function questStatusKey(log?: QuestLog): string {
630
+ if (!log) return '-';
631
+ let key = '';
632
+ for (const q of log.quests) key += `${q.entry}${q.status[0]}`;
633
+ return key;
634
+ }
635
+
636
+ // One dropper search walks 2,605 huntable creatures and their whole tables — about 8M rows on one
637
+ // Quest Log paint before this cache. Same shape as the planner's, plus the status generation.
638
+ const dropperCache = new WeakMap<DataStore, { gen: string; hits: Map<string, number[]> }>();
639
+
640
+ // The creature entries whose baked loot can yield an item, scanned from the zone huntable lists so no
641
+ // dropper is invented. Reverse-loot elites far above the band and near-zero free drops are dropped.
642
+ export function droppersForItem(
643
+ ds: DataStore,
644
+ itemId: number,
645
+ log?: QuestLog,
646
+ level?: number,
647
+ questLevel?: number,
648
+ auras?: readonly number[],
649
+ ): number[] {
650
+ const bandAnchor = (questLevel != null && questLevel > 0)
651
+ ? questLevel
652
+ : (level != null && level > 0 ? level : 0);
653
+ // Never cache before the lazy loot chunk mounts — the answer is empty until then.
654
+ const cacheable = ds.lootAttached();
655
+ const cacheKey = `${itemId}|${level ?? ''}|${questLevel ?? ''}|${auras ? [...auras].sort((a, b) => a - b).join(',') : ''}`;
656
+ let slot = cacheable ? dropperCache.get(ds) : undefined;
657
+ if (cacheable) {
658
+ const gen = questStatusKey(log);
659
+ if (!slot || slot.gen !== gen) {
660
+ slot = { gen, hits: new Map() };
661
+ dropperCache.set(ds, slot);
662
+ }
663
+ const hit = slot.hits.get(cacheKey);
664
+ // Callers only read the result (iterate / .length / [0]) — the array is shared.
665
+ if (hit) return hit;
666
+ }
667
+ const snapshot = lootSnap(log, level, auras);
668
+ const index = huntableDroppers(ds);
669
+ const farmLevels = index.farmLevel;
670
+ const farmRanks = index.farmRank;
671
+ const scored: { ce: number; chance: number; level: number; rank: number }[] = [];
672
+ for (const ce of huntableCandidates(ds, itemId)) {
673
+ const cl = farmLevels.get(ce) ?? 0;
674
+ if (bandAnchor > 0) {
675
+ // Same high slack as outdoor huntable / farm filter (KI-786/800/826).
676
+ if (cl > 0 && cl > bandAnchor + FARM_LEVEL_HI_SLACK) continue;
677
+ }
678
+ const chance = lootChanceWithSnap(ds, ce, itemId, snapshot);
679
+ if (chance <= 0) continue;
680
+ scored.push({ ce, chance, level: cl, rank: farmRanks.get(ce) ?? 0 });
681
+ }
682
+ // Floor from the item's WHOLE table, the way huntableQuests reads it. A max over this banded
683
+ // subset collapses the floor and hands back the 0.02% row the hunt gate then refuses.
684
+ const floor = dropperNoiseFloor(ds, itemId, snapshot);
685
+ const rankLevel = (level != null && level > 0) ? level : bandAnchor;
686
+ const out = scored
687
+ .filter((s) => s.chance >= floor)
688
+ .sort((a, b) => compareQuestDroppers(a, b, rankLevel) || a.ce - b.ce)
689
+ .map((s) => s.ce);
690
+ slot?.hits.set(cacheKey, out);
691
+ return out;
692
+ }
693
+
694
+ // True when this creature's loot can yield the item under the condition snapshot, at any chance. Do not
695
+ // use it alone to pick: the fight selection ranks by level closeness and chance first.
696
+ export function creatureDropsItem(
697
+ ds: DataStore,
698
+ creatureEntry: number,
699
+ itemId: number,
700
+ log?: QuestLog,
701
+ level?: number,
702
+ auras?: readonly number[],
703
+ ): boolean {
704
+ return lootChanceForItem(ds, creatureEntry, itemId, log, level, auras) > 0;
705
+ }
706
+
707
+ // The zones where the player can advance this quest's remaining work — a kill spawn, an item dropper,
708
+ // an object or a talk ender. Empty when it is unsolvable with baked data.
709
+ export function acquisitionZonesForQuest(
710
+ ds: DataStore,
711
+ quest: QuestDef,
712
+ qs: QuestState,
713
+ inv?: Inventory,
714
+ log?: QuestLog,
715
+ level?: number,
716
+ auras?: readonly number[],
717
+ ): string[] {
718
+ const zones = new Set<string>();
719
+ const snapshot = lootSnap(log, level, auras);
720
+ // Kill targets still needed → creature spawn zones.
721
+ for (const kt of quest.killTargets ?? []) {
722
+ if ((qs.kills[String(kt.creatureEntry)] ?? 0) < kt.count) {
723
+ const entries = corpseUseSummonedEntries(ds, quest).includes(kt.creatureEntry)
724
+ ? corpseUseFarmEntries(ds, quest)
725
+ : [kt.creatureEntry];
726
+ for (const ce of entries) {
727
+ for (const z of spawnZonesForCreature(ds, ce)) zones.add(z);
728
+ }
729
+ }
730
+ }
731
+ // Item targets still needed → dropper spawn zones (condition-gated reverse index).
732
+ for (const it of quest.itemTargets ?? []) {
733
+ if (inv && countItem(inv, it.item) >= it.count) continue;
734
+ for (const ce of droppersForItem(ds, it.item, log, level, quest.questLevel, auras)) {
735
+ for (const z of spawnZonesForCreature(ds, ce)) zones.add(z);
736
+ }
737
+ for (const zid of vendorZonesForItem(ds, it.item)) zones.add(zid);
738
+ // Item objectives often come from quest-gated CHEST loot rather than a creature, so a zone listing
739
+ // a gameobject whose table yields the item is an acquisition zone too. World-chest group loot is not.
740
+ const yields = (key: number | string): boolean =>
741
+ ds.lootHasItem(key, it.item)
742
+ && ds.lootTable(key).some((d) =>
743
+ lootRowIsQuestItemSource(d, it.item) && lootDropAllowed(ds, d.conditionId, snapshot));
744
+ for (const z of ds.zones()) {
745
+ for (const ge of z.objects ?? []) {
746
+ const go = ds.gameObject(ge);
747
+ if (!go?.lootId) continue;
748
+ if (yields(`go:${go.lootId}`)) {
749
+ zones.add(z.id);
750
+ break;
751
+ }
752
+ }
753
+ }
754
+ }
755
+ // Object targets: zones that list the GO, else ender/starter zones as a soft hint.
756
+ for (const ot of quest.objectTargets ?? []) {
757
+ const cur = (qs.objects || {})[String(ot.objectEntry)] ?? 0;
758
+ if (cur >= ot.count) continue;
759
+ let found = false;
760
+ for (const z of ds.zones()) {
761
+ if ((z.objects ?? []).includes(ot.objectEntry)) {
762
+ zones.add(z.id);
763
+ found = true;
764
+ }
765
+ }
766
+ if (!found) {
767
+ for (const z of quest.enderZones ?? []) zones.add(z);
768
+ for (const z of quest.starterZones ?? []) zones.add(z);
769
+ }
770
+ }
771
+ // Talk / deliver: ender zone is the completion zone (QI2.5 same-zone rule).
772
+ // Empty enderZones still have a home: the starter zone (Welcome to the Jungle / Barnil).
773
+ if (isTalkToNpcQuest(quest) && !talkMet(qs, quest)) {
774
+ const enders = quest.enderZones ?? [];
775
+ if (enders.length > 0) {
776
+ for (const z of enders) zones.add(z);
777
+ } else {
778
+ for (const z of quest.starterZones ?? []) zones.add(z);
779
+ }
780
+ }
781
+ if (isDeliverQuest(quest) && inv && itemsMet(quest, inv) && !talkMet(qs, quest)) {
782
+ for (const z of quest.enderZones ?? []) zones.add(z);
783
+ }
784
+ return [...zones];
785
+ }
786
+
787
+ /** Instance name for a map id, cutting a shared map's wing suffix the way instanceForCreature does. */
788
+ function instanceNameForMap(ds: DataStore, mapId: number): string | undefined {
789
+ const onMap = ds.instances().filter((i) => i.mapId === mapId);
790
+ if (onMap.length === 0) return undefined;
791
+ if (onMap.length === 1) return onMap[0]!.name;
792
+ const cut = onMap[0]!.name.search(/ - | \(/);
793
+ if (cut > 0) {
794
+ const shared = onMap[0]!.name.slice(0, cut);
795
+ if (onMap.every((i) => i.name.startsWith(shared))) return shared;
796
+ }
797
+ // Wings with no shared prefix (Lower / Upper Blackrock Spire): name both rather than "a dungeon".
798
+ return onMap.map((i) => i.name).join(' / ');
799
+ }
800
+
801
+ // Why a GO-ender quest is out of the outdoor autoquester's reach, or null when the GameObject is on
802
+ // a baked zone.objects list. The GO Use is its only credit path (questlog.ts:1295 refuses the talk).
803
+ export function goEnderOutOfReach(ds: DataStore, quest: QuestDef): string | null {
804
+ if (!hasGoEnder(quest)) return null;
805
+ const entries = [quest.goEnder ?? 0, ...(quest.goEnderEntries ?? [])].filter((e) => e > 0);
806
+ if (entries.length === 0) return null;
807
+ for (const z of ds.zones()) {
808
+ if ((z.objects ?? []).some((o) => entries.includes(o))) return null;
809
+ }
810
+ for (const e of entries) {
811
+ const spawn = firstDungeonGameObjectForEntry(e);
812
+ if (!spawn) continue;
813
+ const goName = ds.gameObject(e)?.name ?? spawn.name;
814
+ const inst = instanceNameForMap(ds, spawn.map);
815
+ return `"${quest.title}" ends at ${goName}${inst ? ` inside ${inst}` : ' inside a dungeon'}, Auto Quest leaves it to you`;
816
+ }
817
+ const e0 = entries[0]!;
818
+ const nm = ds.gameObject(e0)?.name ?? `Object#${e0}`;
819
+ return `ender-not-in-world: "${quest.title}" ends at ${nm}, which no baked zone.objects list carries`;
820
+ }
821
+
822
+ // An honest skip or travel hint for an active quest that cannot advance here, never claiming complete
823
+ // while work remains. `noPath` marks a baked-data ABSENCE, which waiting or walking cannot fix.
824
+ export function questWorkSkipReason(
825
+ ds: DataStore,
826
+ quest: QuestDef,
827
+ qs: QuestState,
828
+ inv: Inventory,
829
+ currentZone: string,
830
+ log?: QuestLog,
831
+ level?: number,
832
+ auras?: readonly number[],
833
+ ): { reason: string; routeZone?: string; noPath?: boolean } {
834
+ if (questComplete(quest, qs, inv)) {
835
+ const enders = quest.enderZones ?? [];
836
+ if (enders.length > 0 && !enders.includes(currentZone)) {
837
+ return {
838
+ reason: `Turn in "${quest.title}" in ${enders.map((z) => ds.zone(z)?.name ?? z).join(' / ')}`,
839
+ routeZone: enders[0],
840
+ };
841
+ }
842
+ return { reason: `"${quest.title}" is ready to turn in` };
843
+ }
844
+
845
+ // Reputation objective: Auto Quest has no rep-grinding step, so it parks rather than reporting a
846
+ // false "Quest complete!" (questsWithWork saw no trackable and dropped the quest entirely).
847
+ const rep = quest.repObjective;
848
+ if (rep && (rep.value || 0) > 0) {
849
+ const fname = ds.faction(rep.faction)?.name ?? `faction ${rep.faction}`;
850
+ return {
851
+ reason: `"${quest.title}" needs ${fname} standing, which Auto Quest does not grind`,
852
+ noPath: true,
853
+ };
854
+ }
855
+
856
+ // Talk / deliver not in ender zone.
857
+ if (isTalkToNpcQuest(quest) && !talkMet(qs, quest)) {
858
+ // The GO Use is the only credit path a GO-ender quest has; an unreachable one is noPath, so the
859
+ // planner parks it instead of ending the run on a speak that can never land.
860
+ const outOfReach = goEnderOutOfReach(ds, quest);
861
+ if (outOfReach) return { reason: outOfReach, noPath: true };
862
+ const enders = quest.enderZones ?? [];
863
+ const homes = enders.length > 0 ? enders : (quest.starterZones ?? []);
864
+ if (homes.length === 0) {
865
+ const npc = (quest.enderName ?? '').trim() || (quest.starterName ?? '').trim();
866
+ if (npc) {
867
+ return { reason: `"${quest.title}" needs talk credit with ${npc}` };
868
+ }
869
+ return {
870
+ reason: `"${quest.title}" needs talk credit but has no baked enderZones (scripted/QI4?)`,
871
+ noPath: true,
872
+ };
873
+ }
874
+ if (!homes.includes(currentZone)) {
875
+ return {
876
+ reason: `Talk for "${quest.title}" needs ${homes.map((z) => ds.zone(z)?.name ?? z).join(' / ')}`,
877
+ routeZone: homes[0],
878
+ };
879
+ }
880
+ const goEnder = goEnderEntryOf(quest);
881
+ if (goEnder != null) {
882
+ return {
883
+ reason: `"${quest.title}" needs ${ds.gameObject(goEnder)?.name ?? `Object#${goEnder}`} opened in ${ds.zone(currentZone)?.name ?? currentZone}`,
884
+ };
885
+ }
886
+ return { reason: `"${quest.title}" talk objective not yet credited in ${currentZone}` };
887
+ }
888
+ if (isDeliverQuest(quest)) {
889
+ if (!itemsMet(quest, inv)) {
890
+ const missing = (quest.itemTargets ?? []).filter((it) => countItem(inv, it.item) < it.count);
891
+ const item = missing[0];
892
+ if (item) {
893
+ const droppers = droppersForItem(ds, item.item, log, level, quest.questLevel, auras);
894
+ const vendorZones = vendorZonesForItem(ds, item.item);
895
+ if (droppers.length === 0) {
896
+ if (vendorZones.length > 0) {
897
+ const route = vendorZones.includes(currentZone) ? undefined : vendorZones[0];
898
+ return {
899
+ reason: `"${quest.title}" needs ${ds.item(item.item)?.name ?? item.item} from a vendor${route ? ` in ${ds.zone(route)?.name ?? route}` : ''}`,
900
+ routeZone: route,
901
+ };
902
+ }
903
+ // A container holds it: questHasBakedPath already accepts on this source, so the skip must
904
+ // route to it rather than call the quest unfinishable. Cite: doQuest.ts itemGoStep.
905
+ const goSources = goLootSourcesForItem(ds, item.item, log ?? { quests: [] }, level ?? 1, auras);
906
+ if (goSources.length > 0) {
907
+ const iname = ds.item(item.item)?.name ?? String(item.item);
908
+ // Every container is locked: name what opens it the way the objectTarget branch does,
909
+ // instead of routing to a chest nothing in the run can open.
910
+ const gate = containerGateForSources(ds, goSources, inv);
911
+ if (gate) {
912
+ return {
913
+ reason: `"${quest.title}" needs ${iname} from ${gate.goName}, which requires ${gate.need}`,
914
+ noPath: true,
915
+ };
916
+ }
917
+ const goZones = [...new Set(goSources.map((g) => g.zoneId))];
918
+ const route = goZones.includes(currentZone) ? undefined : goZones[0];
919
+ return {
920
+ reason: `"${quest.title}" needs ${iname} from a container${route ? ` in ${ds.zone(route)?.name ?? route}` : ` in ${ds.zone(currentZone)?.name ?? currentZone}`}`,
921
+ routeZone: route,
922
+ };
923
+ }
924
+ return {
925
+ reason: `"${quest.title}" needs ${ds.item(item.item)?.name ?? item.item} but no condition-satisfied dropper is baked`,
926
+ noPath: true,
927
+ };
928
+ }
929
+ const zid = spawnZonesForCreature(ds, droppers[0])[0];
930
+ return {
931
+ reason: `"${quest.title}" needs ${ds.item(item.item)?.name ?? item.item} from ${ds.creature(droppers[0])?.name ?? droppers[0]}${zid ? ` (${ds.zone(zid)?.name ?? zid})` : ''}`,
932
+ routeZone: zid,
933
+ };
934
+ }
935
+ }
936
+ const enders = quest.enderZones ?? [];
937
+ if (enders.length > 0 && !enders.includes(currentZone)) {
938
+ return {
939
+ reason: `Deliver "${quest.title}" in ${enders.map((z) => ds.zone(z)?.name ?? z).join(' / ')}`,
940
+ routeZone: enders[0],
941
+ };
942
+ }
943
+ }
944
+
945
+ // Kill targets elsewhere. Type-2 corpse-use farms the script-target corpse, not the summon.
946
+ for (const kt of quest.killTargets ?? []) {
947
+ if ((qs.kills[String(kt.creatureEntry)] ?? 0) >= kt.count) continue;
948
+ const farm = corpseUseSummonedEntries(ds, quest).includes(kt.creatureEntry)
949
+ ? corpseUseFarmEntries(ds, quest)
950
+ : [kt.creatureEntry];
951
+ const zids = [...new Set(farm.flatMap((ce) => spawnZonesForCreature(ds, ce)))];
952
+ // Script credit, not a kill: no zone hosts a pull that can advance it, so never route for one.
953
+ if (farm.length > 0 && farm.every((ce) => isScriptCreditOnlyTarget(ds, ce, log))) {
954
+ const nm = creatureObjectiveLabel(
955
+ quest,
956
+ kt.creatureEntry,
957
+ ds.creature(kt.creatureEntry)?.name ?? String(kt.creatureEntry),
958
+ );
959
+ return {
960
+ reason: `"${quest.title}" credits ${nm} by script, not by a kill Auto Quest can make`,
961
+ noPath: true,
962
+ };
963
+ }
964
+ if (!zids.includes(currentZone)) {
965
+ const home = (quest.starterZones ?? []).find((z) => z !== currentZone) ?? (quest.starterZones ?? [])[0];
966
+ const route = zids[0] ?? home;
967
+ return {
968
+ reason: `"${quest.title}" needs ${creatureObjectiveLabel(quest, kt.creatureEntry, ds.creature(kt.creatureEntry)?.name ?? String(kt.creatureEntry))} in ${zids.map((z) => ds.zone(z)?.name ?? z).join(' / ') || (route ? (ds.zone(route)?.name ?? route) : 'an unknown zone')}`,
969
+ routeZone: route,
970
+ noPath: zids.length === 0 && !home,
971
+ };
972
+ }
973
+ return {
974
+ reason: `"${quest.title}" still needs ${creatureObjectiveLabel(quest, kt.creatureEntry, ds.creature(kt.creatureEntry)?.name ?? String(kt.creatureEntry))} here but none are huntable (greyed / filtered)`,
975
+ };
976
+ }
977
+
978
+ // Item targets with no local condition-satisfied dropper.
979
+ for (const it of quest.itemTargets ?? []) {
980
+ if (countItem(inv, it.item) >= it.count) continue;
981
+ const droppers = droppersForItem(ds, it.item, log, level, quest.questLevel, auras);
982
+ const itemName = ds.item(it.item)?.name ?? String(it.item);
983
+ const vendorZones = vendorZonesForItem(ds, it.item);
984
+ if (droppers.length === 0) {
985
+ if (vendorZones.length > 0) {
986
+ const route = vendorZones.includes(currentZone) ? undefined : vendorZones[0];
987
+ return {
988
+ reason: `"${quest.title}" needs ${itemName} from a vendor${route ? ` in ${ds.zone(route)?.name ?? route}` : ''}`,
989
+ routeZone: route,
990
+ };
991
+ }
992
+ // Same container question the deliver branch asks: itemHasBakedSource accepts on GO loot, so
993
+ // this line must route to the chest or name its lock rather than claim nothing is baked.
994
+ const goSources = goLootSourcesForItem(ds, it.item, log ?? { quests: [] }, level ?? 1, auras);
995
+ if (goSources.length > 0) {
996
+ const gate = containerGateForSources(ds, goSources, inv);
997
+ if (gate) {
998
+ return {
999
+ reason: `"${quest.title}" needs ${itemName} from ${gate.goName}, which requires ${gate.need}`,
1000
+ noPath: true,
1001
+ };
1002
+ }
1003
+ const goZones = [...new Set(goSources.map((g) => g.zoneId))];
1004
+ const route = goZones.includes(currentZone) ? undefined : goZones[0];
1005
+ return {
1006
+ reason: `"${quest.title}" needs ${itemName} from a container in ${ds.zone(route ?? currentZone)?.name ?? (route ?? currentZone)}`,
1007
+ routeZone: route,
1008
+ };
1009
+ }
1010
+ return {
1011
+ reason: `"${quest.title}" needs ${itemName} but no condition-satisfied dropper is baked (prereq or QI4 script?)`,
1012
+ noPath: true,
1013
+ };
1014
+ }
1015
+ const local = droppers.some((ce) => (ds.zone(currentZone)?.huntable ?? []).includes(ce));
1016
+ if (!local) {
1017
+ const zid = spawnZonesForCreature(ds, droppers[0])[0];
1018
+ return {
1019
+ reason: `"${quest.title}" needs ${itemName} from ${ds.creature(droppers[0])?.name ?? droppers[0]}${zid ? ` in ${ds.zone(zid)?.name ?? zid}` : ''}`,
1020
+ routeZone: zid,
1021
+ };
1022
+ }
1023
+ // Droppers live here but the planner still could not pull them (greyed/red-filtered,
1024
+ // empty hunt list, or not in view.hunt). Never claim "no acquisition path".
1025
+ return {
1026
+ reason: `"${quest.title}" still needs ${itemName} from local droppers (${droppers
1027
+ .slice(0, 3)
1028
+ .map((ce) => ds.creature(ce)?.name ?? ce)
1029
+ .join(', ')}) but none are currently huntable here`,
1030
+ };
1031
+ }
1032
+
1033
+ for (const ot of quest.objectTargets ?? []) {
1034
+ const cur = (qs.objects || {})[String(ot.objectEntry)] ?? 0;
1035
+ if (cur >= ot.count) continue;
1036
+ const go = ds.gameObject(ot.objectEntry);
1037
+ const name = go?.name ?? `Object#${ot.objectEntry}`;
1038
+ if (!go) {
1039
+ return {
1040
+ reason: `ender-not-in-world: "${quest.title}" needs ${name} but no GameObject template is baked`,
1041
+ noPath: true,
1042
+ };
1043
+ }
1044
+ // The lock gate: a free-open case passes, a missing key skips honestly with the key's name and its
1045
+ // route when a dropper is baked, and a hard skill gate names the skill and value.
1046
+ if (go.lockId && inv) {
1047
+ const lock = ds.lock(go.lockId);
1048
+ const skillValueFor = (lockType: number): number | undefined => {
1049
+ // Planner has no combatSkills on inv-only path — skill-only hard locks still report.
1050
+ void lockType;
1051
+ return undefined;
1052
+ };
1053
+ const openFree = canOpenLock({ lock, skillValueFor });
1054
+ if (!openFree.ok) {
1055
+ const keyEntry =
1056
+ go.keyId ??
1057
+ requiredKeyItemEntry(lock);
1058
+ if (keyEntry != null && countItem(inv, keyEntry) < 1) {
1059
+ const keyName = ds.item(keyEntry)?.name ?? `item ${keyEntry}`;
1060
+ const droppers = droppersForItem(ds, keyEntry, log, level, undefined, auras);
1061
+ // Taxonomy token locked-no-key (SESSION 5) surfaces in autopilot log verbatim.
1062
+ if (droppers.length > 0) {
1063
+ const zid = spawnZonesForCreature(ds, droppers[0])[0];
1064
+ return {
1065
+ reason: `locked-no-key: "${quest.title}" needs ${name} but you lack ${keyName} (from ${ds.creature(droppers[0])?.name ?? droppers[0]}${zid ? ` in ${ds.zone(zid)?.name ?? zid}` : ''})`,
1066
+ routeZone: zid,
1067
+ };
1068
+ }
1069
+ return {
1070
+ reason: `locked-no-key: "${quest.title}" needs ${name} but you lack ${keyName}`,
1071
+ };
1072
+ }
1073
+ const hard = hardestSkillLockCase(lock);
1074
+ if (hard && openFree.reason === 'low_skill') {
1075
+ return {
1076
+ reason: `"${quest.title}" needs ${name} but requires ${hard.skillName} ${hard.skillReq}`,
1077
+ };
1078
+ }
1079
+ if (openFree.reason === 'bad_targets') {
1080
+ const keyName = keyEntry != null
1081
+ ? (ds.item(keyEntry)?.name ?? `item ${keyEntry}`)
1082
+ : 'the matching key';
1083
+ return {
1084
+ reason: `locked-no-key: "${quest.title}" needs ${name} but you lack ${keyName}`,
1085
+ };
1086
+ }
1087
+ }
1088
+ } else if (go.keyId && inv && countItem(inv, go.keyId) < 1) {
1089
+ // keyId without lock cases (legacy): still name the key.
1090
+ const keyName = ds.item(go.keyId)?.name ?? `item ${go.keyId}`;
1091
+ return {
1092
+ reason: `locked-no-key: "${quest.title}" needs ${name} but you lack ${keyName}`,
1093
+ };
1094
+ }
1095
+ const zones = acquisitionZonesForQuest(ds, quest, qs, inv, log, level, auras);
1096
+ const route = zones.find((z) => z !== currentZone) ?? zones[0];
1097
+ if (route && route !== currentZone) {
1098
+ return {
1099
+ reason: `"${quest.title}" needs ${name} in ${ds.zone(route)?.name ?? route}`,
1100
+ routeZone: route,
1101
+ };
1102
+ }
1103
+ return { reason: `"${quest.title}" still needs ${name}` };
1104
+ }
1105
+
1106
+ const zones = acquisitionZonesForQuest(ds, quest, qs, inv, log, level, auras);
1107
+ const route = zones.find((z) => z !== currentZone);
1108
+ if (route) {
1109
+ return {
1110
+ reason: `"${quest.title}" has work in ${ds.zone(route)?.name ?? route}`,
1111
+ routeZone: route,
1112
+ };
1113
+ }
1114
+ if (zones.includes(currentZone)) {
1115
+ return { reason: `"${quest.title}" has remaining work here` };
1116
+ }
1117
+ return {
1118
+ reason: `"${quest.title}" has remaining work but no baked acquisition path from here`,
1119
+ noPath: true,
1120
+ };
1121
+ }
1122
+
1123
+ // Active quests whose remaining targets or drops can still be hunted HERE. Kill objectives check
1124
+ // progress and presence; item ones look for a local mob whose loot yields a need. On-level first.
1125
+ export function huntableQuests(
1126
+ ds: DataStore,
1127
+ log: QuestLog,
1128
+ huntableEntries: Iterable<number>,
1129
+ inv?: Inventory,
1130
+ level?: number,
1131
+ auras?: readonly number[],
1132
+ ): QuestDef[] {
1133
+ const here = new Set(huntableEntries);
1134
+ const out: QuestDef[] = [];
1135
+ for (const qs of log.quests) {
1136
+ if (qs.status !== 'active') continue;
1137
+ const qd = ds.quest(qs.entry);
1138
+ if (!qd) continue;
1139
+ // A quest with every objective already met has no work left here — leave it ready to turn in. This
1140
+ // is what stops the planner re-picking a finished collect quest and crashing on an empty list.
1141
+ if (inv && questComplete(qd, qs, inv)) continue;
1142
+ const hasKillTarget = qd.killTargets.some(
1143
+ (kt) => (qs.kills[String(kt.creatureEntry)] ?? 0) < kt.count && here.has(kt.creatureEntry),
1144
+ );
1145
+ let hasTarget = hasKillTarget;
1146
+ if (!hasTarget && corpseUseSpellId(ds, qd) && corpseUseWorkRemaining(ds, qd, qs, inv)) {
1147
+ hasTarget = corpseUseFightEntries(ds, qd, qs, inv).some((e) => here.has(e));
1148
+ }
1149
+ if (!hasTarget && qd.itemTargets.length > 0) {
1150
+ // Item pickup: usable-rate droppers only. Floor is the item's best known table, not this
1151
+ // zone's pool — Searing Gorge only has the 0.02% molt rows and a local floor collapsed.
1152
+ const stillNeeded = (item: number, count: number): boolean => !inv || countItem(inv, item) < count;
1153
+ const neededItems = qd.itemTargets.filter((it) => stillNeeded(it.item, it.count));
1154
+ const snap = lootSnap(log, level, auras);
1155
+ found: for (const entry of here) {
1156
+ for (const it of neededItems) {
1157
+ const ch = lootChanceWithSnap(ds, entry, it.item, snap);
1158
+ if (ch > 0 && ch >= dropperNoiseFloor(ds, it.item, snap)) {
1159
+ hasTarget = true;
1160
+ break found;
1161
+ }
1162
+ }
1163
+ }
1164
+ }
1165
+ if (hasTarget) out.push(qd);
1166
+ }
1167
+ return out.sort((a, b) => compareQuestWorkOrder(a, b, level));
1168
+ }
1169
+
1170
+ // Non-gray first, then closest to the player. Lowest-first travelled back to starter zones.
1171
+ function compareQuestWorkOrder(a: QuestDef, b: QuestDef, playerLevel?: number): number {
1172
+ if (playerLevel != null && playerLevel > 0) {
1173
+ const aGray = difficultyTier(a.questLevel, playerLevel) === 'gray' ? 1 : 0;
1174
+ const bGray = difficultyTier(b.questLevel, playerLevel) === 'gray' ? 1 : 0;
1175
+ if (aGray !== bGray) return aGray - bGray;
1176
+ const dist = Math.abs(a.questLevel - playerLevel) - Math.abs(b.questLevel - playerLevel);
1177
+ if (dist) return dist;
1178
+ }
1179
+ return a.questLevel - b.questLevel || a.minLevel - b.minLevel || a.title.localeCompare(b.title);
1180
+ }
1181
+
1182
+ // Every active quest with at least one unmet objective ANYWHERE — the broad twin of the huntable
1183
+ // list, used to tell remaining work from travel-needed. An unmet event credit counts.
1184
+ export function questsWithWork(
1185
+ ds: DataStore,
1186
+ log: QuestLog,
1187
+ inv?: Inventory,
1188
+ playerLevel?: number,
1189
+ ): QuestDef[] {
1190
+ const out: QuestDef[] = [];
1191
+ for (const qs of log.quests) {
1192
+ if (qs.status !== 'active') continue;
1193
+ const qd = ds.quest(qs.entry);
1194
+ if (!qd) continue;
1195
+ // Fully complete (including talk) is ready to turn in — not "work".
1196
+ if (inv && questComplete(qd, qs, inv)) continue;
1197
+ // creatureCreditTargets, not killTargets: the bake strips a friendly credit target off the slim
1198
+ // list (Guard Roberts 12423), and questComplete still tracks it, so the two must agree.
1199
+ const hasKillTarget = creatureCreditTargets(qd).some(
1200
+ (kt) => (qs.kills[String(kt.creatureEntry)] ?? 0) < kt.count,
1201
+ );
1202
+ // Item/source still needed only when inventory is known and short, or always when inv omitted.
1203
+ const hasItemTarget = inv
1204
+ ? qd.itemTargets.some((it) => countItem(inv, it.item) < it.count)
1205
+ : qd.itemTargets.length > 0;
1206
+ const hasSourceTarget = inv
1207
+ ? (qd.sourceItemTargets || []).some((it) => countItem(inv, it.item) < it.count)
1208
+ : (qd.sourceItemTargets || []).length > 0;
1209
+ const hasObjTarget = (qd.objectTargets || []).some(
1210
+ (ot) => (((qs as any).objects || (qs.objects || {}))[String(ot.objectEntry)] ?? 0) < ot.count,
1211
+ );
1212
+ const hasTalk = isTalkToNpcQuest(qd) && !talkMet(qs, qd);
1213
+ const hasDeliverTalk =
1214
+ isDeliverQuest(qd) && inv && itemsMet(qd, inv) && !talkMet(qs, qd);
1215
+ // Exploration / scripted event credit still pending (not a free-complete).
1216
+ const hasEvent = requiresEventCredit(qd) && !exploreMet(qs, qd);
1217
+ const hasRep = !!(qd.repObjective && (qd.repObjective.value || 0) > 0);
1218
+ if (
1219
+ hasRep ||
1220
+ hasKillTarget ||
1221
+ hasItemTarget ||
1222
+ hasSourceTarget ||
1223
+ hasObjTarget ||
1224
+ hasTalk ||
1225
+ hasDeliverTalk ||
1226
+ hasEvent
1227
+ ) {
1228
+ out.push(qd);
1229
+ }
1230
+ }
1231
+ return out.sort((a, b) => compareQuestWorkOrder(a, b, playerLevel));
1232
+ }
1233
+
1234
+ /** All quests currently in the log (active or complete, not yet turned in), with their def. */
1235
+ export function trackedQuests(ds: DataStore, log: QuestLog): { quest: QuestDef; state: QuestState }[] {
1236
+ const out: { quest: QuestDef; state: QuestState }[] = [];
1237
+ for (const st of log.quests) {
1238
+ if (st.status === 'turnedIn') continue;
1239
+ const quest = ds.quest(st.entry);
1240
+ if (quest) out.push({ quest, state: st });
1241
+ }
1242
+ return out;
1243
+ }
1244
+
1245
+ /** Log entries painted gray by `difficultyTier` — trivial, no XP. Ones ready to turn in are left
1246
+ * out: a completable quest still pays money and items. Same tier call the log titles use. */
1247
+ export function grayLogQuests(
1248
+ ds: DataStore,
1249
+ log: QuestLog,
1250
+ playerLevel: number,
1251
+ ): { quest: QuestDef; state: QuestState }[] {
1252
+ return trackedQuests(ds, log).filter((t) =>
1253
+ t.state.status !== 'complete' && difficultyTier(t.quest.questLevel, playerLevel) === 'gray');
1254
+ }
1255
+
1256
+ // Quest-log entries ordered for display: ready to turn in first, then the active ones by how close
1257
+ // their level is to the player's, with a low-to-high tiebreak. Each is annotated with its colour.
1258
+ export function orderTracked(
1259
+ tracked: { quest: QuestDef; state: QuestState }[],
1260
+ playerLevel: number,
1261
+ ): { quest: QuestDef; state: QuestState; tier: DifficultyTier }[] {
1262
+ return tracked
1263
+ .map((t) => ({ ...t, tier: difficultyTier(t.quest.questLevel, playerLevel) }))
1264
+ .sort((a, b) => {
1265
+ const ar = a.state.status === 'complete' ? 0 : 1;
1266
+ const br = b.state.status === 'complete' ? 0 : 1;
1267
+ return (
1268
+ ar - br ||
1269
+ Math.abs(a.quest.questLevel - playerLevel) - Math.abs(b.quest.questLevel - playerLevel) ||
1270
+ a.quest.questLevel - b.quest.questLevel ||
1271
+ a.quest.title.localeCompare(b.quest.title)
1272
+ );
1273
+ });
1274
+ }
1275
+
1276
+ /** Quest Log zone header. ZoneOrSort > 0 is AreaTable; else starter/ender ZoneDef.name. Cite: ObjectMgr.cpp:4320 ZoneOrSort; QuestLogFrame.lua GetQuestLogTitle isHeader. */
1277
+ export function questLogZoneHeader(ds: DataStore, quest: QuestDef): { id: string; name: string } | null {
1278
+ const zos = (quest.rawZoneOrSort ?? 0) | 0;
1279
+ if (zos > 0) {
1280
+ for (const z of ds.zones()) {
1281
+ if ((z.areaId ?? 0) === zos) return { id: z.id, name: z.name };
1282
+ }
1283
+ }
1284
+ for (const id of [...(quest.starterZones ?? []), ...(quest.enderZones ?? [])]) {
1285
+ const z = ds.zone(id);
1286
+ if (z) return { id: z.id, name: z.name };
1287
+ }
1288
+ for (const z of ds.zones()) {
1289
+ if (z.quests.includes(quest.entry)) return { id: z.id, name: z.name };
1290
+ }
1291
+ return null;
1292
+ }
1293
+
1294
+ const vendorSoldCache = new WeakMap<object, Set<number>>();
1295
+ const vendorZonesCache = new WeakMap<object, Map<number, string[]>>();
1296
+
1297
+ function vendorSoldItems(ds: DataStore): Set<number> {
1298
+ let set = vendorSoldCache.get(ds);
1299
+ if (set) return set;
1300
+ set = new Set<number>();
1301
+ for (const v of ds.vendors()) {
1302
+ for (const item of v.items) set.add(item);
1303
+ }
1304
+ vendorSoldCache.set(ds, set);
1305
+ return set;
1306
+ }
1307
+
1308
+ function itemSoldByVendor(ds: DataStore, itemId: number): boolean {
1309
+ return vendorSoldItems(ds).has(itemId);
1310
+ }
1311
+
1312
+ function vendorZonesForItem(ds: DataStore, itemId: number): string[] {
1313
+ let idx = vendorZonesCache.get(ds);
1314
+ if (!idx) {
1315
+ idx = new Map();
1316
+ for (const z of ds.zones()) {
1317
+ const entries = [
1318
+ ...(z.vendors ?? []),
1319
+ ...(z.innkeepers ?? []).map((i) => i.entry),
1320
+ ];
1321
+ const seen = new Set<number>();
1322
+ for (const e of entries) {
1323
+ if (!(e > 0) || seen.has(e)) continue;
1324
+ seen.add(e);
1325
+ const v = ds.vendor(e);
1326
+ if (!v) continue;
1327
+ for (const item of v.items) {
1328
+ const list = idx.get(item);
1329
+ if (list) {
1330
+ if (!list.includes(z.id)) list.push(z.id);
1331
+ } else {
1332
+ idx.set(item, [z.id]);
1333
+ }
1334
+ }
1335
+ }
1336
+ }
1337
+ vendorZonesCache.set(ds, idx);
1338
+ }
1339
+ return idx.get(itemId) ?? [];
1340
+ }
1341
+
1342
+ /** True when a GO listed on some zone.objects can yield `itemId` under loot conditions. */
1343
+ // Zones whose baked objects hold a container that yields this quest item (grape buckets, pumpkins).
1344
+ // The same rows doQuest.ts itemGoStep opens, so the skip line and the step agree on the source.
1345
+ // What every one of these containers demands to open, or null when any of them opens free. The
1346
+ // objectTarget branch already reports both shapes; the item branch reported neither.
1347
+ // Cite: openLock.ts hardestSkillLockCase / requiredKeyItemEntry.
1348
+ function containerGateForSources(
1349
+ ds: DataStore,
1350
+ sources: readonly { goEntry: number }[],
1351
+ inv: Inventory,
1352
+ ): { goName: string; need: string } | null {
1353
+ let gate: { goName: string; need: string } | null = null;
1354
+ for (const src of sources) {
1355
+ const go = ds.gameObject(src.goEntry);
1356
+ if (!go?.lockId && !go?.keyId) return null;
1357
+ const lock = go.lockId != null ? ds.lock(go.lockId) : undefined;
1358
+ if (lock && canOpenLock({ lock, skillValueFor: () => undefined }).ok) return null;
1359
+ const keyEntry = go.keyId ?? (lock ? requiredKeyItemEntry(lock) : undefined);
1360
+ if (keyEntry != null) {
1361
+ if (countItem(inv, keyEntry) > 0) return null;
1362
+ gate ??= { goName: go.name, need: ds.item(keyEntry)?.name ?? `item ${keyEntry}` };
1363
+ continue;
1364
+ }
1365
+ const hard = lock ? hardestSkillLockCase(lock) : undefined;
1366
+ if (!hard) return null;
1367
+ gate ??= { goName: go.name, need: `${hard.skillName} ${hard.skillReq}` };
1368
+ }
1369
+ return gate;
1370
+ }
1371
+
1372
+ export function goLootSourcesForItem(
1373
+ ds: DataStore,
1374
+ itemId: number,
1375
+ log: QuestLog,
1376
+ level: number,
1377
+ auras?: readonly number[],
1378
+ ): { zoneId: string; goEntry: number }[] {
1379
+ const snap = lootSnap(log, level, auras);
1380
+ const yields = (key: number | string): boolean =>
1381
+ ds.lootHasItem(key, itemId)
1382
+ && ds.lootTable(key).some((d) =>
1383
+ lootRowIsQuestItemSource(d, itemId) && lootDropAllowed(ds, d.conditionId, snap));
1384
+ const out: { zoneId: string; goEntry: number }[] = [];
1385
+ for (const z of ds.zones()) {
1386
+ for (const ge of z.objects ?? []) {
1387
+ const go = ds.gameObject(ge);
1388
+ if (!go?.lootId) continue;
1389
+ if (yields(`go:${go.lootId}`)) out.push({ zoneId: z.id, goEntry: ge });
1390
+ }
1391
+ }
1392
+ return out;
1393
+ }
1394
+
1395
+ export function goLootZonesForItem(
1396
+ ds: DataStore,
1397
+ itemId: number,
1398
+ log: QuestLog,
1399
+ level: number,
1400
+ auras?: readonly number[],
1401
+ ): string[] {
1402
+ return [...new Set(goLootSourcesForItem(ds, itemId, log, level, auras).map((s) => s.zoneId))];
1403
+ }
1404
+
1405
+ function itemHasGoLootSource(
1406
+ ds: DataStore,
1407
+ itemId: number,
1408
+ log: QuestLog,
1409
+ level: number,
1410
+ auras?: readonly number[],
1411
+ ): boolean {
1412
+ return goLootZonesForItem(ds, itemId, log, level, auras).length > 0;
1413
+ }
1414
+
1415
+ // Instance / battleground creatures grouped by lootId, so an item objective can answer "run it
1416
+ // inside X" the way the kill-objective branch already does. Built once per store, on first miss.
1417
+ const instanceLootIndex = new WeakMap<DataStore, { gen: number; byLootId: Map<number, number[]> }>();
1418
+
1419
+ function instanceDroppersForItem(ds: DataStore, itemId: number): number[] {
1420
+ const gen = ds.zoneWorldGen();
1421
+ let slot = instanceLootIndex.get(ds);
1422
+ let byLootId = slot && slot.gen === gen ? slot.byLootId : null;
1423
+ if (!byLootId) {
1424
+ byLootId = new Map<number, number[]>();
1425
+ for (const cr of ds.creatures()) {
1426
+ const lootId = cr.lootId ?? 0;
1427
+ if (!lootId) continue;
1428
+ if (!ds.isInstanceCreature(cr.entry) && !ds.isBattlegroundCreature(cr.entry)) continue;
1429
+ let list = byLootId.get(lootId);
1430
+ if (list === undefined) byLootId.set(lootId, (list = []));
1431
+ list.push(cr.entry);
1432
+ }
1433
+ slot = { gen, byLootId };
1434
+ instanceLootIndex.set(ds, slot);
1435
+ }
1436
+ const out: number[] = [];
1437
+ for (const key of ds.lootKeysForItem(itemId)) {
1438
+ if (typeof key !== 'number') continue;
1439
+ if (!ds.lootHasItem(key, itemId)) continue;
1440
+ for (const ce of byLootId.get(key) ?? []) if (!out.includes(ce)) out.push(ce);
1441
+ }
1442
+ return out;
1443
+ }
1444
+
1445
+ function itemHasBakedSource(
1446
+ ds: DataStore,
1447
+ itemId: number,
1448
+ log: QuestLog,
1449
+ level: number,
1450
+ questLevel: number,
1451
+ quest?: QuestDef,
1452
+ auras?: readonly number[],
1453
+ ): boolean {
1454
+ if (droppersForItem(ds, itemId, log, level, questLevel, auras).length > 0) return true;
1455
+ if (itemHasGoLootSource(ds, itemId, log, level, auras)) return true;
1456
+ if (itemSoldByVendor(ds, itemId)) return true;
1457
+ if (quest?.srcItem?.item === itemId && !isWorldLootSrcItemObjective(quest)) return true;
1458
+ if (quest?.startedByItem === itemId) return true;
1459
+ for (const ce of quest?.farm ?? []) {
1460
+ if (spawnZonesForCreature(ds, ce).length === 0) continue;
1461
+ if (lootChanceForItem(ds, ce, itemId, log, level, auras) > 0) return true;
1462
+ }
1463
+ return false;
1464
+ }
1465
+
1466
+ // Whether Auto Quest can finish this quest on baked data: every remaining kill and item objective needs
1467
+ // a source. A hint for the first item is not enough — the route then dies on the second.
1468
+ export function questHasBakedPath(
1469
+ ds: DataStore,
1470
+ quest: QuestDef,
1471
+ log: QuestLog,
1472
+ inv: Inventory,
1473
+ currentZone: string,
1474
+ level: number,
1475
+ auras?: readonly number[],
1476
+ ): { ok: true } | { ok: false; reason: string } {
1477
+ const dummy = accept(
1478
+ { quests: log.quests.filter((q) => q.entry !== quest.entry) },
1479
+ quest,
1480
+ );
1481
+ const qs = getQuest(dummy, quest.entry);
1482
+ if (!qs) return { ok: false, reason: `"${quest.title}" cannot be taken` };
1483
+ let bag = inv;
1484
+ if (shouldGrantSrcItemOnAccept(quest) && quest.srcItem && quest.srcItem.item > 0) {
1485
+ const item = quest.srcItem.item;
1486
+ const need = quest.srcItem.count > 0 ? quest.srcItem.count : 1;
1487
+ const have = countItem(bag, item);
1488
+ if (have < need) {
1489
+ bag = addItem(bag, item, need - have, ds.item(item)?.stackable ?? 1);
1490
+ }
1491
+ }
1492
+ if (questComplete(quest, qs, bag)) return { ok: true };
1493
+
1494
+ if (isTalkToNpcQuest(quest) || isEventTalkQuest(quest)) {
1495
+ const outOfReach = goEnderOutOfReach(ds, quest);
1496
+ if (outOfReach) return { ok: false, reason: outOfReach };
1497
+ const npc = (quest.enderName ?? '').trim() || (quest.starterName ?? '').trim();
1498
+ if (npc || (quest.enderZones?.length ?? 0) > 0 || (quest.starterZones?.length ?? 0) > 0) {
1499
+ return { ok: true };
1500
+ }
1501
+ }
1502
+
1503
+ for (const kt of quest.killTargets ?? []) {
1504
+ if ((qs.kills[String(kt.creatureEntry)] ?? 0) >= kt.count) continue;
1505
+ const farm = corpseUseSummonedEntries(ds, quest).includes(kt.creatureEntry)
1506
+ ? corpseUseFarmEntries(ds, quest)
1507
+ : [kt.creatureEntry];
1508
+ if (farm.length > 0 && farm.every((ce) => isScriptCreditOnlyTarget(ds, ce, log))) {
1509
+ const nm = creatureObjectiveLabel(
1510
+ quest,
1511
+ kt.creatureEntry,
1512
+ ds.creature(kt.creatureEntry)?.name ?? String(kt.creatureEntry),
1513
+ );
1514
+ return { ok: false, reason: `"${quest.title}" credits ${nm} by script, Auto Quest leaves it to you` };
1515
+ }
1516
+ const zids = [...new Set(farm.flatMap((ce) => spawnZonesForCreature(ds, ce)))];
1517
+ if (zids.length === 0) {
1518
+ // Instance-only target: no world spawn and no outdoor pull. The starterZones escape
1519
+ // below would otherwise call every dungeon quest walkable (318 of 1390 fight steps).
1520
+ if (farm.every((ce) => ds.isInstanceCreature(ce) || ds.isBattlegroundCreature(ce))) {
1521
+ const bg = ds.battlegroundForCreature(kt.creatureEntry);
1522
+ if (bg) {
1523
+ return { ok: false, reason: `"${quest.title}" is a battleground quest, run it inside ${bg}` };
1524
+ }
1525
+ const inst = ds.instanceForCreature(kt.creatureEntry)?.name;
1526
+ return {
1527
+ ok: false,
1528
+ reason: `"${quest.title}" is a dungeon quest${inst ? `, run it inside ${inst}` : ''}`,
1529
+ };
1530
+ }
1531
+ const home = quest.starterZones ?? [];
1532
+ if (!(ds.creature(kt.creatureEntry) && home.length > 0)) {
1533
+ const nm = creatureObjectiveLabel(
1534
+ quest,
1535
+ kt.creatureEntry,
1536
+ ds.creature(kt.creatureEntry)?.name ?? String(kt.creatureEntry),
1537
+ );
1538
+ return { ok: false, reason: `"${quest.title}" needs ${nm} in an unknown zone` };
1539
+ }
1540
+ }
1541
+ }
1542
+
1543
+ const stillNeeded = [
1544
+ ...(quest.itemTargets ?? []),
1545
+ ...(quest.sourceItemTargets ?? []),
1546
+ ];
1547
+ for (const it of stillNeeded) {
1548
+ if (countItem(bag, it.item) >= it.count) continue;
1549
+ if (itemHasBakedSource(ds, it.item, dummy, level, quest.questLevel, quest, auras)) continue;
1550
+ const itemName = ds.item(it.item)?.name ?? String(it.item);
1551
+ // Same answer the kill branch gives: the drop exists, it is just behind an instance door.
1552
+ const inside = instanceDroppersForItem(ds, it.item);
1553
+ if (inside.length > 0) {
1554
+ const bg = inside.map((ce) => ds.battlegroundForCreature(ce)).find(Boolean);
1555
+ if (bg) {
1556
+ return { ok: false, reason: `"${quest.title}" is a battleground quest, run it inside ${bg}` };
1557
+ }
1558
+ const inst = inside.map((ce) => ds.instanceForCreature(ce)?.name).find(Boolean);
1559
+ return {
1560
+ ok: false,
1561
+ reason: `"${quest.title}" is a dungeon quest${inst ? `, run it inside ${inst}` : ''}`,
1562
+ };
1563
+ }
1564
+ return {
1565
+ ok: false,
1566
+ reason: `"${quest.title}" needs ${itemName} but no condition-satisfied dropper is baked`,
1567
+ };
1568
+ }
1569
+
1570
+ // Escort / empty explore: finishEscort is the baked end-condition (FO6). Mixed
1571
+ // exploreTriggers still count as a path (zone-enter or the same event credit).
1572
+ if (canDegradedAutoFinish(quest) || (quest.exploreTriggers?.length ?? 0) > 0) return { ok: true };
1573
+
1574
+ const skip = questWorkSkipReason(ds, quest, qs, bag, currentZone, dummy, level, auras);
1575
+ if (skip.routeZone) return { ok: true };
1576
+ if (!skip.noPath) return { ok: true };
1577
+ const zones = acquisitionZonesForQuest(ds, quest, qs, bag, dummy, level, auras);
1578
+ if (zones.length > 0) return { ok: true };
1579
+ return { ok: false, reason: skip.reason };
1580
+ }