azeroth-shell 0.2.0 → 0.3.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 +43 -10
- package/bin/azsh.mjs +15 -1
- package/package.json +5 -2
- package/src/cli/hdArt.mjs +1 -1
- package/src/cli/iconArt.mjs +11 -1
- package/src/cli/modelArt.mjs +7 -4
- package/src/cli/oocPrep.mjs +32 -0
- package/src/cli/repl.mjs +16 -0
- package/src/cli/sixel.mjs +26 -14
- package/src/cli/text.mjs +16 -0
- package/src/cli/tui.mjs +33 -9
- package/src/cli/tuiAutopilot.mjs +17 -8
- package/src/cli/tuiBags.mjs +37 -4
- package/src/cli/tuiCheck.mjs +276 -15
- package/src/cli/tuiCity.mjs +31 -31
- package/src/cli/tuiCompanionKit.mjs +6 -6
- package/src/cli/tuiCompanions.mjs +189 -29
- package/src/cli/tuiCreate.mjs +7 -0
- package/src/cli/tuiDungeon.mjs +56 -20
- package/src/cli/tuiFight.mjs +63 -21
- package/src/cli/tuiFightPanes.mjs +15 -6
- package/src/cli/tuiFloor.mjs +20 -8
- package/src/cli/tuiGear.mjs +9 -10
- package/src/cli/tuiLoot.mjs +5 -6
- package/src/cli/tuiMap.mjs +214 -0
- package/src/cli/tuiPaperdoll.mjs +6 -5
- package/src/cli/tuiPartyStage.mjs +36 -7
- package/src/cli/tuiPet.mjs +16 -8
- package/src/cli/tuiProfessions.mjs +23 -23
- package/src/cli/tuiSaves.mjs +14 -0
- package/src/cli/tuiSearch.mjs +4 -5
- package/src/cli/tuiSettings.mjs +3 -4
- package/src/cli/tuiStage.mjs +72 -16
- package/src/cli/tuiTalents.mjs +3 -4
- package/src/cli/tuiViews.mjs +371 -84
- package/src/cli/tuiWiki.mjs +2 -3
- package/src/cli/tuiWorld.mjs +88 -5
- package/src/data/gear-art.bin.br +0 -0
- package/src/data/hd-art.json +36 -36
- package/src/data/model-art-worn.bin.br +0 -0
- package/src/data/model-art.bin.gz +0 -0
- package/src/data/tables/creatures.json.gz +0 -0
- package/src/data/world-map-sixel.bin.br +0 -0
- package/src/data/world-map.bin.gz +0 -0
- package/src/domain/character/character.ts +6 -0
- package/src/domain/pet/pet.ts +31 -0
- package/src/engine/combat/pickAction.ts +3494 -3487
- package/src/engine/combat/sessionChase.ts +12 -1
- package/src/engine/game/loadReconcile.ts +21 -1
- package/src/engine/game/pets.ts +33 -1
- package/src/engine/game/types.ts +4 -0
|
@@ -248,7 +248,18 @@ export function createChaseArm(deps: ChaseDeps): {
|
|
|
248
248
|
if (isEnemySide(f.side)) {
|
|
249
249
|
// The GRADE is the full contact ring while the PARK is half of it, which is the split the C++
|
|
250
250
|
// makes — and it retires the knife edge where one ULP of step error triggered a full-HP evade.
|
|
251
|
-
|
|
251
|
+
let reach = Math.max(pairContact, longestOffenseRangeYd(f) + EVADE_REACH_EPS_YD);
|
|
252
|
+
// With no ranged kit the park below stops at the short offense, so grade against it too. A
|
|
253
|
+
// Razormane Scout's Shoot 6660 infers instant_nuke: it parked at 24 yd, shot, and reset every 5 s.
|
|
254
|
+
// Scheduled spells only, so a kit row the mob never casts still lets it evade.
|
|
255
|
+
if (dist > reach) {
|
|
256
|
+
const castable = castableKitIds(f, ctx.spellOf);
|
|
257
|
+
for (const s of shortOffenseList(f)) {
|
|
258
|
+
if (castable && !castable.has(s.id | 0)) continue;
|
|
259
|
+
if (!spellTargetTypeAllowed(s, tgt.creatureType ?? 0) || focusImmunityBlocksSpell(s, tgt, 1)) continue;
|
|
260
|
+
reach = Math.max(reach, (s.rangeMax ?? 0) + EVADE_REACH_EPS_YD);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
252
263
|
const prev = unreachableFrom.get(f.side);
|
|
253
264
|
const victimMoved = !!prev && (
|
|
254
265
|
to.z != null && prev.victimZ != null
|
|
@@ -48,7 +48,7 @@ import { emptyInventory } from '../../domain/items/inventory.ts';
|
|
|
48
48
|
import { trimSoulShards } from '../../domain/bots/soulShardCap.ts';
|
|
49
49
|
import { companionLevel } from '../../domain/bots/squad.ts';
|
|
50
50
|
import { syncCompanionProfessionTools } from './companionTraining.ts';
|
|
51
|
-
import { petHasHappiness, reconcilePetPartyAuras } from '../../domain/pet/pet.ts';
|
|
51
|
+
import { petHasHappiness, reconcilePetPartyAuras, tpSpentOnKnown } from '../../domain/pet/pet.ts';
|
|
52
52
|
import { playerbotJunkItem } from '../../domain/bots/playerbotItemEligibility.ts';
|
|
53
53
|
import type { EquippedMap } from '../../domain/character/character.ts';
|
|
54
54
|
import { createAliveDeathFlow, isPlayerAlive } from '../../domain/run/death.ts';
|
|
@@ -324,6 +324,26 @@ export function applyLoadReconcile(ds: DataStore, state: GameState, opts?: impor
|
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
// A pet tamed before the bake dropped its rank-1 filler was charged training points for rank 1 of
|
|
328
|
+
// every family chain on top of its real kit, and may still queue those ranks to teach. Refund the
|
|
329
|
+
// difference once and keep the queue to the real kit. Cite: Pet.cpp:1955-1998 InitPetCreateSpells.
|
|
330
|
+
{
|
|
331
|
+
for (const p of [state.char.pet, state.char.storedPet, ...(state.char.stablePets ?? [])]) {
|
|
332
|
+
if (!p || p.innateKit) continue;
|
|
333
|
+
const cdef = ds.creature(p.creature);
|
|
334
|
+
if (!cdef || !petHasHappiness(cdef)) continue;
|
|
335
|
+
const kit = cdef.petDefaultSpells ?? [];
|
|
336
|
+
const filler = (cdef.petSpells ?? []).filter((id) => {
|
|
337
|
+
const s = ds.spell(id);
|
|
338
|
+
return !!s && !s.passive && (s.rank | 0) <= 1;
|
|
339
|
+
});
|
|
340
|
+
const refund = tpSpentOnKnown([...kit, ...filler], ds) - tpSpentOnKnown(kit, ds);
|
|
341
|
+
if (refund > 0 && p.trainingPoints != null) p.trainingPoints += refund;
|
|
342
|
+
if (p.teachSpells) p.teachSpells = p.teachSpells.filter((id) => kit.includes(id));
|
|
343
|
+
p.innateKit = [...kit];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
327
347
|
// Action bars: normalize any loaded/legacy shape, and for a save that predates them
|
|
328
348
|
// (empty) seed bar 1 with the player's usable abilities so combat isn't blank.
|
|
329
349
|
state.actionBars = normalizeBars(state.actionBars);
|
package/src/engine/game/pets.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
newPet, petHasHappiness, petTaughtIds, petRepertoireHas,
|
|
6
|
-
feedPetHappinessGain, petDiet, isPetFood, tpForSpell, hasTpForSpell,
|
|
6
|
+
feedPetHappinessGain, petDiet, isPetFood, tpForSpell, hasTpForSpell, petUntrainCost, untrainPetState,
|
|
7
7
|
LOYALTY_REBELLIOUS, FEED_PET_DURATION_S,
|
|
8
8
|
autoFeedThreshold, AUTO_FEED_TARGET, HAPPINESS_MAX,
|
|
9
9
|
applyPetPartyAuras, stripPetPartyAuras, petPartyAuraSpellIds, reconcilePetPartyAuras,
|
|
@@ -156,6 +156,8 @@ export function createPetsModule(ctx: GameCtx, extras: PetsExtras): Pick<
|
|
|
156
156
|
| 'setPetAutoFeed'
|
|
157
157
|
| 'buyPetSpell'
|
|
158
158
|
| 'teachPetSpell'
|
|
159
|
+
| 'untrainPet'
|
|
160
|
+
| 'untrainPetCost'
|
|
159
161
|
| 'togglePetAutocast'
|
|
160
162
|
| 'setPetCommand'
|
|
161
163
|
| 'setPetReact'
|
|
@@ -319,6 +321,36 @@ export function createPetsModule(ctx: GameCtx, extras: PetsExtras): Pick<
|
|
|
319
321
|
return { ok: true };
|
|
320
322
|
},
|
|
321
323
|
|
|
324
|
+
untrainPetCost(nowMs) {
|
|
325
|
+
const pet = state.char.pet;
|
|
326
|
+
const cdef = pet ? ds.creature(pet.creature) : undefined;
|
|
327
|
+
if (!pet || !cdef || !petHasHappiness(cdef)) return null;
|
|
328
|
+
return petUntrainCost(pet, nowMs ?? Date.now());
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
untrainPet(trainerEntry, nowMs) {
|
|
332
|
+
const trainer = ds.trainer(trainerEntry);
|
|
333
|
+
if (!trainer || !(trainer.subname ?? '').toLowerCase().includes('pet trainer')) {
|
|
334
|
+
return { ok: false, reason: 'That is not a pet trainer' };
|
|
335
|
+
}
|
|
336
|
+
if (!canInteractWithCreature(ds, state.char.race, trainerEntry)) {
|
|
337
|
+
return { ok: false, reason: 'They refuse to deal with you.' };
|
|
338
|
+
}
|
|
339
|
+
const pet = state.char.pet;
|
|
340
|
+
if (!pet) return { ok: false, reason: 'You have no pet' };
|
|
341
|
+
if (pet.health <= 0) return { ok: false, reason: 'Your pet is dead' };
|
|
342
|
+
const cdef = ds.creature(pet.creature);
|
|
343
|
+
if (!cdef || !petHasHappiness(cdef)) return { ok: false, reason: 'Only a hunter pet can be untrained' };
|
|
344
|
+
if (!petTaughtIds(state.char, cdef).length) return { ok: false, reason: `${pet.name} has nothing to forget` };
|
|
345
|
+
const now = nowMs ?? Date.now();
|
|
346
|
+
const cost = petUntrainCost(pet, now);
|
|
347
|
+
if (state.char.gold < cost) return { ok: false, reason: 'Not enough money' };
|
|
348
|
+
state.char.gold -= cost;
|
|
349
|
+
state.char.pet = { ...untrainPetState(pet, cdef, state.char.level, ds), resetTalentsCost: cost, resetTalentsTime: now };
|
|
350
|
+
log(`${pet.name} forgets its training for ${moneyMarked(cost)} - ${state.char.pet.trainingPoints} training points to spend again.`);
|
|
351
|
+
return { ok: true, spent: cost };
|
|
352
|
+
},
|
|
353
|
+
|
|
322
354
|
togglePetAutocast(spellId) {
|
|
323
355
|
const pet = state.char.pet;
|
|
324
356
|
if (!pet) return { ok: false, reason: 'You have no pet' };
|
package/src/engine/game/types.ts
CHANGED
|
@@ -768,6 +768,10 @@ export interface Game {
|
|
|
768
768
|
// Beast Training (spell 5149): teach the CURRENT pet a rank from the repertoire, spending ITS training
|
|
769
769
|
// points (chain-aware). Cite: Pet.cpp GetTPForSpell/learnSpell.
|
|
770
770
|
teachPetSpell(spellId: number): ActionResult;
|
|
771
|
+
// Untrain the current hunter pet at a pet trainer: forget every taught ability, training points back
|
|
772
|
+
// to level × (loyalty − 1), for the escalating fee petUntrainCost reads. Cite: HandlePetUnlearnOpcode.
|
|
773
|
+
untrainPet(trainerEntry: number, nowMs?: number): ActionResult<{ spent: number }>;
|
|
774
|
+
untrainPetCost(nowMs?: number): number | null;
|
|
771
775
|
// Toggle autocast for a pet ability (right-click on the pet bar). Rank-agnostic by name chain; disabled
|
|
772
776
|
// abilities stay learned but leave the combat kit (buildPet).
|
|
773
777
|
togglePetAutocast(spellId: number): ActionResult<{ enabled: boolean }>;
|