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,1643 @@
1
+ // The autopilot's between-fight planner: one pure decision per call. The UI driver executes
2
+ // the returned step (in real time, with real attacks — never auto-resolve) and calls back in.
3
+ // PURE: no DOM/RNG/Date — time is passed in via `progress.elapsedMs`.
4
+ //
5
+ // QI3 fully-autonomous quest loop (docs/archive/QUEST-INTERPRETER.md §5):
6
+ // 1. ACCEPT available in-zone quests
7
+ // 2. ADVANCE unmet objectives via the baked acquisition graph
8
+ // 3. DELIVER/talk when enderZones ∋ currentZone
9
+ // 4. TURN IN when questComplete
10
+ // 5. NEVER dead-stop — skip cleanly with an honest reason; travel to acquisition zone when known
11
+ //
12
+ // SESSION 5 (docs/archive/GO-QUEST-RUNSHEET.md): planner self-memory — latches failed steps through
13
+ // AutopilotProgress even when the driver forgets blockedObjects/blockedAccepts/blockedSpeaks;
14
+ // complete-but-not-turned-in always re-emits turnin/object; accept/advance-at-GO routes via
15
+ // zone.objects + acquisitionZonesForQuest; named honest-skip taxonomy (never false "Quest complete!").
16
+
17
+ import type { DataStore } from '../data/dataStore.ts';
18
+ import type { GameState } from '../engine/game.ts';
19
+ import { powerType, combatantCreatureType } from '../domain/character/character.ts';
20
+ import { knows } from '../domain/character/spellbook.ts';
21
+ import { isPlayerAlive } from '../domain/run/death.ts';
22
+ import { CREATURE_TYPE } from '../data/schema.ts';
23
+ import {
24
+ getQuest,
25
+ activeQuestCount,
26
+ MAX_QUEST_SLOTS,
27
+ itemsMet,
28
+ itemStartGateMet,
29
+ questComplete,
30
+ isEventTalkQuest,
31
+ isTalkToNpcQuest,
32
+ hasGoEnder,
33
+ goEnderEntryOf,
34
+ isDeliverQuest,
35
+ talkMet,
36
+ } from '../domain/quests/questlog.ts';
37
+ import { countItem } from '../domain/items/inventory.ts';
38
+ import { huntableCreatures } from '../world/world.ts';
39
+ import {
40
+ huntableQuests,
41
+ questsWithWork,
42
+ questWorkSkipReason,
43
+ lootChanceForItem,
44
+ acquisitionZonesForQuest,
45
+ offeredQuests,
46
+ prereqMet,
47
+ dropperNoiseFloorFor,
48
+ compareQuestDroppers,
49
+ } from '../quests/quests.ts';
50
+ import { ownedInventory } from '../domain/items/keyring.ts';
51
+ import { mailboxAsInventory } from '../domain/economy/mail.ts';
52
+ import { UNSUPPORTED_GO_TYPES } from '../domain/world/gameObjects.ts';
53
+ import {
54
+ corpseUseFarmEntries,
55
+ corpseUseSummonedEntries,
56
+ corpseUseWorkRemaining,
57
+ itemIsQuestCorpseUse,
58
+ pendingCorpseUseItem,
59
+ } from '../domain/quests/questCorpseUse.ts';
60
+ import { pendingScriptItemUse } from '../domain/quests/questScriptItemUse.ts';
61
+ import {
62
+ canOpenLock,
63
+ requiredKeyItemEntry,
64
+ } from '../domain/world/openLock.ts';
65
+ import {
66
+ nextDoQuestStep, questHasDoableStep, questVendorBuyStep, itemGoStep, questLockKeyFightTarget,
67
+ questLockKeyObjectStep,
68
+ } from '../domain/quests/doQuest.ts';
69
+ import {
70
+ buildView, bestHuntTarget, nextAutoGatherNode, partyCanHarvestNode, type BotView,
71
+ } from './view.ts';
72
+ import { bestConsumable, type ConsumeEffect } from './consumables.ts';
73
+ import { questAcceptGate, questRunGate, zoneSwapGate } from './questPolicy.ts';
74
+ import { auraIdsForSnapshot } from '../engine/spells/conditions.ts';
75
+ import type { AutopilotConfig } from './config.ts';
76
+
77
+ function lootAuras(state: GameState): number[] {
78
+ return auraIdsForSnapshot(state.char.activeAuras);
79
+ }
80
+
81
+ /** Bags + keyring + overflow mail. Auto Quest must see mailed quest items as owned. */
82
+ function ownedOf(state: GameState) {
83
+ return ownedInventory(state.inventory, state.keyring, mailboxAsInventory(state.mailbox ?? []));
84
+ }
85
+
86
+ /**
87
+ * What the autopilot is doing. `farm` with a `creatureEntry` grinds that exact mob the player
88
+ * picked; `farm` without one auto-picks the safest rewarding target. `quest` runs the full
89
+ * accept → advance → deliver → turn-in loop (QI3); `entry` is an optional UI seed (world
90
+ * Auto Quest). `pin` is set by quest-log Do Quest: that entry only — if it cannot
91
+ * move, stop (never fall through to the Auto Quest campaign).
92
+ */
93
+ export type AutopilotMode =
94
+ | { kind: 'farm'; creatureEntry?: number }
95
+ /** `handInByHand`: stop as soon as the pinned quest is payable and leave the turn-in to the
96
+ * player, so the quest page they launched from reopens on its hand-in step. */
97
+ | { kind: 'quest'; entry?: number; pin?: boolean; handInByHand?: boolean };
98
+
99
+ export type { AutopilotConfig } from './config.ts';
100
+ export { DEFAULT_AUTOPILOT_CONFIG, normalizeAutopilotConfig } from './config.ts';
101
+
102
+ /**
103
+ * Named skip taxonomy for the GO-quest class (SESSION 5). These tokens surface in
104
+ * stop `detail` / autopilot log verbatim — never a false "Quest complete!".
105
+ * Cite: docs/archive/GO-QUEST-RUNSHEET.md SESSION 5 honest skip taxonomy.
106
+ */
107
+ export const GO_CLASS_SKIP = {
108
+ LOCKED_NO_KEY: 'locked-no-key',
109
+ ALL_INSTANCES_CONSUMED: 'all-instances-consumed',
110
+ ENDER_NOT_IN_WORLD: 'ender-not-in-world',
111
+ PREREQ_UNMET: 'prereq-unmet',
112
+ UNSUPPORTED_TYPE: 'unsupported-type-dispositioned',
113
+ } as const;
114
+
115
+ /** Orchestration state the driver owns and threads back in (keeps the planner pure). */
116
+ export interface AutopilotProgress {
117
+ elapsedMs: number;
118
+ recovering: boolean; // hysteresis latch: once recovering, keep going until recoverTo
119
+ consecutiveDefeats: number;
120
+ /**
121
+ * Quest entries whose engine accept FAILED this session. The view's acceptable list should
122
+ * already mirror every engine gate, but if any gate is ever missed again the planner must
123
+ * degrade to "skip this quest" — re-proposing the same refused accept every tick deadlocks
124
+ * the whole quest loop (Cortello's Riddle item-start gate did exactly that).
125
+ */
126
+ blockedAccepts?: ReadonlySet<number>;
127
+ /**
128
+ * CQ6: quest entries whose speak step ran but did NOT credit the talk objective (name
129
+ * mismatch / scripted gate). Same degrade-to-skip contract as blockedAccepts — the
130
+ * planner must never re-propose a speak that provably does nothing.
131
+ */
132
+ blockedSpeaks?: ReadonlySet<number>;
133
+ /**
134
+ * GameObject entries whose interact failed this session (missing def, locked without
135
+ * key, permanently despawned, unsupported type). Same degrade-to-skip contract — without
136
+ * this the planner re-proposes the same object step every 400ms and soft-locks Auto Quest
137
+ * on "Interacting with object…" / stuck objective progress.
138
+ */
139
+ blockedObjects?: ReadonlySet<number>;
140
+ // SESSION 5 self-memory: quest entries whose turn-in step produced no state change (engine refused /
141
+ // driver no-op). Filtered out of turnInCandidates so we never spin.
142
+ blockedTurnins?: ReadonlySet<number>;
143
+ /** Item entries whose use step produced no state change this session. */
144
+ blockedUses?: ReadonlySet<number>;
145
+ /** Creature entries whose fight step produced no state change this session. */
146
+ blockedFights?: ReadonlySet<number>;
147
+ /** Zone ids whose travel step produced no state change this session. */
148
+ blockedTravels?: ReadonlySet<string>;
149
+ /**
150
+ * Quest entries Auto Quest skips for the rest of the session after too many deaths while
151
+ * advancing them (default 3 — {@link AutopilotConfig.maxConsecutiveDefeats}). Turn-in of a
152
+ * completed quest is still allowed; only accept/advance/travel/speak for the entry stops.
153
+ */
154
+ blockedQuests?: ReadonlySet<number>;
155
+ /** Defeat counts while fighting for a given quest entry this session (driver-owned). */
156
+ questDefeats?: ReadonlyMap<number, number>;
157
+ /** Wall-clock for gather node respawn. Driver-owned; 0 treats depleted nodes as not ready. */
158
+ nowMs?: number;
159
+ /**
160
+ * SESSION 5: last step planStep proposed + quest-relevant state fingerprint at proposal
161
+ * time. On the next call, if the fingerprint is unchanged the proposal produced no state
162
+ * change and is latched into the matching blocked* set with an honest reason — even when
163
+ * the driver forgets to populate blockedObjects/blockedAccepts/blockedSpeaks.
164
+ * Pure-function shape: no globals; all memory lives on this threaded object.
165
+ */
166
+ lastProposal?: AutopilotStep;
167
+ lastStateKey?: string;
168
+ /** Most recent self-latch / honest-skip reason (for log surface). */
169
+ lastSkipReason?: string;
170
+ /**
171
+ * Latched once an elite quest was refused for an empty squad (questPolicy code
172
+ * 'elite-needs-squad'). The driver reads it to prompt the hire once; never cleared here, so a
173
+ * refusal early in the pass survives the rest of it.
174
+ */
175
+ eliteNeedsSquad?: boolean;
176
+ }
177
+
178
+ export type StopReason =
179
+ | 'timeUp'
180
+ | 'questComplete'
181
+ | 'noTargets'
182
+ | 'defeated'
183
+ | 'outOfConsumables'
184
+ | 'levelCap'
185
+ /** Multi-choice reward pause ended without a pick (questRewardChoice: wait + Stop). */
186
+ | 'rewardChoice';
187
+
188
+ export type AutopilotStep =
189
+ | { kind: 'fight'; creatureEntry: number; forQuest: boolean; questEntry?: number }
190
+ | { kind: 'consume'; itemEntry: number; effect: ConsumeEffect }
191
+ | { kind: 'cannibalize' } // Forsaken free heal off a fresh Humanoid/Undead corpse (preferred over food)
192
+ | { kind: 'wait' }
193
+ | { kind: 'turnin'; questEntry: number } // hand in a completed quest (closes the auto-quest loop)
194
+ | { kind: 'accept'; questEntry: number } // accept an offered quest here (for kill/item quests etc)
195
+ | { kind: 'object'; objectEntry: number; forQuest?: boolean; questEntry?: number } // pickup / open / use a GameObject for quest objective; also used for accept/turn-in at GO givers
196
+ | { kind: 'use'; itemEntry: number; targetCreatureEntry?: number; questEntry?: number } // use a provided/source item for a tracked objective (Lane H)
197
+ /** CQ6: speak to the quest ender (name-based talk credit) — the manual "Speak to {ender}"
198
+ * button's autopilot twin. Emitted only when standing in the ender zone (or the quest has
199
+ * no baked enderZones), so talk quests never soft-lock an auto run. */
200
+ | { kind: 'speak'; questEntry: number; npcName: string }
201
+ /** QI3: journey to an acquisition / ender zone so work can continue (never silent dead-stop). */
202
+ | { kind: 'travel'; zoneId: string; reason: string; questEntry?: number }
203
+ /** Wear a bagged item the current farm needs (Argent Dawn Commission for type 10 loot). */
204
+ | { kind: 'equip'; itemEntry: number; questEntry?: number }
205
+ /** FO6 finishEscort: escort / empty explore / mixed event bit when that is the remaining work. */
206
+ | { kind: 'event'; questEntry: number }
207
+ /** Ready mine/herb node in this zone (party skill + tool). Not latchable — respawn re-opens it. */
208
+ | { kind: 'gather'; nodeId: number }
209
+ /** The skinnable corpse the fight left standing. skin() is the manual click, so the take-list
210
+ * never speaks here: a companion skinner reading gray still gets its hide on an auto run. */
211
+ | { kind: 'skin' }
212
+ /** Buy a quest material from a zone merchant (beer, reagents). Driver calls game.buyItem. */
213
+ | { kind: 'buy'; vendorEntry: number; itemEntry: number; count: number; questEntry?: number }
214
+ | { kind: 'stop'; reason: StopReason; detail?: string };
215
+
216
+ // ---- SESSION 5 helpers (self-memory, zone presence, skip taxonomy) --------
217
+
218
+ /**
219
+ * True if this step kind can soft-lock when re-proposed after a no-op.
220
+ *
221
+ * Fight is intentionally excluded. Fights are async (live combat between planStep calls);
222
+ * SESSION 5 fingerprint latch was built for sync GO/accept/turnin/use steps that re-plan in
223
+ * the same tick. Fingerprint-latching fights false-blocked every grind target in a zone
224
+ * ("fight vs creature N produced no state change") whenever a pull left zone/level/health/
225
+ * questlog/inventory unchanged (grey XP-only, empty loot, abandon, or re-plan before resolve).
226
+ * The driver owns beginFight refusals via blockedFights — same contract as blockedAccepts.
227
+ */
228
+ function isLatchableStep(step: AutopilotStep): boolean {
229
+ return (
230
+ step.kind === 'object' ||
231
+ step.kind === 'accept' ||
232
+ step.kind === 'speak' ||
233
+ step.kind === 'turnin' ||
234
+ step.kind === 'use' ||
235
+ step.kind === 'equip' ||
236
+ step.kind === 'travel' ||
237
+ step.kind === 'event' ||
238
+ step.kind === 'buy'
239
+ );
240
+ }
241
+
242
+ function stepsEqual(a: AutopilotStep, b: AutopilotStep): boolean {
243
+ return JSON.stringify(a) === JSON.stringify(b);
244
+ }
245
+
246
+ /**
247
+ * Quest-relevant state fingerprint. Changes when a successful step would mutate progress
248
+ * (zone, log, inventory, health, XP, gold). Used to detect "proposal produced no state change".
249
+ * XP/gold included so grind progress is visible even when HP/inv/questlog are unchanged.
250
+ */
251
+ function planStateKey(state: GameState): string {
252
+ const ql = state.questlog.quests
253
+ .map((q) => {
254
+ const kills = JSON.stringify(q.kills ?? {});
255
+ const objects = JSON.stringify(q.objects ?? {});
256
+ const spells = JSON.stringify((q as { spells?: Record<string, number> }).spells ?? {});
257
+ return `${q.entry}:${q.status}:${q.talked === true ? 1 : 0}:${q.explored === true ? 1 : 0}:${kills}:${objects}:${spells}`;
258
+ })
259
+ .join('|');
260
+ const inv = [...ownedOf(state).stacks]
261
+ .map((s) => `${s.item}:${s.count}`)
262
+ .sort()
263
+ .join(',');
264
+ const nodes = Object.keys(state.nodeState ?? {})
265
+ .sort()
266
+ .map((k) => {
267
+ const s = state.nodeState[k];
268
+ return `${k}:${s.charges}:${s.readyAt}`;
269
+ })
270
+ .join(',');
271
+ return `${state.char.zoneId}|L${state.char.level}|H${state.char.health}|X${state.char.xp}|G${state.char.gold}|${ql}|${inv}|N${nodes}`;
272
+ }
273
+
274
+ function mutableNumSet(
275
+ prog: AutopilotProgress,
276
+ field:
277
+ | 'blockedAccepts'
278
+ | 'blockedSpeaks'
279
+ | 'blockedObjects'
280
+ | 'blockedTurnins'
281
+ | 'blockedUses'
282
+ | 'blockedFights'
283
+ | 'blockedQuests',
284
+ ): Set<number> {
285
+ const cur = prog[field];
286
+ if (cur instanceof Set) return cur;
287
+ const s = new Set<number>(cur ?? []);
288
+ prog[field] = s;
289
+ return s;
290
+ }
291
+
292
+ function questIsDeathSkipped(prog: AutopilotProgress, entry: number): boolean {
293
+ return prog.blockedQuests?.has(entry) === true;
294
+ }
295
+
296
+ /** Hired companions who would join a pull (Auto Quest setup elite policy reads this). */
297
+ function squadSize(state: GameState): number {
298
+ return (state.squad ?? []).length;
299
+ }
300
+
301
+ /**
302
+ * Every reason the quest run leaves an ACTIVE quest alone this step: the per-quest death cap,
303
+ * plus the Auto Quest setup policy (elite handling). Turn-in of a completed quest is never
304
+ * gated by this — a finished quest is always worth handing in.
305
+ */
306
+ function questIsSkipped(
307
+ state: GameState,
308
+ ds: DataStore,
309
+ cfg: AutopilotConfig,
310
+ prog: AutopilotProgress,
311
+ entry: number,
312
+ ): boolean {
313
+ if (questIsDeathSkipped(prog, entry)) return true;
314
+ const def = ds.quest(entry);
315
+ if (!def) return false;
316
+ const gate = questRunGate(ds, def, cfg, squadSize(state), state.char.level, state.questlog);
317
+ if (gate.ok) return false;
318
+ prog.lastSkipReason = gate.reason;
319
+ if (gate.code === 'elite-needs-squad') prog.eliteNeedsSquad = true;
320
+ return true;
321
+ }
322
+
323
+ /** True when `entry` is huntable or spawned in the zone Auto Quest is standing in. */
324
+ function creatureIsLocal(ds: DataStore, state: GameState, entry: number): boolean {
325
+ const z = ds.zone(state.char.zoneId);
326
+ if (z?.huntable?.includes(entry)) return true;
327
+ if ((z?.spawns ?? []).some((s) => s.entry === entry)) return true;
328
+ return huntableCreatures(ds, state.char.zoneId, state.questlog, state.char.race)
329
+ .some((c) => c.entry === entry);
330
+ }
331
+
332
+ // A campaign step Do Quest would take for an in-log entry, if Auto Quest can run it here (local fight / use
333
+ // / object / speak) or travel to it under zone-swap policy.
334
+ function campaignMoveStep(
335
+ state: GameState,
336
+ ds: DataStore,
337
+ cfg: AutopilotConfig,
338
+ prog: AutopilotProgress,
339
+ entry: number,
340
+ ): AutopilotStep | null {
341
+ const step = pinnedQuestStep(state, ds, cfg, prog, entry);
342
+ if (!step || step.kind === 'stop' || step.kind === 'accept') return null;
343
+ if (step.kind === 'fight' && !creatureIsLocal(ds, state, step.creatureEntry)) return null;
344
+ if (step.kind === 'travel' && !travelAllowed(state, ds, cfg, prog, step.zoneId)) return null;
345
+ return step;
346
+ }
347
+
348
+ /** Park log quests with no remaining fight/use/object/speak/travel (Warsong marks). */
349
+ function parkUnfinishableQuests(
350
+ state: GameState,
351
+ ds: DataStore,
352
+ cfg: AutopilotConfig,
353
+ prog: AutopilotProgress,
354
+ skipEntry?: number,
355
+ ): void {
356
+ const inv = ownedOf(state);
357
+ const zoneId = state.char.zoneId;
358
+ for (const qs of state.questlog.quests) {
359
+ if (qs.status !== 'active') continue;
360
+ if (skipEntry != null && qs.entry === skipEntry) continue;
361
+ if (prog.blockedQuests?.has(qs.entry)) continue;
362
+ const def = ds.quest(qs.entry);
363
+ if (!def) continue;
364
+ if (questComplete(def, qs, inv)) continue;
365
+ if (campaignMoveStep(state, ds, cfg, prog, qs.entry)) continue;
366
+ const skip = questWorkSkipReason(ds, def, qs, inv, zoneId, state.questlog, state.char.level, lootAuras(state));
367
+ if (
368
+ skip.routeZone &&
369
+ skip.routeZone !== zoneId &&
370
+ !prog.blockedTravels?.has(skip.routeZone) &&
371
+ travelAllowed(state, ds, cfg, prog, skip.routeZone)
372
+ ) {
373
+ continue;
374
+ }
375
+ // No move and no usable travel: a skip naming this same zone (or nowhere) has no route left, so
376
+ // park it and keep the rest of the log rather than ending the run. Cite: quest 5561.
377
+ if (!skip.noPath && skip.routeZone != null && skip.routeZone !== zoneId) continue;
378
+ mutableNumSet(prog, 'blockedQuests').add(qs.entry);
379
+ prog.lastSkipReason = skip.reason;
380
+ }
381
+ }
382
+
383
+ // Quest-log Do Quest pin. World Auto Quest does not set `pin`, so this is a no-op there. Turn-in ends the
384
+ // pin; the player tapped this quest so level-band does not drop it.
385
+ function launchFocus(
386
+ state: GameState,
387
+ ds: DataStore,
388
+ mode: AutopilotMode,
389
+ _cfg: AutopilotConfig,
390
+ prog: AutopilotProgress,
391
+ _acceptable: readonly number[],
392
+ ): number | undefined {
393
+ if (mode.kind !== 'quest' || mode.entry == null || !mode.pin) return undefined;
394
+ const entry = mode.entry;
395
+ if (questIsDeathSkipped(prog, entry)) return undefined;
396
+ const qs = getQuest(state.questlog, entry);
397
+ if (qs?.status === 'turnedIn') return undefined;
398
+ if (!ds.quest(entry)) return undefined;
399
+ return entry;
400
+ }
401
+
402
+ /** Map nextDoQuestStep onto an AutopilotStep so Do Quest uses this same UI. */
403
+ function pinnedQuestStep(
404
+ state: GameState,
405
+ ds: DataStore,
406
+ cfg: AutopilotConfig,
407
+ prog: AutopilotProgress,
408
+ focus: number,
409
+ ): AutopilotStep | null {
410
+ const def = ds.quest(focus);
411
+ if (!def) return { kind: 'stop', reason: 'noTargets', detail: 'Unknown quest' };
412
+ const dq = nextDoQuestStep(state, ds, focus, {
413
+ skipObjects: prog.blockedObjects,
414
+ skipCreatures: prog.blockedFights,
415
+ });
416
+ if (dq.kind === 'done') return null;
417
+ if (dq.kind === 'blocked') return { kind: 'stop', reason: 'noTargets', detail: dq.reason };
418
+ if (dq.kind === 'accept') {
419
+ if (prog.blockedAccepts?.has(focus)) {
420
+ return { kind: 'stop', reason: 'noTargets', detail: `Cannot accept "${def.title}"` };
421
+ }
422
+ if (!questHasDoableStep(state, ds, focus)) {
423
+ return { kind: 'stop', reason: 'noTargets', detail: `Cannot finish "${def.title}" on this data` };
424
+ }
425
+ return { kind: 'accept', questEntry: focus };
426
+ }
427
+ if (dq.kind === 'talk') {
428
+ // A GO ender IS the talk objective — recordTalkToNpc refuses every hasGoEnder quest
429
+ // (questlog.ts:1295), so open the object instead of speaking to a chest.
430
+ const goEnder = goEnderEntryOf(def);
431
+ if (goEnder != null) {
432
+ if (prog.blockedObjects?.has(goEnder)) return null;
433
+ const step = objectOrTravelStep(
434
+ state,
435
+ ds,
436
+ goEnder,
437
+ {
438
+ forQuest: true,
439
+ questEntry: focus,
440
+ reasonHint: `Talk for "${def.title}" at ${ds.gameObject(goEnder)?.name ?? goEnder}`,
441
+ },
442
+ prog.blockedTravels,
443
+ (z) => travelAllowed(state, ds, cfg, prog, z),
444
+ );
445
+ return step.kind === 'stop' ? null : step;
446
+ }
447
+ if (prog.blockedSpeaks?.has(focus)) return null;
448
+ const npcName = isEventTalkQuest(def)
449
+ ? eventTalkAutopilotNpcName(def)
450
+ : ((def.enderName ?? '').trim() || (def.starterName ?? '').trim());
451
+ if (!npcName) return null;
452
+ return { kind: 'speak', questEntry: focus, npcName };
453
+ }
454
+ if (dq.kind === 'object') {
455
+ return { kind: 'object', objectEntry: dq.objectEntry, forQuest: true, questEntry: focus };
456
+ }
457
+ if (dq.kind === 'use') {
458
+ return { kind: 'use', itemEntry: dq.itemEntry, targetCreatureEntry: dq.targetCreatureEntry, questEntry: focus };
459
+ }
460
+ if (dq.kind === 'equip') {
461
+ if (prog.blockedUses?.has(dq.itemEntry)) {
462
+ const nm = ds.item(dq.itemEntry)?.name ?? `item ${dq.itemEntry}`;
463
+ return { kind: 'stop', reason: 'noTargets', detail: `Cannot equip ${nm}` };
464
+ }
465
+ return { kind: 'equip', itemEntry: dq.itemEntry, questEntry: focus };
466
+ }
467
+ if (dq.kind === 'travel') {
468
+ return { kind: 'travel', zoneId: dq.zoneId, reason: dq.reason, questEntry: focus };
469
+ }
470
+ if (dq.kind === 'turnin' || dq.kind === 'pick-reward') {
471
+ return turnInStepFor(state, ds, cfg, focus, prog);
472
+ }
473
+ if (dq.kind === 'fight') {
474
+ if (prog.blockedFights?.has(dq.creatureEntry)) return null;
475
+ return { kind: 'fight', creatureEntry: dq.creatureEntry, forQuest: true, questEntry: focus };
476
+ }
477
+ if (dq.kind === 'grant-src') {
478
+ return { kind: 'stop', reason: 'noTargets', detail: `Need the source item for "${def.title}"` };
479
+ }
480
+ if (dq.kind === 'buy') {
481
+ if (prog.blockedUses?.has(dq.itemEntry)) {
482
+ const nm = ds.item(dq.itemEntry)?.name ?? `item ${dq.itemEntry}`;
483
+ return { kind: 'stop', reason: 'noTargets', detail: `Cannot buy ${nm}` };
484
+ }
485
+ return {
486
+ kind: 'buy',
487
+ vendorEntry: dq.vendorEntry,
488
+ itemEntry: dq.itemEntry,
489
+ count: dq.count,
490
+ questEntry: focus,
491
+ };
492
+ }
493
+ if (dq.kind === 'escort') {
494
+ return { kind: 'event', questEntry: focus };
495
+ }
496
+ if (dq.kind === 'garments') {
497
+ return { kind: 'stop', reason: 'noTargets', detail: `"${def.title}" needs a spell on the patient` };
498
+ }
499
+ return null;
500
+ }
501
+
502
+ function questWantsObject(ds: DataStore, questEntry: number, objectEntry: number): boolean {
503
+ const def = ds.quest(questEntry);
504
+ return !!def && (def.objectTargets ?? []).some((ot) => ot.objectEntry === objectEntry);
505
+ }
506
+
507
+ /** Whether the run may route to `zoneId` under the setup's zone-swap policy. */
508
+ function travelAllowed(
509
+ state: GameState,
510
+ ds: DataStore,
511
+ cfg: AutopilotConfig,
512
+ prog: AutopilotProgress,
513
+ zoneId: string,
514
+ ): boolean {
515
+ const gate = zoneSwapGate(ds, zoneId, state.char.level, cfg);
516
+ if (gate.ok) return true;
517
+ prog.lastSkipReason = gate.reason;
518
+ return false;
519
+ }
520
+
521
+ /**
522
+ * Record a defeat while advancing `questEntry`. At {@link AutopilotConfig.maxConsecutiveDefeats}
523
+ * (default 3) the quest is session-skipped and consecutiveDefeats is cleared so Auto Quest
524
+ * continues with the next quest instead of stopping the whole run.
525
+ * Returns true when the quest was just blocked (caller should surface a feed line).
526
+ */
527
+ export function noteQuestDefeat(
528
+ prog: AutopilotProgress,
529
+ questEntry: number,
530
+ maxDefeats: number,
531
+ ): boolean {
532
+ const map = prog.questDefeats instanceof Map
533
+ ? prog.questDefeats as Map<number, number>
534
+ : new Map<number, number>(prog.questDefeats ?? []);
535
+ prog.questDefeats = map;
536
+ const n = (map.get(questEntry) ?? 0) + 1;
537
+ map.set(questEntry, n);
538
+ const cap = Math.max(1, maxDefeats | 0);
539
+ if (n < cap) {
540
+ prog.consecutiveDefeats++;
541
+ prog.recovering = true;
542
+ return false;
543
+ }
544
+ mutableNumSet(prog, 'blockedQuests').add(questEntry);
545
+ prog.consecutiveDefeats = 0;
546
+ prog.recovering = true;
547
+ prog.lastSkipReason = `skipped quest ${questEntry} after ${n} defeats`;
548
+ return true;
549
+ }
550
+
551
+ function mutableStrSet(prog: AutopilotProgress, field: 'blockedTravels'): Set<string> {
552
+ const cur = prog[field];
553
+ if (cur instanceof Set) return cur;
554
+ const s = new Set<string>(cur ?? []);
555
+ prog[field] = s;
556
+ return s;
557
+ }
558
+
559
+ /** Baked zones that list `entry` on zone.objects (SESSION 1 visibility). */
560
+ function zonesListingObject(ds: DataStore, entry: number): string[] {
561
+ const out: string[] = [];
562
+ for (const z of ds.zones()) {
563
+ if ((z.objects ?? []).includes(entry)) out.push(z.id);
564
+ }
565
+ return out;
566
+ }
567
+
568
+ function objectPresentInZone(ds: DataStore, zoneId: string, entry: number): boolean {
569
+ const z = ds.zone(zoneId);
570
+ if (!z) return false;
571
+ return (z.objects ?? []).includes(entry);
572
+ }
573
+
574
+ function isUnsupportedGoType(type: number): boolean {
575
+ return UNSUPPORTED_GO_TYPES.some((t) => t.type === type);
576
+ }
577
+
578
+ // Classify why a latchable step produced no progress — named taxonomy tokens when known. Cite:
579
+ // docs/archive/GO-QUEST-RUNSHEET.md SESSION 5; GameObject Use / openLock / zone.objects.
580
+ function classifyFailedStep(step: AutopilotStep, state: GameState, ds: DataStore): string {
581
+ if (step.kind === 'object') {
582
+ const go = ds.gameObject(step.objectEntry);
583
+ if (!go) {
584
+ return `${GO_CLASS_SKIP.ENDER_NOT_IN_WORLD}: Object#${step.objectEntry} has no baked template`;
585
+ }
586
+ if (isUnsupportedGoType(go.type)) {
587
+ return `${GO_CLASS_SKIP.UNSUPPORTED_TYPE}: ${go.name} (type ${go.type})`;
588
+ }
589
+ if (!objectPresentInZone(ds, state.char.zoneId, step.objectEntry)) {
590
+ const elsewhere = zonesListingObject(ds, step.objectEntry);
591
+ if (elsewhere.length === 0) {
592
+ return `${GO_CLASS_SKIP.ENDER_NOT_IN_WORLD}: ${go.name} is not on any baked zone.objects list`;
593
+ }
594
+ return `${go.name} is not in this zone (lives in ${elsewhere.map((z) => ds.zone(z)?.name ?? z).join(' / ')})`;
595
+ }
596
+ if (go.lockId || go.keyId) {
597
+ const lock = go.lockId != null ? ds.lock(go.lockId) : undefined;
598
+ const keyEntry = go.keyId ?? requiredKeyItemEntry(lock);
599
+ // A quest key sits in the KEYRING, not the bags: reading state.inventory classified the
600
+ // Defias Tower Key as missing at 1/1 and latched the door into blockedObjects.
601
+ const hasKey = keyEntry != null && countItem(ownedOf(state), keyEntry) >= 1;
602
+ if (keyEntry != null && !hasKey) {
603
+ const keyName = ds.item(keyEntry)?.name ?? `item ${keyEntry}`;
604
+ return `${GO_CLASS_SKIP.LOCKED_NO_KEY}: ${go.name} needs ${keyName}`;
605
+ }
606
+ const openFree = canOpenLock({
607
+ lock,
608
+ keyItemEntry: hasKey ? keyEntry : undefined,
609
+ skillValueFor: () => undefined,
610
+ });
611
+ if (!openFree.ok) {
612
+ return `${GO_CLASS_SKIP.LOCKED_NO_KEY}: ${go.name} (${openFree.reason ?? 'locked'})`;
613
+ }
614
+ }
615
+ // Consumable multi-spawn exhaustion surfaces this way after interact leaves state unchanged.
616
+ return `${GO_CLASS_SKIP.ALL_INSTANCES_CONSUMED}: ${go.name} interact produced no progress`;
617
+ }
618
+ if (step.kind === 'accept') {
619
+ const def = ds.quest(step.questEntry);
620
+ if (def && !prereqMet(ds, def, state.questlog)) {
621
+ return `${GO_CLASS_SKIP.PREREQ_UNMET}: cannot accept "${def.title}"`;
622
+ }
623
+ return `accept of ${def?.title ?? step.questEntry} produced no state change`;
624
+ }
625
+ if (step.kind === 'speak') {
626
+ return `speak to ${step.npcName} for quest ${step.questEntry} produced no talk credit`;
627
+ }
628
+ if (step.kind === 'turnin') {
629
+ const def = ds.quest(step.questEntry);
630
+ if (def?.goEnder && def.goEnder > 0) {
631
+ const zones = zonesListingObject(ds, def.goEnder);
632
+ if (zones.length === 0) {
633
+ return `${GO_CLASS_SKIP.ENDER_NOT_IN_WORLD}: "${def.title}" goEnder ${def.goEnder} not in world`;
634
+ }
635
+ }
636
+ return `turn-in of "${def?.title ?? step.questEntry}" produced no state change`;
637
+ }
638
+ if (step.kind === 'use') {
639
+ return `use of item ${step.itemEntry} produced no state change`;
640
+ }
641
+ if (step.kind === 'equip') {
642
+ return `equip of item ${step.itemEntry} produced no state change`;
643
+ }
644
+ if (step.kind === 'buy') {
645
+ const it = ds.item(step.itemEntry);
646
+ return `buy of ${it?.name ?? step.itemEntry} produced no state change`;
647
+ }
648
+ if (step.kind === 'fight') {
649
+ return `fight vs creature ${step.creatureEntry} produced no state change`;
650
+ }
651
+ if (step.kind === 'travel') {
652
+ return step.reason;
653
+ }
654
+ if (step.kind === 'event') {
655
+ const def = ds.quest(step.questEntry);
656
+ return `event credit of "${def?.title ?? step.questEntry}" produced no state change`;
657
+ }
658
+ return 'step produced no state change';
659
+ }
660
+
661
+ /** Latch a no-op proposal into the matching blocked* set (SESSION 5 self-memory). */
662
+ function latchFailedStep(prog: AutopilotProgress, step: AutopilotStep, reason: string): void {
663
+ prog.lastSkipReason = reason;
664
+ if (step.kind === 'object') mutableNumSet(prog, 'blockedObjects').add(step.objectEntry);
665
+ else if (step.kind === 'accept') mutableNumSet(prog, 'blockedAccepts').add(step.questEntry);
666
+ else if (step.kind === 'speak') mutableNumSet(prog, 'blockedSpeaks').add(step.questEntry);
667
+ else if (step.kind === 'turnin') mutableNumSet(prog, 'blockedTurnins').add(step.questEntry);
668
+ else if (step.kind === 'use') mutableNumSet(prog, 'blockedUses').add(step.itemEntry);
669
+ else if (step.kind === 'equip') mutableNumSet(prog, 'blockedUses').add(step.itemEntry);
670
+ else if (step.kind === 'buy') mutableNumSet(prog, 'blockedUses').add(step.itemEntry);
671
+ else if (step.kind === 'fight') mutableNumSet(prog, 'blockedFights').add(step.creatureEntry);
672
+ else if (step.kind === 'travel') mutableStrSet(prog, 'blockedTravels').add(step.zoneId);
673
+ else if (step.kind === 'event') mutableNumSet(prog, 'blockedQuests').add(step.questEntry);
674
+ }
675
+
676
+ function markProposal(prog: AutopilotProgress, step: AutopilotStep, stateKey: string): void {
677
+ if (step.kind === 'stop' || step.kind === 'wait' || step.kind === 'consume' || step.kind === 'cannibalize') {
678
+ // Recovery / terminal steps are not soft-lock proposals.
679
+ prog.lastProposal = step.kind === 'stop' ? undefined : step;
680
+ prog.lastStateKey = step.kind === 'stop' ? undefined : stateKey;
681
+ return;
682
+ }
683
+ prog.lastProposal = step;
684
+ prog.lastStateKey = stateKey;
685
+ }
686
+
687
+ /** Prefer a GO that is present in the current zone; else travel with the real zone named. */
688
+ function objectOrTravelStep(
689
+ state: GameState,
690
+ ds: DataStore,
691
+ objectEntry: number,
692
+ opts: { forQuest?: boolean; questEntry?: number; reasonHint?: string },
693
+ blockedTravels?: ReadonlySet<string>,
694
+ /** Zone-swap policy filter (Auto Quest setup); absent = every unblocked zone is routeable. */
695
+ zoneAllowed?: (zoneId: string) => boolean,
696
+ ): AutopilotStep {
697
+ if (objectPresentInZone(ds, state.char.zoneId, objectEntry)) {
698
+ return {
699
+ kind: 'object',
700
+ objectEntry,
701
+ forQuest: opts.forQuest,
702
+ questEntry: opts.questEntry,
703
+ };
704
+ }
705
+ const zones = zonesListingObject(ds, objectEntry);
706
+ const route = zones.find((z) =>
707
+ z !== state.char.zoneId && !blockedTravels?.has(z) && (zoneAllowed ? zoneAllowed(z) : true));
708
+ const go = ds.gameObject(objectEntry);
709
+ const name = go?.name ?? `Object#${objectEntry}`;
710
+ if (route) {
711
+ const zname = ds.zone(route)?.name ?? route;
712
+ const reason =
713
+ opts.reasonHint ??
714
+ (opts.questEntry != null
715
+ ? `"${ds.quest(opts.questEntry)?.title ?? opts.questEntry}" needs ${name} in ${zname}`
716
+ : `${name} is in ${zname}`);
717
+ return { kind: 'travel', zoneId: route, reason, questEntry: opts.questEntry };
718
+ }
719
+ if (zones.length === 0) {
720
+ // No baked zone lists this GO — honest skip token (caller may degrade further).
721
+ return {
722
+ kind: 'stop',
723
+ reason: 'noTargets',
724
+ detail: `${GO_CLASS_SKIP.ENDER_NOT_IN_WORLD}: ${name} is not on any baked zone.objects list`,
725
+ };
726
+ }
727
+ // Only routes are blocked — surface that.
728
+ return {
729
+ kind: 'stop',
730
+ reason: 'noTargets',
731
+ detail: `${name} lives in ${zones.map((z) => ds.zone(z)?.name ?? z).join(' / ')} but those routes are blocked this session`,
732
+ };
733
+ }
734
+
735
+ /** True if the Forsaken can devour the last kill's corpse right now (undead + learned + edible corpse). */
736
+ function canCannibalize(state: GameState): boolean {
737
+ const type = state.lastCorpse?.creatureType;
738
+ return combatantCreatureType(state.char) === CREATURE_TYPE.UNDEAD
739
+ && knows(state.spellbook, 20577)
740
+ && (type === CREATURE_TYPE.HUMANOID || type === CREATURE_TYPE.UNDEAD);
741
+ }
742
+
743
+ interface RecoverNeed { health: boolean; mana: boolean; petPower: boolean }
744
+
745
+ /** Recovery need with hysteresis: enter below the threshold, keep going until recoverTo. */
746
+ function recoverNeed(
747
+ view: BotView,
748
+ cfg: AutopilotConfig,
749
+ prog: AutopilotProgress,
750
+ isManaUser: boolean,
751
+ ): RecoverNeed {
752
+ const below = (pct: number, enterPct: number): boolean =>
753
+ pct < enterPct || (prog.recovering && pct < cfg.recoverToPct);
754
+ return {
755
+ health: below(view.healthPct, cfg.healthEnterPct),
756
+ mana: isManaUser && below(view.powerPct, cfg.manaEnterPct),
757
+ // 1 for an absent/dead/powerless pet, so a pet never gates on what rest cannot fix.
758
+ petPower: below(view.petPowerPct, cfg.manaEnterPct),
759
+ };
760
+ }
761
+
762
+ /** Cannibalize, food, drink, then sit; null when nothing here refills (eat-only, empty bags). */
763
+ function recoverStep(
764
+ state: GameState,
765
+ ds: DataStore,
766
+ cfg: AutopilotConfig,
767
+ need: RecoverNeed,
768
+ ): AutopilotStep | null {
769
+ // A Forsaken devours a fresh corpse first - free health, no consumable spent.
770
+ if (need.health && canCannibalize(state)) return { kind: 'cannibalize' };
771
+ if (need.health && cfg.autoEat) {
772
+ const food = bestConsumable(state.char, state.inventory, ds, 'food');
773
+ if (food) return { kind: 'consume', itemEntry: food.itemEntry, effect: 'food' };
774
+ }
775
+ if (need.mana && cfg.autoDrink) {
776
+ const drink = bestConsumable(state.char, state.inventory, ds, 'drink');
777
+ if (drink) return { kind: 'consume', itemEntry: drink.itemEntry, effect: 'drink' };
778
+ }
779
+ return cfg.restMode === 'eat-then-wait' ? { kind: 'wait' } : null;
780
+ }
781
+
782
+ /** Nothing left here: the next zone offering quests this run would accept, nearest band first.
783
+ * Same count the map badge shows (world.ts:766 availableQuestCount, mapPanel.ts:74). */
784
+ function questPickupTravelStep(
785
+ state: GameState,
786
+ ds: DataStore,
787
+ cfg: AutopilotConfig,
788
+ prog: AutopilotProgress,
789
+ ): AutopilotStep | null {
790
+ if (!cfg.questAutoAccept || cfg.questZoneSwap === 'off') return null;
791
+ if (activeQuestCount(state.questlog) >= MAX_QUEST_SLOTS) return null;
792
+ const here = state.char.zoneId;
793
+ const level = state.char.level;
794
+ const inv = ownedOf(state);
795
+ const neighbours = new Set(ds.zone(here)?.neighbors ?? []);
796
+ // Cheap pass over every zone first: canAccept offers only. The full accept gate below walks the
797
+ // dropper index per quest, so it runs on the shortlist in order and stops at the first hit.
798
+ const shortlist: { zoneId: string; offers: ReturnType<typeof offeredQuests>; band: number; near: number }[] = [];
799
+ for (const z of ds.zones()) {
800
+ if (z.id === here || prog.blockedTravels?.has(z.id)) continue;
801
+ if (!zoneSwapGate(ds, z.id, level, cfg).ok) continue;
802
+ const offers = offeredQuests(ds, z.id, state.char, state.questlog)
803
+ .filter((o) => o.canAccept && itemStartGateMet(o.quest, inv)
804
+ && !prog.blockedAccepts?.has(o.quest.entry));
805
+ if (offers.length === 0) continue;
806
+ const [lo, hi] = z.levelRange;
807
+ const band = level < lo ? lo - level : level > hi ? level - hi : 0;
808
+ shortlist.push({ zoneId: z.id, offers, band, near: neighbours.has(z.id) ? 0 : 1 });
809
+ }
810
+ shortlist.sort((a, b) =>
811
+ a.band - b.band || a.near - b.near || b.offers.length - a.offers.length
812
+ || (a.zoneId < b.zoneId ? -1 : 1));
813
+ const squad = squadSize(state);
814
+ const auras = lootAuras(state);
815
+ for (const cand of shortlist) {
816
+ let take = 0;
817
+ let first = '';
818
+ for (const o of cand.offers) {
819
+ const gate = questAcceptGate(ds, o.quest, level, cfg, squad, {
820
+ log: state.questlog,
821
+ inv,
822
+ zoneId: cand.zoneId,
823
+ auras,
824
+ });
825
+ if (!gate.ok) continue;
826
+ if (!take) first = o.quest.title;
827
+ take++;
828
+ }
829
+ if (take === 0) continue;
830
+ const name = ds.zone(cand.zoneId)?.name ?? cand.zoneId;
831
+ const more = take > 1 ? ` and ${take - 1} more quest${take > 2 ? 's' : ''}` : '';
832
+ return {
833
+ kind: 'travel',
834
+ zoneId: cand.zoneId,
835
+ reason: `Travel to ${name} for "${first}"${more}`,
836
+ };
837
+ }
838
+ return null;
839
+ }
840
+
841
+ /**
842
+ * Why a quest run ended once no work remains advanceable here.
843
+ * Uses full questComplete (talk/deliver included) so talk-only quests never mis-report as complete.
844
+ * QI3: never dead-stop on one unsolvable quest while another still has a route —
845
+ * walk the work list, travel for the first routeable quest, only stop when none can move.
846
+ * SESSION 5: if a complete-not-turned-in quest remains, re-emit turnin (never false questComplete).
847
+ * Cite: docs/archive/QUEST-INTERPRETER.md §5 QI3 "honest skip" / "move to the next quest".
848
+ */
849
+ function questRunReason(
850
+ state: GameState,
851
+ ds: DataStore,
852
+ cfg: AutopilotConfig,
853
+ prog: AutopilotProgress,
854
+ onlyEntry?: number,
855
+ ): AutopilotStep {
856
+ // Complete-but-not-turned-in must never die as "Quest complete!" while still in the log.
857
+ const stillReady = turnInCandidates(state, ds, prog);
858
+ if (stillReady.length > 0) {
859
+ const pick = onlyEntry != null && stillReady.includes(onlyEntry) ? onlyEntry
860
+ : (onlyEntry == null ? stillReady[0] : undefined);
861
+ if (pick != null) return turnInStepFor(state, ds, cfg, pick, prog);
862
+ }
863
+
864
+ const zoneId = state.char.zoneId;
865
+ // Bags + keyring. The planner's lock gate reported "locked-no-key" forever for any key that
866
+ // had migrated to the keyring, so auto-quest re-planned the same blocked step every tick.
867
+ const inv = ownedOf(state);
868
+ const withWork = questsWithWork(ds, state.questlog, inv, state.char.level)
869
+ .filter((q) => !questIsSkipped(state, ds, cfg, prog, q.entry))
870
+ .filter((q) => onlyEntry == null || q.entry === onlyEntry);
871
+ if (withWork.length === 0) {
872
+ // Pinned launch quest cannot move — caller falls through to the rest of the log.
873
+ if (onlyEntry != null) {
874
+ return { kind: 'stop', reason: 'noTargets', detail: 'launch quest has no route from here' };
875
+ }
876
+ // Out of work HERE is not out of work: the world still offers quests, so pick the next zone
877
+ // that has some rather than ending the run on an empty local board.
878
+ const restock = questPickupTravelStep(state, ds, cfg, prog);
879
+ if (restock) return restock;
880
+ // Nothing advanceable left. "All done" and "everything left is skipped" are different
881
+ // endings — a skipped log must never report as a cleared one (QI3 honest skip).
882
+ const leftover = questsWithWork(ds, state.questlog, inv, state.char.level);
883
+ if (leftover.length > 0) {
884
+ const names = leftover.slice(0, 3).map((q) => `"${q.title}"`).join(', ');
885
+ const extra = leftover.length > 3 ? ` +${leftover.length - 3} more` : '';
886
+ const full = activeQuestCount(state.questlog) >= MAX_QUEST_SLOTS
887
+ ? ' Quest log is full. Abandon one to take something else.'
888
+ : '';
889
+ return {
890
+ kind: 'stop',
891
+ reason: 'noTargets',
892
+ detail: (prog.lastSkipReason ?? `Skipped ${leftover.length} quest${leftover.length === 1 ? '' : 's'} still in the log (${names}${extra})`)
893
+ + full,
894
+ };
895
+ }
896
+ return { kind: 'stop', reason: 'questComplete' };
897
+ }
898
+ // First pass: any remaining quest with a known acquisition/ender zone → travel there.
899
+ let firstHonest: string | undefined;
900
+ const unfinishable: { entry: number; title: string; reason: string }[] = [];
901
+ for (const q of withWork) {
902
+ const qs = getQuest(state.questlog, q.entry);
903
+ if (!qs) continue;
904
+ const skip = questWorkSkipReason(ds, q, qs, inv, zoneId, state.questlog, state.char.level, lootAuras(state));
905
+ if (!firstHonest) firstHonest = skip.reason;
906
+ if (
907
+ skip.routeZone && skip.routeZone !== zoneId && !prog.blockedTravels?.has(skip.routeZone)
908
+ && travelAllowed(state, ds, cfg, prog, skip.routeZone)
909
+ ) {
910
+ return { kind: 'travel', zoneId: skip.routeZone, reason: skip.reason, questEntry: q.entry };
911
+ }
912
+ if (skip.noPath && !skip.routeZone) {
913
+ unfinishable.push({ entry: q.entry, title: q.title, reason: skip.reason });
914
+ continue;
915
+ }
916
+ }
917
+ // Nothing routes. An objective with NO source in the bake (no dropper, no ender zone, no GO
918
+ // template) never becomes doable by waiting or walking, so park those entries for the run the
919
+ // same way the Skip button does — turn-in stays allowed, accept/advance/travel/speak stop.
920
+ // Without this the planner re-picked the dead quest every tick and ended the run on it.
921
+ if (unfinishable.length > 0) {
922
+ const parked = mutableNumSet(prog, 'blockedQuests');
923
+ for (const u of unfinishable) parked.add(u.entry);
924
+ }
925
+ // No route for any remaining quest. Everything in the log is parked or unreachable, so the run
926
+ // still has somewhere to be: a zone with quests it can take.
927
+ if (onlyEntry == null) {
928
+ const restock = questPickupTravelStep(state, ds, cfg, prog);
929
+ if (restock) return restock;
930
+ }
931
+ // No route and nothing to pick up — honest stop (real reason, not false "Quest complete!").
932
+ const head = unfinishable.length > 0
933
+ ? `${unfinishable[0].reason} - "${unfinishable[0].title}" cannot be finished on this data, `
934
+ + `${unfinishable.length > 1 ? `${unfinishable.length} quests parked` : 'parked'} (abandon it to free the slot)`
935
+ : (firstHonest ?? `"${withWork[0].title}" has remaining work but no baked acquisition path from here`);
936
+ return {
937
+ kind: 'stop',
938
+ reason: 'noTargets',
939
+ detail: head,
940
+ };
941
+ }
942
+
943
+ // Complete quests ready to hand in (status complete OR live questComplete including talk). Lowest
944
+ // questLevel first. SESSION 5: excludes blockedTurnins.
945
+ function turnInCandidates(state: GameState, ds: DataStore, prog?: AutopilotProgress): number[] {
946
+ const ready = new Set<number>();
947
+ const owned = ownedOf(state);
948
+ for (const qs of state.questlog.quests) {
949
+ if (qs.status === 'turnedIn') continue;
950
+ if (prog?.blockedTurnins?.has(qs.entry)) continue;
951
+ if (qs.status === 'complete') {
952
+ ready.add(qs.entry);
953
+ continue;
954
+ }
955
+ const def = ds.quest(qs.entry);
956
+ if (def && questComplete(def, qs, owned)) ready.add(qs.entry);
957
+ }
958
+ return [...ready]
959
+ .map((e) => ({ entry: e, def: ds.quest(e) }))
960
+ .filter((x): x is { entry: number; def: NonNullable<ReturnType<DataStore['quest']>> } => !!x.def)
961
+ .sort((a, b) => a.def.questLevel - b.def.questLevel || a.entry - b.entry)
962
+ .map((x) => x.entry);
963
+ }
964
+
965
+ /** Emit object (GO ender in zone) or turnin for a complete quest — every cycle until turned in. */
966
+ function turnInStepFor(
967
+ state: GameState,
968
+ ds: DataStore,
969
+ cfg: AutopilotConfig,
970
+ entry: number,
971
+ prog: AutopilotProgress,
972
+ ): AutopilotStep {
973
+ const def = ds.quest(entry)!;
974
+ const enders = def.enderZones ?? [];
975
+ const inEnder = enders.length === 0 || enders.includes(state.char.zoneId);
976
+ if (
977
+ inEnder &&
978
+ def.goEnder != null &&
979
+ def.goEnder > 0 &&
980
+ !prog.blockedObjects?.has(def.goEnder)
981
+ ) {
982
+ // Prefer GO ender interact when the object is visible here; else travel or plain turnin.
983
+ if (objectPresentInZone(ds, state.char.zoneId, def.goEnder)) {
984
+ return { kind: 'object', objectEntry: def.goEnder, questEntry: entry };
985
+ }
986
+ const zones = zonesListingObject(ds, def.goEnder);
987
+ const route = zones.find((z) =>
988
+ z !== state.char.zoneId && !prog.blockedTravels?.has(z)
989
+ && travelAllowed(state, ds, cfg, prog, z));
990
+ if (route) {
991
+ const name = ds.gameObject(def.goEnder)?.name ?? `Object#${def.goEnder}`;
992
+ return {
993
+ kind: 'travel',
994
+ zoneId: route,
995
+ reason: `Turn in "${def.title}" at ${name} in ${ds.zone(route)?.name ?? route}`,
996
+ questEntry: entry,
997
+ };
998
+ }
999
+ // goEnder not in world (or all routes blocked) — engine turnIn still works without the GO.
1000
+ if (zones.length === 0) {
1001
+ // Keep advancing via turnin; surface taxonomy on the progress for the log.
1002
+ prog.lastSkipReason = `${GO_CLASS_SKIP.ENDER_NOT_IN_WORLD}: "${def.title}" goEnder not on any zone.objects`;
1003
+ }
1004
+ }
1005
+ return { kind: 'turnin', questEntry: entry };
1006
+ }
1007
+
1008
+ /**
1009
+ * Same-zone deliver/talk: when enderZones ∋ currentZone and talk not yet credited, the engine
1010
+ * credits on zone enter (QI2.5). If still active but objectives (items) are met and we're in
1011
+ * the ender zone, prefer turn-in path after refresh — if still incomplete talk, surface object
1012
+ * ender interact when goEnder is baked, else re-check complete.
1013
+ * Cite: questlog.recordSameZoneTalkOrDeliver; docs/archive/QUEST-INTERPRETER.md §5.
1014
+ */
1015
+ function deliverOrTalkStep(
1016
+ state: GameState,
1017
+ ds: DataStore,
1018
+ cfg: AutopilotConfig,
1019
+ prog: AutopilotProgress,
1020
+ focus?: number,
1021
+ ): AutopilotStep | null {
1022
+ const zoneId = state.char.zoneId;
1023
+ const blockedSpeaks = prog.blockedSpeaks;
1024
+ const owned = ownedOf(state);
1025
+ for (const qs of state.questlog.quests) {
1026
+ if (focus != null && qs.entry !== focus) continue;
1027
+ if (qs.status === 'turnedIn' || qs.status === 'complete') continue;
1028
+ if (questIsSkipped(state, ds, cfg, prog, qs.entry)) continue;
1029
+ const def = ds.quest(qs.entry);
1030
+ if (!def) continue;
1031
+ const enders = def.enderZones ?? [];
1032
+ if (enders.length > 0 && !enders.includes(zoneId)) continue;
1033
+
1034
+ if (isTalkToNpcQuest(def) || isEventTalkQuest(def)) {
1035
+ if (isTalkToNpcQuest(def) && (talkMet(qs, def) || questComplete(def, qs, owned))) {
1036
+ // Ready — turn-in path handles it.
1037
+ continue;
1038
+ }
1039
+ if (isEventTalkQuest(def) && (qs.explored === true || questComplete(def, qs, owned))) {
1040
+ continue;
1041
+ }
1042
+ // In ender zone but talk not credited (e.g. accepted without zone re-enter), or the
1043
+ // quest has no baked enderZones at all (outer filter passes those through).
1044
+ // GO ender interact, else SPEAK for a quest with no GO — the driver calls game.talkToNpc
1045
+ // (name-based credit, same as the manual button). This closes the CQ6 soft-lock:
1046
+ // Training the Beast / priest racials / Tome quests stalled 'active' forever on AUTO.
1047
+ // Empty-SF2 gossip-event (KI-778/KI-788): same Speak step sets m_explored.
1048
+ const talkGo = goEnderEntryOf(def);
1049
+ if (talkGo != null && !prog.blockedObjects?.has(talkGo)) {
1050
+ const step = objectOrTravelStep(
1051
+ state,
1052
+ ds,
1053
+ talkGo,
1054
+ {
1055
+ forQuest: true,
1056
+ questEntry: qs.entry,
1057
+ reasonHint: `Talk for "${def.title}" at ${ds.gameObject(talkGo)?.name ?? talkGo}`,
1058
+ },
1059
+ prog.blockedTravels,
1060
+ (z) => travelAllowed(state, ds, cfg, prog, z),
1061
+ );
1062
+ if (step.kind === 'stop') {
1063
+ // Fall through to NPC speak when GO is unusable.
1064
+ } else {
1065
+ return step;
1066
+ }
1067
+ }
1068
+ // recordTalkToNpc refuses every hasGoEnder quest (questlog.ts:1295), so a name-based speak
1069
+ // at a chest or a brand can never credit — leave it to the skip reason.
1070
+ if (hasGoEnder(def)) continue;
1071
+ const npcName = isEventTalkQuest(def)
1072
+ ? eventTalkAutopilotNpcName(def)
1073
+ : ((def.enderName ?? '').trim() || (def.starterName ?? '').trim());
1074
+ if (npcName && !blockedSpeaks?.has(qs.entry)) return { kind: 'speak', questEntry: qs.entry, npcName };
1075
+ continue;
1076
+ }
1077
+ if (isDeliverQuest(def) && itemsMet(def, owned) && !talkMet(qs, def)) {
1078
+ const deliverGo = goEnderEntryOf(def);
1079
+ if (deliverGo != null && !prog.blockedObjects?.has(deliverGo)) {
1080
+ const step = objectOrTravelStep(
1081
+ state,
1082
+ ds,
1083
+ deliverGo,
1084
+ {
1085
+ forQuest: true,
1086
+ questEntry: qs.entry,
1087
+ reasonHint: `Deliver "${def.title}" at ${ds.gameObject(deliverGo)?.name ?? deliverGo}`,
1088
+ },
1089
+ prog.blockedTravels,
1090
+ (z) => travelAllowed(state, ds, cfg, prog, z),
1091
+ );
1092
+ if (step.kind !== 'stop') return step;
1093
+ }
1094
+ }
1095
+ }
1096
+ return null;
1097
+ }
1098
+
1099
+ /** Mid-event gossip NPC for empty-SF2 listen/speak quests (KI-778 Great Bear Spirit). */
1100
+ function eventTalkAutopilotNpcName(def: {
1101
+ objectivesText?: string;
1102
+ enderName?: string;
1103
+ starterName?: string;
1104
+ }): string {
1105
+ const text = def.objectivesText ?? '';
1106
+ const listen = text.match(/\blisten as ([^.]+?) tells/i);
1107
+ if (listen?.[1]?.trim()) return listen[1].trim();
1108
+ const seek = text.match(/\bseek out the ([^.]+?) in /i);
1109
+ if (seek?.[1]?.trim()) return seek[1].trim();
1110
+ const speakAfter = text.match(/\bafter speaking with ([^.]+?)\.?$/i);
1111
+ if (speakAfter?.[1]?.trim()) return speakAfter[1].trim();
1112
+ return (def.enderName ?? '').trim() || (def.starterName ?? '').trim();
1113
+ }
1114
+
1115
+ /** Core QI3 / farm decision (no self-memory). Called once, or again after latching a no-op. */
1116
+ function planStepCore(
1117
+ state: GameState,
1118
+ ds: DataStore,
1119
+ mode: AutopilotMode,
1120
+ cfg: AutopilotConfig,
1121
+ prog: AutopilotProgress,
1122
+ ): AutopilotStep {
1123
+ // 1) Hard stops first: the session clock, the repeated-defeat guard, and the level cap
1124
+ // (checked between fights, so the level that crosses the cap still lands its full kill).
1125
+ // sessionMs <= 0 → no time cap (UI "no limit"). Cite: AutopilotConfig.sessionMs.
1126
+ if (cfg.sessionMs > 0 && prog.elapsedMs >= cfg.sessionMs) return { kind: 'stop', reason: 'timeUp' };
1127
+ // A dead or ghost character has no legal step: every engine verb refuses YOU_ARE_DEAD, and
1128
+ // `gather` is not latchable, so the driver re-proposed it every 200 ms forever.
1129
+ if (!isPlayerAlive(state.char.health, state.deathFlow)) {
1130
+ return {
1131
+ kind: 'stop',
1132
+ reason: 'defeated',
1133
+ detail: 'You are dead - reclaim your corpse or use the spirit healer to continue.',
1134
+ };
1135
+ }
1136
+ if (prog.consecutiveDefeats >= cfg.maxConsecutiveDefeats) return { kind: 'stop', reason: 'defeated' };
1137
+ if (cfg.stopAtLevel > 0 && state.char.level >= cfg.stopAtLevel) return { kind: 'stop', reason: 'levelCap' };
1138
+
1139
+ // The corpse at your feet before anything else — nothing else in the run can reach it, and the
1140
+ // next fight clears the slot. Only when the group holds the skill AND the knife, so the driver
1141
+ // never re-proposes a step skin() refuses; a proposal that changed nothing is not repeated.
1142
+ if (cfg.autoGather && state.corpse != null) {
1143
+ const corpseNode = ds.gatherNode(state.corpse.node);
1144
+ const repeat = prog.lastProposal?.kind === 'skin' && prog.lastStateKey === planStateKey(state);
1145
+ if (corpseNode && !repeat && partyCanHarvestNode(state, ds, corpseNode)) return { kind: 'skin' };
1146
+ }
1147
+
1148
+ // Ready mine/herb first — before accept, turn-in, rest, or a pull. Depleted nodes are
1149
+ // skipped; a later tick takes them again after respawn. Cite: view.ts nextAutoGatherNode.
1150
+ if (cfg.autoGather) {
1151
+ const skipId =
1152
+ prog.lastProposal?.kind === 'gather' && prog.lastStateKey === planStateKey(state)
1153
+ ? prog.lastProposal.nodeId
1154
+ : undefined;
1155
+ const nodeId = nextAutoGatherNode(state, ds, prog.nowMs ?? 0, skipId);
1156
+ if (nodeId != null) return { kind: 'gather', nodeId };
1157
+ }
1158
+
1159
+ const view = buildView(state, ds);
1160
+ const isManaUser = powerType(state.char) === 'mana';
1161
+
1162
+ // 2) Recover BEFORE the quest errands, not only before a pull: every quest step below returns
1163
+ // early, so a run at 8% health walked its turn-ins and accepts without ever eating (measured).
1164
+ const need = recoverNeed(view, cfg, prog, isManaUser);
1165
+ if (need.health || need.mana) {
1166
+ const recover = recoverStep(state, ds, cfg, need);
1167
+ // eat-only with nothing to eat: errands still run, and the pull gate below owns the stop.
1168
+ if (recover) return recover;
1169
+ }
1170
+
1171
+ // For a quest run, resolve which quest to work RIGHT NOW: an on-level active quest that
1172
+ // still has a safe target (or dropper for pickup loot) huntable in this zone. Chaining
1173
+ // happens automatically. Empty list means accept more / travel / honest stop.
1174
+ let questFight: { creatureEntry: number; questEntry: number } | null = null;
1175
+ if (mode.kind === 'quest') {
1176
+ parkUnfinishableQuests(state, ds, cfg, prog, mode.pin ? mode.entry : undefined);
1177
+ // ---- QI3 policy: turn-in → deliver/talk → accept → advance → travel/honest-skip ----
1178
+ // Cite: docs/archive/QUEST-INTERPRETER.md §5 QI3.
1179
+ const focus = launchFocus(state, ds, mode, cfg, prog, view.acceptable);
1180
+ if (mode.pin && mode.entry != null) {
1181
+ const pinDef = ds.quest(mode.entry);
1182
+ const pinQs = getQuest(state.questlog, mode.entry);
1183
+ if (pinQs?.status === 'turnedIn') {
1184
+ return {
1185
+ kind: 'stop',
1186
+ reason: 'questComplete',
1187
+ detail: `Finished "${pinDef?.title ?? mode.entry}"`,
1188
+ };
1189
+ }
1190
+ if (mode.handInByHand && turnInCandidates(state, ds, prog).includes(mode.entry)) {
1191
+ return {
1192
+ kind: 'stop',
1193
+ reason: 'questComplete',
1194
+ detail: `Ready to hand in "${pinDef?.title ?? mode.entry}"`,
1195
+ };
1196
+ }
1197
+ if (questIsDeathSkipped(prog, mode.entry)) {
1198
+ return {
1199
+ kind: 'stop',
1200
+ reason: 'noTargets',
1201
+ detail: prog.lastSkipReason ?? `Skipped "${pinDef?.title ?? mode.entry}"`,
1202
+ };
1203
+ }
1204
+ }
1205
+
1206
+ // TURN IN when questComplete. Engine turnInQuest does NOT require the ender zone
1207
+ // (items-met delivers / timed "bring this in 60 min" are already complete in bags).
1208
+ // Prefer immediate turn-in so Auto Quest closes the loop without a multi-leg trip
1209
+ // to the hub that players were doing by hand. GO enders use the object step when the
1210
+ // GO is present in-zone; otherwise turnIn awards XP/rewards the same way.
1211
+ // SESSION 5: re-emit every cycle until turned in — never stall on a dead "Quest complete!".
1212
+ const turnReady = turnInCandidates(state, ds, prog);
1213
+ if (turnReady.length > 0) {
1214
+ const pick = focus != null
1215
+ ? (turnReady.includes(focus) ? focus : undefined)
1216
+ : turnReady[0];
1217
+ if (pick != null) return turnInStepFor(state, ds, cfg, pick, prog);
1218
+ }
1219
+
1220
+ // DELIVER / talk when enderZones ∋ currentZone (GO ender interact / CQ6 speak path).
1221
+ const deliver = deliverOrTalkStep(state, ds, cfg, prog, focus);
1222
+ if (deliver) return deliver;
1223
+
1224
+ if (focus != null) {
1225
+ const pinned = pinnedQuestStep(state, ds, cfg, prog, focus);
1226
+ if (pinned?.kind === 'fight') {
1227
+ questFight = { creatureEntry: pinned.creatureEntry, questEntry: focus };
1228
+ } else if (pinned) {
1229
+ return pinned;
1230
+ } else {
1231
+ return {
1232
+ kind: 'stop',
1233
+ reason: 'noTargets',
1234
+ detail: prog.lastSkipReason
1235
+ ?? `Cannot advance "${ds.quest(focus)?.title ?? focus}"`,
1236
+ };
1237
+ }
1238
+ } else if (!questFight) {
1239
+ // ACCEPT available in-zone quests (one per step; frees the accept→advance loop).
1240
+ // Entries whose accept the engine already refused this session are skipped, not retried.
1241
+ // Setup gates (level band, elite policy) are re-read every pass rather than latched:
1242
+ // a level-up or a fresh hire legitimately re-opens a quest the band closed a minute ago.
1243
+ // A launched quest pins accept: take that one (if still offered) and leave the rest.
1244
+ const hasSlot = activeQuestCount(state.questlog) < MAX_QUEST_SLOTS;
1245
+ if (hasSlot && cfg.questAutoAccept) {
1246
+ const aEntry = view.acceptable.find((e) => {
1247
+ if (focus != null && e !== focus) return false;
1248
+ if (prog.blockedAccepts?.has(e)) return false;
1249
+ const def = ds.quest(e);
1250
+ if (!def) return true;
1251
+ // Do Quest is an explicit pick — take that offer even if the setup band would skip it.
1252
+ if (focus != null && e === focus) return true;
1253
+ const gate = questAcceptGate(ds, def, state.char.level, cfg, squadSize(state), {
1254
+ log: state.questlog,
1255
+ inv: ownedOf(state),
1256
+ zoneId: state.char.zoneId,
1257
+ auras: lootAuras(state),
1258
+ });
1259
+ if (gate.code === 'elite-needs-squad') prog.eliteNeedsSquad = true;
1260
+ return gate.ok;
1261
+ });
1262
+ if (aEntry != null) {
1263
+ const aDef = ds.quest(aEntry);
1264
+ if (aDef && !prereqMet(ds, aDef, state.questlog)) {
1265
+ // Should be rare (view gates prereq) — latch and keep scanning.
1266
+ mutableNumSet(prog, 'blockedAccepts').add(aEntry);
1267
+ prog.lastSkipReason = `${GO_CLASS_SKIP.PREREQ_UNMET}: "${aDef.title}"`;
1268
+ } else if (aDef?.goStarter != null && aDef.goStarter > 0 && !prog.blockedObjects?.has(aDef.goStarter)) {
1269
+ // Accept-at-GO: only interact when zone.objects lists the starter; else plain accept
1270
+ // (offered here) or travel to a zone that carries the GO.
1271
+ if (objectPresentInZone(ds, state.char.zoneId, aDef.goStarter)) {
1272
+ return { kind: 'object', objectEntry: aDef.goStarter, questEntry: aEntry };
1273
+ }
1274
+ const zones = zonesListingObject(ds, aDef.goStarter);
1275
+ const route = zones.find((z) =>
1276
+ z !== state.char.zoneId && !prog.blockedTravels?.has(z)
1277
+ && travelAllowed(state, ds, cfg, prog, z));
1278
+ if (route) {
1279
+ const name = ds.gameObject(aDef.goStarter)?.name ?? `Object#${aDef.goStarter}`;
1280
+ return {
1281
+ kind: 'travel',
1282
+ zoneId: route,
1283
+ reason: `Accept "${aDef.title}" at ${name} in ${ds.zone(route)?.name ?? route}`,
1284
+ questEntry: aEntry,
1285
+ };
1286
+ }
1287
+ // GO starter not visible in any baked zone — NPC-style accept still works when offered.
1288
+ return { kind: 'accept', questEntry: aEntry };
1289
+ } else {
1290
+ return { kind: 'accept', questEntry: aEntry };
1291
+ }
1292
+ }
1293
+ }
1294
+
1295
+ // ADVANCE — sourceItem use, GO interact, kill/dropper fights.
1296
+ const ownedNow = ownedOf(state);
1297
+ for (const qs of state.questlog.quests) {
1298
+ if (focus != null && qs.entry !== focus) continue;
1299
+ if (qs.status === 'turnedIn' || qs.status === 'complete') continue;
1300
+ if (questIsSkipped(state, ds, cfg, prog, qs.entry)) continue;
1301
+ const def = ds.quest(qs.entry);
1302
+ if (!def) continue;
1303
+ if (questComplete(def, qs, ownedNow)) continue;
1304
+ const corpseItem = pendingCorpseUseItem(ds, def, state.inventory, state.lastCorpse);
1305
+ if (corpseItem && !prog.blockedUses?.has(corpseItem)) {
1306
+ return { kind: 'use', itemEntry: corpseItem, questEntry: qs.entry };
1307
+ }
1308
+ // Kill credit from a ScriptDev spell hit on a live NPC (Lazy Peons 5441). The target is
1309
+ // CIVILIAN, so it never reaches the fight lane. Cite: questScriptItemUse.ts.
1310
+ const scriptUse = pendingScriptItemUse(ds, def, qs, ownedNow, state.char.zoneId);
1311
+ if (scriptUse && !prog.blockedUses?.has(scriptUse.item)) {
1312
+ return {
1313
+ kind: 'use',
1314
+ itemEntry: scriptUse.item,
1315
+ targetCreatureEntry: scriptUse.targetCreatureEntry,
1316
+ questEntry: qs.entry,
1317
+ };
1318
+ }
1319
+ for (const st of (def.sourceItemTargets || [])) {
1320
+ if (prog.blockedUses?.has(st.item)) continue;
1321
+ if (itemIsQuestCorpseUse(ds, st.item)) continue;
1322
+ if (countItem(ownedNow, st.item) >= st.count) {
1323
+ return { kind: 'use', itemEntry: st.item, questEntry: qs.entry };
1324
+ }
1325
+ }
1326
+ // A container in this zone holds an item objective (grape buckets, pumpkins). view.objects
1327
+ // carries objectTargets only, so the world run never saw these; the Do Quest pin already did.
1328
+ // A lock between us and the objective needs a key a mob here drops: farm it BEFORE walking to
1329
+ // the object, or the open is refused and latches. Cite: quests.ts:1035 names that dropper.
1330
+ const keyMob = questLockKeyFightTarget(state, ds, def, qs, prog.blockedFights);
1331
+ if (keyMob != null) {
1332
+ return { kind: 'fight', creatureEntry: keyMob, forQuest: true, questEntry: qs.entry };
1333
+ }
1334
+ const keyGo = questLockKeyObjectStep(state, ds, def, qs, prog.blockedObjects);
1335
+ if (keyGo != null) {
1336
+ return { kind: 'object', objectEntry: keyGo, forQuest: true, questEntry: qs.entry };
1337
+ }
1338
+ // A container in this zone holds an item objective (grape buckets, pumpkins). view.objects
1339
+ // carries objectTargets only, so the world run never saw these; the Do Quest pin already did.
1340
+ const goLoot = itemGoStep(state, ds, def);
1341
+ if (goLoot && !prog.blockedObjects?.has(goLoot.objectEntry)) {
1342
+ return { kind: 'object', objectEntry: goLoot.objectEntry, forQuest: true, questEntry: qs.entry };
1343
+ }
1344
+ }
1345
+ // Object interact — skip entries the driver already proved unusable this session
1346
+ // (locked without key, missing template, permanent despawn). Prefer a usable one that
1347
+ // is actually present in the current zone (SESSION 1 zone.objects); otherwise travel.
1348
+ // When every pending object is blocked/missing, fall through to questRunReason
1349
+ // (honest skip / travel) instead of spinning forever on interactObject failure.
1350
+ const pendingObjs = view.objects.filter(
1351
+ (o) => o.forQuest && !prog.blockedObjects?.has(o.entry) && ds.gameObject(o.entry)
1352
+ && (focus == null || questWantsObject(ds, focus, o.entry)),
1353
+ );
1354
+ // Prefer local GO first.
1355
+ const localObj = pendingObjs.find((o) => objectPresentInZone(ds, state.char.zoneId, o.entry));
1356
+ if (localObj) {
1357
+ const go = ds.gameObject(localObj.entry)!;
1358
+ if (isUnsupportedGoType(go.type)) {
1359
+ mutableNumSet(prog, 'blockedObjects').add(localObj.entry);
1360
+ prog.lastSkipReason = `${GO_CLASS_SKIP.UNSUPPORTED_TYPE}: ${go.name}`;
1361
+ } else {
1362
+ // Attach questEntry when a single active quest owns this object target.
1363
+ let questEntry: number | undefined;
1364
+ let ownedBySkippedOnly = false;
1365
+ for (const qs of state.questlog.quests) {
1366
+ if (qs.status === 'turnedIn' || qs.status === 'complete') continue;
1367
+ const def = ds.quest(qs.entry);
1368
+ if (!def) continue;
1369
+ if (!(def.objectTargets ?? []).some((ot) => ot.objectEntry === localObj.entry)) continue;
1370
+ if (questIsSkipped(state, ds, cfg, prog, qs.entry)) {
1371
+ ownedBySkippedOnly = true;
1372
+ continue;
1373
+ }
1374
+ questEntry = qs.entry;
1375
+ ownedBySkippedOnly = false;
1376
+ break;
1377
+ }
1378
+ if (questEntry == null && ownedBySkippedOnly) {
1379
+ // Only skipped quests (defeat cap / setup policy) want this GO — do not spin on it.
1380
+ mutableNumSet(prog, 'blockedObjects').add(localObj.entry);
1381
+ } else {
1382
+ return { kind: 'object', objectEntry: localObj.entry, forQuest: true, questEntry };
1383
+ }
1384
+ }
1385
+ } else if (pendingObjs.length > 0) {
1386
+ // Remote objective GO — travel via acquisitionZones / zone.objects listing.
1387
+ for (const o of pendingObjs) {
1388
+ const step = objectOrTravelStep(
1389
+ state,
1390
+ ds,
1391
+ o.entry,
1392
+ { forQuest: true },
1393
+ prog.blockedTravels,
1394
+ (z) => travelAllowed(state, ds, cfg, prog, z),
1395
+ );
1396
+ if (step.kind === 'travel') {
1397
+ // Prefer quest-named reason when we can bind the object to a quest.
1398
+ for (const qs of state.questlog.quests) {
1399
+ if (qs.status === 'turnedIn' || qs.status === 'complete') continue;
1400
+ const def = ds.quest(qs.entry);
1401
+ if (!def) continue;
1402
+ if ((def.objectTargets ?? []).some((ot) => ot.objectEntry === o.entry)) {
1403
+ const zones = acquisitionZonesForQuest(
1404
+ ds, def, qs, ownedOf(state), state.questlog, state.char.level,
1405
+ lootAuras(state),
1406
+ );
1407
+ const route = zones.find((z) =>
1408
+ z !== state.char.zoneId && !prog.blockedTravels?.has(z)
1409
+ && travelAllowed(state, ds, cfg, prog, z));
1410
+ if (route) {
1411
+ const skip = questWorkSkipReason(
1412
+ ds, def, qs, ownedOf(state), state.char.zoneId, state.questlog, state.char.level,
1413
+ lootAuras(state),
1414
+ );
1415
+ return {
1416
+ kind: 'travel',
1417
+ zoneId: route,
1418
+ reason: skip.reason,
1419
+ questEntry: qs.entry,
1420
+ };
1421
+ }
1422
+ return { ...step, questEntry: qs.entry };
1423
+ }
1424
+ }
1425
+ return step;
1426
+ }
1427
+ if (step.kind === 'object') return step;
1428
+ // stop from objectOrTravelStep — remember and try next object
1429
+ if (step.kind === 'stop' && step.detail) prog.lastSkipReason = step.detail;
1430
+ mutableNumSet(prog, 'blockedObjects').add(o.entry);
1431
+ }
1432
+ }
1433
+
1434
+ // Buy quest materials from a merchant in this zone before farming droppers.
1435
+ for (const qs of state.questlog.quests) {
1436
+ if (focus != null && qs.entry !== focus) continue;
1437
+ if (qs.status === 'turnedIn' || qs.status === 'complete') continue;
1438
+ if (questIsSkipped(state, ds, cfg, prog, qs.entry)) continue;
1439
+ const buy = questVendorBuyStep(state, ds, qs.entry);
1440
+ if (buy && !prog.blockedUses?.has(buy.itemEntry)) {
1441
+ return {
1442
+ kind: 'buy',
1443
+ vendorEntry: buy.vendorEntry,
1444
+ itemEntry: buy.itemEntry,
1445
+ count: buy.count,
1446
+ questEntry: qs.entry,
1447
+ };
1448
+ }
1449
+ }
1450
+
1451
+ const huntable = huntableQuests(
1452
+ ds,
1453
+ state.questlog,
1454
+ view.hunt.map((h) => h.entry),
1455
+ ownedOf(state),
1456
+ state.char.level,
1457
+ lootAuras(state),
1458
+ ).filter((q) => !questIsSkipped(state, ds, cfg, prog, q.entry));
1459
+ let local = focus != null ? huntable.find((q) => q.entry === focus) : huntable[0];
1460
+ if (!local && focus != null) {
1461
+ // Launched quest has no local fight — travel for it before grinding someone else.
1462
+ const focused = questRunReason(state, ds, cfg, prog, focus);
1463
+ if (focused.kind !== 'stop') return focused;
1464
+ local = huntable[0];
1465
+ }
1466
+ if (local) {
1467
+ const qs = getQuest(state.questlog, local.entry)!;
1468
+ const killNeed = new Set<number>();
1469
+ // Best loot chance among still-needed itemTargets for this entry (0 = not an item dropper).
1470
+ const itemChance = new Map<number, number>();
1471
+ // Direct kill targets still needed.
1472
+ for (const kt of local.killTargets) {
1473
+ if ((qs.kills[String(kt.creatureEntry)] ?? 0) < kt.count) {
1474
+ if (corpseUseSummonedEntries(ds, local).includes(kt.creatureEntry)) {
1475
+ for (const e of corpseUseFarmEntries(ds, local)) killNeed.add(e);
1476
+ } else {
1477
+ killNeed.add(kt.creatureEntry);
1478
+ }
1479
+ }
1480
+ }
1481
+ if (corpseUseWorkRemaining(ds, local, qs, state.inventory)) {
1482
+ for (const e of corpseUseFarmEntries(ds, local)) killNeed.add(e);
1483
+ }
1484
+ // Pickup-loot / itemTargets: real drop chance + our-level first (QI3). Lowest-level-with-any-row
1485
+ // farmed Small Crag Boar (0.02%); highest-drop-first pulled above-level Large Crag Boar.
1486
+ const aurasNow = lootAuras(state);
1487
+ for (const it of local.itemTargets) {
1488
+ if (countItem(ownedNow, it.item) >= it.count) continue;
1489
+ const floor = dropperNoiseFloorFor(ds, it.item, state.questlog, state.char.level, aurasNow);
1490
+ for (const h of view.hunt) {
1491
+ const ch = lootChanceForItem(ds, h.entry, it.item, state.questlog, state.char.level, aurasNow);
1492
+ if (ch <= 0 || ch < floor) continue;
1493
+ const prev = itemChance.get(h.entry) ?? 0;
1494
+ if (ch > prev) itemChance.set(h.entry, ch);
1495
+ }
1496
+ }
1497
+ const candidates = view.hunt.filter((h) => {
1498
+ if (prog.blockedFights?.has(h.entry)) return false;
1499
+ if (killNeed.has(h.entry)) return true;
1500
+ const ch = itemChance.get(h.entry) ?? 0;
1501
+ return ch > 0;
1502
+ });
1503
+ if (candidates.length > 0) {
1504
+ // Kill objectives first, then a normal dropper over an elite, then closer, then highest drop.
1505
+ const target = candidates.reduce((a, b) => {
1506
+ const aKill = killNeed.has(a.entry);
1507
+ const bKill = killNeed.has(b.entry);
1508
+ if (aKill !== bKill) return aKill ? a : b;
1509
+ const cmp = compareQuestDroppers(
1510
+ { level: a.level, chance: itemChance.get(a.entry) ?? 0, rank: a.rank ?? 0 },
1511
+ { level: b.level, chance: itemChance.get(b.entry) ?? 0, rank: b.rank ?? 0 },
1512
+ state.char.level,
1513
+ );
1514
+ return cmp <= 0 ? a : b;
1515
+ });
1516
+ questFight = { creatureEntry: target.entry, questEntry: local.entry };
1517
+ }
1518
+ }
1519
+
1520
+ if (!questFight) {
1521
+ const invNow = ownedOf(state);
1522
+ const order = questsWithWork(ds, state.questlog, invNow, state.char.level)
1523
+ .filter((q) => !questIsSkipped(state, ds, cfg, prog, q.entry));
1524
+ const ranked = focus != null
1525
+ ? [...order.filter((q) => q.entry === focus), ...order.filter((q) => q.entry !== focus)]
1526
+ : order;
1527
+ for (const q of ranked) {
1528
+ const step = campaignMoveStep(state, ds, cfg, prog, q.entry);
1529
+ if (!step) continue;
1530
+ if (step.kind === 'fight') {
1531
+ questFight = { creatureEntry: step.creatureEntry, questEntry: q.entry };
1532
+ break;
1533
+ }
1534
+ return step;
1535
+ }
1536
+ }
1537
+
1538
+ if (!questFight) {
1539
+ // No local fight, nothing to accept → travel or honest skip (never dead-stop).
1540
+ if (focus != null) {
1541
+ const focused = questRunReason(state, ds, cfg, prog, focus);
1542
+ if (focused.kind !== 'stop') return focused;
1543
+ }
1544
+ return questRunReason(state, ds, cfg, prog);
1545
+ }
1546
+ }
1547
+ }
1548
+
1549
+ // 2b) The pull also weighs the pet: it is half the damage for a warlock or hunter, and
1550
+ // regenTick (its only refill) runs solely in `wait` - measured 377 -> 327 mana over 8 pulls.
1551
+ if (need.health || need.mana || need.petPower) {
1552
+ const recover = recoverStep(state, ds, cfg, need);
1553
+ if (recover) return recover;
1554
+ // A drained pet is NOT an out-of-consumables condition - no food refills a pet, only time,
1555
+ // so wait it out rather than stranding the run on a reason the player cannot act on.
1556
+ if (need.petPower && !need.health && !need.mana) return { kind: 'wait' };
1557
+ return { kind: 'stop', reason: 'outOfConsumables' };
1558
+ }
1559
+
1560
+ // 3) Safe to fight. In quest mode the target was resolved above (the on-level chainable quest);
1561
+ // forQuest is always true here, and questEntry tells the driver which quest it's crediting.
1562
+ if (mode.kind === 'quest') {
1563
+ return { kind: 'fight', creatureEntry: questFight!.creatureEntry, forQuest: true, questEntry: questFight!.questEntry };
1564
+ }
1565
+
1566
+ // A player-picked mob is farmed as chosen (only gated by "is it in this zone"), so their
1567
+ // choice is respected even if it's a tougher con than the auto-picker would select.
1568
+ // Driver-blocked pins stop with the refusal reason rather than spinning forever.
1569
+ if (mode.creatureEntry != null) {
1570
+ if (prog.blockedFights?.has(mode.creatureEntry)) {
1571
+ return {
1572
+ kind: 'stop',
1573
+ reason: 'noTargets',
1574
+ detail: prog.lastSkipReason ?? `cannot fight creature ${mode.creatureEntry}`,
1575
+ };
1576
+ }
1577
+ const here = huntableCreatures(ds, state.char.zoneId, state.questlog, state.char.race).some((c) => c.entry === mode.creatureEntry);
1578
+ if (!here) return { kind: 'stop', reason: 'noTargets' };
1579
+ const forQuest = view.hunt.some((h) => h.entry === mode.creatureEntry && h.forQuest);
1580
+ return { kind: 'fight', creatureEntry: mode.creatureEntry, forQuest };
1581
+ }
1582
+
1583
+ // Skip driver-blocked entries and pick the next best con — never stop the whole zone farm
1584
+ // because one template refused beginFight (was: best target blocked → stop immediately).
1585
+ const huntOpen = view.hunt.filter((h) => !prog.blockedFights?.has(h.entry));
1586
+ const target = bestHuntTarget({ ...view, hunt: huntOpen });
1587
+ if (!target) {
1588
+ return {
1589
+ kind: 'stop',
1590
+ reason: 'noTargets',
1591
+ detail: prog.lastSkipReason,
1592
+ };
1593
+ }
1594
+ return { kind: 'fight', creatureEntry: target.entry, forQuest: target.forQuest };
1595
+ }
1596
+
1597
+ /** Decide the next thing the autopilot should do. */
1598
+ export function planStep(
1599
+ state: GameState,
1600
+ ds: DataStore,
1601
+ mode: AutopilotMode,
1602
+ cfg: AutopilotConfig,
1603
+ prog: AutopilotProgress,
1604
+ ): AutopilotStep {
1605
+ const stateKey = planStateKey(state);
1606
+
1607
+ // SESSION 5 self-memory: if the last proposal produced no state change, latch it into the
1608
+ // matching blocked* set with an honest reason — even when the driver forgot to populate
1609
+ // blockedObjects / blockedAccepts / blockedSpeaks. Pure: memory lives only on `prog`.
1610
+ if (
1611
+ prog.lastProposal &&
1612
+ prog.lastStateKey === stateKey &&
1613
+ isLatchableStep(prog.lastProposal)
1614
+ ) {
1615
+ const reason = classifyFailedStep(prog.lastProposal, state, ds);
1616
+ latchFailedStep(prog, prog.lastProposal, reason);
1617
+ }
1618
+
1619
+ let step = planStepCore(state, ds, mode, cfg, prog);
1620
+
1621
+ // If core still re-emits the identical latchable step after a no-op, force honest skip
1622
+ // (covers step kinds whose latch did not change the decision path).
1623
+ if (
1624
+ prog.lastProposal &&
1625
+ prog.lastStateKey === stateKey &&
1626
+ isLatchableStep(step) &&
1627
+ stepsEqual(step, prog.lastProposal)
1628
+ ) {
1629
+ const reason = prog.lastSkipReason ?? classifyFailedStep(step, state, ds);
1630
+ latchFailedStep(prog, step, reason);
1631
+ step = planStepCore(state, ds, mode, cfg, prog);
1632
+ if (isLatchableStep(step) && stepsEqual(step, prog.lastProposal!)) {
1633
+ step = {
1634
+ kind: 'stop',
1635
+ reason: 'noTargets',
1636
+ detail: reason,
1637
+ };
1638
+ }
1639
+ }
1640
+
1641
+ markProposal(prog, step, stateKey);
1642
+ return step;
1643
+ }