azeroth-shell 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -2
- package/bin/azsh.mjs +19 -0
- package/docs/aifid/eventai-texts.json +4414 -0
- package/docs/dungeons/atlas/atlas-rows-dungeons.json +8989 -0
- package/docs/dungeons/survey/pack-id-migration.json +2418 -0
- package/package.json +26 -4
- package/src/autopilot/bossSkip.ts +26 -0
- package/src/autopilot/combatRotation.ts +824 -0
- package/src/autopilot/config.ts +533 -0
- package/src/autopilot/consumables.ts +58 -0
- package/src/autopilot/encounterStand.ts +121 -0
- package/src/autopilot/objectGatePlan.ts +53 -0
- package/src/autopilot/pathEngage.ts +229 -0
- package/src/autopilot/plan.ts +1643 -0
- package/src/autopilot/questPolicy.ts +274 -0
- package/src/autopilot/routeStep.ts +69 -0
- package/src/autopilot/sessionReport.ts +88 -0
- package/src/autopilot/topOffPower.ts +26 -0
- package/src/autopilot/view.ts +476 -0
- package/src/cli/bootCheck.mjs +34 -0
- package/src/cli/bootData.mjs +57 -0
- package/src/cli/combatLogFilter.ts +153 -0
- package/src/cli/combatLogFormat.ts +613 -0
- package/src/cli/fileRepository.mjs +105 -0
- package/src/cli/gzLoader.mjs +31 -0
- package/src/cli/liveGauntletEscort.ts +978 -0
- package/src/cli/liveStratUndead.ts +305 -0
- package/src/cli/macroHostCombat.ts +545 -0
- package/src/cli/macroHostWorld.ts +330 -0
- package/src/cli/prefs.mjs +31 -0
- package/src/cli/register.mjs +4 -0
- package/src/cli/repl.mjs +693 -0
- package/src/cli/rotationText.mjs +226 -0
- package/src/cli/squad.mjs +105 -0
- package/src/cli/term.mjs +126 -0
- package/src/cli/text.mjs +122 -0
- package/src/cli/tui.mjs +626 -0
- package/src/cli/tuiArena.mjs +109 -0
- package/src/cli/tuiAutopilotSettings.mjs +93 -0
- package/src/cli/tuiBags.mjs +180 -0
- package/src/cli/tuiBars.mjs +133 -0
- package/src/cli/tuiBg.mjs +243 -0
- package/src/cli/tuiBulk.mjs +162 -0
- package/src/cli/tuiCheck.mjs +1882 -0
- package/src/cli/tuiCity.mjs +396 -0
- package/src/cli/tuiCompanionKit.mjs +300 -0
- package/src/cli/tuiCompanions.mjs +471 -0
- package/src/cli/tuiDev.mjs +62 -0
- package/src/cli/tuiDungeon.mjs +606 -0
- package/src/cli/tuiEnchantTarget.mjs +120 -0
- package/src/cli/tuiFight.mjs +577 -0
- package/src/cli/tuiFightPanes.mjs +228 -0
- package/src/cli/tuiFloor.mjs +506 -0
- package/src/cli/tuiGear.mjs +158 -0
- package/src/cli/tuiHonor.mjs +74 -0
- package/src/cli/tuiLoot.mjs +97 -0
- package/src/cli/tuiMacros.mjs +144 -0
- package/src/cli/tuiOutfitter.mjs +115 -0
- package/src/cli/tuiPet.mjs +116 -0
- package/src/cli/tuiProfessions.mjs +800 -0
- package/src/cli/tuiRaid.mjs +127 -0
- package/src/cli/tuiRep.mjs +65 -0
- package/src/cli/tuiSaves.mjs +69 -0
- package/src/cli/tuiSearch.mjs +363 -0
- package/src/cli/tuiSettings.mjs +151 -0
- package/src/cli/tuiSimLab.mjs +80 -0
- package/src/cli/tuiTalents.mjs +60 -0
- package/src/cli/tuiViews.mjs +1178 -0
- package/src/cli/tuiWiki.mjs +181 -0
- package/src/cli/tuiWorld.mjs +361 -0
- package/src/cli/wikiIndex.ts +259 -0
- package/src/cli/wikiSources.ts +355 -0
- package/src/data/alteracValley.ts +176 -0
- package/src/data/appearanceOptions.ts +534 -0
- package/src/data/applyLootChanceSanity.ts +21 -0
- package/src/data/createFlavor.ts +96 -0
- package/src/data/customItems.ts +33 -0
- package/src/data/dataStore.ts +1904 -0
- package/src/data/dungeonGameObjects.ts +84 -0
- package/src/data/dungeonPlateCover.ts +66 -0
- package/src/data/dungeonSpawns.ts +294 -0
- package/src/data/dungeons/alterac-valley.ts +135 -0
- package/src/data/dungeons/apply-dungeon-rooms.ts +29 -0
- package/src/data/dungeons/apply-encounter-outdoors.ts +72 -0
- package/src/data/dungeons/apply-instance-geometry.ts +65 -0
- package/src/data/dungeons/apply-spawn-placement.ts +171 -0
- package/src/data/dungeons/blackfathom-deeps.ts +539 -0
- package/src/data/dungeons/blackrock-depths.ts +1410 -0
- package/src/data/dungeons/blackwing-lair.ts +129 -0
- package/src/data/dungeons/boss-eventai.ts +8 -0
- package/src/data/dungeons/deadmines.ts +410 -0
- package/src/data/dungeons/dire-maul-east.ts +980 -0
- package/src/data/dungeons/dire-maul-north.ts +661 -0
- package/src/data/dungeons/dire-maul-west.ts +785 -0
- package/src/data/dungeons/dungeon-rooms.json +5479 -0
- package/src/data/dungeons/encounter-outdoors.json +359 -0
- package/src/data/dungeons/eventai/blackfathom-deeps.ts +308 -0
- package/src/data/dungeons/eventai/blackrock-depths.ts +1257 -0
- package/src/data/dungeons/eventai/deadmines.ts +264 -0
- package/src/data/dungeons/eventai/dire-maul-east.ts +320 -0
- package/src/data/dungeons/eventai/gnomeregan.ts +213 -0
- package/src/data/dungeons/eventai/helpers.ts +79 -0
- package/src/data/dungeons/eventai/index.ts +133 -0
- package/src/data/dungeons/eventai/lbrs.ts +372 -0
- package/src/data/dungeons/eventai/maraudon.ts +323 -0
- package/src/data/dungeons/eventai/ragefire-chasm.ts +108 -0
- package/src/data/dungeons/eventai/razorfen-downs.ts +207 -0
- package/src/data/dungeons/eventai/razorfen-kraul.ts +326 -0
- package/src/data/dungeons/eventai/scholomance.ts +680 -0
- package/src/data/dungeons/eventai/shadowfang-keep.ts +230 -0
- package/src/data/dungeons/eventai/sm-cathedral.ts +75 -0
- package/src/data/dungeons/eventai/sm-graveyard.ts +150 -0
- package/src/data/dungeons/eventai/sm-library.ts +55 -0
- package/src/data/dungeons/eventai/stratholme-undead.ts +127 -0
- package/src/data/dungeons/eventai/sunken-temple.ts +582 -0
- package/src/data/dungeons/eventai/the-stockade.ts +228 -0
- package/src/data/dungeons/eventai/ubrs.ts +225 -0
- package/src/data/dungeons/eventai/uldaman.ts +355 -0
- package/src/data/dungeons/eventai/wailing-caverns.ts +396 -0
- package/src/data/dungeons/eventai/zul-farrak.ts +591 -0
- package/src/data/dungeons/gnomeregan.ts +909 -0
- package/src/data/dungeons/index.ts +126 -0
- package/src/data/dungeons/instance-geometry.json +5233 -0
- package/src/data/dungeons/key-sources.json +169 -0
- package/src/data/dungeons/lbrs.ts +996 -0
- package/src/data/dungeons/maraudon.ts +786 -0
- package/src/data/dungeons/molten-core.ts +194 -0
- package/src/data/dungeons/naxxramas.ts +173 -0
- package/src/data/dungeons/onyxias-lair.ts +71 -0
- package/src/data/dungeons/pack-from-clusters.ts +1256 -0
- package/src/data/dungeons/ragefire-chasm.ts +238 -0
- package/src/data/dungeons/razorfen-downs.ts +698 -0
- package/src/data/dungeons/razorfen-kraul.ts +766 -0
- package/src/data/dungeons/ruins-of-ahnqiraj.ts +121 -0
- package/src/data/dungeons/scholomance.ts +1076 -0
- package/src/data/dungeons/shadowfang-keep.ts +740 -0
- package/src/data/dungeons/sm-armory.ts +266 -0
- package/src/data/dungeons/sm-cathedral.ts +430 -0
- package/src/data/dungeons/sm-graveyard.ts +482 -0
- package/src/data/dungeons/sm-library.ts +337 -0
- package/src/data/dungeons/stratholme-live.ts +808 -0
- package/src/data/dungeons/stratholme-undead.ts +1164 -0
- package/src/data/dungeons/sunken-temple.ts +931 -0
- package/src/data/dungeons/temple-of-ahnqiraj.ts +119 -0
- package/src/data/dungeons/the-stockade.ts +282 -0
- package/src/data/dungeons/ubrs.ts +1286 -0
- package/src/data/dungeons/uldaman.ts +685 -0
- package/src/data/dungeons/wailing-caverns.ts +356 -0
- package/src/data/dungeons/zul-farrak.ts +686 -0
- package/src/data/dungeons/zul-gurub.ts +142 -0
- package/src/data/dungeons.ts +42 -0
- package/src/data/fixtures.ts +446 -0
- package/src/data/gameobject-data.json.gz +0 -0
- package/src/data/item-data.json.gz +0 -0
- package/src/data/loot-data.json.gz +0 -0
- package/src/data/maps/atlas-approach-maps.json +1425 -0
- package/src/data/maps/atlas-landmarks.json +1711 -0
- package/src/data/maps/dungeon-viewports.json +392 -0
- package/src/data/materializeModDamagePercentTaken.ts +70 -0
- package/src/data/materializeModHealthRegen.ts +89 -0
- package/src/data/materializeSpellReputation.ts +26 -0
- package/src/data/pageTexts.ts +19 -0
- package/src/data/profession-data.json.gz +0 -0
- package/src/data/raid-plans/blackfathom-deeps.json +86 -0
- package/src/data/raid-plans/blackrock-depths.json +419 -0
- package/src/data/raid-plans/blackwing-lair.json +328 -0
- package/src/data/raid-plans/deadmines.json +55 -0
- package/src/data/raid-plans/dire-maul-east.json +94 -0
- package/src/data/raid-plans/dire-maul-north.json +70 -0
- package/src/data/raid-plans/dire-maul-west.json +75 -0
- package/src/data/raid-plans/gnomeregan.json +131 -0
- package/src/data/raid-plans/lbrs.json +176 -0
- package/src/data/raid-plans/maraudon.json +102 -0
- package/src/data/raid-plans/molten-core.json +169 -0
- package/src/data/raid-plans/naxxramas.json +354 -0
- package/src/data/raid-plans/onyxias-lair.json +18 -0
- package/src/data/raid-plans/ragefire-chasm.json +30 -0
- package/src/data/raid-plans/razorfen-downs.json +75 -0
- package/src/data/raid-plans/razorfen-kraul.json +53 -0
- package/src/data/raid-plans/ruins-of-ahnqiraj.json +110 -0
- package/src/data/raid-plans/scholomance.json +170 -0
- package/src/data/raid-plans/shadowfang-keep.json +87 -0
- package/src/data/raid-plans/sm-armory.json +53 -0
- package/src/data/raid-plans/sm-cathedral.json +72 -0
- package/src/data/raid-plans/sm-graveyard.json +63 -0
- package/src/data/raid-plans/sm-library.json +23 -0
- package/src/data/raid-plans/stratholme-live.json +129 -0
- package/src/data/raid-plans/stratholme-undead.json +73 -0
- package/src/data/raid-plans/sunken-temple.json +159 -0
- package/src/data/raid-plans/temple-of-ahnqiraj.json +163 -0
- package/src/data/raid-plans/the-stockade.json +42 -0
- package/src/data/raid-plans/ubrs.json +53 -0
- package/src/data/raid-plans/uldaman.json +131 -0
- package/src/data/raid-plans/wailing-caverns.json +90 -0
- package/src/data/raid-plans/zul-farrak.json +156 -0
- package/src/data/raid-plans/zul-gurub.json +307 -0
- package/src/data/raidInstanceCreatures.mjs +473 -0
- package/src/data/rotations/index.ts +152 -0
- package/src/data/rotations/layout.mjs +72 -0
- package/src/data/rotations/pve/druid/balance.json +754 -0
- package/src/data/rotations/pve/druid/feral-tank.json +2448 -0
- package/src/data/rotations/pve/druid/feral.json +2660 -0
- package/src/data/rotations/pve/druid/restoration.json +424 -0
- package/src/data/rotations/pve/hunter/beast-mastery.json +1288 -0
- package/src/data/rotations/pve/hunter/marksmanship.json +1260 -0
- package/src/data/rotations/pve/hunter/survival.json +1373 -0
- package/src/data/rotations/pve/mage/arcane-leveling.json +1798 -0
- package/src/data/rotations/pve/mage/fire-leveling.json +2003 -0
- package/src/data/rotations/pve/mage/frost-leveling.json +1912 -0
- package/src/data/rotations/pve/paladin/holy.json +755 -0
- package/src/data/rotations/pve/paladin/protection.json +962 -0
- package/src/data/rotations/pve/paladin/retribution.json +924 -0
- package/src/data/rotations/pve/priest/discipline.json +772 -0
- package/src/data/rotations/pve/priest/holy.json +707 -0
- package/src/data/rotations/pve/priest/shadow.json +787 -0
- package/src/data/rotations/pve/rogue/assassination.json +2477 -0
- package/src/data/rotations/pve/rogue/combat.json +2483 -0
- package/src/data/rotations/pve/rogue/subtlety.json +2523 -0
- package/src/data/rotations/pve/shaman/elemental.json +1157 -0
- package/src/data/rotations/pve/shaman/enhancement.json +1085 -0
- package/src/data/rotations/pve/shaman/restoration.json +592 -0
- package/src/data/rotations/pve/warlock/affliction.json +1185 -0
- package/src/data/rotations/pve/warlock/demonology.json +1165 -0
- package/src/data/rotations/pve/warlock/destruction.json +1177 -0
- package/src/data/rotations/pve/warrior/arms.json +3020 -0
- package/src/data/rotations/pve/warrior/fury.json +2996 -0
- package/src/data/rotations/pve/warrior/protection.json +2509 -0
- package/src/data/rotations/pvp/druid/balance.json +593 -0
- package/src/data/rotations/pvp/druid/feral.json +3276 -0
- package/src/data/rotations/pvp/druid/restoration.json +662 -0
- package/src/data/rotations/pvp/hunter/beast-mastery.json +665 -0
- package/src/data/rotations/pvp/hunter/marksmanship.json +2406 -0
- package/src/data/rotations/pvp/hunter/survival.json +635 -0
- package/src/data/rotations/pvp/mage/arcane.json +574 -0
- package/src/data/rotations/pvp/mage/fire.json +570 -0
- package/src/data/rotations/pvp/mage/frost.json +4048 -0
- package/src/data/rotations/pvp/paladin/holy.json +1498 -0
- package/src/data/rotations/pvp/paladin/protection.json +567 -0
- package/src/data/rotations/pvp/paladin/retribution.json +1110 -0
- package/src/data/rotations/pvp/priest/discipline.json +1848 -0
- package/src/data/rotations/pvp/priest/holy.json +6340 -0
- package/src/data/rotations/pvp/priest/shadow.json +2329 -0
- package/src/data/rotations/pvp/rogue/assassination.json +1322 -0
- package/src/data/rotations/pvp/rogue/combat.json +5258 -0
- package/src/data/rotations/pvp/rogue/subtlety.json +586 -0
- package/src/data/rotations/pvp/shaman/elemental.json +1404 -0
- package/src/data/rotations/pvp/shaman/enhancement.json +1277 -0
- package/src/data/rotations/pvp/shaman/restoration.json +2490 -0
- package/src/data/rotations/pvp/warlock/affliction.json +1017 -0
- package/src/data/rotations/pvp/warlock/demonology.json +1503 -0
- package/src/data/rotations/pvp/warlock/destruction.json +638 -0
- package/src/data/rotations/pvp/warrior/arms.json +5665 -0
- package/src/data/rotations/pvp/warrior/fury.json +850 -0
- package/src/data/rotations/pvp/warrior/protection.json +777 -0
- package/src/data/schema/bundle.ts +511 -0
- package/src/data/schema/constants.ts +49 -0
- package/src/data/schema/creature.ts +454 -0
- package/src/data/schema/encounter.ts +155 -0
- package/src/data/schema/faction.ts +48 -0
- package/src/data/schema/item.ts +297 -0
- package/src/data/schema/itemEnchant.ts +105 -0
- package/src/data/schema/npc.ts +44 -0
- package/src/data/schema/primitives.ts +295 -0
- package/src/data/schema/profession.ts +226 -0
- package/src/data/schema/quest.ts +154 -0
- package/src/data/schema/spell.ts +1149 -0
- package/src/data/schema/talent.ts +74 -0
- package/src/data/schema/world.ts +124 -0
- package/src/data/schema.ts +19 -0
- package/src/data/skinNodeGapFill.ts +68 -0
- package/src/data/spell-data.json.gz +0 -0
- package/src/data/spell-script-targets.json +1 -0
- package/src/data/tables/ahItemValues.json.gz +0 -0
- package/src/data/tables/alteracValley.json.gz +0 -0
- package/src/data/tables/areatriggerTeleports.json.gz +0 -0
- package/src/data/tables/botNames.json.gz +0 -0
- package/src/data/tables/botPopulation.json.gz +0 -0
- package/src/data/tables/botSpecs.json.gz +0 -0
- package/src/data/tables/classLevelStats.json.gz +0 -0
- package/src/data/tables/combatConditions.json.gz +0 -0
- package/src/data/tables/combatSkillMeta.json.gz +0 -0
- package/src/data/tables/conditions.json.gz +0 -0
- package/src/data/tables/createInfo.json.gz +0 -0
- package/src/data/tables/creatureAggroGroups.json.gz +0 -0
- package/src/data/tables/creatureClassLevelStats.json.gz +0 -0
- package/src/data/tables/creaturePatrolPacks.json.gz +0 -0
- package/src/data/tables/creatureWeapons.json.gz +0 -0
- package/src/data/tables/creatures.json.gz +0 -0
- package/src/data/tables/dbScripts.json.gz +0 -0
- package/src/data/tables/dungeonAuthoredRoutes.json.gz +0 -0
- package/src/data/tables/dungeonCensus.json.gz +0 -0
- package/src/data/tables/dungeonGameObjects.json.gz +0 -0
- package/src/data/tables/dungeonPacks.json.gz +0 -0
- package/src/data/tables/dungeonPlateCover.json.gz +0 -0
- package/src/data/tables/dungeonSpawns.json.gz +0 -0
- package/src/data/tables/dungeonSurvey.json.gz +0 -0
- package/src/data/tables/durabilityCosts.json.gz +0 -0
- package/src/data/tables/durabilityQuality.json.gz +0 -0
- package/src/data/tables/enchantApplicators.json.gz +0 -0
- package/src/data/tables/entranceYards.json.gz +0 -0
- package/src/data/tables/explorationBasexp.json.gz +0 -0
- package/src/data/tables/factionTemplates.json.gz +0 -0
- package/src/data/tables/factions.json.gz +0 -0
- package/src/data/tables/gameEvents.json.gz +0 -0
- package/src/data/tables/gameObjects.json.gz +0 -0
- package/src/data/tables/gossipMenus.json.gz +0 -0
- package/src/data/tables/graveyards.json.gz +0 -0
- package/src/data/tables/instanceMaps.json.gz +0 -0
- package/src/data/tables/itemEnchantmentTemplates.json.gz +0 -0
- package/src/data/tables/itemEnchants.json.gz +0 -0
- package/src/data/tables/itemRandomProperties.json.gz +0 -0
- package/src/data/tables/itemSets.json.gz +0 -0
- package/src/data/tables/items.json.gz +0 -0
- package/src/data/tables/levelStats.json.gz +0 -0
- package/src/data/tables/locks.json.gz +0 -0
- package/src/data/tables/map-pin-overrides.json.gz +0 -0
- package/src/data/tables/nonCombatTrash.json.gz +0 -0
- package/src/data/tables/npcTexts.json.gz +0 -0
- package/src/data/tables/pageTexts.json.gz +0 -0
- package/src/data/tables/petAbilityTP.json.gz +0 -0
- package/src/data/tables/petGenericSpells.json.gz +0 -0
- package/src/data/tables/petLevelStats.json.gz +0 -0
- package/src/data/tables/playerbot-weightscales.json.gz +0 -0
- package/src/data/tables/pools.json.gz +0 -0
- package/src/data/tables/quests.json.gz +0 -0
- package/src/data/tables/races.json.gz +0 -0
- package/src/data/tables/reputationRanks.json.gz +0 -0
- package/src/data/tables/reputationRates.json.gz +0 -0
- package/src/data/tables/reputationSpillover.json.gz +0 -0
- package/src/data/tables/ridingSpells.json.gz +0 -0
- package/src/data/tables/scriptTexts.json.gz +0 -0
- package/src/data/tables/soundClipVolume.json.gz +0 -0
- package/src/data/tables/spellChain.json.gz +0 -0
- package/src/data/tables/spellTeleports.json.gz +0 -0
- package/src/data/tables/spells.json.gz +0 -0
- package/src/data/tables/talents.json.gz +0 -0
- package/src/data/tables/tavernTriggers.json.gz +0 -0
- package/src/data/tables/taxiNodes.json.gz +0 -0
- package/src/data/tables/taxiRoutes.json.gz +0 -0
- package/src/data/tables/trainers.json.gz +0 -0
- package/src/data/tables/transports.json.gz +0 -0
- package/src/data/tables/travelAnchors.json.gz +0 -0
- package/src/data/tables/travelEdges.json.gz +0 -0
- package/src/data/tables/unitConditions.json.gz +0 -0
- package/src/data/tables/vendors.json.gz +0 -0
- package/src/data/tables/weaponEnchantProcs.json.gz +0 -0
- package/src/data/tables/xp.json.gz +0 -0
- package/src/data/tables/zoneWeather.json.gz +0 -0
- package/src/data/tables/zones.json.gz +0 -0
- package/src/data/world-data.ts +56 -0
- package/src/data/zone-music-sets.json +2751 -0
- package/src/dev/etl/lib/loot-chance-sanity.mjs +180 -0
- package/src/domain/audioQuality.mjs +145 -0
- package/src/domain/bg/ab.ts +433 -0
- package/src/domain/bg/arenaComps.ts +644 -0
- package/src/domain/bg/arenaLab.ts +695 -0
- package/src/domain/bg/arenaPace.ts +31 -0
- package/src/domain/bg/arenaRating.ts +110 -0
- package/src/domain/bg/av.ts +758 -0
- package/src/domain/bg/avEnemyPush.ts +219 -0
- package/src/domain/bg/avField.ts +283 -0
- package/src/domain/bg/avInstanceNodes.ts +197 -0
- package/src/domain/bg/avInstanceScore.ts +123 -0
- package/src/domain/bg/avQuestItems.ts +55 -0
- package/src/domain/bg/avQuests.ts +210 -0
- package/src/domain/bg/avSession.ts +196 -0
- package/src/domain/bg/bgConsumes.ts +269 -0
- package/src/domain/bg/bgPrebuff.ts +153 -0
- package/src/domain/bg/bgRest.ts +91 -0
- package/src/domain/bg/bgRoster.ts +305 -0
- package/src/domain/bg/bgTier.ts +61 -0
- package/src/domain/bg/bgWorld.ts +862 -0
- package/src/domain/bg/match.ts +215 -0
- package/src/domain/bg/strategies.ts +552 -0
- package/src/domain/bg/types.ts +291 -0
- package/src/domain/bg/wsg.ts +264 -0
- package/src/domain/bots/actionProviderSpec.ts +38 -0
- package/src/domain/bots/autoConsumables.ts +285 -0
- package/src/domain/bots/betweenPullRest.ts +239 -0
- package/src/domain/bots/botGear.ts +248 -0
- package/src/domain/bots/botInventory.ts +486 -0
- package/src/domain/bots/botPet.ts +535 -0
- package/src/domain/bots/botPool.ts +291 -0
- package/src/domain/bots/botProfessions.ts +334 -0
- package/src/domain/bots/companionActionBar.ts +209 -0
- package/src/domain/bots/companionHireCost.ts +236 -0
- package/src/domain/bots/companionMissions.ts +582 -0
- package/src/domain/bots/companionPrep.ts +254 -0
- package/src/domain/bots/companionQuests.ts +270 -0
- package/src/domain/bots/companionRep.ts +75 -0
- package/src/domain/bots/companionRotation.ts +708 -0
- package/src/domain/bots/companionXp.ts +66 -0
- package/src/domain/bots/consumableShare.ts +811 -0
- package/src/domain/bots/dpsMate.ts +1072 -0
- package/src/domain/bots/groupCompReport.ts +132 -0
- package/src/domain/bots/healthstoneSupply.ts +198 -0
- package/src/domain/bots/outfitter.ts +1056 -0
- package/src/domain/bots/paladinPrePull.ts +231 -0
- package/src/domain/bots/partyRole.ts +40 -0
- package/src/domain/bots/playerbotEquipCache.ts +412 -0
- package/src/domain/bots/playerbotItemEligibility.ts +60 -0
- package/src/domain/bots/playerbotStatWeight.ts +853 -0
- package/src/domain/bots/playerbotUnavailableItems.ts +147 -0
- package/src/domain/bots/prePullBuffs.ts +219 -0
- package/src/domain/bots/prePullStealth.ts +82 -0
- package/src/domain/bots/runSupply.ts +251 -0
- package/src/domain/bots/runSupplyPrefs.ts +28 -0
- package/src/domain/bots/sendTradeGoods.ts +394 -0
- package/src/domain/bots/soulShardCap.ts +35 -0
- package/src/domain/bots/squad.ts +842 -0
- package/src/domain/bots/squadRun.ts +81 -0
- package/src/domain/character/actionbar.ts +346 -0
- package/src/domain/character/appearance.ts +165 -0
- package/src/domain/character/buildCombatant.ts +1726 -0
- package/src/domain/character/character.ts +356 -0
- package/src/domain/character/characterName.ts +43 -0
- package/src/domain/character/characterRatings.ts +424 -0
- package/src/domain/character/characterSpells.ts +282 -0
- package/src/domain/character/characterStats.ts +1559 -0
- package/src/domain/character/characterTalents.ts +398 -0
- package/src/domain/character/combatSkills.ts +994 -0
- package/src/domain/character/displayForm.ts +72 -0
- package/src/domain/character/formTalentAuras.ts +171 -0
- package/src/domain/character/forms.ts +499 -0
- package/src/domain/character/races.ts +40 -0
- package/src/domain/character/resurrectionSickness.ts +49 -0
- package/src/domain/character/spellbook.ts +169 -0
- package/src/domain/character/stanceBar.ts +354 -0
- package/src/domain/character/stancePrereq.ts +70 -0
- package/src/domain/character/talentBuilds.ts +63 -0
- package/src/domain/character/talentImport.ts +130 -0
- package/src/domain/character/talentLink.ts +123 -0
- package/src/domain/character/talents.ts +201 -0
- package/src/domain/core/conditionSnapshot.ts +92 -0
- package/src/domain/core/hover.ts +14 -0
- package/src/domain/core/ids.ts +58 -0
- package/src/domain/core/internalNameTags.ts +5 -0
- package/src/domain/core/runtimeDataPatches.ts +357 -0
- package/src/domain/creature/aiTargeting.ts +588 -0
- package/src/domain/creature/combatCondition.ts +157 -0
- package/src/domain/creature/creatureAggro.ts +804 -0
- package/src/domain/creature/creatureAi.ts +206 -0
- package/src/domain/creature/creatureAiConstants.ts +661 -0
- package/src/domain/creature/creatureCastList.ts +1007 -0
- package/src/domain/creature/creatureEventAi.ts +1409 -0
- package/src/domain/creature/creatureGroups.ts +144 -0
- package/src/domain/creature/creatureLinking.ts +333 -0
- package/src/domain/creature/creatureScaling.ts +320 -0
- package/src/domain/creature/creatureTrigger.ts +53 -0
- package/src/domain/creature/creatureWander.ts +74 -0
- package/src/domain/creature/eventAiTexts.ts +145 -0
- package/src/domain/creature/faction.ts +287 -0
- package/src/domain/creature/forcedReaction.ts +48 -0
- package/src/domain/creature/outdoorHuntable.ts +74 -0
- package/src/domain/creature/patrolPacks.ts +210 -0
- package/src/domain/creature/reputation.ts +967 -0
- package/src/domain/creature/unitFlags.ts +74 -0
- package/src/domain/creature/worldBosses.ts +393 -0
- package/src/domain/dungeonMusic.mjs +48 -0
- package/src/domain/economy/auction.ts +282 -0
- package/src/domain/economy/auctionBot.ts +1060 -0
- package/src/domain/economy/honorPrice.ts +100 -0
- package/src/domain/economy/mail.ts +958 -0
- package/src/domain/economy/money.ts +42 -0
- package/src/domain/economy/syntheticClassTrainers.ts +62 -0
- package/src/domain/economy/trading.ts +296 -0
- package/src/domain/economy/vendorSellAll.ts +252 -0
- package/src/domain/economy/vendorStock.ts +146 -0
- package/src/domain/items/ammo.ts +817 -0
- package/src/domain/items/bank.ts +162 -0
- package/src/domain/items/consumableFamily.ts +205 -0
- package/src/domain/items/craftUpgrades.ts +83 -0
- package/src/domain/items/createItem.ts +93 -0
- package/src/domain/items/durability.ts +581 -0
- package/src/domain/items/equipment.ts +441 -0
- package/src/domain/items/equipmentHelper.ts +767 -0
- package/src/domain/items/inventory.ts +561 -0
- package/src/domain/items/itemBonding.ts +93 -0
- package/src/domain/items/itemContainer.ts +245 -0
- package/src/domain/items/itemDisplayName.ts +33 -0
- package/src/domain/items/itemInstance.ts +181 -0
- package/src/domain/items/itemRandomEnchantOutcomes.ts +315 -0
- package/src/domain/items/itemRandomProps.ts +77 -0
- package/src/domain/items/itemRestrictions.ts +329 -0
- package/src/domain/items/itemTeaches.ts +82 -0
- package/src/domain/items/itemUseCooldown.ts +52 -0
- package/src/domain/items/itemUseTarget.ts +90 -0
- package/src/domain/items/junkCategories.ts +174 -0
- package/src/domain/items/junkFilter.ts +368 -0
- package/src/domain/items/keyring.ts +131 -0
- package/src/domain/items/loot.ts +221 -0
- package/src/domain/items/mounts.ts +190 -0
- package/src/domain/items/onUsePayload.ts +102 -0
- package/src/domain/items/pendingLoot.ts +107 -0
- package/src/domain/items/relic.ts +105 -0
- package/src/domain/items/rocketBoots.ts +36 -0
- package/src/domain/items/sortContainer.ts +139 -0
- package/src/domain/items/spareContainers.ts +71 -0
- package/src/domain/items/spellReagentIndex.ts +131 -0
- package/src/domain/items/staleConsumables.ts +66 -0
- package/src/domain/items/weaponSpender.ts +110 -0
- package/src/domain/listDungeonIds.mjs +28 -0
- package/src/domain/macro/compose.ts +239 -0
- package/src/domain/macro/conditions.ts +260 -0
- package/src/domain/macro/creatureTypes.ts +40 -0
- package/src/domain/macro/describe.ts +169 -0
- package/src/domain/macro/icon.ts +60 -0
- package/src/domain/macro/index.ts +20 -0
- package/src/domain/macro/normalize.ts +112 -0
- package/src/domain/macro/parse.ts +316 -0
- package/src/domain/macro/plan.ts +288 -0
- package/src/domain/macro/rotation.ts +590 -0
- package/src/domain/macro/rotationEnv.ts +173 -0
- package/src/domain/macro/slots.ts +101 -0
- package/src/domain/macro/snapshot.ts +114 -0
- package/src/domain/macro/templates.ts +446 -0
- package/src/domain/macro/types.ts +70 -0
- package/src/domain/macro/units.ts +93 -0
- package/src/domain/macro/weaponTypes.ts +83 -0
- package/src/domain/nav/cityTeleports.ts +10 -0
- package/src/domain/nav/mounting.ts +113 -0
- package/src/domain/nav/nav.ts +43 -0
- package/src/domain/nav/navCollision.ts +606 -0
- package/src/domain/nav/navOutdoors.ts +91 -0
- package/src/domain/nav/navRegistry.ts +179 -0
- package/src/domain/nav/navRoomGraph.ts +302 -0
- package/src/domain/nav/navTypes.ts +43 -0
- package/src/domain/nav/navmesh.ts +1715 -0
- package/src/domain/nav/speed.ts +200 -0
- package/src/domain/nav/storey.ts +10 -0
- package/src/domain/nav/travel.ts +725 -0
- package/src/domain/nav/travelTypes.ts +75 -0
- package/src/domain/pet/pet.ts +1150 -0
- package/src/domain/pet/petActionBar.ts +180 -0
- package/src/domain/pet/petSpellbook.ts +39 -0
- package/src/domain/pet/petStable.ts +81 -0
- package/src/domain/professions/autoDisenchant.ts +236 -0
- package/src/domain/professions/characterEnchants.ts +176 -0
- package/src/domain/professions/craftCooldown.ts +52 -0
- package/src/domain/professions/enchantApply.ts +62 -0
- package/src/domain/professions/enchantTargets.ts +114 -0
- package/src/domain/professions/enchanting.ts +236 -0
- package/src/domain/professions/gatherFilter.ts +149 -0
- package/src/domain/professions/gearEnchant.ts +137 -0
- package/src/domain/professions/heldTools.ts +29 -0
- package/src/domain/professions/learnAllRecipes.ts +146 -0
- package/src/domain/professions/miningHarvest.ts +69 -0
- package/src/domain/professions/partyDisenchant.ts +294 -0
- package/src/domain/professions/partyWork.ts +83 -0
- package/src/domain/professions/pickpocket.ts +172 -0
- package/src/domain/professions/poisons.ts +240 -0
- package/src/domain/professions/professions.ts +677 -0
- package/src/domain/professions/rareNodes.ts +45 -0
- package/src/domain/professions/skinLoot.ts +27 -0
- package/src/domain/professions/vendorReagents.ts +152 -0
- package/src/domain/professions/weaponImbueHand.ts +171 -0
- package/src/domain/questFarmFilter.mjs +42 -0
- package/src/domain/quests/doQuest.ts +1204 -0
- package/src/domain/quests/gossip.ts +308 -0
- package/src/domain/quests/gossipTalk.ts +298 -0
- package/src/domain/quests/interpreter/canComplete.ts +150 -0
- package/src/domain/quests/interpreter/claim.ts +137 -0
- package/src/domain/quests/interpreter/dbscript/handlers.ts +1371 -0
- package/src/domain/quests/interpreter/dbscript/handlersBatch.ts +1251 -0
- package/src/domain/quests/interpreter/dbscript/hooks.ts +331 -0
- package/src/domain/quests/interpreter/dbscript/index.ts +212 -0
- package/src/domain/quests/interpreter/dbscript/opcodes.ts +257 -0
- package/src/domain/quests/interpreter/dbscript/registry.ts +1194 -0
- package/src/domain/quests/interpreter/dbscript/registryAreatriggerScripts.ts +539 -0
- package/src/domain/quests/interpreter/dbscript/registryAshenvale.ts +553 -0
- package/src/domain/quests/interpreter/dbscript/registryAzshara.ts +272 -0
- package/src/domain/quests/interpreter/dbscript/registryDarkshore.ts +637 -0
- package/src/domain/quests/interpreter/dbscript/registryDesolace.ts +501 -0
- package/src/domain/quests/interpreter/dbscript/registryDustwallowMarsh.ts +735 -0
- package/src/domain/quests/interpreter/dbscript/registryFelwood.ts +493 -0
- package/src/domain/quests/interpreter/dbscript/registryFeralas.ts +485 -0
- package/src/domain/quests/interpreter/dbscript/registryGoScripts.ts +203 -0
- package/src/domain/quests/interpreter/dbscript/registryMoonglade.ts +288 -0
- package/src/domain/quests/interpreter/dbscript/registryNpcProfessions.ts +230 -0
- package/src/domain/quests/interpreter/dbscript/registryNpcsSpecial.ts +545 -0
- package/src/domain/quests/interpreter/dbscript/registryRazorfenDowns.ts +293 -0
- package/src/domain/quests/interpreter/dbscript/registryRazorfenKraul.ts +221 -0
- package/src/domain/quests/interpreter/dbscript/registryRedridge.ts +112 -0
- package/src/domain/quests/interpreter/dbscript/registrySearingGorge.ts +125 -0
- package/src/domain/quests/interpreter/dbscript/registrySiblings.ts +51 -0
- package/src/domain/quests/interpreter/dbscript/registrySilverpineForest.ts +172 -0
- package/src/domain/quests/interpreter/dbscript/registryStonetalonMountains.ts +180 -0
- package/src/domain/quests/interpreter/dbscript/registryStormwindCity.ts +369 -0
- package/src/domain/quests/interpreter/dbscript/registryStranglethornVale.ts +115 -0
- package/src/domain/quests/interpreter/dbscript/registryStratholme.ts +146 -0
- package/src/domain/quests/interpreter/dbscript/registrySunkenTemple.ts +149 -0
- package/src/domain/quests/interpreter/dbscript/registrySwampOfSorrows.ts +163 -0
- package/src/domain/quests/interpreter/dbscript/registryTanaris.ts +318 -0
- package/src/domain/quests/interpreter/dbscript/registryTeldrassil.ts +120 -0
- package/src/domain/quests/interpreter/dbscript/registryTheBarrens.ts +603 -0
- package/src/domain/quests/interpreter/dbscript/registryThousandNeedles.ts +499 -0
- package/src/domain/quests/interpreter/dbscript/registryTirisfalGlades.ts +222 -0
- package/src/domain/quests/interpreter/dbscript/registryUngoroCrater.ts +291 -0
- package/src/domain/quests/interpreter/dbscript/registryWarEffort.ts +773 -0
- package/src/domain/quests/interpreter/dbscript/registryWesternPlaguelands.ts +206 -0
- package/src/domain/quests/interpreter/dbscript/registryWestfall.ts +190 -0
- package/src/domain/quests/interpreter/dbscript/registryWetlands.ts +186 -0
- package/src/domain/quests/interpreter/dbscript/registryWinterspring.ts +235 -0
- package/src/domain/quests/interpreter/dbscript/types.ts +817 -0
- package/src/domain/quests/interpreter/dbscript/vm.ts +171 -0
- package/src/domain/quests/interpreter/diff.ts +349 -0
- package/src/domain/quests/interpreter/fixtures/oracle-questlog-qi2.json +2417 -0
- package/src/domain/quests/interpreter/flags.ts +101 -0
- package/src/domain/quests/interpreter/index.ts +113 -0
- package/src/domain/quests/interpreter/live.ts +178 -0
- package/src/domain/quests/interpreter/types.ts +81 -0
- package/src/domain/quests/interpreter/updaters.ts +412 -0
- package/src/domain/quests/questCorpseUse.ts +155 -0
- package/src/domain/quests/questDialog.ts +43 -0
- package/src/domain/quests/questFight.ts +34 -0
- package/src/domain/quests/questObjectiveLabel.ts +39 -0
- package/src/domain/quests/questRewardChoice.ts +85 -0
- package/src/domain/quests/questScriptCredit.ts +26 -0
- package/src/domain/quests/questScriptItemUse.ts +74 -0
- package/src/domain/quests/questTameRods.ts +79 -0
- package/src/domain/quests/questlog.ts +1545 -0
- package/src/domain/quests/talkDeliver.ts +170 -0
- package/src/domain/raid/globalActionBar.ts +195 -0
- package/src/domain/raid/instanceRoster.ts +194 -0
- package/src/domain/raid/partySummon.ts +198 -0
- package/src/domain/raid/partyVault.ts +320 -0
- package/src/domain/raid/raidBossAbilities.ts +268 -0
- package/src/domain/raid/raidComp.ts +335 -0
- package/src/domain/raid/raidCustomRows.ts +453 -0
- package/src/domain/raid/raidEncounterGear.ts +298 -0
- package/src/domain/raid/raidEncounterTactics.ts +412 -0
- package/src/domain/raid/raidPlan.ts +509 -0
- package/src/domain/raid/raidPlanEdits.ts +98 -0
- package/src/domain/raid/raidPlanPacks.ts +480 -0
- package/src/domain/raid/raidRecovery.ts +183 -0
- package/src/domain/raid/raidRosterTemplate.ts +138 -0
- package/src/domain/rotation/auraSide.ts +68 -0
- package/src/domain/rotation/build.ts +3031 -0
- package/src/domain/rotation/classes/druid.ts +58 -0
- package/src/domain/rotation/classes/hooks.ts +59 -0
- package/src/domain/rotation/classes/hunter.ts +156 -0
- package/src/domain/rotation/classes/index.ts +104 -0
- package/src/domain/rotation/classes/mage.ts +99 -0
- package/src/domain/rotation/classes/paladin.ts +296 -0
- package/src/domain/rotation/classes/priest.ts +94 -0
- package/src/domain/rotation/classes/pullOpener.ts +92 -0
- package/src/domain/rotation/classes/rogue.ts +69 -0
- package/src/domain/rotation/classes/shaman.ts +29 -0
- package/src/domain/rotation/classes/supportSpec.ts +94 -0
- package/src/domain/rotation/classes/warlock.ts +81 -0
- package/src/domain/rotation/classes/warrior.ts +402 -0
- package/src/domain/rotation/classify.ts +1381 -0
- package/src/domain/rotation/condMetricPicker.ts +379 -0
- package/src/domain/rotation/consumables.ts +242 -0
- package/src/domain/rotation/evaluate.ts +1115 -0
- package/src/domain/rotation/finisherLadder.ts +145 -0
- package/src/domain/rotation/invent.ts +938 -0
- package/src/domain/rotation/killOrder.ts +30 -0
- package/src/domain/rotation/openerForm.ts +22 -0
- package/src/domain/rotation/paladinBuffs.ts +568 -0
- package/src/domain/rotation/prebuff.ts +283 -0
- package/src/domain/rotation/raidRowTuning.ts +34 -0
- package/src/domain/rotation/rotation.ts +11 -0
- package/src/domain/rotation/rotationProfiles.ts +180 -0
- package/src/domain/rotation/rotationSuggestions.ts +851 -0
- package/src/domain/rotation/ruleAfterChain.ts +49 -0
- package/src/domain/rotation/spec.ts +70 -0
- package/src/domain/rotation/spellPicker.ts +140 -0
- package/src/domain/rotation/stand.ts +143 -0
- package/src/domain/rotation/types.ts +718 -0
- package/src/domain/run/battlegroundRun.ts +111 -0
- package/src/domain/run/canEnterContext.ts +32 -0
- package/src/domain/run/death.ts +561 -0
- package/src/domain/run/deathRecovery.ts +289 -0
- package/src/domain/run/duel.ts +141 -0
- package/src/domain/run/dungeonRun.ts +344 -0
- package/src/domain/run/floorRest.ts +159 -0
- package/src/domain/run/honor.ts +557 -0
- package/src/domain/run/honorWeek.ts +180 -0
- package/src/domain/run/whenMap.ts +977 -0
- package/src/domain/scriptdev/arathiHighlands.ts +445 -0
- package/src/domain/scriptdev/ashenvale.ts +635 -0
- package/src/domain/scriptdev/ashenvaleHelpers.ts +1215 -0
- package/src/domain/scriptdev/azshara.ts +892 -0
- package/src/domain/scriptdev/blackfathomDeeps.ts +212 -0
- package/src/domain/scriptdev/blackrockDepths.ts +1434 -0
- package/src/domain/scriptdev/blackrockSpire.ts +730 -0
- package/src/domain/scriptdev/blackwingLair.ts +521 -0
- package/src/domain/scriptdev/burningSteppes.ts +62 -0
- package/src/domain/scriptdev/classFilter.ts +43 -0
- package/src/domain/scriptdev/darkshore.ts +817 -0
- package/src/domain/scriptdev/darkshoreHelpers.ts +853 -0
- package/src/domain/scriptdev/deadmines.ts +331 -0
- package/src/domain/scriptdev/desolace.ts +1239 -0
- package/src/domain/scriptdev/direMaul.ts +859 -0
- package/src/domain/scriptdev/durotar.ts +532 -0
- package/src/domain/scriptdev/dustwallowMarsh.ts +758 -0
- package/src/domain/scriptdev/dustwallowMarshHelpers.ts +994 -0
- package/src/domain/scriptdev/easternPlaguelands.ts +21 -0
- package/src/domain/scriptdev/felwood.ts +1055 -0
- package/src/domain/scriptdev/feralas.ts +571 -0
- package/src/domain/scriptdev/feralasHelpers.ts +838 -0
- package/src/domain/scriptdev/gnomeregan.ts +466 -0
- package/src/domain/scriptdev/guards.ts +616 -0
- package/src/domain/scriptdev/hinterlands.ts +693 -0
- package/src/domain/scriptdev/host.ts +1134 -0
- package/src/domain/scriptdev/index.ts +123 -0
- package/src/domain/scriptdev/instanceRaids.ts +136 -0
- package/src/domain/scriptdev/itemScripts.ts +37 -0
- package/src/domain/scriptdev/lochModan.ts +283 -0
- package/src/domain/scriptdev/maraudon.ts +232 -0
- package/src/domain/scriptdev/moltenCore.ts +831 -0
- package/src/domain/scriptdev/moonglade.ts +659 -0
- package/src/domain/scriptdev/moongladeHelpers.ts +933 -0
- package/src/domain/scriptdev/naxxramas.ts +577 -0
- package/src/domain/scriptdev/naxxramasConstruct.ts +364 -0
- package/src/domain/scriptdev/naxxramasFrostwyrm.ts +378 -0
- package/src/domain/scriptdev/naxxramasMilitary.ts +276 -0
- package/src/domain/scriptdev/npcProfessions.ts +1025 -0
- package/src/domain/scriptdev/npcsSpecial.ts +1379 -0
- package/src/domain/scriptdev/onyxiasLair.ts +313 -0
- package/src/domain/scriptdev/orgrimmar.ts +147 -0
- package/src/domain/scriptdev/professionSpecConsumer.ts +481 -0
- package/src/domain/scriptdev/razorfenDowns.ts +468 -0
- package/src/domain/scriptdev/razorfenKraul.ts +410 -0
- package/src/domain/scriptdev/redridgeMountains.ts +163 -0
- package/src/domain/scriptdev/registry.ts +945 -0
- package/src/domain/scriptdev/ruinsOfAhnqiraj.ts +356 -0
- package/src/domain/scriptdev/scarletMonastery.ts +713 -0
- package/src/domain/scriptdev/scholomance.ts +682 -0
- package/src/domain/scriptdev/searingGorge.ts +295 -0
- package/src/domain/scriptdev/shadowfangKeep.ts +181 -0
- package/src/domain/scriptdev/silithus.ts +71 -0
- package/src/domain/scriptdev/silverpineForest.ts +662 -0
- package/src/domain/scriptdev/stonetalonMountains.ts +493 -0
- package/src/domain/scriptdev/stormwindCity.ts +1192 -0
- package/src/domain/scriptdev/stranglethornVale.ts +402 -0
- package/src/domain/scriptdev/stratholme.ts +650 -0
- package/src/domain/scriptdev/stratholmeScripts.ts +505 -0
- package/src/domain/scriptdev/sunkenTemple.ts +736 -0
- package/src/domain/scriptdev/swampOfSorrows.ts +22 -0
- package/src/domain/scriptdev/tanaris.ts +391 -0
- package/src/domain/scriptdev/tanarisHelpers.ts +661 -0
- package/src/domain/scriptdev/teldrassil.ts +143 -0
- package/src/domain/scriptdev/templeOfAhnqiraj.ts +1013 -0
- package/src/domain/scriptdev/theBarrens.ts +709 -0
- package/src/domain/scriptdev/theBarrensHelpers.ts +1026 -0
- package/src/domain/scriptdev/thousandNeedles.ts +426 -0
- package/src/domain/scriptdev/thousandNeedlesHelpers.ts +566 -0
- package/src/domain/scriptdev/tirisfalGlades.ts +533 -0
- package/src/domain/scriptdev/types.ts +1026 -0
- package/src/domain/scriptdev/uldaman.ts +437 -0
- package/src/domain/scriptdev/ungoroCrater.ts +549 -0
- package/src/domain/scriptdev/ungoroCraterHelpers.ts +1006 -0
- package/src/domain/scriptdev/wailingCaverns.ts +700 -0
- package/src/domain/scriptdev/warEffort.ts +371 -0
- package/src/domain/scriptdev/westernPlaguelands.ts +952 -0
- package/src/domain/scriptdev/westfall.ts +767 -0
- package/src/domain/scriptdev/wetlands.ts +660 -0
- package/src/domain/scriptdev/winterspring.ts +482 -0
- package/src/domain/scriptdev/winterspringHelpers.ts +801 -0
- package/src/domain/scriptdev/worldBosses.ts +605 -0
- package/src/domain/scriptdev/zulfarrak.ts +478 -0
- package/src/domain/scriptdev/zulgurub.ts +1152 -0
- package/src/domain/sim/dummySim.ts +408 -0
- package/src/domain/sim/targetDummy.ts +29 -0
- package/src/domain/social/guild.ts +148 -0
- package/src/domain/spells/areaAuraMembership.ts +93 -0
- package/src/domain/spells/breakableStunCc.ts +166 -0
- package/src/domain/spells/cancelAura.ts +80 -0
- package/src/domain/spells/diminishing.ts +167 -0
- package/src/domain/spells/dispel.ts +149 -0
- package/src/domain/spells/dispelMechanic.ts +105 -0
- package/src/domain/spells/equipSpells.ts +247 -0
- package/src/domain/spells/learnOnTargets.ts +260 -0
- package/src/domain/spells/mergeSchoolImmunityFromRaw.ts +66 -0
- package/src/domain/spells/resurrectSpell.ts +124 -0
- package/src/domain/spells/spellPushback.ts +39 -0
- package/src/domain/spells/spellReflect.ts +43 -0
- package/src/domain/spells/stealth.ts +183 -0
- package/src/domain/spells/totem.ts +394 -0
- package/src/domain/spells/worldCast.ts +187 -0
- package/src/domain/world/areaTriggerLocks.ts +177 -0
- package/src/domain/world/dungeonMusic.ts +108 -0
- package/src/domain/world/gameEvents.ts +119 -0
- package/src/domain/world/gameObjects.ts +1030 -0
- package/src/domain/world/goEventFire.ts +234 -0
- package/src/domain/world/goMoTransport.ts +74 -0
- package/src/domain/world/goTransport.ts +81 -0
- package/src/domain/world/hazards.ts +459 -0
- package/src/domain/world/openLock.ts +398 -0
- package/src/domain/world/pools.ts +470 -0
- package/src/domain/world/restArea.ts +37 -0
- package/src/domain/world/weather.ts +201 -0
- package/src/domain/zoneAmbience.mjs +87 -0
- package/src/domain/zoneMusic.mjs +62 -0
- package/src/domain/zoneMusicQueue.mjs +7 -0
- package/src/engine/combat/activeSeal.ts +39 -0
- package/src/engine/combat/aiTargetResolve.ts +64 -0
- package/src/engine/combat/aoeCast.ts +289 -0
- package/src/engine/combat/applyConsumeAura.ts +74 -0
- package/src/engine/combat/applyDemonicSacrifice.ts +70 -0
- package/src/engine/combat/applyDispelMechanic.ts +30 -0
- package/src/engine/combat/applyHealEffect.ts +301 -0
- package/src/engine/combat/applySchoolAbsorb.ts +114 -0
- package/src/engine/combat/applySpell.ts +3169 -0
- package/src/engine/combat/applySpellLate.ts +2026 -0
- package/src/engine/combat/applySpellLateBag.ts +144 -0
- package/src/engine/combat/areaAura.ts +283 -0
- package/src/engine/combat/attackTable.ts +508 -0
- package/src/engine/combat/battleRez.ts +87 -0
- package/src/engine/combat/castAction.ts +36 -0
- package/src/engine/combat/castTargetGates.ts +75 -0
- package/src/engine/combat/castableKit.ts +68 -0
- package/src/engine/combat/ccMirrorUpgrade.ts +39 -0
- package/src/engine/combat/chainFan.ts +210 -0
- package/src/engine/combat/channel.ts +423 -0
- package/src/engine/combat/chaseCloseness.ts +95 -0
- package/src/engine/combat/cmangosCastGates.ts +129 -0
- package/src/engine/combat/combatFlag.ts +77 -0
- package/src/engine/combat/combatGeometry.ts +1329 -0
- package/src/engine/combat/combustion.ts +68 -0
- package/src/engine/combat/context.ts +730 -0
- package/src/engine/combat/createSession.ts +1165 -0
- package/src/engine/combat/creatureLeash.ts +38 -0
- package/src/engine/combat/crowdControl.ts +1259 -0
- package/src/engine/combat/dispelDuty.ts +54 -0
- package/src/engine/combat/dispelReflect.ts +609 -0
- package/src/engine/combat/displaceFloor.ts +113 -0
- package/src/engine/combat/duelBounds.ts +47 -0
- package/src/engine/combat/equippedItemGate.ts +297 -0
- package/src/engine/combat/evadeLifecycle.ts +63 -0
- package/src/engine/combat/eventAi.ts +2220 -0
- package/src/engine/combat/facing.ts +74 -0
- package/src/engine/combat/fearFlee.ts +137 -0
- package/src/engine/combat/fighterFactory.ts +1144 -0
- package/src/engine/combat/forceDespawnAction.ts +51 -0
- package/src/engine/combat/formulas.ts +807 -0
- package/src/engine/combat/friendlyFan.ts +55 -0
- package/src/engine/combat/groundPoolTick.ts +210 -0
- package/src/engine/combat/hazards.ts +114 -0
- package/src/engine/combat/healTargets.ts +226 -0
- package/src/engine/combat/heartbeatResist.ts +49 -0
- package/src/engine/combat/hostileItemUse.ts +75 -0
- package/src/engine/combat/interruptAuras.ts +66 -0
- package/src/engine/combat/itemRescue.ts +49 -0
- package/src/engine/combat/itemUse.ts +260 -0
- package/src/engine/combat/kitById.ts +23 -0
- package/src/engine/combat/knockBack.ts +201 -0
- package/src/engine/combat/leap.ts +89 -0
- package/src/engine/combat/mitigation.ts +587 -0
- package/src/engine/combat/modTaunt.ts +43 -0
- package/src/engine/combat/motionClaim.ts +268 -0
- package/src/engine/combat/motionGate.ts +150 -0
- package/src/engine/combat/motionGenerator.ts +27 -0
- package/src/engine/combat/navSight.ts +138 -0
- package/src/engine/combat/perfSink.ts +28 -0
- package/src/engine/combat/periodicDue.ts +55 -0
- package/src/engine/combat/petAuras.ts +86 -0
- package/src/engine/combat/pickAction.ts +3449 -0
- package/src/engine/combat/powerForm.ts +735 -0
- package/src/engine/combat/powerPools.ts +49 -0
- package/src/engine/combat/predicates.ts +538 -0
- package/src/engine/combat/procsDamage.ts +1823 -0
- package/src/engine/combat/reactiveGates.ts +135 -0
- package/src/engine/combat/roles.ts +389 -0
- package/src/engine/combat/rotationSubject.ts +78 -0
- package/src/engine/combat/ruleAfter.ts +106 -0
- package/src/engine/combat/seedDisplayAuras.ts +203 -0
- package/src/engine/combat/seedOocEffects.ts +317 -0
- package/src/engine/combat/seedSchoolImmunities.ts +45 -0
- package/src/engine/combat/session-types.ts +458 -0
- package/src/engine/combat/sessionApi.ts +1785 -0
- package/src/engine/combat/sessionCharge.ts +124 -0
- package/src/engine/combat/sessionChase.ts +869 -0
- package/src/engine/combat/sessionDeaths.ts +469 -0
- package/src/engine/combat/sessionFocus.ts +506 -0
- package/src/engine/combat/sessionFollow.ts +167 -0
- package/src/engine/combat/sessionGeometry.ts +188 -0
- package/src/engine/combat/sessionHome.ts +75 -0
- package/src/engine/combat/sessionInject.ts +719 -0
- package/src/engine/combat/sessionMechanicMove.ts +555 -0
- package/src/engine/combat/sessionMotion.ts +1553 -0
- package/src/engine/combat/sessionMotionKit.ts +247 -0
- package/src/engine/combat/sessionMoveIntent.ts +49 -0
- package/src/engine/combat/sessionMoveOrder.ts +200 -0
- package/src/engine/combat/sessionPathTo.ts +657 -0
- package/src/engine/combat/sessionPools.ts +688 -0
- package/src/engine/combat/sessionSteer.ts +144 -0
- package/src/engine/combat/sessionStepBack.ts +151 -0
- package/src/engine/combat/sessionStepWrap.ts +116 -0
- package/src/engine/combat/sessionView.ts +442 -0
- package/src/engine/combat/simEffects.ts +32 -0
- package/src/engine/combat/simFacade.ts +620 -0
- package/src/engine/combat/simPump.ts +264 -0
- package/src/engine/combat/simSnapshot.ts +165 -0
- package/src/engine/combat/simThreadPool.ts +35 -0
- package/src/engine/combat/skeramImages.ts +57 -0
- package/src/engine/combat/slowUpdate.ts +178 -0
- package/src/engine/combat/snapToFloor.ts +42 -0
- package/src/engine/combat/speedAura.ts +29 -0
- package/src/engine/combat/spellReagents.ts +71 -0
- package/src/engine/combat/stealthDetect.ts +93 -0
- package/src/engine/combat/stepBackMotion.ts +171 -0
- package/src/engine/combat/stepSession.ts +3102 -0
- package/src/engine/combat/summonCast.ts +243 -0
- package/src/engine/combat/thaddiusPolarity.ts +97 -0
- package/src/engine/combat/threat.ts +388 -0
- package/src/engine/combat/threatOps.ts +272 -0
- package/src/engine/combat/tickWorkerBody.ts +211 -0
- package/src/engine/combat/totemAuras.ts +122 -0
- package/src/engine/combat/triggerPayload.ts +42 -0
- package/src/engine/combat/triggeredCompanion.ts +63 -0
- package/src/engine/combat/types.ts +1897 -0
- package/src/engine/combat/useObjectTrap.ts +140 -0
- package/src/engine/combat/walkSurface.ts +54 -0
- package/src/engine/combat/weapon.ts +1248 -0
- package/src/engine/combat/worldFrame.ts +43 -0
- package/src/engine/combat.solo-golden.json +2242 -0
- package/src/engine/combat.ts +84 -0
- package/src/engine/core/detMath.ts +289 -0
- package/src/engine/core/events.ts +357 -0
- package/src/engine/core/rng.ts +36 -0
- package/src/engine/game/actionBars.ts +124 -0
- package/src/engine/game/activateObjectFromSpell.ts +320 -0
- package/src/engine/game/applyActiveObjectFromDbScript.ts +98 -0
- package/src/engine/game/applyDeathAndSpellScripts.ts +268 -0
- package/src/engine/game/applyDoorFromDbScript.ts +388 -0
- package/src/engine/game/applyEscortFromDbScript.ts +290 -0
- package/src/engine/game/applyGoSpellcasterCast.ts +40 -0
- package/src/engine/game/applyHoverFromDbScript.ts +110 -0
- package/src/engine/game/applyOocResurrect.ts +71 -0
- package/src/engine/game/applySpellDurability.ts +115 -0
- package/src/engine/game/applyTailFromDbScript.ts +478 -0
- package/src/engine/game/applyTerminateFromDbScript.ts +95 -0
- package/src/engine/game/auctionMail.ts +269 -0
- package/src/engine/game/bagOverflow.ts +130 -0
- package/src/engine/game/bank.ts +110 -0
- package/src/engine/game/barAutofill.ts +26 -0
- package/src/engine/game/battleground.ts +369 -0
- package/src/engine/game/combatEnemySpec.ts +216 -0
- package/src/engine/game/combatObjectTraps.ts +167 -0
- package/src/engine/game/combatSeed.ts +320 -0
- package/src/engine/game/combatSession.ts +3241 -0
- package/src/engine/game/combatSessionClock.ts +62 -0
- package/src/engine/game/companionQuestActions.ts +242 -0
- package/src/engine/game/companionTraining.ts +40 -0
- package/src/engine/game/consumables.ts +624 -0
- package/src/engine/game/context.ts +53 -0
- package/src/engine/game/createHealthstone.ts +45 -0
- package/src/engine/game/deathFlow.ts +127 -0
- package/src/engine/game/devGrant.ts +117 -0
- package/src/engine/game/dynamicObject.ts +365 -0
- package/src/engine/game/enchantItem.ts +177 -0
- package/src/engine/game/equipmentHelper.ts +806 -0
- package/src/engine/game/factionState.ts +108 -0
- package/src/engine/game/garmentsOoc.ts +69 -0
- package/src/engine/game/globalActionBar.ts +179 -0
- package/src/engine/game/goInteractQuestCredit.ts +51 -0
- package/src/engine/game/goMoTransportUse.ts +61 -0
- package/src/engine/game/goTransportUse.ts +60 -0
- package/src/engine/game/healthstoneUpkeep.ts +59 -0
- package/src/engine/game/killSkin.ts +124 -0
- package/src/engine/game/learnFromItem.ts +118 -0
- package/src/engine/game/loadReconcile.ts +551 -0
- package/src/engine/game/loot.ts +294 -0
- package/src/engine/game/macros.ts +125 -0
- package/src/engine/game/mail.ts +293 -0
- package/src/engine/game/meetingStoneQueue.ts +75 -0
- package/src/engine/game/meetingStoneUse.ts +54 -0
- package/src/engine/game/newGameState.ts +363 -0
- package/src/engine/game/oocAuraInterrupt.ts +92 -0
- package/src/engine/game/oocCast.ts +241 -0
- package/src/engine/game/oocCastEffects.ts +803 -0
- package/src/engine/game/oocCastMethods.ts +1077 -0
- package/src/engine/game/openLockFromSpell.ts +174 -0
- package/src/engine/game/outdoorPvp.ts +187 -0
- package/src/engine/game/outfitter.ts +644 -0
- package/src/engine/game/paladinBoard.ts +393 -0
- package/src/engine/game/partyAuras.ts +121 -0
- package/src/engine/game/persistCombatAuras.ts +91 -0
- package/src/engine/game/pets.ts +459 -0
- package/src/engine/game/prePullHealthstones.ts +154 -0
- package/src/engine/game/professions.ts +2227 -0
- package/src/engine/game/pruneStaleRuntime.ts +39 -0
- package/src/engine/game/questFightCredit.ts +36 -0
- package/src/engine/game/questItemOnCorpse.ts +166 -0
- package/src/engine/game/questItemOnCreature.ts +103 -0
- package/src/engine/game/questLog.ts +879 -0
- package/src/engine/game/questSpellComplete.ts +21 -0
- package/src/engine/game/questTurnIn.ts +598 -0
- package/src/engine/game/raidParty.ts +32 -0
- package/src/engine/game/reagentBuyIn.ts +39 -0
- package/src/engine/game/regenRest.ts +186 -0
- package/src/engine/game/rotation.ts +425 -0
- package/src/engine/game/runFullDungeon.ts +611 -0
- package/src/engine/game/runSupply.ts +80 -0
- package/src/engine/game/sanitizeDriftedItems.ts +135 -0
- package/src/engine/game/scriptDevGoUse.ts +397 -0
- package/src/engine/game/scriptDevInnkeeper.ts +150 -0
- package/src/engine/game/scriptDevProfessionSpec.ts +99 -0
- package/src/engine/game/sendEventFromSpell.ts +87 -0
- package/src/engine/game/sortContainers.ts +85 -0
- package/src/engine/game/spellReputation.ts +69 -0
- package/src/engine/game/squad/squadDungeonSeating.ts +197 -0
- package/src/engine/game/squadDungeon.ts +4222 -0
- package/src/engine/game/squadQuestShare.ts +260 -0
- package/src/engine/game/summonObjectSlot.ts +69 -0
- package/src/engine/game/summonObjectWild.ts +73 -0
- package/src/engine/game/summoningRitualUse.ts +141 -0
- package/src/engine/game/talents.ts +356 -0
- package/src/engine/game/tamingRodUse.ts +66 -0
- package/src/engine/game/tempSpawnCreature.ts +136 -0
- package/src/engine/game/tickDungeonPatrolScripts.ts +205 -0
- package/src/engine/game/tickTimedQuests.ts +28 -0
- package/src/engine/game/trade.ts +586 -0
- package/src/engine/game/travel.ts +1155 -0
- package/src/engine/game/types.ts +1855 -0
- package/src/engine/game/vendorOffers.ts +244 -0
- package/src/engine/game/weaponImbueItem.ts +281 -0
- package/src/engine/game/worldSession.ts +183 -0
- package/src/engine/game/worldState.ts +166 -0
- package/src/engine/game.ts +968 -0
- package/src/engine/spells/GAP.md +67 -0
- package/src/engine/spells/absorbs.ts +376 -0
- package/src/engine/spells/addTargetTrigger.ts +53 -0
- package/src/engine/spells/auraPerSecondCost.ts +50 -0
- package/src/engine/spells/auraStacking.ts +497 -0
- package/src/engine/spells/cmangos-special-cases-combustion.ts +13 -0
- package/src/engine/spells/cmangos-special-cases-s18.ts +445 -0
- package/src/engine/spells/cmangos-special-cases-s19.ts +324 -0
- package/src/engine/spells/cmangos-special-cases-s20.ts +385 -0
- package/src/engine/spells/cmangos-special-cases-s21.ts +116 -0
- package/src/engine/spells/cmangos-special-cases-s22.ts +24 -0
- package/src/engine/spells/cmangos-special-cases-s23.ts +52 -0
- package/src/engine/spells/cmangos-special-cases.ts +1570 -0
- package/src/engine/spells/conditions.ts +718 -0
- package/src/engine/spells/effects.ts +203 -0
- package/src/engine/spells/fixtures/oracle-effects-lock9a.json +1 -0
- package/src/engine/spells/immunity.ts +942 -0
- package/src/engine/spells/intentEffects.ts +351 -0
- package/src/engine/spells/interpreter/GAPS.md +21 -0
- package/src/engine/spells/interpreter/amount.ts +109 -0
- package/src/engine/spells/interpreter/class-scripts-kit.ts +478 -0
- package/src/engine/spells/interpreter/diff.ts +246 -0
- package/src/engine/spells/interpreter/duration.ts +90 -0
- package/src/engine/spells/interpreter/form-attrs.json +1 -0
- package/src/engine/spells/interpreter/form-attrs.ts +95 -0
- package/src/engine/spells/interpreter/handlers/aura-remove.ts +113 -0
- package/src/engine/spells/interpreter/handlers/auras-control.ts +1547 -0
- package/src/engine/spells/interpreter/handlers/auras-core.ts +1583 -0
- package/src/engine/spells/interpreter/handlers/auras-misc.ts +1774 -0
- package/src/engine/spells/interpreter/handlers/effect-disenchant.ts +17 -0
- package/src/engine/spells/interpreter/handlers/effect-skinning.ts +20 -0
- package/src/engine/spells/interpreter/handlers/effects-action-utility.ts +1455 -0
- package/src/engine/spells/interpreter/handlers/effects-core.ts +432 -0
- package/src/engine/spells/interpreter/handlers/effects-remainder.ts +105 -0
- package/src/engine/spells/interpreter/handlers/effects-summon-item.ts +687 -0
- package/src/engine/spells/interpreter/handlers/mod-increase-energy-percent.ts +36 -0
- package/src/engine/spells/interpreter/handlers/mod-increase-energy.ts +36 -0
- package/src/engine/spells/interpreter/handlers/periodic-trigger-zero.ts +327 -0
- package/src/engine/spells/interpreter/handlers/procs.ts +131 -0
- package/src/engine/spells/interpreter/handlers/registry.ts +975 -0
- package/src/engine/spells/interpreter/handlers/shared.ts +667 -0
- package/src/engine/spells/interpreter/handlers.ts +9 -0
- package/src/engine/spells/interpreter/hunter-kit.ts +202 -0
- package/src/engine/spells/interpreter/index.ts +525 -0
- package/src/engine/spells/interpreter/item-triggers.ts +1121 -0
- package/src/engine/spells/interpreter/lift-consume-energy.ts +37 -0
- package/src/engine/spells/interpreter/mage-portals.ts +37 -0
- package/src/engine/spells/interpreter/materialize-charm.ts +88 -0
- package/src/engine/spells/interpreter/materialize-dispel-mechanic.ts +25 -0
- package/src/engine/spells/interpreter/materialize-rating-buffs.ts +81 -0
- package/src/engine/spells/interpreter/materialize-speed.ts +109 -0
- package/src/engine/spells/interpreter/materialize-spell-magnet.ts +22 -0
- package/src/engine/spells/interpreter/opcodes.ts +789 -0
- package/src/engine/spells/interpreter/paladin-kit.ts +95 -0
- package/src/engine/spells/interpreter/paladin-seals.ts +499 -0
- package/src/engine/spells/interpreter/priest-warlock-kit.ts +135 -0
- package/src/engine/spells/interpreter/proc.ts +443 -0
- package/src/engine/spells/interpreter/registry/helpers.ts +16 -0
- package/src/engine/spells/interpreter/registry/index.ts +216 -0
- package/src/engine/spells/interpreter/registry/packs/class-abilities.ts +304 -0
- package/src/engine/spells/interpreter/registry/packs/dire-maul.ts +44 -0
- package/src/engine/spells/interpreter/registry/packs/dummy-script-tail.ts +1065 -0
- package/src/engine/spells/interpreter/registry/packs/ex-wave-m.ts +70 -0
- package/src/engine/spells/interpreter/registry/packs/life-tap.ts +107 -0
- package/src/engine/spells/interpreter/registry/packs/paladin.ts +246 -0
- package/src/engine/spells/interpreter/registry/packs/raid-script-summons.ts +26 -0
- package/src/engine/spells/interpreter/registry/packs/raid-threat-wipe.ts +22 -0
- package/src/engine/spells/interpreter/registry/packs/shaman.ts +214 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-a.ts +269 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-b.ts +370 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-generic-c.ts +172 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-hunter.ts +60 -0
- package/src/engine/spells/interpreter/registry/packs/si4-dummy-warrior.ts +31 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-a.ts +114 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-b.ts +249 -0
- package/src/engine/spells/interpreter/registry/packs/si4-script-generic-c.ts +233 -0
- package/src/engine/spells/interpreter/registry/types.ts +48 -0
- package/src/engine/spells/interpreter/talent-scripted.ts +329 -0
- package/src/engine/spells/interpreter/targeting.ts +90 -0
- package/src/engine/spells/interpreter/types-intent.ts +1501 -0
- package/src/engine/spells/interpreter/types.ts +419 -0
- package/src/engine/spells/interpreter/warrior-druid-kit.ts +314 -0
- package/src/engine/spells/interpreter/weapon-imbue-kit.ts +384 -0
- package/src/engine/spells/interrupt.ts +217 -0
- package/src/engine/spells/meleeAttackReset.ts +60 -0
- package/src/engine/spells/oocTriggerSpell.ts +49 -0
- package/src/engine/spells/overrideClassScripts.ts +319 -0
- package/src/engine/spells/parryHaste.ts +20 -0
- package/src/engine/spells/procs.ts +300 -0
- package/src/engine/spells/recovery.ts +203 -0
- package/src/engine/spells/scriptTargetDest.ts +76 -0
- package/src/engine/spells/scriptTargets.ts +85 -0
- package/src/engine/spells/singleTargetAura.ts +82 -0
- package/src/engine/spells/specialCasePayloads.ts +57 -0
- package/src/engine/spells/spellAttributes.ts +249 -0
- package/src/engine/spells/spellBonus.ts +143 -0
- package/src/engine/spells/spellModIntents.ts +419 -0
- package/src/engine/spells/spellMods.ts +283 -0
- package/src/engine/spells/spellRadius.ts +15 -0
- package/src/engine/spells/trapObjects.ts +26 -0
- package/src/json.d.ts +7 -0
- package/src/persistence/memoryRepository.ts +23 -0
- package/src/persistence/saveBackups.ts +162 -0
- package/src/persistence/saveFile.ts +68 -0
- package/src/persistence/saveRepository.ts +11 -0
- package/src/persistence/saveShape.ts +668 -0
- package/src/progression/leveling.ts +89 -0
- package/src/progression/rest.ts +1434 -0
- package/src/progression/training.ts +226 -0
- package/src/progression/xpGain.ts +369 -0
- package/src/quests/quests.ts +1580 -0
- package/src/world/aoePull.ts +198 -0
- package/src/world/areaTableDbc.ts +39 -0
- package/src/world/areaTableMapIdPairs.ts +141 -0
- package/src/world/bakeSlug.ts +43 -0
- package/src/world/continuousDungeon.ts +1250 -0
- package/src/world/dungeonBakeProjection.ts +190 -0
- package/src/world/dungeonMapDensity.ts +342 -0
- package/src/world/dungeonMapProjection.ts +135 -0
- package/src/world/dungeonNav.ts +904 -0
- package/src/world/dungeonNpcSim.ts +147 -0
- package/src/world/dungeonObjectives.ts +234 -0
- package/src/world/dungeonPreset.ts +35 -0
- package/src/world/dungeonRoute.ts +311 -0
- package/src/world/dungeonRouteGeometry.ts +732 -0
- package/src/world/dungeonRouteOrder.ts +1037 -0
- package/src/world/dungeonWingRooms.ts +426 -0
- package/src/world/escort.ts +296 -0
- package/src/world/goPoolRoll.ts +75 -0
- package/src/world/instanceObjects.ts +785 -0
- package/src/world/instances.ts +1572 -0
- package/src/world/meetingStones.ts +56 -0
- package/src/world/navmeshFloors.ts +145 -0
- package/src/world/nearGrid.ts +167 -0
- package/src/world/packBudget.ts +613 -0
- package/src/world/rareRoll.ts +335 -0
- package/src/world/routeAuthoring.ts +132 -0
- package/src/world/routeEdit.ts +209 -0
- package/src/world/world.ts +812 -0
- package/src/world/zoneHazards.ts +47 -0
|
@@ -0,0 +1,1434 @@
|
|
|
1
|
+
// Out-of-combat regen and rested XP accrual. Cite: Player.cpp ComputeRest ~20000 (the 1152000 divisor,
|
|
2
|
+
// with the wilderness quarter rate); SetRestBonus ~16949 (the 75% cap).
|
|
3
|
+
import type { ActiveAura, Character } from '../domain/character/character.ts';
|
|
4
|
+
import {
|
|
5
|
+
applyAura,
|
|
6
|
+
applyInebriate,
|
|
7
|
+
effectiveStats,
|
|
8
|
+
equippedStatBonuses,
|
|
9
|
+
getActiveBuffIds,
|
|
10
|
+
currentMana,
|
|
11
|
+
maxHealth,
|
|
12
|
+
maxMana,
|
|
13
|
+
maxPower,
|
|
14
|
+
powerType,
|
|
15
|
+
pruneExpiredAuras,
|
|
16
|
+
} from '../domain/character/character.ts';
|
|
17
|
+
import { isPlayerAlive, YOU_ARE_DEAD, type DeathFlowState } from '../domain/run/death.ts';
|
|
18
|
+
import { classHasParallelMana } from '../domain/character/forms.ts';
|
|
19
|
+
import { racialSpells } from '../domain/character/races.ts';
|
|
20
|
+
|
|
21
|
+
/** OOC HoT bookkeeping on ActiveAura (optional; old saves omit; not a schema version bump). */
|
|
22
|
+
type OocHotAura = ActiveAura & {
|
|
23
|
+
nextTickRemainingMs?: number;
|
|
24
|
+
durationRemainingMs?: number;
|
|
25
|
+
};
|
|
26
|
+
import type { DataStore } from '../data/dataStore.ts';
|
|
27
|
+
import { hasModHealthRegenInCombat } from '../data/materializeModHealthRegen.ts';
|
|
28
|
+
import type { ItemDef } from '../data/schema.ts';
|
|
29
|
+
import type { Inventory } from '../domain/items/inventory.ts';
|
|
30
|
+
import { addItem, canFit, countItem, removeItem } from '../domain/items/inventory.ts';
|
|
31
|
+
import { bagCapacity } from '../domain/items/equipment.ts';
|
|
32
|
+
import { getDispelMask } from '../domain/spells/dispel.ts';
|
|
33
|
+
import { MECHANIC_MOUNT, peelMechanicAuras } from '../domain/spells/dispelMechanic.ts';
|
|
34
|
+
import { dismount } from '../domain/items/mounts.ts';
|
|
35
|
+
import { equippedEntry, equippedInstance } from '../domain/items/itemInstance.ts';
|
|
36
|
+
import { castItemSurvivesUse, consumeItemCharges, hasChargesLeft, isExpendableCastItem, maxChargesOf, NO_CHARGES_REMAIN } from '../domain/items/inventory.ts';
|
|
37
|
+
import { itemTeachIntent } from '../domain/items/itemTeaches.ts';
|
|
38
|
+
import {
|
|
39
|
+
petMaxHealth, petMaxPower, petHasHappiness, decayHappiness, tickLoyalty, tickFeeding,
|
|
40
|
+
LOYALTY_REBELLIOUS,
|
|
41
|
+
} from '../domain/pet/pet.ts';
|
|
42
|
+
import {
|
|
43
|
+
healthRegenPerSec,
|
|
44
|
+
updateManaRegen,
|
|
45
|
+
powerRegenPercentMultiplier,
|
|
46
|
+
rageDecayAmount,
|
|
47
|
+
petOocManaRegenPerSec,
|
|
48
|
+
TUNING,
|
|
49
|
+
} from '../engine/combat/formulas.ts';
|
|
50
|
+
import { powerTypeName, resolvePeriodicPctOfMax } from '../engine/spells/effects.ts';
|
|
51
|
+
import { resolveCombatSpell } from '../engine/spells/interpreter/item-triggers.ts';
|
|
52
|
+
import { manaPerSecondOf } from '../engine/spells/auraPerSecondCost.ts';
|
|
53
|
+
import { durMs } from '../engine/spells/interpreter/duration.ts';
|
|
54
|
+
import {
|
|
55
|
+
refuseCmangosCheckCast,
|
|
56
|
+
RECENTLY_BANDAGED_SPELL_ID,
|
|
57
|
+
} from '../engine/combat/cmangosCastGates.ts';
|
|
58
|
+
|
|
59
|
+
// ComputeRest: every eight hours in a rest zone gains one bubble of 5% of the level's XP, as the time
|
|
60
|
+
// passed times the next level's XP over 1152000. Cite: Player.cpp:20000-20008.
|
|
61
|
+
export function computeRestGain(
|
|
62
|
+
logoutSec: number,
|
|
63
|
+
isCityOrInn: boolean,
|
|
64
|
+
rate = 1,
|
|
65
|
+
nextLevelXp = 0,
|
|
66
|
+
opts?: { offline?: boolean },
|
|
67
|
+
): number {
|
|
68
|
+
// NaN <= 0 is false — must reject non-finite or the gain (and restedXp) become NaN (KI-783).
|
|
69
|
+
if (!Number.isFinite(logoutSec) || logoutSec <= 0 || !Number.isFinite(nextLevelXp) || nextLevelXp <= 0) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
let bonus = logoutSec * (nextLevelXp / 1152000.0);
|
|
73
|
+
const offline = opts?.offline ?? true;
|
|
74
|
+
if (!offline) {
|
|
75
|
+
bonus *= rate; // CONFIG_FLOAT_RATE_REST_INGAME
|
|
76
|
+
} else if (isCityOrInn) {
|
|
77
|
+
bonus *= rate; // CONFIG_FLOAT_RATE_REST_OFFLINE_IN_TAVERN_OR_CITY
|
|
78
|
+
} else {
|
|
79
|
+
bonus *= rate / 4.0; // wilderness offline reduced by 4
|
|
80
|
+
}
|
|
81
|
+
return Number.isFinite(bonus) ? bonus : 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** SetRestBonus max: nextLevelXp * 1.5 / 2 = 0.75 * nextLevelXp (Player.cpp ~16949). */
|
|
85
|
+
export function restBonusCap(nextLevelXp: number): number {
|
|
86
|
+
if (!Number.isFinite(nextLevelXp) || nextLevelXp <= 0) return 0;
|
|
87
|
+
return (nextLevelXp * 1.5) / 2.0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Clamp rested pool into [0, cap]. Non-finite input → 0 (KI-783). */
|
|
91
|
+
export function clampRestBonus(restBonus: number, nextLevelXp: number): number {
|
|
92
|
+
const cap = restBonusCap(nextLevelXp);
|
|
93
|
+
if (!Number.isFinite(restBonus) || restBonus < 0) return 0;
|
|
94
|
+
if (restBonus > cap) return cap;
|
|
95
|
+
return restBonus;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Out-of-combat periodic heal and energize auras, on the same seconds budget as spirit regen. The
|
|
99
|
+
// duration is tracked on the aura, so wall-clock drift between calls cannot drop the last tick.
|
|
100
|
+
export function tickOocRestoreAuras(
|
|
101
|
+
char: Character,
|
|
102
|
+
ds: DataStore,
|
|
103
|
+
seconds: number,
|
|
104
|
+
now: number = Date.now(),
|
|
105
|
+
): Character {
|
|
106
|
+
if (!(seconds > 0)) return char;
|
|
107
|
+
const aurasIn = char.activeAuras;
|
|
108
|
+
if (!aurasIn?.length) return char;
|
|
109
|
+
|
|
110
|
+
const mh = maxHealth(char, ds);
|
|
111
|
+
const mp = maxPower(char, ds);
|
|
112
|
+
let health = char.health;
|
|
113
|
+
let power = char.power;
|
|
114
|
+
let petHealth = char.pet?.health;
|
|
115
|
+
let any = false;
|
|
116
|
+
const out: ActiveAura[] = [];
|
|
117
|
+
|
|
118
|
+
for (const rawA of aurasIn) {
|
|
119
|
+
const a = rawA as OocHotAura;
|
|
120
|
+
// Prefer the RESOLVED spell when the row has raw effects: the bake stores periodic amounts in
|
|
121
|
+
// internal units, so Bloodrage's rage tick granted ten a second straight off the bake.
|
|
122
|
+
const bakeSp = ds.spell(a.spellId);
|
|
123
|
+
const sp = bakeSp?.rawEffects?.length ? resolveCombatSpell(bakeSp) : bakeSp;
|
|
124
|
+
if (!sp) {
|
|
125
|
+
out.push(a);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// Every discrete restore on the aura — Dreamless Sleep carries both on one spell. The always-on
|
|
129
|
+
// regen aura is folded into the rate below, and a hostile mana leech never self-ticks here.
|
|
130
|
+
type RestoreEff = {
|
|
131
|
+
kind: 'restore_health' | 'restore_power';
|
|
132
|
+
amountPerTick?: number;
|
|
133
|
+
tickMs?: number;
|
|
134
|
+
durationMs?: number;
|
|
135
|
+
pctOfMax?: number | null;
|
|
136
|
+
applyTo?: 'self' | 'pet' | 'friendly' | 'party';
|
|
137
|
+
};
|
|
138
|
+
const restores: RestoreEff[] = [];
|
|
139
|
+
for (const e of sp.effects ?? []) {
|
|
140
|
+
if (e.kind !== 'restore_health' && e.kind !== 'restore_power') continue;
|
|
141
|
+
if (e.kind === 'restore_power' && (e.amountPerTick ?? 0) < 0 && e.pctOfMax == null) continue;
|
|
142
|
+
if (e.kind === 'restore_health' && hasModHealthRegenInCombat(sp)) continue;
|
|
143
|
+
restores.push(e as RestoreEff);
|
|
144
|
+
}
|
|
145
|
+
if (!restores.length) {
|
|
146
|
+
out.push(a);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const he0 = restores[0]!;
|
|
150
|
+
const tickMs = Math.max(1, (he0.tickMs ?? 0) | 0);
|
|
151
|
+
let full = 0;
|
|
152
|
+
for (const r of restores) {
|
|
153
|
+
if ((r.durationMs ?? 0) > full) full = r.durationMs ?? 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Wall-clock hard stop: aura fully past expiresAt (player AFK'd past duration).
|
|
157
|
+
if (a.expiresAt != null && a.expiresAt <= now) {
|
|
158
|
+
any = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Prefer the stored duration budget so repeated small ticks are exact. A fresh cast is a few ms
|
|
163
|
+
// short of full, which would drop the final tick, so snap to full within a quarter second.
|
|
164
|
+
let durRem = a.durationRemainingMs;
|
|
165
|
+
if (durRem == null || !(durRem > 0)) {
|
|
166
|
+
const wall = a.expiresAt != null ? Math.max(0, a.expiresAt - now) : 0;
|
|
167
|
+
if (full > 0 && (wall <= 0 || wall + 250 >= full)) {
|
|
168
|
+
durRem = full;
|
|
169
|
+
} else {
|
|
170
|
+
durRem = wall;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (durRem <= 0) {
|
|
174
|
+
any = true;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const processMs = Math.min(Math.round(seconds * 1000), durRem);
|
|
179
|
+
if (processMs <= 0) {
|
|
180
|
+
out.push(a);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let nextTick = a.nextTickRemainingMs != null && a.nextTickRemainingMs > 0
|
|
185
|
+
? a.nextTickRemainingMs
|
|
186
|
+
: tickMs;
|
|
187
|
+
let t = 0;
|
|
188
|
+
while (t < processMs) {
|
|
189
|
+
const step = Math.min(nextTick, processMs - t);
|
|
190
|
+
t += step;
|
|
191
|
+
nextTick -= step;
|
|
192
|
+
if (nextTick > 0) continue;
|
|
193
|
+
// Fire every restore on this tick, flat or as a percent of maximum. A tick at the duration's end
|
|
194
|
+
// is intentional: a 12 s Rejuvenation ticks at 3, 6, 9 and 12.
|
|
195
|
+
for (const he of restores) {
|
|
196
|
+
if (he.kind === 'restore_health') {
|
|
197
|
+
const toPet = he.applyTo === 'pet' || sp.role === 'pet_heal';
|
|
198
|
+
if (toPet) {
|
|
199
|
+
const pet = char.pet;
|
|
200
|
+
if (pet && (pet.health ?? 0) > 0 && petHealth != null) {
|
|
201
|
+
const petMax = petMaxHealth(char, ds);
|
|
202
|
+
const raw = he.pctOfMax != null && he.pctOfMax > 0
|
|
203
|
+
? resolvePeriodicPctOfMax({ maxResource: petMax, pctOfMax: he.pctOfMax })
|
|
204
|
+
: Math.round(he.amountPerTick ?? 0);
|
|
205
|
+
const amt = he.pctOfMax != null && he.pctOfMax > 0 ? raw : Math.max(1, raw);
|
|
206
|
+
if (amt > 0 && petHealth < petMax) {
|
|
207
|
+
petHealth = Math.min(petMax, Math.round(petHealth + amt));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// ManaPerSecond POWER_HEALTH — owner pays after the pet tick.
|
|
211
|
+
// Cite: SpellAuras.cpp:5686–5707; Health Funnel 755 = 5/sec.
|
|
212
|
+
const mps = manaPerSecondOf(sp);
|
|
213
|
+
if (mps > 0 && sp.powerType === 'health') {
|
|
214
|
+
const cost = Math.max(1, Math.round(mps * tickMs / 1000));
|
|
215
|
+
if (health <= cost) {
|
|
216
|
+
any = true;
|
|
217
|
+
nextTick = 0;
|
|
218
|
+
t = processMs;
|
|
219
|
+
durRem = processMs;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
health -= cost;
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
const raw = he.pctOfMax != null && he.pctOfMax > 0
|
|
226
|
+
? resolvePeriodicPctOfMax({ maxResource: mh, pctOfMax: he.pctOfMax })
|
|
227
|
+
: Math.round(he.amountPerTick ?? 0);
|
|
228
|
+
const amt = he.pctOfMax != null && he.pctOfMax > 0 ? raw : Math.max(1, raw);
|
|
229
|
+
if (amt > 0 && health < mh) {
|
|
230
|
+
health = Math.min(mh, Math.round(health + amt));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
const raw = he.pctOfMax != null && he.pctOfMax > 0
|
|
235
|
+
? resolvePeriodicPctOfMax({ maxResource: mp, pctOfMax: he.pctOfMax })
|
|
236
|
+
: Math.round(he.amountPerTick ?? 0);
|
|
237
|
+
const amt = he.pctOfMax != null && he.pctOfMax > 0 ? raw : Math.max(1, raw);
|
|
238
|
+
if (amt > 0 && power < mp) {
|
|
239
|
+
power = Math.min(mp, Math.round(power + amt));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
nextTick = tickMs;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const remainingAfter = durRem - processMs;
|
|
247
|
+
any = true;
|
|
248
|
+
if (remainingAfter > 0) {
|
|
249
|
+
const next: OocHotAura = {
|
|
250
|
+
spellId: a.spellId,
|
|
251
|
+
expiresAt: now + remainingAfter,
|
|
252
|
+
...(a.charges != null ? { charges: a.charges } : {}),
|
|
253
|
+
nextTickRemainingMs: nextTick,
|
|
254
|
+
durationRemainingMs: remainingAfter,
|
|
255
|
+
};
|
|
256
|
+
out.push(next);
|
|
257
|
+
}
|
|
258
|
+
// else: duration fully consumed — drop aura
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const petChanged = petHealth != null && char.pet != null && petHealth !== char.pet.health;
|
|
262
|
+
if (!any && health === char.health && power === char.power && !petChanged) return char;
|
|
263
|
+
const pruned = pruneExpiredAuras(out, now);
|
|
264
|
+
return {
|
|
265
|
+
...char,
|
|
266
|
+
health,
|
|
267
|
+
power,
|
|
268
|
+
...(petChanged && char.pet
|
|
269
|
+
? { pet: { ...char.pet, health: petHealth! } }
|
|
270
|
+
: {}),
|
|
271
|
+
activeAuras: pruned,
|
|
272
|
+
activeBuffs: getActiveBuffIds({ ...char, activeAuras: pruned }, now, ds),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Continuous out-of-combat regen for a number of seconds, off spirit and the class tables, with sitting
|
|
277
|
+
// multiplying health regen. Below: advance the seated well-fed gate, which standing cancels.
|
|
278
|
+
export function tickPendingWellFed(
|
|
279
|
+
char: Character,
|
|
280
|
+
ds: DataStore,
|
|
281
|
+
seconds: number,
|
|
282
|
+
sitting: boolean,
|
|
283
|
+
now: number = Date.now(),
|
|
284
|
+
): Character {
|
|
285
|
+
const pending = char.pendingWellFed;
|
|
286
|
+
if (!pending || !(pending.triggerSpellId > 0) || !(pending.needMs > 0)) return char;
|
|
287
|
+
if (!sitting) {
|
|
288
|
+
if (char.pendingWellFed === undefined) return char;
|
|
289
|
+
const { pendingWellFed: _drop, ...rest } = char;
|
|
290
|
+
return rest as Character;
|
|
291
|
+
}
|
|
292
|
+
const elapsed = (pending.elapsedMs || 0) + Math.max(0, seconds) * 1000;
|
|
293
|
+
if (elapsed < pending.needMs) {
|
|
294
|
+
return { ...char, pendingWellFed: { ...pending, elapsedMs: elapsed } };
|
|
295
|
+
}
|
|
296
|
+
// Threshold met — cast the triggered Well Fed / Increased Stat spell.
|
|
297
|
+
const dur = pending.durationMs > 0 ? pending.durationMs : 900_000;
|
|
298
|
+
const staBefore = effectiveStats(char, ds).sta;
|
|
299
|
+
let c = applyAura(char, pending.triggerSpellId, dur, now, ds);
|
|
300
|
+
const { pendingWellFed: _done, ...cleared } = c;
|
|
301
|
+
c = cleared as Character;
|
|
302
|
+
// +Stamina raises max HP; bump current by the delta so the bar does not look empty.
|
|
303
|
+
// Cite: game.consumeItem stamina buff path; TUNING.hpPerStamina.
|
|
304
|
+
const staGain = effectiveStats(c, ds).sta - staBefore;
|
|
305
|
+
if (staGain > 0) {
|
|
306
|
+
const mh = maxHealth(c, ds);
|
|
307
|
+
c = { ...c, health: Math.min(mh, c.health + Math.round(staGain * TUNING.hpPerStamina)) };
|
|
308
|
+
}
|
|
309
|
+
return c;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Sit while injured, drinking mana, or waiting on a food well-fed gate. Travel and death stand. */
|
|
313
|
+
export function shouldSitForOocRegen(char: Character, ds: DataStore): boolean {
|
|
314
|
+
if (char.health <= 0 || char.journey) return false;
|
|
315
|
+
if (char.pendingWellFed) return true;
|
|
316
|
+
const mh = maxHealth(char, ds);
|
|
317
|
+
if (mh > 0 && char.health < mh) return true;
|
|
318
|
+
if (powerType(char) === 'mana' || classHasParallelMana(char.cls)) {
|
|
319
|
+
const mp = maxMana(char, ds);
|
|
320
|
+
if (mp > 0 && currentMana(char) < mp) return true;
|
|
321
|
+
}
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** PERIODIC_TRIGGER well-fed on a food/drink use spell. Cite: SpellAuras.cpp:4993 TriggerSpell. */
|
|
326
|
+
export function wellFedPendingFromItem(
|
|
327
|
+
item: ItemDef | undefined,
|
|
328
|
+
ds: DataStore,
|
|
329
|
+
): Character['pendingWellFed'] {
|
|
330
|
+
if (!item?.useSpell) return undefined;
|
|
331
|
+
const raw = ds.spell(item.useSpell);
|
|
332
|
+
if (!raw) return undefined;
|
|
333
|
+
return pendingWellFedFromEffects(resolveCombatSpell(raw).effects ?? [], ds);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Lump-sum eat already finished the channel, so the 10s amplitude has elapsed. */
|
|
337
|
+
export function applyWellFedFromItem(
|
|
338
|
+
char: Character,
|
|
339
|
+
item: ItemDef | undefined,
|
|
340
|
+
ds: DataStore,
|
|
341
|
+
now?: number,
|
|
342
|
+
): Character {
|
|
343
|
+
const pending = wellFedPendingFromItem(item, ds);
|
|
344
|
+
if (!pending) return char;
|
|
345
|
+
return tickPendingWellFed(
|
|
346
|
+
{ ...char, pendingWellFed: pending },
|
|
347
|
+
ds,
|
|
348
|
+
pending.needMs / 1000,
|
|
349
|
+
true,
|
|
350
|
+
now,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function grantSeatedWellFed(
|
|
355
|
+
char: Character,
|
|
356
|
+
ds: DataStore,
|
|
357
|
+
pending: NonNullable<Character['pendingWellFed']>,
|
|
358
|
+
now?: number,
|
|
359
|
+
): Character {
|
|
360
|
+
return tickPendingWellFed(
|
|
361
|
+
{ ...char, pendingWellFed: pending },
|
|
362
|
+
ds,
|
|
363
|
+
pending.needMs / 1000,
|
|
364
|
+
true,
|
|
365
|
+
now,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// The product of the health-regen percent auras, out of combat only, from racial passives and live
|
|
370
|
+
// auras. Cite: Player.cpp:2395-2398; Troll Regeneration 20555.
|
|
371
|
+
export function healthRegenPercentMultiplier(char: Character, ds: DataStore): number {
|
|
372
|
+
let mult = 1;
|
|
373
|
+
const seen = new Set<number>();
|
|
374
|
+
const fold = (spellId: number): void => {
|
|
375
|
+
const id = spellId | 0;
|
|
376
|
+
if (!id || seen.has(id)) return;
|
|
377
|
+
seen.add(id);
|
|
378
|
+
const raw = ds.spell(id);
|
|
379
|
+
if (!raw) return;
|
|
380
|
+
const sp = resolveCombatSpell(raw, { casterLevel: char.level });
|
|
381
|
+
for (const e of sp.effects ?? []) {
|
|
382
|
+
if (e.kind !== 'mod_health_regen_percent') continue;
|
|
383
|
+
const amt = e.amount | 0;
|
|
384
|
+
if (amt) mult *= (100 + amt) / 100;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
for (const s of racialSpells(ds, char.race)) fold(s.id);
|
|
388
|
+
for (const a of pruneExpiredAuras(char.activeAuras)) fold(a.spellId);
|
|
389
|
+
return mult;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/** HasAuraType(94) skips OOC rage decay; 5229/29131 dummy combat also skips health regen. */
|
|
393
|
+
function interruptRegenFromAuras(char: Character, ds: DataStore): {
|
|
394
|
+
skipRageDecay: boolean;
|
|
395
|
+
dummyCombat: boolean;
|
|
396
|
+
} {
|
|
397
|
+
let skipRageDecay = false;
|
|
398
|
+
let dummyCombat = false;
|
|
399
|
+
for (const a of pruneExpiredAuras(char.activeAuras)) {
|
|
400
|
+
const raw = ds.spell(a.spellId);
|
|
401
|
+
if (!raw) continue;
|
|
402
|
+
const sp = resolveCombatSpell(raw, { casterLevel: char.level });
|
|
403
|
+
for (const e of sp.effects ?? []) {
|
|
404
|
+
if (e.kind !== 'interrupt_regen') continue;
|
|
405
|
+
skipRageDecay = true;
|
|
406
|
+
if (e.setInDummyCombatState) dummyCombat = true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return { skipRageDecay, dummyCombat };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function regen(
|
|
413
|
+
char: Character,
|
|
414
|
+
ds: DataStore,
|
|
415
|
+
seconds: number,
|
|
416
|
+
sitting: boolean,
|
|
417
|
+
deathFlow?: Pick<DeathFlowState, 'deathState'> | null,
|
|
418
|
+
): Character {
|
|
419
|
+
// Reject NaN/±Inf/negative: rate×seconds would poison health/power (KI-783). Zero is a no-op.
|
|
420
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return char;
|
|
421
|
+
// Dead/ghost: no OOC regen (KI-906 / KI-948). Cite: Player.cpp:1515–1519 if IsAlive();
|
|
422
|
+
// ghost keeps SetHealth(1) (Player.cpp:4288 BuildPlayerRepop) so HP-only is wrong.
|
|
423
|
+
if (!isPlayerAlive(char.health, deathFlow)) return char;
|
|
424
|
+
let c: Character = { ...char };
|
|
425
|
+
// Well-fed gate first so a same-tick threshold lands before spirit/HoT math.
|
|
426
|
+
c = tickPendingWellFed(c, ds, seconds, sitting);
|
|
427
|
+
const spi = effectiveStats(c, ds).spi;
|
|
428
|
+
const mult = sitting ? TUNING.sitRegenMult : 1;
|
|
429
|
+
const mh = maxHealth(c, ds);
|
|
430
|
+
// Cite: SpellAuras.cpp:5111–5119; Player.cpp:2298–2304; Unit.cpp:7886–7894.
|
|
431
|
+
const interruptRegen = interruptRegenFromAuras(c, ds);
|
|
432
|
+
// Health regen: the spirit table times the sitting multiplier, plus the always-on flat auras, whose
|
|
433
|
+
// baked amount is health per five seconds. A dummy combat state skips the spirit half.
|
|
434
|
+
if (c.health < mh && !interruptRegen.dummyCombat) {
|
|
435
|
+
// OOC: spirit × MOD_HEALTH_REGEN_PERCENT (88) × sit. Cite: Player.cpp:2390–2403.
|
|
436
|
+
let hpPerSec = healthRegenPerSec(spi, c.level, c.cls) * healthRegenPercentMultiplier(c, ds) * mult;
|
|
437
|
+
for (const a of pruneExpiredAuras(c.activeAuras)) {
|
|
438
|
+
const sp = ds.spell(a.spellId);
|
|
439
|
+
if (!sp) continue;
|
|
440
|
+
// Only the always-on modifiers belong in the rate: the periodic heal auras are ticked discretely
|
|
441
|
+
// below, and folding them in here too healed them twice.
|
|
442
|
+
if (!hasModHealthRegenInCombat(sp)) continue;
|
|
443
|
+
for (const e of sp.effects) {
|
|
444
|
+
if (e.kind !== 'restore_health') continue;
|
|
445
|
+
// Aura 161 amount is HP per 5 sec when amp is unset. Cite: materializeModHealthRegen.
|
|
446
|
+
const tickMs = e.tickMs > 0 ? e.tickMs : 5000;
|
|
447
|
+
const amt = e.amountPerTick | 0;
|
|
448
|
+
if (amt > 0) hpPerSec += amt / (tickMs / 1000);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
c.health = Math.min(mh, Math.round(c.health + hpPerSec * seconds));
|
|
452
|
+
}
|
|
453
|
+
const pt = powerType(c);
|
|
454
|
+
// Mana always regenerates, even when the active power is rage or energy: it is the parallel pool for
|
|
455
|
+
// a multi-power class and the primary bar otherwise. Cite: Player.cpp:2304-2306.
|
|
456
|
+
{
|
|
457
|
+
const usesPrimaryMana = pt === 'mana';
|
|
458
|
+
const parallelMana = !usesPrimaryMana && classHasParallelMana(c.cls);
|
|
459
|
+
if (usesPrimaryMana || parallelMana) {
|
|
460
|
+
const mp = usesPrimaryMana ? maxPower(c, ds) : maxMana(c, ds);
|
|
461
|
+
const cur = usesPrimaryMana ? c.power : (c.mana ?? 0);
|
|
462
|
+
if (cur < mp) {
|
|
463
|
+
let mp5 = equippedStatBonuses(c, ds).mp5 ?? 0;
|
|
464
|
+
const pctAmounts: number[] = [];
|
|
465
|
+
let interruptPct = 0;
|
|
466
|
+
for (const a of pruneExpiredAuras(c.activeAuras)) {
|
|
467
|
+
const raw = ds.spell(a.spellId);
|
|
468
|
+
if (!raw) continue;
|
|
469
|
+
const sp = resolveCombatSpell(raw, { casterLevel: c.level });
|
|
470
|
+
for (const e of sp.effects) {
|
|
471
|
+
if (e.kind !== 'buff') continue;
|
|
472
|
+
if (e.mod === 'mp5') mp5 += e.amount | 0;
|
|
473
|
+
else if (e.mod === 'mana_regen_pct') pctAmounts.push(e.amount | 0);
|
|
474
|
+
else if (e.mod === 'mana_regen_interrupt') interruptPct += e.amount | 0;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// OOC standing uses full spirit (interrupt only under 5SR / casting).
|
|
478
|
+
// Cite: Player.cpp Regenerate POWER_MANA; IsUnderLastManaUseEffect; StatSystem.cpp:547–564.
|
|
479
|
+
const mult = powerRegenPercentMultiplier(pctAmounts);
|
|
480
|
+
const { modManaRegen } = updateManaRegen(c.cls, spi, mp5, interruptPct, mult);
|
|
481
|
+
const next = Math.min(mp, Math.round(cur + modManaRegen * seconds));
|
|
482
|
+
if (usesPrimaryMana) c.power = next;
|
|
483
|
+
else c.mana = next;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (pt === 'energy') {
|
|
488
|
+
// Energy regens in and out of combat at a flat tick (CMaNGOS Player::Regenerate POWER_ENERGY).
|
|
489
|
+
// Not spirit-based; sitting does not change the rate. 10 energy/sec → full in ~10s from empty.
|
|
490
|
+
const mp = maxPower(c, ds);
|
|
491
|
+
if (c.power < mp) c.power = Math.min(mp, Math.round(c.power + TUNING.energyPerSec * seconds));
|
|
492
|
+
} else if (pt === 'rage' && c.power > 0) {
|
|
493
|
+
// HasAuraType(INTERRUPT_REGEN) skips OOC rage decay. Cite: Player.cpp:2303–2304, :2337–2340.
|
|
494
|
+
if (!interruptRegen.skipRageDecay) {
|
|
495
|
+
const lost = rageDecayAmount(Math.round(seconds * 1000));
|
|
496
|
+
c.power = Math.max(0, Math.round((c.power - lost) * 10) / 10);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// The periodic heal and energize auras, after spirit regen so their deltas stack on top of it. An
|
|
501
|
+
// inline copy of this loop once ran beside it and ticked every HoT twice — measured 32 against 64.
|
|
502
|
+
c = tickOocRestoreAuras(c, ds, seconds);
|
|
503
|
+
// Pet health and power out of combat: power persists between fights and refills here, with happiness
|
|
504
|
+
// decay and the loyalty tick for a hunter pet. Cite: Pet.cpp RegenerateAll ~698-708.
|
|
505
|
+
if (c.pet && c.pet.health > 0) {
|
|
506
|
+
const pmh = petMaxHealth(c, ds);
|
|
507
|
+
let next = c.pet;
|
|
508
|
+
if (c.pet.health < pmh) {
|
|
509
|
+
next = {
|
|
510
|
+
...next,
|
|
511
|
+
health: Math.min(pmh, Math.round(c.pet.health + healthRegenPerSec(spi, c.level, c.cls) * mult * seconds)),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
const cdef = ds.creature(c.pet.creature);
|
|
515
|
+
if (cdef) {
|
|
516
|
+
const { powerType: ppt, maxPower: pmp } = petMaxPower(c, cdef, ds);
|
|
517
|
+
if (ppt === 'focus' && pmp > 0) {
|
|
518
|
+
const cur = next.power ?? pmp;
|
|
519
|
+
if (cur < pmp) {
|
|
520
|
+
// 24 focus per 4s → 6/sec at Rate.Focus 1.0
|
|
521
|
+
const perSec = TUNING.focusPer4Sec / (TUNING.focusRegenPeriodMs / 1000);
|
|
522
|
+
next = { ...next, power: Math.min(pmp, Math.round(cur + perSec * seconds)) };
|
|
523
|
+
}
|
|
524
|
+
} else if (ppt === 'mana' && pmp > 0) {
|
|
525
|
+
const cur = next.power ?? pmp;
|
|
526
|
+
if (cur < pmp) {
|
|
527
|
+
// maxValue/3.0 per 4s quantum → per-sec (mirrors focus OOC conversion).
|
|
528
|
+
// source: Creature.cpp:830; Pet.cpp:698–708
|
|
529
|
+
const perSec = petOocManaRegenPerSec(pmp);
|
|
530
|
+
next = { ...next, power: Math.min(pmp, Math.round(cur + perSec * seconds)) };
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Hunter pets only: eat absorption, then OOC happiness decay + loyalty tick.
|
|
534
|
+
if (petHasHappiness(cdef)) {
|
|
535
|
+
// Feed Pet Effect 1539: an in-progress feeding restores its benefit over 20s
|
|
536
|
+
// (10 ticks) — absorbed here, cancelled by game.ts when a fight begins.
|
|
537
|
+
next = tickFeeding(next, seconds);
|
|
538
|
+
const loyalty = next.loyalty ?? LOYALTY_REBELLIOUS;
|
|
539
|
+
const h = decayHappiness(next.happiness, loyalty, seconds, false);
|
|
540
|
+
const ownerXp = ds.xpToNext(c.level);
|
|
541
|
+
next = tickLoyalty(
|
|
542
|
+
{ ...next, happiness: h },
|
|
543
|
+
seconds,
|
|
544
|
+
c.level,
|
|
545
|
+
Number.isFinite(ownerXp) ? ownerXp : undefined,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (next !== c.pet) c.pet = next;
|
|
550
|
+
}
|
|
551
|
+
return c;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export interface ConsumeResult {
|
|
555
|
+
ok: boolean;
|
|
556
|
+
reason?: string;
|
|
557
|
+
char: Character;
|
|
558
|
+
inventory: Inventory;
|
|
559
|
+
restored: number;
|
|
560
|
+
kind?: 'food' | 'drink';
|
|
561
|
+
// For useBuff consumables (elixirs etc.): the applied buff details (duration from DB)
|
|
562
|
+
buff?: { mod: string; amount: number; durationMs: number };
|
|
563
|
+
// The poison and sharpening-stone path, where the item's use spell is a temporary weapon imbue. The
|
|
564
|
+
// game layer applies it to the chosen hand. Cite: SpellEffects.cpp EffectEnchantItemTmp.
|
|
565
|
+
weaponImbueSpellId?: number;
|
|
566
|
+
// The armor kit and arcanum path, a permanent enchant. Like the imbue it DECIDES without applying:
|
|
567
|
+
// consume() does not know which slot the player picked, or even whose gear it is.
|
|
568
|
+
permEnchant?: { spellId: number; enchantId: number };
|
|
569
|
+
// True when this was permanent on-use gear (trinket, etc.): effect applied, item kept. Game.consumeItem
|
|
570
|
+
// may start item/spell cooldown from baked data when present.
|
|
571
|
+
permanentUse?: boolean;
|
|
572
|
+
// Multi-aura chips — utility potions, food secondaries and trigger parents. When set, the game layer
|
|
573
|
+
// applies each instead of the single use-spell path.
|
|
574
|
+
applyAuras?: Array<{ spellId: number; durationMs?: number }>;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// On-use gear that keeps its stack slot through a use: unlimited (0) or a POSITIVE charge count, which
|
|
578
|
+
// runs out without being destroyed. Cite: Spell.cpp:4209 TakeCastItem — `expendable` is negative-only.
|
|
579
|
+
export function isPermanentOnUseGear(it: ItemDef | null | undefined): boolean {
|
|
580
|
+
return it != null && castItemSurvivesUse(it);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// A permanent tool held in bags rather than worn — a shovel, a blackjack, a lock key. It is never
|
|
584
|
+
// destroyed, and the equip requirement applies only to equippable types, so it works from the bag.
|
|
585
|
+
function isPermanentBagTool(it: ItemDef | null | undefined): boolean {
|
|
586
|
+
return it != null && it.slot == null && castItemSurvivesUse(it);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** True when `itemEntry` occupies any paperdoll slot. */
|
|
590
|
+
export function isItemEquipped(char: Character, itemEntry: number): boolean {
|
|
591
|
+
const eq = char.equipped;
|
|
592
|
+
if (!eq) return false;
|
|
593
|
+
for (const v of Object.values(eq)) {
|
|
594
|
+
if (equippedEntry(v) === itemEntry) return true;
|
|
595
|
+
}
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Charges left on the unit about to be used — the worn instance when it is worn, else the first bag
|
|
600
|
+
// stack. No counter yet means a fresh unit at the template maximum.
|
|
601
|
+
function remainingChargesOf(
|
|
602
|
+
char: Character,
|
|
603
|
+
inv: Inventory,
|
|
604
|
+
itemEntry: number,
|
|
605
|
+
max: number,
|
|
606
|
+
): number {
|
|
607
|
+
for (const v of Object.values(char.equipped ?? {})) {
|
|
608
|
+
if (equippedEntry(v) !== itemEntry) continue;
|
|
609
|
+
return equippedInstance(v)?.charges ?? max;
|
|
610
|
+
}
|
|
611
|
+
const stack = (inv.stacks ?? []).find((st) => st.item === itemEntry);
|
|
612
|
+
return stack?.charges ?? max;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Spend one charge off a WORN unit. The slot empties only on a NEGATIVE row; a positive one sits at 0
|
|
616
|
+
// charges and refuses further use. Null when not worn. Cite: Spell.cpp:4209 TakeCastItem.
|
|
617
|
+
function spendWornCharge(
|
|
618
|
+
char: Character,
|
|
619
|
+
itemEntry: number,
|
|
620
|
+
max: number,
|
|
621
|
+
expendable: boolean,
|
|
622
|
+
): Character | null {
|
|
623
|
+
if (!char.equipped) return null;
|
|
624
|
+
const next = { ...char.equipped };
|
|
625
|
+
let spent = false;
|
|
626
|
+
for (const slot of Object.keys(next) as (keyof typeof next)[]) {
|
|
627
|
+
if (equippedEntry(next[slot]) !== itemEntry) continue;
|
|
628
|
+
const inst = equippedInstance(next[slot]);
|
|
629
|
+
if (!inst) continue;
|
|
630
|
+
const left = (inst.charges ?? max) - 1;
|
|
631
|
+
if (left <= 0 && expendable) delete next[slot];
|
|
632
|
+
else next[slot] = { ...inst, charges: Math.max(0, left) };
|
|
633
|
+
spent = true;
|
|
634
|
+
}
|
|
635
|
+
return spent ? { ...char, equipped: next } : null;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// The inventory after a use. Three outcomes from the charge column's sign: unlimited spends nothing, a
|
|
639
|
+
// positive count decrements and keeps the item, a negative one destroys it at zero. Cite: Spell.cpp:4209.
|
|
640
|
+
function takeForUse(
|
|
641
|
+
char: Character,
|
|
642
|
+
inv: Inventory,
|
|
643
|
+
itemEntry: number,
|
|
644
|
+
permanent: boolean,
|
|
645
|
+
it?: ItemDef | null,
|
|
646
|
+
): { ok: true; inventory: Inventory; permanentUse: boolean; char?: Character } | { ok: false; reason: string } {
|
|
647
|
+
const safeInv = inv && inv.stacks ? inv : { stacks: [] as Inventory['stacks'] };
|
|
648
|
+
const equipped = isItemEquipped(char, itemEntry);
|
|
649
|
+
// An equippable is used from the paperdoll, never from the bag it is sitting in.
|
|
650
|
+
// Cite: HandleUseItemOpcode InventoryType gate (same rule as combatItems).
|
|
651
|
+
if (it?.slot != null && !equipped) return { ok: false, reason: 'Must be equipped' };
|
|
652
|
+
|
|
653
|
+
const charges = it?.charges ?? 0;
|
|
654
|
+
const max = maxChargesOf(it);
|
|
655
|
+
const expendable = isExpendableCastItem(it);
|
|
656
|
+
if (charges === 0) {
|
|
657
|
+
// No charge column: unlimited for a permanent tool, one unit per use for anything else.
|
|
658
|
+
if (permanent) return { ok: true, inventory: safeInv as Inventory, permanentUse: true };
|
|
659
|
+
const taken = consumeItemCharges(safeInv as Inventory, itemEntry, it);
|
|
660
|
+
if (!taken.ok) return { ok: false, reason: "You don't have this" };
|
|
661
|
+
return { ok: true, inventory: taken.inventory, permanentUse: false };
|
|
662
|
+
}
|
|
663
|
+
// Charge-bearing: a spent unit refuses before any effect runs, and is not destroyed by trying.
|
|
664
|
+
const remaining = remainingChargesOf(char, safeInv as Inventory, itemEntry, max);
|
|
665
|
+
if (!hasChargesLeft(it, remaining)) return { ok: false, reason: NO_CHARGES_REMAIN };
|
|
666
|
+
if (equipped) {
|
|
667
|
+
const worn = spendWornCharge(char, itemEntry, max, expendable);
|
|
668
|
+
if (worn) return { ok: true, inventory: safeInv as Inventory, permanentUse: !expendable, char: worn };
|
|
669
|
+
}
|
|
670
|
+
const taken = consumeItemCharges(safeInv as Inventory, itemEntry, it);
|
|
671
|
+
if (!taken.ok) return { ok: false, reason: taken.reason };
|
|
672
|
+
return { ok: true, inventory: taken.inventory, permanentUse: !expendable };
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** One expanded useSpell effect tagged with the aura spell id that owns it. */
|
|
676
|
+
type UseSpellWork = {
|
|
677
|
+
auraSpellId: number;
|
|
678
|
+
e: { kind: string; spellId?: number; [k: string]: unknown };
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// Expand a use spell for an out-of-combat consume, following ONE level of trigger so a dual food
|
|
682
|
+
// restores both halves. Periodic triggers are NOT expanded — those wait for the seated well-fed gate.
|
|
683
|
+
function expandUseSpellWork(
|
|
684
|
+
sp: { id?: number; effects?: ReadonlyArray<{ kind: string; spellId?: number; [k: string]: unknown }> },
|
|
685
|
+
useSpId: number,
|
|
686
|
+
ds: DataStore,
|
|
687
|
+
): UseSpellWork[] {
|
|
688
|
+
const out: UseSpellWork[] = [];
|
|
689
|
+
for (const e of sp.effects ?? []) {
|
|
690
|
+
if (e.kind === 'trigger_spell' && typeof e.spellId === 'number' && e.spellId > 0) {
|
|
691
|
+
const childRaw = ds.spell(e.spellId);
|
|
692
|
+
if (!childRaw) {
|
|
693
|
+
// Outcome spell absent from bake (Noggenfogger 16595/93/91) — still land a chip.
|
|
694
|
+
out.push({
|
|
695
|
+
auraSpellId: e.spellId,
|
|
696
|
+
e: { kind: 'buff', mod: 'transform', amount: 1, durationMs: 0 },
|
|
697
|
+
});
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
const child = resolveCombatSpell(childRaw);
|
|
701
|
+
const childId = child.id || e.spellId;
|
|
702
|
+
const childFx = child.effects ?? [];
|
|
703
|
+
if (!childFx.length) {
|
|
704
|
+
out.push({
|
|
705
|
+
auraSpellId: childId,
|
|
706
|
+
e: { kind: 'buff', mod: 'transform', amount: 1, durationMs: 0 },
|
|
707
|
+
});
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
for (const ce of childFx) {
|
|
711
|
+
out.push({ auraSpellId: childId, e: ce as UseSpellWork['e'] });
|
|
712
|
+
}
|
|
713
|
+
} else {
|
|
714
|
+
out.push({ auraSpellId: useSpId, e: e as UseSpellWork['e'] });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return out;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/** Flat effects list (pendingWellFed / channel checks). */
|
|
721
|
+
function workEffects(work: UseSpellWork[]): Array<{ kind: string; [k: string]: unknown }> {
|
|
722
|
+
return work.map((w) => w.e);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// The first periodic-trigger payload on a food or drink channel: the amplitude is the wait and the
|
|
726
|
+
// duration comes off the triggered spell's own buff. Cite: spell_template EffectAmplitude.
|
|
727
|
+
function pendingWellFedFromEffects(
|
|
728
|
+
effects: ReadonlyArray<{ kind: string; [k: string]: unknown }>,
|
|
729
|
+
ds: DataStore,
|
|
730
|
+
): Character['pendingWellFed'] | undefined {
|
|
731
|
+
let best: Character['pendingWellFed'] | undefined;
|
|
732
|
+
for (const e of effects) {
|
|
733
|
+
// Aura 23 PERIODIC_TRIGGER (Herb Baked Egg) uses triggerSpellId; SPELL_EFFECT_TRIGGER_SPELL 64
|
|
734
|
+
// (Nightfin Soup 18233 → 18194) uses spellId. Cite: SpellEffects.cpp EffectTriggerSpell.
|
|
735
|
+
let triggerSpellId = 0;
|
|
736
|
+
let needMs = 0;
|
|
737
|
+
if (e.kind === 'proc_trigger_spell') {
|
|
738
|
+
triggerSpellId = Number(e.triggerSpellId) || 0;
|
|
739
|
+
needMs = Number(e.tickMs) || 0;
|
|
740
|
+
if (needMs <= 0) continue;
|
|
741
|
+
} else if (e.kind === 'trigger_spell') {
|
|
742
|
+
triggerSpellId = Number(e.spellId) || 0;
|
|
743
|
+
needMs = Number(e.tickMs) || 10_000;
|
|
744
|
+
} else continue;
|
|
745
|
+
if (triggerSpellId <= 0) continue;
|
|
746
|
+
const trigRaw = ds.spell(triggerSpellId);
|
|
747
|
+
if (!trigRaw) continue;
|
|
748
|
+
const trig = resolveCombatSpell(trigRaw);
|
|
749
|
+
// Stat Well Fed is kind:buff; Sagefish Brain Food / Nightfin 18194 is long restore_power (mp5).
|
|
750
|
+
const payloads = (trig.effects ?? []).filter(
|
|
751
|
+
(ce) => ce.kind === 'buff' || ce.kind === 'restore_power' || ce.kind === 'restore_health',
|
|
752
|
+
);
|
|
753
|
+
if (!payloads.length) continue;
|
|
754
|
+
let durationMs = 0;
|
|
755
|
+
for (const b of payloads) {
|
|
756
|
+
const d = Number((b as { durationMs?: number }).durationMs) || 0;
|
|
757
|
+
if (d > durationMs) durationMs = d;
|
|
758
|
+
}
|
|
759
|
+
if (!(durationMs > 0)) continue;
|
|
760
|
+
// Effect 64 also fires the 21s sit-and-eat (Sagefish 25888). Keep the carried buff.
|
|
761
|
+
if (e.kind === 'trigger_spell' && durationMs <= LONG_REGEN_AURA_MS) continue;
|
|
762
|
+
const row = { triggerSpellId, needMs, elapsedMs: 0, durationMs };
|
|
763
|
+
if (!best || durationMs > best.durationMs) best = row;
|
|
764
|
+
}
|
|
765
|
+
return best;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** SharedDefines Powers → Character powerType string. Cite: SharedDefines.h Powers. */
|
|
769
|
+
function energizePowerWanted(powerTypeId: number): 'mana' | 'rage' | 'energy' | null {
|
|
770
|
+
if (powerTypeId === 0) return 'mana';
|
|
771
|
+
if (powerTypeId === 1) return 'rage';
|
|
772
|
+
if (powerTypeId === 3) return 'energy';
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/** Sit-and-eat food/drink channels are ≤ ~30s; 1h regen pots (Troll's Blood / Mageblood) stick as auras. */
|
|
777
|
+
export const LONG_REGEN_AURA_MS = 60_000;
|
|
778
|
+
|
|
779
|
+
// True when a short restore must tick as an aura rather than dump: a bandage channel, or a multi-effect
|
|
780
|
+
// channel with a control payload. Pure food and drink keep the instant dump.
|
|
781
|
+
function mustTickRestoreChannel(
|
|
782
|
+
sp: { mechanic?: string },
|
|
783
|
+
effects: ReadonlyArray<{ kind: string }>,
|
|
784
|
+
): boolean {
|
|
785
|
+
if (sp.mechanic === 'bandage') return true;
|
|
786
|
+
return effects.some((e) => {
|
|
787
|
+
if (
|
|
788
|
+
e.kind === 'restore_health'
|
|
789
|
+
|| e.kind === 'restore_power'
|
|
790
|
+
|| e.kind === 'trigger_spell'
|
|
791
|
+
|| e.kind === 'proc_trigger_spell'
|
|
792
|
+
|| e.kind === 'buff'
|
|
793
|
+
|| e.kind === 'resist'
|
|
794
|
+
) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
return true;
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// The prefer-effect fast path may dump only a short pure food or drink. An hour-long regen, a bandage
|
|
802
|
+
// or a cooking secondary defers to the full consume.
|
|
803
|
+
export function canPreferConsumableDump(
|
|
804
|
+
item: ItemDef,
|
|
805
|
+
cons: NonNullable<ItemDef['consumable']>,
|
|
806
|
+
ds: DataStore,
|
|
807
|
+
): boolean {
|
|
808
|
+
if ((cons.durationMs | 0) > LONG_REGEN_AURA_MS) return false;
|
|
809
|
+
const useSp = item.useSpell ?? 0;
|
|
810
|
+
if (!(useSp > 0)) return true;
|
|
811
|
+
const spRaw = ds.spell(useSp);
|
|
812
|
+
if (!spRaw) return true;
|
|
813
|
+
const sp = resolveCombatSpell(spRaw);
|
|
814
|
+
if (sp.mechanic === 'bandage') return false;
|
|
815
|
+
for (const e of sp.effects ?? []) {
|
|
816
|
+
if (e.kind === 'trigger_spell' || e.kind === 'proc_trigger_spell') return false;
|
|
817
|
+
if (e.kind !== 'restore_health' && e.kind !== 'restore_power') return false;
|
|
818
|
+
if ((Number((e as { durationMs?: number }).durationMs) || 0) > LONG_REGEN_AURA_MS) return false;
|
|
819
|
+
}
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Eat food or drink, or use a buff consumable, out of combat only. A multi-effect use spell applies ALL
|
|
824
|
+
// its resolved effects — never returning after the first heal or energize.
|
|
825
|
+
export function consume(
|
|
826
|
+
char: Character,
|
|
827
|
+
inv: Inventory,
|
|
828
|
+
ds: DataStore,
|
|
829
|
+
itemEntry: number,
|
|
830
|
+
deathFlow?: Pick<DeathFlowState, 'deathState'> | null,
|
|
831
|
+
): ConsumeResult {
|
|
832
|
+
const fail = (reason: string): ConsumeResult => ({ ok: false, reason, char, inventory: inv, restored: 0 });
|
|
833
|
+
// Dead/ghost cannot eat/drink (KI-948). Cite: Player.cpp:9740 CanUseItem !IsAlive;
|
|
834
|
+
// ghost HP=1 (Player.cpp:4288) still fails IsAlive.
|
|
835
|
+
if (!isPlayerAlive(char.health, deathFlow)) return fail(YOU_ARE_DEAD);
|
|
836
|
+
const it = ds.item(itemEntry);
|
|
837
|
+
if (!it) return fail('Unknown item');
|
|
838
|
+
// A recipe or book is never consumed here: its use spell is the learn stub carrying the taught
|
|
839
|
+
// spell's mirrored effects, so this branch would eat the book and teach nothing.
|
|
840
|
+
if (itemTeachIntent(it, ds)) return fail('Read it from your bags to learn it');
|
|
841
|
+
const permanent = isPermanentOnUseGear(it);
|
|
842
|
+
|
|
843
|
+
// The item's own required level is the ONLY bag-use floor. A spell level over-gates a potion and a
|
|
844
|
+
// hollow one under-gates an elixir. Cite: Player.cpp:9797 CanUseItem.
|
|
845
|
+
const itemReq = (it.requiredLevel | 0);
|
|
846
|
+
if (itemReq > 0 && char.level < itemReq) return fail(`Requires level ${itemReq}`);
|
|
847
|
+
|
|
848
|
+
// Priority: the cached single-mod buff, then the full use spell, then the in-combat item, then food.
|
|
849
|
+
// The use spell wins when it carries non-buff payloads, as a stat buff plus an inebriate does.
|
|
850
|
+
const useB = it.useBuff;
|
|
851
|
+
const useSp = it.useSpell;
|
|
852
|
+
let skipUseBuff = false;
|
|
853
|
+
if (useB && useSp) {
|
|
854
|
+
const spRaw = ds.spell(useSp);
|
|
855
|
+
if (spRaw) {
|
|
856
|
+
const fx = resolveCombatSpell(spRaw).effects ?? [];
|
|
857
|
+
if (fx.length > 0 && fx.some((e) => e.kind !== 'buff')) skipUseBuff = true;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (useB && !skipUseBuff) {
|
|
861
|
+
const taken = takeForUse(char, inv, itemEntry, permanent, it);
|
|
862
|
+
if (!taken.ok) return fail(taken.reason);
|
|
863
|
+
const c: Character = { ...(taken.char ?? char) };
|
|
864
|
+
return {
|
|
865
|
+
ok: true, char: c, inventory: taken.inventory, restored: 0,
|
|
866
|
+
buff: { mod: useB.mod, amount: useB.amount, durationMs: useB.durationMs },
|
|
867
|
+
permanentUse: taken.permanentUse || undefined,
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
if (useSp) {
|
|
871
|
+
const spRaw = ds.spell(useSp);
|
|
872
|
+
// SI5: materialize weapon_imbue / restore_* / mechanic_immunity / speed when bake effects[] empty.
|
|
873
|
+
const sp = spRaw ? resolveCombatSpell(spRaw) : undefined;
|
|
874
|
+
if (sp) {
|
|
875
|
+
const work = expandUseSpellWork(sp, useSp, ds);
|
|
876
|
+
const effects = workEffects(work);
|
|
877
|
+
|
|
878
|
+
// Offensive items (grenades, bombs, damage trinkets) cannot be self-consumed out of combat.
|
|
879
|
+
const isHostileOnly = effects.some((e) => e.kind === 'direct_damage' || e.kind === 'dot' || (e.kind === 'cc' && !!(e as { mechanic?: string }).mechanic))
|
|
880
|
+
&& !effects.some((e) => e.kind === 'energize' || e.kind === 'heal' || e.kind === 'buff' || e.kind === 'restore_health' || e.kind === 'restore_power');
|
|
881
|
+
if (isHostileOnly) return fail('Requires an enemy target');
|
|
882
|
+
|
|
883
|
+
// A pure power restore refuses a wrong power type BEFORE the item is taken, so the stack is kept:
|
|
884
|
+
// the energize already no-ops on a mismatch, but the item was destroyed anyway.
|
|
885
|
+
const hasManaEnergize = effects.some(
|
|
886
|
+
(e) => e.kind === 'energize' && (e as { powerType?: number }).powerType === 0,
|
|
887
|
+
);
|
|
888
|
+
const hasRageEnergize = effects.some(
|
|
889
|
+
(e) => e.kind === 'energize' && (e as { powerType?: number }).powerType === 1,
|
|
890
|
+
);
|
|
891
|
+
const hasEnergyEnergize = effects.some(
|
|
892
|
+
(e) => e.kind === 'energize' && (e as { powerType?: number }).powerType === 3,
|
|
893
|
+
);
|
|
894
|
+
const hasManaRestore = effects.some((e) => e.kind === 'restore_power');
|
|
895
|
+
const hasSidePayload = effects.some((e) =>
|
|
896
|
+
e.kind === 'heal'
|
|
897
|
+
|| e.kind === 'restore_health'
|
|
898
|
+
|| e.kind === 'buff'
|
|
899
|
+
|| e.kind === 'resist'
|
|
900
|
+
|| e.kind === 'proc_trigger_spell'
|
|
901
|
+
|| e.kind === 'mechanic_immunity'
|
|
902
|
+
|| e.kind === 'school_immunity'
|
|
903
|
+
|| e.kind === 'damage_immunity'
|
|
904
|
+
|| e.kind === 'speed'
|
|
905
|
+
|| e.kind === 'pacify'
|
|
906
|
+
|| e.kind === 'weapon_imbue'
|
|
907
|
+
|| e.kind === 'school_absorb'
|
|
908
|
+
|| e.kind === 'direct_damage'
|
|
909
|
+
|| e.kind === 'cc'
|
|
910
|
+
|| e.kind === 'root'
|
|
911
|
+
|| e.kind === 'dispel'
|
|
912
|
+
|| e.kind === 'dispel_mechanic'
|
|
913
|
+
|| e.kind === 'reflect'
|
|
914
|
+
|| e.kind === 'stealth_detect'
|
|
915
|
+
|| e.kind === 'mod_invisibility'
|
|
916
|
+
|| e.kind === 'track_creatures'
|
|
917
|
+
|| e.kind === 'mod_invisibility_detection'
|
|
918
|
+
|| e.kind === 'mod_scale',
|
|
919
|
+
);
|
|
920
|
+
const tickChannel = mustTickRestoreChannel(sp, effects);
|
|
921
|
+
const charPt = powerType(char);
|
|
922
|
+
// Pure mana pots / drink restore: non-mana classes cannot use (item preserved).
|
|
923
|
+
if ((hasManaEnergize || hasManaRestore) && charPt !== 'mana' && !hasSidePayload) {
|
|
924
|
+
return fail("You don't use mana");
|
|
925
|
+
}
|
|
926
|
+
// Rune path: energize + direct_damage only — still mana-gated (no warrior free mana).
|
|
927
|
+
if (hasManaEnergize && charPt !== 'mana') {
|
|
928
|
+
const onlyRuneShape = effects.every(
|
|
929
|
+
(e) => e.kind === 'energize' || e.kind === 'direct_damage',
|
|
930
|
+
);
|
|
931
|
+
if (onlyRuneShape) return fail("You don't use mana");
|
|
932
|
+
}
|
|
933
|
+
// Pure rage pots (5631/5633): non-rage classes refuse like mana pots. Cite: KI-757.
|
|
934
|
+
if (hasRageEnergize && charPt !== 'rage' && !hasSidePayload) {
|
|
935
|
+
return fail("You don't use rage");
|
|
936
|
+
}
|
|
937
|
+
// Pure energy (Thistle Tea 7676): same gate for energy classes only.
|
|
938
|
+
if (hasEnergyEnergize && charPt !== 'energy' && !hasSidePayload) {
|
|
939
|
+
return fail("You don't use energy");
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Spell reagents are ADDITIONAL to the cast item itself: an essence combine spends the item plus
|
|
943
|
+
// two more as reagents. Cite: Spell.cpp:6560-6593 CheckItems; :4332-4350 TakeReagents.
|
|
944
|
+
const spellReagents = spRaw?.reagents ?? [];
|
|
945
|
+
{
|
|
946
|
+
const need = new Map<number, number>();
|
|
947
|
+
// The cast item counts against the BAGS only when that is where it lives: a worn charged
|
|
948
|
+
// trinket spends a charge off the equipped instance, not a bag stack.
|
|
949
|
+
if (!permanent && !isItemEquipped(char, itemEntry)) {
|
|
950
|
+
need.set(itemEntry, (need.get(itemEntry) ?? 0) + 1);
|
|
951
|
+
}
|
|
952
|
+
for (const r of spellReagents) {
|
|
953
|
+
if ((r.item | 0) <= 0 || (r.count | 0) <= 0) continue;
|
|
954
|
+
need.set(r.item, (need.get(r.item) ?? 0) + r.count);
|
|
955
|
+
}
|
|
956
|
+
for (const [entry, n] of need) {
|
|
957
|
+
if (countItem(inv, entry) < n) {
|
|
958
|
+
const idef = ds.item(entry);
|
|
959
|
+
return fail(idef ? `Requires ${n}× ${idef.name}` : `Missing reagents (item ${entry})`);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// A create-item on a use spell, such as an empty vial to a filled one. The bag fit is probed AFTER
|
|
965
|
+
// the cast item and reagents are spent, and refused before the take — never destroy without grant.
|
|
966
|
+
{
|
|
967
|
+
const conjures: Array<{ kind: 'conjure'; item: number; count: number }> = [];
|
|
968
|
+
for (const e of effects) {
|
|
969
|
+
if (e.kind !== 'conjure') continue;
|
|
970
|
+
const item = Number(e.item) | 0;
|
|
971
|
+
const count = Number(e.count) | 0;
|
|
972
|
+
if (item > 0 && count > 0) conjures.push({ kind: 'conjure', item, count });
|
|
973
|
+
}
|
|
974
|
+
if (conjures.length > 0) {
|
|
975
|
+
const cap = bagCapacity(char, ds);
|
|
976
|
+
let probe = inv;
|
|
977
|
+
// Bag-space dry run. A worn charged item frees no bag slot, so it is not probed here.
|
|
978
|
+
if (!permanent && !isItemEquipped(char, itemEntry)) {
|
|
979
|
+
const [afterTake, removed] = removeItem(inv, itemEntry, 1);
|
|
980
|
+
if (removed <= 0) return fail("You don't have this");
|
|
981
|
+
probe = afterTake;
|
|
982
|
+
}
|
|
983
|
+
for (const r of spellReagents) {
|
|
984
|
+
if ((r.item | 0) <= 0 || (r.count | 0) <= 0) continue;
|
|
985
|
+
const [afterRg, rgRemoved] = removeItem(probe, r.item, r.count);
|
|
986
|
+
if (rgRemoved < r.count) return fail('Missing reagents');
|
|
987
|
+
probe = afterRg;
|
|
988
|
+
}
|
|
989
|
+
for (const ce of conjures) {
|
|
990
|
+
const maxStack = ds.item(ce.item)?.stackable ?? 1;
|
|
991
|
+
if (!canFit(probe, ce.item, ce.count, maxStack, cap)) {
|
|
992
|
+
return fail('Your bags are full');
|
|
993
|
+
}
|
|
994
|
+
probe = addItem(probe, ce.item, ce.count, maxStack);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Bandage lockout before spend. Cite: Spell.cpp:3114 AddPrecastSpell(11196).
|
|
1000
|
+
if (sp.mechanic === 'bandage' && refuseCmangosCheckCast(sp, {
|
|
1001
|
+
auraSpellIds: new Set(getActiveBuffIds(char, Date.now(), ds)),
|
|
1002
|
+
}, undefined, false)) {
|
|
1003
|
+
return fail(ds.spell(RECENTLY_BANDAGED_SPELL_ID)?.name ?? 'Recently Bandaged');
|
|
1004
|
+
}
|
|
1005
|
+
const taken = takeForUse(char, inv, itemEntry, permanent, it);
|
|
1006
|
+
if (!taken.ok) return fail(taken.reason);
|
|
1007
|
+
let c: Character = { ...(taken.char ?? char) };
|
|
1008
|
+
let invOut = taken.inventory;
|
|
1009
|
+
for (const r of spellReagents) {
|
|
1010
|
+
if ((r.item | 0) <= 0 || (r.count | 0) <= 0) continue;
|
|
1011
|
+
const [afterRg, rgRemoved] = removeItem(invOut, r.item, r.count);
|
|
1012
|
+
if (rgRemoved < r.count) return fail('Missing reagents');
|
|
1013
|
+
invOut = afterRg;
|
|
1014
|
+
}
|
|
1015
|
+
let restored = 0;
|
|
1016
|
+
let kind: 'food' | 'drink' | undefined;
|
|
1017
|
+
let buff: { mod: string; amount: number; durationMs: number } | undefined;
|
|
1018
|
+
let weaponImbueSpellId: number | undefined;
|
|
1019
|
+
let permEnchant: { spellId: number; enchantId: number } | undefined;
|
|
1020
|
+
const applyAuras: Array<{ spellId: number; durationMs?: number }> = [];
|
|
1021
|
+
const queuedAuraIds = new Set<number>();
|
|
1022
|
+
|
|
1023
|
+
const stickAura = (mod: string, amount: number, durationMs: number) => {
|
|
1024
|
+
if (!(durationMs > 0)) return;
|
|
1025
|
+
if (!buff || durationMs > buff.durationMs) {
|
|
1026
|
+
buff = { mod, amount, durationMs };
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
/** Queue a real aura chip under the owning spell id (food secondaries, utility pots). */
|
|
1030
|
+
const queueAura = (spellId: number, mod: string, amount: number, durationMs: number) => {
|
|
1031
|
+
if (spellId > 0 && !queuedAuraIds.has(spellId)) {
|
|
1032
|
+
queuedAuraIds.add(spellId);
|
|
1033
|
+
applyAuras.push({ spellId, durationMs: durationMs > 0 ? durationMs : undefined });
|
|
1034
|
+
}
|
|
1035
|
+
stickAura(mod, amount, durationMs > 0 ? durationMs : 1);
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
// Food/drink seated well-fed: arm AFTER restores so we know kind, but detect now for
|
|
1039
|
+
// proc_trigger skip (do not double-apply the parent as an instant aura).
|
|
1040
|
+
const willArmPendingWellFed = effects.some((e) => e.kind === 'restore_health')
|
|
1041
|
+
|| effects.some((e) => e.kind === 'restore_power' && Number(e.durationMs) > 0
|
|
1042
|
+
&& Number(e.durationMs) <= LONG_REGEN_AURA_MS);
|
|
1043
|
+
|
|
1044
|
+
// Apply ALL effects — do not early-return after first heal/energize (KI-492, KI-493).
|
|
1045
|
+
for (const { auraSpellId, e } of work) {
|
|
1046
|
+
if (e.kind === 'conjure') {
|
|
1047
|
+
const itemId = Number(e.item) | 0;
|
|
1048
|
+
const count = Math.max(1, Number(e.count) | 0);
|
|
1049
|
+
if (itemId > 0) {
|
|
1050
|
+
const maxStack = ds.item(itemId)?.stackable ?? 1;
|
|
1051
|
+
invOut = addItem(invOut, itemId, count, maxStack);
|
|
1052
|
+
}
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (e.kind === 'restore_health' || e.kind === 'restore_power') {
|
|
1056
|
+
const amountPerTick = Number(e.amountPerTick) || 0;
|
|
1057
|
+
const durationMs = Number(e.durationMs) || 0;
|
|
1058
|
+
const tickMs = Number(e.tickMs) || 3000;
|
|
1059
|
+
// Long-duration MOD_HEALTH_REGEN_IN_COMBAT (161) / hour-long mana regen: aura, not food dump.
|
|
1060
|
+
// Cite: Player.cpp RegenerateHealth aura 161; KI-495 / KI-443 sibling.
|
|
1061
|
+
if (durationMs > LONG_REGEN_AURA_MS) {
|
|
1062
|
+
const mod = e.kind === 'restore_health' ? 'health_regen' : 'mp5';
|
|
1063
|
+
// Nightfin Soup secondary (18194) is a long restore_power on the child spell id.
|
|
1064
|
+
queueAura(auraSpellId, mod, amountPerTick, durationMs);
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
// A bandage or a multi-effect channel potion sticks its aura so the tick loop delivers the
|
|
1068
|
+
// amount over the duration. Do NOT lump-sum it as food.
|
|
1069
|
+
if (tickChannel && durationMs > 0) {
|
|
1070
|
+
const mod = e.kind === 'restore_health' ? 'restore_health' : 'restore_power';
|
|
1071
|
+
queueAura(auraSpellId, mod, amountPerTick, durationMs);
|
|
1072
|
+
continue;
|
|
1073
|
+
}
|
|
1074
|
+
// An instant full-period restore for an out-of-combat channel, the same total as the ticks.
|
|
1075
|
+
// A percent-of-max aura ticks a PERCENTAGE: reading it flat healed Harvest Fish 2 a second.
|
|
1076
|
+
const ticks = Math.max(1, Math.floor(durationMs / tickMs));
|
|
1077
|
+
const pctOfMax = Number((e as { pctOfMax?: number }).pctOfMax) || 0;
|
|
1078
|
+
const maxRes = e.kind === 'restore_health' ? maxHealth(c, ds) : maxPower(c, ds);
|
|
1079
|
+
const perTick = pctOfMax > 0 ? Math.floor((maxRes * pctOfMax) / 100) : amountPerTick;
|
|
1080
|
+
const amt = Math.round(perTick * ticks);
|
|
1081
|
+
if (e.kind === 'restore_health') {
|
|
1082
|
+
const before = c.health;
|
|
1083
|
+
c.health = Math.min(maxHealth(c, ds), before + amt);
|
|
1084
|
+
restored += Math.max(0, c.health - before);
|
|
1085
|
+
if (!kind) kind = 'food';
|
|
1086
|
+
} else if (powerType(c) === 'mana') {
|
|
1087
|
+
const before = c.power;
|
|
1088
|
+
c.power = Math.min(maxPower(c, ds), before + amt);
|
|
1089
|
+
restored += Math.max(0, c.power - before);
|
|
1090
|
+
kind = kind === 'food' ? kind : 'drink';
|
|
1091
|
+
}
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
if (e.kind === 'cc' || e.kind === 'root') {
|
|
1095
|
+
// Dreamless Sleep (and any self-CC consumable): stick so Game.consumeItem applies the
|
|
1096
|
+
// useSpell aura for the full channel. Cite: spell 15822/24360; KI-707.
|
|
1097
|
+
const durationMs = Number((e as { durationMs?: number }).durationMs) || 0;
|
|
1098
|
+
const control = e.kind === 'root'
|
|
1099
|
+
? 'root'
|
|
1100
|
+
: String((e as { control?: string; mechanic?: string }).control
|
|
1101
|
+
?? (e as { mechanic?: string }).mechanic
|
|
1102
|
+
?? 'cc');
|
|
1103
|
+
queueAura(auraSpellId, control, 1, durationMs);
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
if (e.kind === 'heal') {
|
|
1107
|
+
const min = Number((e as { min?: number }).min) || 0;
|
|
1108
|
+
const max = Number((e as { max?: number }).max) || 0;
|
|
1109
|
+
const amt = Math.round((min + max) / 2);
|
|
1110
|
+
const before = c.health;
|
|
1111
|
+
c.health = Math.min(maxHealth(c, ds), before + amt);
|
|
1112
|
+
restored += Math.max(0, c.health - before);
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
if (e.kind === 'energize') {
|
|
1116
|
+
// Instant power restore — mana pots, Thistle Tea energy, rage potions.
|
|
1117
|
+
// Cite: SpellEffects.cpp EffectEnergize; SharedDefines Powers; KI-646.
|
|
1118
|
+
const power = Number((e as { powerType?: number }).powerType);
|
|
1119
|
+
const want = energizePowerWanted(power);
|
|
1120
|
+
if (!want || powerType(c) !== want) continue;
|
|
1121
|
+
const min = Number((e as { min?: number }).min) || 0;
|
|
1122
|
+
const max = Number((e as { max?: number }).max) || 0;
|
|
1123
|
+
const amt = Math.round((min + max) / 2);
|
|
1124
|
+
const before = c.power;
|
|
1125
|
+
c.power = Math.min(maxPower(c, ds), before + amt);
|
|
1126
|
+
restored += Math.max(0, c.power - before);
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (e.kind === 'direct_damage') {
|
|
1130
|
+
// Self-cost payloads (Demonic/Dark Rune). Deterministic avg; floor at 1 HP OOC.
|
|
1131
|
+
// Cite: SpellEffects.cpp EffectSchoolDMG; spell 16666/27869 raw effect 2.
|
|
1132
|
+
const min = Number((e as { min?: number }).min) || 0;
|
|
1133
|
+
const max = Number((e as { max?: number }).max) || 0;
|
|
1134
|
+
const amt = Math.round((min + max) / 2);
|
|
1135
|
+
if (amt > 0) c.health = Math.max(1, c.health - amt);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
if (e.kind === 'buff') {
|
|
1139
|
+
const mod = String((e as { mod?: string }).mod ?? 'unknown');
|
|
1140
|
+
const amount = Number((e as { amount?: number }).amount) || 0;
|
|
1141
|
+
const durationMs = Number((e as { durationMs?: number }).durationMs) || 0;
|
|
1142
|
+
queueAura(auraSpellId, mod, amount, durationMs);
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
if (e.kind === 'resist') {
|
|
1146
|
+
// A resistance aura — the flasks and resist elixirs. The bake often omits the duration on a
|
|
1147
|
+
// resist row, so it comes off the spell's own duration index. Cite: HandleModResistance.
|
|
1148
|
+
const amount = Number((e as { amount?: number }).amount) || 0;
|
|
1149
|
+
if (amount <= 0) continue;
|
|
1150
|
+
let durationMs = Number((e as { durationMs?: number }).durationMs) || 0;
|
|
1151
|
+
if (!(durationMs > 0) && auraSpellId > 0) {
|
|
1152
|
+
const owner = ds.spell(auraSpellId);
|
|
1153
|
+
const fromIdx = owner?.durationIndex != null ? durMs(owner.durationIndex) : undefined;
|
|
1154
|
+
if (fromIdx != null && fromIdx > 0) durationMs = fromIdx;
|
|
1155
|
+
}
|
|
1156
|
+
queueAura(auraSpellId, 'resist', amount, durationMs);
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
if (
|
|
1160
|
+
e.kind === 'mechanic_immunity'
|
|
1161
|
+
|| e.kind === 'school_immunity'
|
|
1162
|
+
|| e.kind === 'damage_immunity'
|
|
1163
|
+
|| e.kind === 'pacify'
|
|
1164
|
+
) {
|
|
1165
|
+
// Utility pots (Free Action, Living Action, LIP). Game.consumeItem sticks aura via buff.
|
|
1166
|
+
// Cite: SpellAuras HandleModMechanicImmunity / SchoolImmunity; KI-494.
|
|
1167
|
+
const durationMs = Number((e as { durationMs?: number }).durationMs) || 0;
|
|
1168
|
+
queueAura(auraSpellId, e.kind, 1, durationMs);
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
if (e.kind === 'speed') {
|
|
1172
|
+
// Swiftness / Swim Speed potions. Cite: Unit::UpdateSpeed; aura 31/58.
|
|
1173
|
+
const durationMs = Number((e as { durationMs?: number }).durationMs) || 0;
|
|
1174
|
+
const pct = Number((e as { pct?: number }).pct) || 0;
|
|
1175
|
+
queueAura(auraSpellId, 'speed', pct, durationMs);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
if (e.kind === 'mod_scale') {
|
|
1179
|
+
// SPELL_AURA_MOD_SCALE (61) — Noggenfogger shrink/grow. Cosmetic size aura.
|
|
1180
|
+
// Cite: SpellAuras.cpp HandleAuraModScale; applySpellLate mod_scale; KI-908.
|
|
1181
|
+
let durationMs = Number((e as { durationMs?: number }).durationMs) || 0;
|
|
1182
|
+
if (!(durationMs > 0) && auraSpellId > 0) {
|
|
1183
|
+
const owner = ds.spell(auraSpellId);
|
|
1184
|
+
const fromIdx = owner?.durationIndex != null ? durMs(owner.durationIndex) : undefined;
|
|
1185
|
+
if (fromIdx != null && fromIdx > 0) durationMs = fromIdx;
|
|
1186
|
+
}
|
|
1187
|
+
const pct = Number((e as { pct?: number }).pct) || 0;
|
|
1188
|
+
queueAura(auraSpellId, 'mod_scale', pct, durationMs);
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (e.kind === 'school_absorb' || e.kind === 'mana_shield') {
|
|
1192
|
+
const durationMs = Number((e as { durationMs?: number }).durationMs) || 0;
|
|
1193
|
+
const min = Number((e as { min?: number }).min) || 0;
|
|
1194
|
+
const max = Number((e as { max?: number }).max) || min;
|
|
1195
|
+
queueAura(auraSpellId, e.kind, Math.round((min + max) / 2), durationMs);
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
if (
|
|
1199
|
+
e.kind === 'stealth_detect'
|
|
1200
|
+
|| e.kind === 'mod_invisibility'
|
|
1201
|
+
|| e.kind === 'mod_invisibility_detection'
|
|
1202
|
+
|| e.kind === 'track_creatures'
|
|
1203
|
+
|| e.kind === 'track_resources'
|
|
1204
|
+
|| e.kind === 'water_breathing'
|
|
1205
|
+
|| e.kind === 'mod_water_breathing'
|
|
1206
|
+
|| e.kind === 'water_walk'
|
|
1207
|
+
|| e.kind === 'safe_fall'
|
|
1208
|
+
|| e.kind === 'feather_fall'
|
|
1209
|
+
|| e.kind === 'hover'
|
|
1210
|
+
|| e.kind === 'reflect'
|
|
1211
|
+
|| e.kind === 'aura_dummy_holder'
|
|
1212
|
+
|| e.kind === 'aura_dummy'
|
|
1213
|
+
|| e.kind === 'add_farsight'
|
|
1214
|
+
) {
|
|
1215
|
+
// Utility pots / elixirs: Catseye, invis, Detect Undead, Water Breathing, Dream Vision.
|
|
1216
|
+
// Cite: oocCastEffects; SpellAuras ReflectSpells / WaterBreathing; KI-592/593/625/647/665.
|
|
1217
|
+
let durationMs = Number((e as { durationMs?: number }).durationMs) || 0;
|
|
1218
|
+
if (!(durationMs > 0) && auraSpellId > 0) {
|
|
1219
|
+
const owner = ds.spell(auraSpellId);
|
|
1220
|
+
const fromIdx = owner?.durationIndex != null ? durMs(owner.durationIndex) : undefined;
|
|
1221
|
+
if (fromIdx != null && fromIdx > 0) durationMs = fromIdx;
|
|
1222
|
+
}
|
|
1223
|
+
const amount = Number(
|
|
1224
|
+
(e as { amount?: number; pct?: number }).amount
|
|
1225
|
+
?? (e as { pct?: number }).pct
|
|
1226
|
+
?? 1,
|
|
1227
|
+
) || 1;
|
|
1228
|
+
if (e.kind === 'aura_dummy_holder' || e.kind === 'aura_dummy') {
|
|
1229
|
+
const owner = auraSpellId > 0 ? ds.spell(auraSpellId) : undefined;
|
|
1230
|
+
const mixed = owner?.effects?.some(
|
|
1231
|
+
(x) => x.kind !== 'aura_dummy' && x.kind !== 'aura_dummy_holder',
|
|
1232
|
+
);
|
|
1233
|
+
if (mixed) continue;
|
|
1234
|
+
if (!(durationMs > 0) && ((owner?.durationIndex ?? 0) | 0) !== 21 && !owner?.passive) continue;
|
|
1235
|
+
}
|
|
1236
|
+
queueAura(auraSpellId, e.kind, amount, durationMs);
|
|
1237
|
+
continue;
|
|
1238
|
+
}
|
|
1239
|
+
if (e.kind === 'dispel') {
|
|
1240
|
+
// Antidote / Jungle Remedy / Elixir of Poison Resistance — strip matching auras OOC.
|
|
1241
|
+
// Cite: SpellEffects.cpp EffectDispel; domain/spells/dispel.ts; KI-556.
|
|
1242
|
+
const mask = getDispelMask(Number((e as { dispelType?: number }).dispelType) || 0);
|
|
1243
|
+
let remaining = Math.max(1, Number((e as { count?: number }).count) || 1);
|
|
1244
|
+
if (mask && remaining > 0 && c.activeAuras?.length) {
|
|
1245
|
+
const kept: typeof c.activeAuras = [];
|
|
1246
|
+
for (const a of c.activeAuras) {
|
|
1247
|
+
if (remaining <= 0) { kept.push(a); continue; }
|
|
1248
|
+
const auraSp = ds.spell(a.spellId);
|
|
1249
|
+
const dt = (auraSp?.dispel ?? 0) | 0;
|
|
1250
|
+
if (dt > 0 && (getDispelMask(dt) & mask) !== 0) {
|
|
1251
|
+
remaining -= 1;
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1254
|
+
kept.push(a);
|
|
1255
|
+
}
|
|
1256
|
+
c = {
|
|
1257
|
+
...c,
|
|
1258
|
+
activeAuras: kept,
|
|
1259
|
+
activeBuffs: getActiveBuffIds({ ...c, activeAuras: kept }, Date.now(), ds),
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
if (e.kind === 'dispel_mechanic') {
|
|
1265
|
+
// Restorative Potion family / item DISPEL_MECHANIC. Cite: SpellEffects.cpp:2499–2519.
|
|
1266
|
+
const mechanic = Number((e as { mechanic?: number }).mechanic) || 0;
|
|
1267
|
+
const count = Math.max(1, Number((e as { count?: number }).count) || 1);
|
|
1268
|
+
if (mechanic && c.activeAuras?.length) {
|
|
1269
|
+
const rng = {
|
|
1270
|
+
next: () => 0.5,
|
|
1271
|
+
int: (min: number, max: number) => Math.min(max, Math.max(min, min)),
|
|
1272
|
+
};
|
|
1273
|
+
const { kept, removed } = peelMechanicAuras(
|
|
1274
|
+
c.activeAuras, mechanic, count, (id) => ds.spell(id), rng,
|
|
1275
|
+
);
|
|
1276
|
+
if (removed.length) {
|
|
1277
|
+
c = {
|
|
1278
|
+
...c,
|
|
1279
|
+
activeAuras: kept,
|
|
1280
|
+
activeBuffs: getActiveBuffIds({ ...c, activeAuras: kept }, Date.now(), ds),
|
|
1281
|
+
};
|
|
1282
|
+
if (mechanic === MECHANIC_MOUNT && c.mountedItem != null) {
|
|
1283
|
+
const d = dismount(c);
|
|
1284
|
+
if (d.ok) c = d.char;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
if (e.kind === 'proc_trigger_spell') {
|
|
1291
|
+
// A food's well-fed metronome waits on the seated gate, so no instant apply. A restorative
|
|
1292
|
+
// potion or an oil is its OWN aura, whose parent IS the periodic effect.
|
|
1293
|
+
const triggerSpellId = Number((e as { triggerSpellId?: number }).triggerSpellId) || 0;
|
|
1294
|
+
const durationMs = Number((e as { durationMs?: number }).durationMs) || 0;
|
|
1295
|
+
const needMs = Number((e as { tickMs?: number }).tickMs) || 0;
|
|
1296
|
+
const isFoodGate = willArmPendingWellFed && needMs > 0 && triggerSpellId > 0
|
|
1297
|
+
&& !!pendingWellFedFromEffects([e], ds);
|
|
1298
|
+
if (isFoodGate) continue;
|
|
1299
|
+
if (durationMs > 0 || triggerSpellId > 0) {
|
|
1300
|
+
queueAura(auraSpellId, 'proc_trigger_spell', triggerSpellId || 1, durationMs || 1);
|
|
1301
|
+
}
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (e.kind === 'weapon_imbue') {
|
|
1305
|
+
weaponImbueSpellId = useSp;
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
if (e.kind === 'enchant_item_perm') {
|
|
1309
|
+
// Decide only. consume() does not know which slot was chosen, and the target may be
|
|
1310
|
+
// a companion's gear rather than this character's — Game.consumeItem writes it.
|
|
1311
|
+
permEnchant = { spellId: useSp, enchantId: Number((e as { enchantId?: number }).enchantId) || 0 };
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
if (e.kind === 'inebriate') {
|
|
1315
|
+
// Alcohol: baked drunkMod (= damage×256). Cite: EffectInebriate; KI-734.
|
|
1316
|
+
const mod = Number((e as { drunkMod?: number }).drunkMod) || 0;
|
|
1317
|
+
if (mod > 0) c = { ...c, drunkValue: applyInebriate(c.drunkValue, mod) };
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Precast Recently Bandaged on the unit that was bandaged (self on this path).
|
|
1323
|
+
// Cite: Spell.cpp:3114 MECHANIC_BANDAGE AddPrecastSpell(11196).
|
|
1324
|
+
if (sp.mechanic === 'bandage') {
|
|
1325
|
+
const recently = ds.spell(RECENTLY_BANDAGED_SPELL_ID);
|
|
1326
|
+
const imm = recently?.effects?.find((e) => e.kind === 'mechanic_immunity');
|
|
1327
|
+
const dur = imm && imm.kind === 'mechanic_immunity' ? (imm.durationMs | 0) : 0;
|
|
1328
|
+
if (dur > 0) queueAura(RECENTLY_BANDAGED_SPELL_ID, 'mechanic_immunity', 1, dur);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// Lump-sum restore already finished the sit-channel, so the aura-23 amplitude has elapsed.
|
|
1332
|
+
// Cite: SpellAuras.cpp:4993 TriggerSpell; Food 24869 amp=10000 → Well Fed 24870.
|
|
1333
|
+
const pending = (kind === 'food' || kind === 'drink'
|
|
1334
|
+
|| effects.some((e) => e.kind === 'restore_health' || e.kind === 'restore_power'))
|
|
1335
|
+
? pendingWellFedFromEffects(effects, ds)
|
|
1336
|
+
: undefined;
|
|
1337
|
+
if (pending) {
|
|
1338
|
+
c = grantSeatedWellFed(c, ds, pending);
|
|
1339
|
+
if (pending.triggerSpellId > 0 && !queuedAuraIds.has(pending.triggerSpellId)) {
|
|
1340
|
+
queuedAuraIds.add(pending.triggerSpellId);
|
|
1341
|
+
applyAuras.push({ spellId: pending.triggerSpellId, durationMs: pending.durationMs });
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
return {
|
|
1346
|
+
ok: true,
|
|
1347
|
+
char: c,
|
|
1348
|
+
inventory: invOut,
|
|
1349
|
+
restored,
|
|
1350
|
+
kind,
|
|
1351
|
+
buff,
|
|
1352
|
+
weaponImbueSpellId,
|
|
1353
|
+
permEnchant,
|
|
1354
|
+
permanentUse: taken.permanentUse || undefined,
|
|
1355
|
+
applyAuras: applyAuras.length ? applyAuras : undefined,
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// The in-combat item cache is used only when there is no use spell, which is preferred so a
|
|
1361
|
+
// dual-resource item is not truncated to its health half. The level is already gated on the item.
|
|
1362
|
+
const uic = it.useInCombat;
|
|
1363
|
+
if (uic) {
|
|
1364
|
+
// min/max are optional on the bake. Without them this computed NaN and wrote NaN into
|
|
1365
|
+
// char.health / char.power, which then persists into the save — check BEFORE consuming.
|
|
1366
|
+
const amt = Math.round(((uic.min ?? 0) + (uic.max ?? 0)) / 2); // deterministic avg out of combat
|
|
1367
|
+
if (!(amt > 0)) return fail('This item has no restore amount in the bake');
|
|
1368
|
+
const taken = takeForUse(char, inv, itemEntry, permanent, it);
|
|
1369
|
+
if (!taken.ok) return fail(taken.reason);
|
|
1370
|
+
const c: Character = { ...(taken.char ?? char) };
|
|
1371
|
+
let restored = 0;
|
|
1372
|
+
if (uic.effect === 'restore_health') {
|
|
1373
|
+
const before = c.health;
|
|
1374
|
+
c.health = Math.min(maxHealth(c, ds), before + amt);
|
|
1375
|
+
restored = c.health - before;
|
|
1376
|
+
} else if (powerTypeName((uic.powerType ?? 0) | 0) === powerType(c)) {
|
|
1377
|
+
// A Mana Potion pays mana and a Thistle Tea energy; the wrong bar gets nothing.
|
|
1378
|
+
// Cite: SpellEffects.cpp EffectEnergize EffectMiscValue; item.ts useInCombat.powerType.
|
|
1379
|
+
const before = c.power;
|
|
1380
|
+
c.power = Math.min(maxPower(c, ds), before + amt);
|
|
1381
|
+
restored = c.power - before;
|
|
1382
|
+
}
|
|
1383
|
+
return {
|
|
1384
|
+
ok: true, char: c, inventory: taken.inventory, restored,
|
|
1385
|
+
kind: uic.effect === 'restore_health' ? undefined : 'drink' as any,
|
|
1386
|
+
permanentUse: taken.permanentUse || undefined,
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
const cons = it.consumable;
|
|
1391
|
+
if (cons) {
|
|
1392
|
+
// Level already gated on item.requiredLevel (food bake may stamp cons.reqLevel from item).
|
|
1393
|
+
if (cons.effect === 'drink' && powerType(char) !== 'mana') return fail("You don't use mana");
|
|
1394
|
+
|
|
1395
|
+
const taken = takeForUse(char, inv, itemEntry, permanent, it);
|
|
1396
|
+
if (!taken.ok) return fail(taken.reason);
|
|
1397
|
+
|
|
1398
|
+
const ticks = Math.max(1, Math.floor(cons.durationMs / cons.tickMs));
|
|
1399
|
+
// OBS_MOD_HEALTH/MANA: percent of max (same as useSpell path). Cite: SpellAuras.cpp:4706.
|
|
1400
|
+
const pctOfMax = Number((cons as { pctOfMax?: number }).pctOfMax) || 0;
|
|
1401
|
+
let c: Character = { ...(taken.char ?? char) };
|
|
1402
|
+
let restored = 0;
|
|
1403
|
+
if (cons.effect === 'food') {
|
|
1404
|
+
const maxRes = maxHealth(c, ds);
|
|
1405
|
+
const perTick = pctOfMax > 0 ? Math.floor((maxRes * pctOfMax) / 100) : cons.amountPerTick;
|
|
1406
|
+
const total = perTick * ticks;
|
|
1407
|
+
const before = c.health;
|
|
1408
|
+
c.health = Math.min(maxRes, c.health + total);
|
|
1409
|
+
restored = c.health - before;
|
|
1410
|
+
} else {
|
|
1411
|
+
const maxRes = maxPower(c, ds);
|
|
1412
|
+
const perTick = pctOfMax > 0 ? Math.floor((maxRes * pctOfMax) / 100) : cons.amountPerTick;
|
|
1413
|
+
const total = perTick * ticks;
|
|
1414
|
+
const before = c.power;
|
|
1415
|
+
c.power = Math.min(maxRes, c.power + total);
|
|
1416
|
+
restored = c.power - before;
|
|
1417
|
+
}
|
|
1418
|
+
if (cons.effect === 'food' && it.useSpell) {
|
|
1419
|
+
const spRaw = ds.spell(it.useSpell);
|
|
1420
|
+
if (spRaw) {
|
|
1421
|
+
const pending = pendingWellFedFromEffects(resolveCombatSpell(spRaw).effects ?? [], ds);
|
|
1422
|
+
if (pending) c = grantSeatedWellFed(c, ds, pending);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return {
|
|
1426
|
+
ok: true, char: c, inventory: taken.inventory, restored, kind: cons.effect,
|
|
1427
|
+
permanentUse: taken.permanentUse || undefined,
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// Not a usable item (gear, reagents like Soul Shards, trade goods, quest items). Never consume
|
|
1432
|
+
// it "for nothing" — that would silently destroy reagents and quest items on a stray right-click.
|
|
1433
|
+
return fail('Cannot be consumed');
|
|
1434
|
+
}
|