azeroth-shell 0.1.2 → 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 +362 -85
- package/bin/azsh.mjs +35 -19
- package/package.json +12 -1
- package/src/autopilot/plan.ts +8 -11
- package/src/autopilot/view.ts +12 -11
- package/src/cli/combatLogFormat.ts +22 -0
- package/src/cli/gzLoader.mjs +47 -6
- package/src/cli/hdArt.mjs +81 -0
- package/src/cli/iconArt.mjs +145 -0
- package/src/cli/iconGrid.mjs +100 -0
- package/src/cli/modelArt.mjs +298 -0
- package/src/cli/oocPrep.mjs +32 -0
- package/src/cli/paperdoll.mjs +76 -0
- package/src/cli/perfCheck.mjs +56 -0
- package/src/cli/repl.mjs +48 -7
- package/src/cli/rotationText.mjs +6 -6
- package/src/cli/sixel.mjs +319 -0
- package/src/cli/term.mjs +106 -12
- package/src/cli/text.mjs +21 -3
- package/src/cli/tui.mjs +262 -102
- package/src/cli/tuiAutopilot.mjs +356 -0
- package/src/cli/tuiBags.mjs +37 -4
- package/src/cli/tuiBg.mjs +370 -41
- package/src/cli/tuiCheck.mjs +633 -37
- package/src/cli/tuiCity.mjs +146 -41
- package/src/cli/tuiCompanionKit.mjs +6 -6
- package/src/cli/tuiCompanions.mjs +211 -41
- package/src/cli/tuiCreate.mjs +230 -0
- package/src/cli/tuiDungeon.mjs +57 -21
- package/src/cli/tuiFight.mjs +467 -88
- package/src/cli/tuiFightPanes.mjs +42 -14
- package/src/cli/tuiFloor.mjs +21 -9
- package/src/cli/tuiGear.mjs +17 -14
- package/src/cli/tuiLoot.mjs +6 -6
- package/src/cli/tuiMap.mjs +214 -0
- package/src/cli/tuiPaperdoll.mjs +108 -0
- package/src/cli/tuiPartyStage.mjs +206 -0
- package/src/cli/tuiPet.mjs +16 -8
- package/src/cli/tuiProfessions.mjs +27 -25
- package/src/cli/tuiRotationCond.mjs +390 -0
- package/src/cli/tuiSaves.mjs +24 -1
- package/src/cli/tuiSearch.mjs +79 -27
- package/src/cli/tuiSettings.mjs +3 -4
- package/src/cli/tuiStage.mjs +360 -0
- package/src/cli/tuiTalentTree.mjs +124 -0
- package/src/cli/tuiTalents.mjs +3 -4
- package/src/cli/tuiViews.mjs +633 -172
- package/src/cli/tuiWiki.mjs +2 -3
- package/src/cli/tuiWorld.mjs +90 -7
- package/src/data/gear-art.bin.br +0 -0
- package/src/data/hd-art.json +114 -0
- package/src/data/icon-art.bin.gz +0 -0
- package/src/data/model-art-worn.bin.br +0 -0
- package/src/data/model-art.bin.gz +0 -0
- package/src/data/rotations/pve/paladin/retribution.json +2 -2
- 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/bg/match.ts +85 -24
- package/src/domain/bg/strategies.ts +144 -29
- package/src/domain/bg/types.ts +2 -0
- package/src/domain/bg/wsg.ts +35 -20
- package/src/domain/character/character.ts +6 -0
- package/src/domain/pet/pet.ts +31 -0
- package/src/domain/quests/doQuest.ts +24 -6
- package/src/domain/rotation/build.ts +3031 -3031
- package/src/domain/rotation/classes/paladin.ts +16 -11
- package/src/domain/rotation/evaluate.ts +3 -0
- package/src/engine/combat/applySpell.ts +20 -9
- package/src/engine/combat/applySpellLate.ts +16 -12
- package/src/engine/combat/areaAura.ts +284 -283
- package/src/engine/combat/fighterFactory.ts +1 -1
- package/src/engine/combat/pickAction.ts +3494 -3449
- package/src/engine/combat/procsDamage.ts +31 -1
- package/src/engine/combat/sessionChase.ts +19 -2
- package/src/engine/combat/sessionMotionKit.ts +40 -2
- package/src/engine/game/loadReconcile.ts +21 -1
- package/src/engine/game/pets.ts +33 -1
- package/src/engine/game/types.ts +4 -0
- package/src/quests/quests.ts +60 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// The Talents tab as the client's trees: each tree a 4x7 grid of talent icons with prerequisite arrows, the
|
|
2
|
+
// rank in the corner, framed gold when maxed, green when you can put a point in and grey (the icon greyed
|
|
3
|
+
// too) when you cannot yet. As many trees as fit stand side by side; Up and Down walk the talents in reading
|
|
4
|
+
// order, Shift+arrows move on the trees (crossing into the next one at the edge). The rows are the talents view's own (a talent row carries `node`); this file
|
|
5
|
+
// only places them, so Enter, u and the tooltip work as in the list. No colour or no room: the list.
|
|
6
|
+
import { canvas, drawCell, gridStep } from './iconGrid.mjs';
|
|
7
|
+
import { iconsOn } from './iconArt.mjs';
|
|
8
|
+
import { availablePoints, spentInTree, TALENT_TIER_POINTS } from '../domain/character/talents.ts';
|
|
9
|
+
import { bold, dim, cyan, fit, plural } from './text.mjs';
|
|
10
|
+
|
|
11
|
+
const TIERS = 7, COLS = 4, GAP = 2;
|
|
12
|
+
|
|
13
|
+
/** maxed, up or locked, the client's three node states. Phonecraft talentTreeGrid.ts talentNodeTone. */
|
|
14
|
+
export function talentTone(ch, t, ds) {
|
|
15
|
+
const cur = ch.talents?.[t.id] ?? 0;
|
|
16
|
+
if (cur >= t.maxRank) return 'maxed';
|
|
17
|
+
if (cur <= 0 && availablePoints(ch) <= 0) return 'locked';
|
|
18
|
+
if (spentInTree(ch, t.tabId, ds) < t.tier * TALENT_TIER_POINTS) return 'locked';
|
|
19
|
+
if (t.prereq && (ch.talents?.[t.prereq.talentId] ?? 0) < t.prereq.rank) return 'locked';
|
|
20
|
+
return 'up';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The talent's icon: its own, else its first rank spell's. */
|
|
24
|
+
export const talentIcon = (ds, t) => t.icon ?? ds.spell(t.ranks?.[0]?.spellId)?.icon;
|
|
25
|
+
|
|
26
|
+
const treeW = (s) => COLS * (s + 2);
|
|
27
|
+
const treeH = (s) => TIERS * (s / 2) + TIERS - 1;
|
|
28
|
+
|
|
29
|
+
/** Icon size and trees side by side for a `w` x `h` list pane: three trees first, big icons next. */
|
|
30
|
+
function treeSize(w, h) {
|
|
31
|
+
for (const [s, n] of [[6, 3], [4, 3], [6, 2], [4, 2], [6, 1], [4, 1]]) {
|
|
32
|
+
if (h >= 2 + treeH(s) && w >= n * treeW(s) + (n - 1) * GAP + 1) return { s, n };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Will the Talents tab draw as trees right now? */
|
|
38
|
+
export function talentPlan(app) {
|
|
39
|
+
if (!iconsOn()) return null;
|
|
40
|
+
const L = app.layout();
|
|
41
|
+
return L.detailW ? treeSize(L.listW, L.mainH) : null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const FRAME = { maxed: '33', up: '32', locked: '90' };
|
|
45
|
+
const BADGE = { maxed: '93', up: '92', locked: '37' };
|
|
46
|
+
|
|
47
|
+
/** The trees' lines for the talents view's `rows` in a `W` x `H` pane, and where the arrows go. */
|
|
48
|
+
export function talentGrid(app, rows, W, H) {
|
|
49
|
+
const plan = iconsOn() ? treeSize(W, H) : null;
|
|
50
|
+
if (!plan) return null;
|
|
51
|
+
const { s, n } = plan, ch = s / 2, cw = s + 2;
|
|
52
|
+
const view = app.top();
|
|
53
|
+
const cur = view?.cursor ?? 0;
|
|
54
|
+
const trees = app.ds.talentTrees(app.ch.cls);
|
|
55
|
+
const nodes = rows.map((r, i) => ({ ...r.node, i })).filter((x) => x.t);
|
|
56
|
+
const curTree = nodes.find((x) => x.i === cur)?.tree ?? 0;
|
|
57
|
+
// The trees on screen follow the cursor when they do not all fit.
|
|
58
|
+
let start = Math.max(0, Math.min(view?.treeStart ?? 0, trees.length - n));
|
|
59
|
+
if (curTree < start) start = curTree;
|
|
60
|
+
if (curTree >= start + n) start = curTree - n + 1;
|
|
61
|
+
if (view) view.treeStart = start;
|
|
62
|
+
|
|
63
|
+
const cv = canvas(W, H);
|
|
64
|
+
const free = availablePoints(app.ch);
|
|
65
|
+
cv.text(1, 0, app.ch.level < 10 ? dim('Talents unlock at level 10.') : `${bold(plural(free, 'point'))} to spend ${dim('Enter: learn u: refund Shift+arrows: move on the trees B: builds')}`);
|
|
66
|
+
const top = 2;
|
|
67
|
+
// Room left over spreads the trees apart (up to a few columns), so three narrow trees do not huddle left.
|
|
68
|
+
const gap = n > 1 ? Math.min(6, Math.floor((W - 1 - n * treeW(s)) / (n - 1))) : 0;
|
|
69
|
+
const ox = (k) => (k - start) * (treeW(s) + gap);
|
|
70
|
+
const at = new Map(nodes.map((x) => [x.t.id, x]));
|
|
71
|
+
const cellX = (x) => ox(x.tree) + x.t.col * cw;
|
|
72
|
+
const cellY = (x) => top + x.t.tier * (ch + 1);
|
|
73
|
+
const paint = (codes, c) => `\x1b[${codes}m${c}\x1b[0m`;
|
|
74
|
+
|
|
75
|
+
for (let k = start; k < Math.min(trees.length, start + n); k++) {
|
|
76
|
+
const tr = trees[k];
|
|
77
|
+
const title = `${tr.name} ${spentInTree(app.ch, tr.tabId, app.ds)}`;
|
|
78
|
+
const pad = Math.max(0, Math.floor((treeW(s) - title.length) / 2));
|
|
79
|
+
const arrows = n < trees.length ? `${k === start && start > 0 ? '◀' : ' '}${' '.repeat(Math.max(0, pad - 1))}` : ' '.repeat(pad);
|
|
80
|
+
cv.text(ox(k), 1, fit(`${arrows}${k === curTree ? cyan(bold(title)) : dim(title)}`, treeW(s) - 1));
|
|
81
|
+
if (k === start + n - 1 && start + n < trees.length) cv.text(ox(k) + treeW(s) - 1, 1, '▶');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const visible = (x) => x.tree >= start && x.tree < start + n;
|
|
85
|
+
const draw = (x) => drawCell(cv, cellX(x), cellY(x), s, {
|
|
86
|
+
icon: x.icon, tone: x.tone === 'locked' && !x.rank ? 'gray' : '', frame: FRAME[x.tone], selected: x.i === cur,
|
|
87
|
+
badge: x.tone === 'locked' && !x.rank ? null : { text: String(x.rank), codes: BADGE[x.tone] },
|
|
88
|
+
});
|
|
89
|
+
for (const x of nodes) if (visible(x)) draw(x);
|
|
90
|
+
|
|
91
|
+
// Prerequisite arrows: along a tier, down a column, or down then across into the node (a diagonal).
|
|
92
|
+
const taken = new Set(nodes.filter(visible).map((x) => `${x.tree}:${x.t.tier}:${x.t.col}`));
|
|
93
|
+
for (const x of nodes) {
|
|
94
|
+
const pre = x.t.prereq && at.get(x.t.prereq.talentId);
|
|
95
|
+
if (!pre || !visible(x)) continue;
|
|
96
|
+
const codes = (app.ch.talents?.[pre.t.id] ?? 0) >= x.t.prereq.rank ? '93' : '90';
|
|
97
|
+
const py = cellY(pre), ty = cellY(x), mid = Math.floor((ch - 1) / 2);
|
|
98
|
+
if (pre.t.tier === x.t.tier) {
|
|
99
|
+
const right = x.t.col > pre.t.col;
|
|
100
|
+
const from = right ? cellX(pre) + s + 1 : cellX(x) + s + 1, to = right ? cellX(x) : cellX(pre);
|
|
101
|
+
for (let c = from; c <= to; c++) cv.put(c, py + mid, paint(codes, c === (right ? to : from) ? (right ? '►' : '◄') : '─'));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const pcx = cellX(pre) + s / 2, tcx = cellX(x) + s / 2;
|
|
105
|
+
const bend = pre.t.col !== x.t.col;
|
|
106
|
+
for (let y = py + ch; y < ty - (bend ? 1 : 0); y++) {
|
|
107
|
+
const tier = Math.floor((y - top) / (ch + 1));
|
|
108
|
+
const onNode = (y - top) % (ch + 1) < ch && taken.has(`${x.tree}:${tier}:${pre.t.col}`);
|
|
109
|
+
if (!onNode) cv.put(pcx, y, paint(codes, y === ty - 1 ? '▼' : '│'));
|
|
110
|
+
}
|
|
111
|
+
if (bend) {
|
|
112
|
+
const y = ty - 1, right = tcx > pcx;
|
|
113
|
+
cv.put(pcx, y, paint(codes, right ? '└' : '┘'));
|
|
114
|
+
for (let c = Math.min(pcx, tcx) + 1; c < Math.max(pcx, tcx); c++) cv.put(c, y, paint(codes, '─'));
|
|
115
|
+
cv.put(tcx, y, paint(codes, '▼'));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// The cursor's frame goes on last so no arrow cuts it.
|
|
119
|
+
const sel = nodes.find((x) => x.i === cur);
|
|
120
|
+
if (sel && visible(sel)) draw(sel);
|
|
121
|
+
|
|
122
|
+
const pts = nodes.map((x) => ({ i: x.i, x: x.tree * (COLS + 1) + x.t.col, y: x.t.tier, g: x.tree }));
|
|
123
|
+
return { lines: cv.lines(), nav: (dir, from) => gridStep(pts, from, dir, { dyW: 10, dxW: 1, slack: TIERS }) };
|
|
124
|
+
}
|
package/src/cli/tuiTalents.mjs
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
// Talent builds for the Talents tab (B): the premade playerbot specs for your class, loadouts you saved, a pasted
|
|
2
2
|
// talent string (Wowhead-style digits or a classicdb link), and your own string to copy. Applying any of them
|
|
3
3
|
// is a respec at the usual price when points are already spent.
|
|
4
|
-
import { bold, dim, yellow, green, red, cyan } from './text.mjs';
|
|
5
|
-
import { moneyText } from '../domain/economy/money.ts';
|
|
4
|
+
import { bold, dim, yellow, green, red, cyan, coinText } from './text.mjs';
|
|
6
5
|
import { talentLinkFromTalents } from '../domain/character/talentLink.ts';
|
|
7
6
|
import { linkFromClassicdbHash } from '../domain/character/talentImport.ts';
|
|
8
7
|
|
|
9
8
|
function applied(app, r) {
|
|
10
|
-
app.result(r, r.ok ? `Your talents are now ${r.name}${r.cost ? ` (respec ${
|
|
9
|
+
app.result(r, r.ok ? `Your talents are now ${r.name}${r.cost ? ` (respec ${coinText(r.cost)})` : ''}.` : '');
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
/** Spent points cost a respec; say the price before doing it. */
|
|
14
13
|
function withRespec(app, label, run) {
|
|
15
14
|
const cost = app.g.respecCost();
|
|
16
|
-
if (cost > 0) app.confirm(`Switch to ${label}? The respec costs ${
|
|
15
|
+
if (cost > 0) app.confirm(`Switch to ${label}? The respec costs ${coinText(cost)}.`, () => applied(app, run()));
|
|
17
16
|
else applied(app, run());
|
|
18
17
|
}
|
|
19
18
|
|