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
package/src/cli/tuiCity.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// City services: the bank, the auction house and the mailbox. Same view shape as tuiViews.mjs; every
|
|
2
2
|
// change is a Game facade call. Where they open follows phonecraft's CITY_PANEL rule: the auction
|
|
3
3
|
// house in the six capitals, the bank there plus the goblin and Crossroads banker towns.
|
|
4
|
-
import { bold, dim, cyan, yellow, green, red, inverse, vis, useColor, qualityPaint, fit, secondsText, plural, itemText } from './text.mjs';
|
|
5
|
-
import { itemLines } from './tuiViews.mjs';
|
|
4
|
+
import { bold, dim, cyan, yellow, green, red, inverse, vis, useColor, qualityPaint, fit, secondsText, plural, itemText, coinText } from './text.mjs';
|
|
6
5
|
import { moneyText } from '../domain/economy/money.ts';
|
|
6
|
+
import { itemLines } from './tuiViews.mjs';
|
|
7
7
|
import { bagCapacity } from '../domain/items/equipment.ts';
|
|
8
8
|
import { countItem } from '../domain/items/inventory.ts';
|
|
9
9
|
import { bankCapacity, nextBankBagSlotCost } from '../domain/items/bank.ts';
|
|
@@ -66,7 +66,7 @@ export function bankDialog(app, startMode = 'deposit') {
|
|
|
66
66
|
'+': { hint: 'buy bag slot', fn: (a) => {
|
|
67
67
|
const cost = nextBankBagSlotCost(a.g.state.bankBags);
|
|
68
68
|
if (cost == null) { a.flashMsg('You already own every bank bag slot.'); return; }
|
|
69
|
-
a.confirm(`Buy a bank bag slot for ${
|
|
69
|
+
a.confirm(`Buy a bank bag slot for ${coinText(cost)}?`, () => a.result(a.g.buyBankBagSlot(), `Bought a bank bag slot for ${coinText(cost)}.`));
|
|
70
70
|
} },
|
|
71
71
|
D: { hint: 'deposit all of it', fn: (a, row) => { if (mode !== 'deposit' || !row?.data?.s) return; const e = row.data.s.item; const r = a.g.bankDeposit(e, countItem(a.g.state.inventory, e)); a.result(r, r.ok ? `Deposited every ${a.itemLabel(e)} (${r.moved}).` : ''); } },
|
|
72
72
|
B: { hint: 'slot a bag', fn: (a, row) => {
|
|
@@ -84,8 +84,8 @@ export function bankDialog(app, startMode = 'deposit') {
|
|
|
84
84
|
const bankCap = bankCapacity(st.bankBags, ds), bagCap = bagCapacity(a.ch, ds);
|
|
85
85
|
const owned = st.bankBags.length, slotted = st.bankBags.filter((b) => b != null).length;
|
|
86
86
|
const rows = [
|
|
87
|
-
{ text: `${bold('Bank')} ${dim(`${st.bank.stacks.length}/${bankCap}`)} ${bold('Bags')} ${dim(`${st.inventory.stacks.length}/${bagCap}`)} ${
|
|
88
|
-
{ text: dim(`Bank bag slots: ${owned} owned, ${slotted} holding a bag${nextBankBagSlotCost(st.bankBags) != null ? `, next costs ${
|
|
87
|
+
{ text: `${bold('Bank')} ${dim(`${st.bank.stacks.length}/${bankCap}`)} ${bold('Bags')} ${dim(`${st.inventory.stacks.length}/${bagCap}`)} ${coinText(a.ch.gold)}`, header: true },
|
|
88
|
+
{ text: dim(`Bank bag slots: ${owned} owned, ${slotted} holding a bag${nextBankBagSlotCost(st.bankBags) != null ? `, next costs ${coinText(nextBankBagSlotCost(st.bankBags))}` : ''}`), header: true },
|
|
89
89
|
];
|
|
90
90
|
// Each bank bag slot: Enter takes the bag in it back to your bags (it has to be empty).
|
|
91
91
|
st.bankBags.forEach((b, slot) => {
|
|
@@ -103,7 +103,7 @@ export function bankDialog(app, startMode = 'deposit') {
|
|
|
103
103
|
const it = ds.item(s.item);
|
|
104
104
|
const tag = it?.slot ? dim(` ${it.slot}`) : it?.questItem ? dim(' quest') : '';
|
|
105
105
|
rows.push({
|
|
106
|
-
text: `${qualityPaint(it?.quality)(it?.name ?? `item ${s.item}`)}${s.count > 1 ? ` x${s.count}` : ''}${tag}`, data: { s, index },
|
|
106
|
+
text: `${qualityPaint(it?.quality)(it?.name ?? `item ${s.item}`)}${s.count > 1 ? ` x${s.count}` : ''}${tag}`, data: { s, index }, icon: it?.icon, sub: dim(it?.typeName ?? ''),
|
|
107
107
|
detail: () => itemLines(a, it, ['', dim(mode === 'deposit' ? 'Enter: deposit n: deposit some' : 'Enter: withdraw n: withdraw some')]),
|
|
108
108
|
act: () => move(a, index),
|
|
109
109
|
});
|
|
@@ -146,7 +146,7 @@ function ahCategory(it) {
|
|
|
146
146
|
* One line of tabs, the picked one inverse; when it does not fit it scrolls to keep the pick in view. A strip
|
|
147
147
|
* under the cursor is drawn inverse whole, so there the pick is bracketed instead.
|
|
148
148
|
*/
|
|
149
|
-
function tabStrip(labels, at, width, focused = false) {
|
|
149
|
+
export function tabStrip(labels, at, width, focused = false) {
|
|
150
150
|
const cells = labels.map((l, i) => (i === at ? (useColor && !focused ? inverse(` ${l} `) : focused ? bold(`[${l}]`) : `[${l}]`) : ` ${l} `));
|
|
151
151
|
let from = 0, to = cells.length;
|
|
152
152
|
const len = () => cells.slice(from, to).reduce((n, c) => n + vis(c), 0) + (from > 0 ? 2 : 0) + (to < cells.length ? 2 : 0);
|
|
@@ -213,10 +213,10 @@ export function auctionDialog(app) {
|
|
|
213
213
|
b: { hint: 'bid', fn: (a, row) => {
|
|
214
214
|
if (mode !== 'browse' || !row?.data?.auction) return;
|
|
215
215
|
const au = row.data.auction, min = minNextBid(au);
|
|
216
|
-
a.prompt(`Bid on ${a.itemLabel(au.item)} (at least ${
|
|
216
|
+
a.prompt(`Bid on ${a.itemLabel(au.item)} (at least ${coinText(min)}): `, (t) => {
|
|
217
217
|
const amount = parseMoney(t);
|
|
218
218
|
if (amount == null) { a.say(red('Say a price like 2g 50s or 250.')); a.render(); return; }
|
|
219
|
-
a.result(a.g.bidAuction(au.id, amount, Date.now()), `You bid ${
|
|
219
|
+
a.result(a.g.bidAuction(au.id, amount, Date.now()), `You bid ${coinText(amount)} on ${a.itemLabel(au.item)}.`);
|
|
220
220
|
a.afterAction();
|
|
221
221
|
}, moneyText(min));
|
|
222
222
|
} },
|
|
@@ -236,7 +236,7 @@ export function auctionDialog(app) {
|
|
|
236
236
|
const L = a.layout(), width = (L.detailW ? L.listW : L.W) - 2;
|
|
237
237
|
const modeLabels = MODES.map((m) => ({ browse: 'Browse', sell: 'Sell', mine: 'My auctions' }[m]));
|
|
238
238
|
const rows = [
|
|
239
|
-
{ text: `${bold('Auction House')} ${tabStrip(modeLabels, MODES.indexOf(mode), 40)} ${
|
|
239
|
+
{ text: `${bold('Auction House')} ${tabStrip(modeLabels, MODES.indexOf(mode), 40)} ${coinText(a.ch.gold)}${unread ? yellow(` ${unread} new mail`) : ''}`, header: true },
|
|
240
240
|
];
|
|
241
241
|
if (mode === 'browse') {
|
|
242
242
|
const b = browse(a, now);
|
|
@@ -261,10 +261,10 @@ export function auctionDialog(app) {
|
|
|
261
261
|
const can = au.buyoutCopper <= a.ch.gold;
|
|
262
262
|
const name = `${qualityPaint(it?.quality)(it?.name ?? au.item)}${au.quantity > 1 ? ` x${au.quantity}` : ''}`;
|
|
263
263
|
rows.push({
|
|
264
|
-
text: `${can ? name : dim(name)} ${
|
|
265
|
-
data: { auction: au },
|
|
266
|
-
detail: () => itemLines(a, it, ['', `Buyout: ${
|
|
267
|
-
act: () => a.confirm(`Buy ${itemText(it, String(au.item))}${au.quantity > 1 ? ` x${au.quantity}` : ''} for ${
|
|
264
|
+
text: `${can ? name : dim(name)} ${coinText(au.buyoutCopper)}${au.quantity > 1 ? dim(` (${coinText(unit(au))} each)`) : ''} ${dim('bid')} ${coinText(au.bidCopper)}${au.highBidder === a.ch.name ? green(' yours') : ''} ${timeLeft(au, now)}${it?.requiredLevel > a.ch.level ? red(` L${it.requiredLevel}`) : ''}`,
|
|
265
|
+
data: { auction: au }, icon: it?.icon, sub: dim(it?.typeName ?? ''),
|
|
266
|
+
detail: () => itemLines(a, it, ['', `Buyout: ${coinText(au.buyoutCopper)}`, `Current bid: ${coinText(au.bidCopper)}${au.highBidder ? dim(` (${au.highBidder === a.ch.name ? 'you' : au.highBidder})`) : dim(' (no bids)')}`, `Next bid: ${coinText(minNextBid(au))}`, dim(`Seller: ${au.seller}`), '', dim('Enter: buy out b: bid')]),
|
|
267
|
+
act: () => a.confirm(`Buy ${itemText(it, String(au.item))}${au.quantity > 1 ? ` x${au.quantity}` : ''} for ${coinText(au.buyoutCopper)}?`, () => a.result(a.g.buyoutAuction(au.id), `Bought ${itemText(it)}${au.quantity > 1 ? ` x${au.quantity}` : ''} for ${coinText(au.buyoutCopper)}.`)),
|
|
268
268
|
});
|
|
269
269
|
}
|
|
270
270
|
} else if (mode === 'sell') {
|
|
@@ -277,7 +277,7 @@ export function auctionDialog(app) {
|
|
|
277
277
|
const soulbound = !!s.bound, quest = !!it?.questItem;
|
|
278
278
|
const name = `${qualityPaint(it?.quality)(it?.name ?? s.item)}${s.count > 1 ? ` x${s.count}` : ''}`;
|
|
279
279
|
rows.push({
|
|
280
|
-
text: `${soulbound || quest ? dim(name) : name}${soulbound ? red(' soulbound') : quest ? dim(' quest') : ''}${it?.sellPrice ? dim(` vendor ${
|
|
280
|
+
text: `${soulbound || quest ? dim(name) : name}${soulbound ? red(' soulbound') : quest ? dim(' quest') : ''}${it?.sellPrice ? dim(` vendor ${coinText(it.sellPrice * s.count)}`) : ''}`, data: { s }, icon: it?.icon, sub: dim(it?.typeName ?? ''),
|
|
281
281
|
detail: () => itemLines(a, it, ['', dim('Enter: set prices and list it')]),
|
|
282
282
|
act: () => { if (soulbound) a.flashMsg('Soulbound items cannot be auctioned.'); else if (quest) a.flashMsg('Quest items cannot be auctioned.'); else sellFlow(a, s, it); },
|
|
283
283
|
});
|
|
@@ -292,9 +292,9 @@ export function auctionDialog(app) {
|
|
|
292
292
|
for (const au of own) {
|
|
293
293
|
const it = ds.item(au.item);
|
|
294
294
|
rows.push({
|
|
295
|
-
text: `${qualityPaint(it?.quality)(it?.name ?? au.item)}${au.quantity > 1 ? ` x${au.quantity}` : ''} ${dim('bid')} ${
|
|
296
|
-
data: { auction: au },
|
|
297
|
-
detail: () => itemLines(a, it, ['', `Buyout: ${
|
|
295
|
+
text: `${qualityPaint(it?.quality)(it?.name ?? au.item)}${au.quantity > 1 ? ` x${au.quantity}` : ''} ${dim('bid')} ${coinText(au.bidCopper)}${au.highBidder ? green(' (bid placed)') : ''} ${dim('buyout')} ${coinText(au.buyoutCopper)} ${timeLeft(au, now)}`,
|
|
296
|
+
data: { auction: au }, icon: it?.icon, sub: dim(it?.typeName ?? ''),
|
|
297
|
+
detail: () => itemLines(a, it, ['', `Buyout: ${coinText(au.buyoutCopper)}`, `Bid: ${coinText(au.bidCopper)}${au.highBidder ? ` by ${au.highBidder}` : ''}`, '', dim('Enter: cancel the listing (the deposit is lost)')]),
|
|
298
298
|
act: () => a.confirm(`Cancel the ${itemText(it)} listing?`, () => a.result(a.g.cancelAuction(au.id), `Listing cancelled. ${itemText(it)} is back in your bags.`)),
|
|
299
299
|
});
|
|
300
300
|
}
|
|
@@ -302,7 +302,7 @@ export function auctionDialog(app) {
|
|
|
302
302
|
if (!bids.length) rows.push({ text: dim(' You hold no high bids.'), header: true });
|
|
303
303
|
for (const au of bids) {
|
|
304
304
|
const it = ds.item(au.item);
|
|
305
|
-
rows.push({ text: ` ${qualityPaint(it?.quality)(it?.name ?? au.item)}${au.quantity > 1 ? ` x${au.quantity}` : ''} ${dim('your bid')} ${
|
|
305
|
+
rows.push({ text: ` ${qualityPaint(it?.quality)(it?.name ?? au.item)}${au.quantity > 1 ? ` x${au.quantity}` : ''} ${dim('your bid')} ${coinText(au.bidCopper)} ${timeLeft(au, now)}`, header: true });
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
if (toList) {
|
|
@@ -322,11 +322,11 @@ function sellFlow(app, s, it) {
|
|
|
322
322
|
const finish = (qty, bid, buyout) => {
|
|
323
323
|
const opts = Object.entries(AH_DURATIONS_MS).map(([k, ms]) => {
|
|
324
324
|
const dep = auctionDeposit(it?.sellPrice ?? 0, qty, ms / 1000);
|
|
325
|
-
return { label: `${{ short: '2 hours', medium: '8 hours', long: '24 hours' }[k]} ${dim(`deposit ${
|
|
325
|
+
return { label: `${{ short: '2 hours', medium: '8 hours', long: '24 hours' }[k]} ${dim(`deposit ${coinText(dep)}`)}`, value: k };
|
|
326
326
|
});
|
|
327
327
|
app.pick('How long should it run?', opts, (dur) => {
|
|
328
328
|
const r = app.g.listAuction(s.item, qty, bid, buyout, dur, Date.now());
|
|
329
|
-
app.result(r, r.ok ? `Listed ${itemText(it)}${qty > 1 ? ` x${qty}` : ''}: bid ${
|
|
329
|
+
app.result(r, r.ok ? `Listed ${itemText(it)}${qty > 1 ? ` x${qty}` : ''}: bid ${coinText(bid)}, buyout ${coinText(buyout)}.` : '');
|
|
330
330
|
app.afterAction();
|
|
331
331
|
});
|
|
332
332
|
};
|
|
@@ -335,7 +335,7 @@ function sellFlow(app, s, it) {
|
|
|
335
335
|
app.prompt(`Starting bid for ${itemText(it)}${qty > 1 ? ` x${qty}` : ''} (e.g. 2g 50s): `, (t1) => {
|
|
336
336
|
const bid = parseMoney(t1);
|
|
337
337
|
if (bid == null || bid < 1) { app.say(red('Say a price like 2g 50s or 250.')); app.render(); return; }
|
|
338
|
-
app.prompt(`Buyout (at least ${
|
|
338
|
+
app.prompt(`Buyout (at least ${coinText(bid)}): `, (t2) => {
|
|
339
339
|
const buyout = parseMoney(t2);
|
|
340
340
|
if (buyout == null || buyout < bid) { app.say(red('The buyout must be at least the starting bid.')); app.render(); return; }
|
|
341
341
|
finish(qty, bid, buyout);
|
|
@@ -375,16 +375,16 @@ export function mailDialog(app) {
|
|
|
375
375
|
rows(a) {
|
|
376
376
|
const now = Date.now();
|
|
377
377
|
const mail = a.g.listMail(now);
|
|
378
|
-
const rows = [{ text: `${bold('Mailbox')} ${dim(plural(mail.length, 'letter'))} ${
|
|
378
|
+
const rows = [{ text: `${bold('Mailbox')} ${dim(plural(mail.length, 'letter'))} ${coinText(a.ch.gold)}${mail.length ? dim(' a: take everything') : ''}`, header: true }, { text: '', header: true }];
|
|
379
379
|
if (!mail.length) rows.push({ text: dim('No mail.'), header: true });
|
|
380
380
|
for (const m of mail) {
|
|
381
381
|
const atts = m.attachments.map((x) => `${a.itemLabel(x.entry)}${x.quantity > 1 ? ` x${x.quantity}` : ''}`);
|
|
382
|
-
const bits = [...(m.money ? [
|
|
382
|
+
const bits = [...(m.money ? [coinText(m.money)] : []), ...atts];
|
|
383
383
|
const read = (m.checked ?? 0) & 1;
|
|
384
384
|
rows.push({
|
|
385
|
-
text: `${read ? dim(m.subject) : bold(m.subject)} ${dim(`from ${m.sender}`)}${bits.length ? ` ${bits.join(', ')}` : ''}${m.cod ? red(` COD ${
|
|
386
|
-
data: { mail: m },
|
|
387
|
-
detail: () => [bold(m.subject), dim(`From ${m.sender}`), '', ...String(m.body ?? '').split('\n').filter(Boolean), ...(bits.length ? ['', cyan('Enclosed'), ...bits.map((b) => ` ${b}`)] : []), ...(m.cod ? ['', red(`Cash on delivery: ${
|
|
385
|
+
text: `${read ? dim(m.subject) : bold(m.subject)} ${dim(`from ${m.sender}`)}${bits.length ? ` ${bits.join(', ')}` : ''}${m.cod ? red(` COD ${coinText(m.cod)}`) : ''}`,
|
|
386
|
+
data: { mail: m }, icon: (m.attachments.length ? a.ds.item(m.attachments[0].entry)?.icon : null) ?? (m.money ? 'inv_misc_coin_03' : 'inv_letter_01'),
|
|
387
|
+
detail: () => [bold(m.subject), dim(`From ${m.sender}`), '', ...String(m.body ?? '').split('\n').filter(Boolean), ...(bits.length ? ['', cyan('Enclosed'), ...bits.map((b) => ` ${b}`)] : []), ...(m.cod ? ['', red(`Cash on delivery: ${coinText(m.cod)}`)] : []), '', dim(`Expires in ${secondsText(Math.max(0, m.expiresAt - now))}`)],
|
|
388
388
|
act: () => takeMail(a, m),
|
|
389
389
|
});
|
|
390
390
|
}
|
|
@@ -396,7 +396,7 @@ export function mailDialog(app) {
|
|
|
396
396
|
function takeMail(app, m) {
|
|
397
397
|
app.g.markMailRead(m.id);
|
|
398
398
|
let took = 0;
|
|
399
|
-
if (m.money) { const r = app.g.takeMailMoney(m.id); if (r.ok && r.taken) { took++; app.say(green(`You take ${
|
|
399
|
+
if (m.money) { const r = app.g.takeMailMoney(m.id); if (r.ok && r.taken) { took++; app.say(green(`You take ${coinText(r.taken)}.`)); } else if (!r.ok) app.say(red(r.reason)); }
|
|
400
400
|
for (let i = 0; i < 16; i++) {
|
|
401
401
|
const cur = app.g.listMail(Date.now()).find((x) => x.id === m.id);
|
|
402
402
|
if (!cur?.attachments.length) break;
|
|
@@ -465,7 +465,7 @@ export function composeDialog(app) {
|
|
|
465
465
|
dest.state.mailbox = r.destMailbox;
|
|
466
466
|
dest.updatedAt = Date.now();
|
|
467
467
|
await a.repo.save(dest);
|
|
468
|
-
a.say(green(`Mail sent to ${dest.state.char.name}.`) + dim(` Postage ${
|
|
468
|
+
a.say(green(`Mail sent to ${dest.state.char.name}.`) + dim(` Postage ${coinText(r.cost)}.`));
|
|
469
469
|
await a.persist();
|
|
470
470
|
if (a.top()?.id === 'mail-compose') a.pop(); else a.render();
|
|
471
471
|
};
|
|
@@ -476,7 +476,7 @@ export function composeDialog(app) {
|
|
|
476
476
|
const rows = [{ text: bold('Send mail'), header: true }, { text: '', header: true }];
|
|
477
477
|
rows.push({ text: `To ${m.to ? cyan(m.to.name) : dim('pick one of your characters')}`, act: (a2) => { view.picking = pickAlt(a2, (s) => { m.to = s; }); } });
|
|
478
478
|
rows.push({ text: `Subject ${m.subject || dim('(the first item, or "A letter")')}`, act: (a2) => a2.prompt('Subject: ', (t) => { m.subject = t.slice(0, 64); a2.render(); }, m.subject) });
|
|
479
|
-
rows.push({ text: `${m.cod ? 'COD ' : 'Money '} ${m.money ?
|
|
479
|
+
rows.push({ text: `${m.cod ? 'COD ' : 'Money '} ${m.money ? coinText(m.money) : dim('none')}`, act: (a2) => a2.prompt(m.cod ? 'Cash on delivery: ' : 'Money to send: ', (t) => { const c = parseMoney(t); if (c == null) a2.flashMsg('Say a price like 2g 50s or 250.'); else m.money = c; a2.render(); }) });
|
|
480
480
|
rows.push({ text: `Pay ${m.cod ? red('cash on delivery: they pay the money to take the items') : dim('the money is a gift')}`, act: () => { m.cod = !m.cod; } });
|
|
481
481
|
rows.push({ text: '', header: true }, { text: `Enclose ${dim(`${m.attach.size}/${MAX_MAIL_ITEMS}`)}`, header: true });
|
|
482
482
|
a.g.state.inventory.stacks.forEach((s, slot) => {
|
|
@@ -487,7 +487,7 @@ export function composeDialog(app) {
|
|
|
487
487
|
act: () => { if (on) m.attach.delete(slot); else if (m.attach.size < MAX_MAIL_ITEMS) m.attach.add(slot); else a.flashMsg(`At most ${MAX_MAIL_ITEMS} stacks in one letter.`); },
|
|
488
488
|
});
|
|
489
489
|
});
|
|
490
|
-
rows.push({ text: '', header: true }, { text: green(`Send ${dim(`S, postage ${
|
|
490
|
+
rows.push({ text: '', header: true }, { text: green(`Send ${dim(`S, postage ${coinText(MAIL_POSTAGE_COPPER)}`)}`), act: () => { view.sending = send(a); } });
|
|
491
491
|
return rows;
|
|
492
492
|
},
|
|
493
493
|
};
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
// The rest of a companion's page and the party-wide companion dialogs: spec, talent link, AoE, off-heal and xp
|
|
2
2
|
// lock; their pet; their quests; their bags and bank; the tavern duel; consumable sharing, the party vault
|
|
3
3
|
// and the hire search. Ports of phonecraft companionsPanel.ts, companionKitPanel.ts and consumableSharePanel.ts.
|
|
4
|
-
import { bold, dim, cyan, yellow, green, red, qualityPaint, fit, plural, itemText } from './text.mjs';
|
|
4
|
+
import { bold, dim, cyan, yellow, green, red, qualityPaint, fit, plural, itemText, coinText } from './text.mjs';
|
|
5
5
|
import { itemLines, noteSale } from './tuiViews.mjs';
|
|
6
6
|
import { companionUseWithPicker } from './tuiEnchantTarget.mjs';
|
|
7
7
|
import { startFight } from './tuiFight.mjs';
|
|
8
|
-
import { moneyText } from '../domain/economy/money.ts';
|
|
9
8
|
import { className } from '../domain/core/ids.ts';
|
|
10
9
|
import { companionLevel, companionCharacter } from '../domain/bots/squad.ts';
|
|
11
10
|
import { listTameablePets, unlockedWarlockDemons, WARLOCK_DEMONS, WARLOCK_DEMON_UNLOCK } from '../domain/bots/botPet.ts';
|
|
@@ -123,7 +122,7 @@ export function questRows(app, comp) {
|
|
|
123
122
|
act: () => {
|
|
124
123
|
if (o.kind === 'accept') { app.result(g.acceptCompanionQuest(id, o.quest), `${comp.name} takes ${o.title}.`); return; }
|
|
125
124
|
const q = ds.quest(o.quest);
|
|
126
|
-
const finish = (choice) => { const r = g.turnInCompanionQuest(id, o.quest, choice); app.result(r, r.ok ? `${comp.name} hands in ${o.title}${r.xp ? `: +${r.xp} xp` : ''}${r.money ? `, ${
|
|
125
|
+
const finish = (choice) => { const r = g.turnInCompanionQuest(id, o.quest, choice); app.result(r, r.ok ? `${comp.name} hands in ${o.title}${r.xp ? `: +${r.xp} xp` : ''}${r.money ? `, ${coinText(r.money)} to you` : ''}.` : ''); };
|
|
127
126
|
if (q?.rewChoiceItems?.length > 1) app.pick(`${comp.name}: choose a reward`, q.rewChoiceItems.map((it, i) => ({ label: app.itemLabel(it.item), detail: () => itemLines(app, ds.item(it.item)), value: i })), finish);
|
|
128
127
|
else finish(0);
|
|
129
128
|
},
|
|
@@ -165,12 +164,13 @@ export function bagRows(app, comp, cap) {
|
|
|
165
164
|
const name = `${itemText(it, 'it')}${s.count > 1 ? ` x${s.count}` : ''}`;
|
|
166
165
|
rows.push({
|
|
167
166
|
text: `${qualityPaint(it?.quality)(it?.name ?? s.item)}${s.count > 1 ? ` x${s.count}` : ''}`,
|
|
167
|
+
icon: it?.icon, sub: dim(`${it?.typeName ?? ''}${it?.sellPrice ? ` sells ${coinText(it.sellPrice * s.count)}` : ''}`),
|
|
168
168
|
detail: () => itemLines(app, it, ['', dim('Enter: take it u: use on them s: sell e: disenchant m: to another companion b: their bank d: destroy')]),
|
|
169
169
|
act: () => { const r = g.takeItemFromBotStack(id, slot); app.result(r, `You take ${it?.name ?? 'it'}${r.moved > 1 ? ` x${r.moved}` : ''} from ${comp.name}.`); },
|
|
170
170
|
keys: {
|
|
171
171
|
u: (a) => companionUseWithPicker(a, comp, s.item),
|
|
172
172
|
d: (a) => a.confirm(`Destroy ${name} from ${comp.name}'s bags?`, () => a.result(g.discardCompanionBagStack(id, slot), `Destroyed ${name}.`)),
|
|
173
|
-
s: (a) => { if (!a.vendor) { a.flashMsg('Open a vendor first.'); return; } const r = g.sellCompanionItemStack(id, slot); if (a.result(r, r.ok ? `Sold ${name} for ${
|
|
173
|
+
s: (a) => { if (!a.vendor) { a.flashMsg('Open a vendor first.'); return; } const r = g.sellCompanionItemStack(id, slot); if (a.result(r, r.ok ? `Sold ${name} for ${coinText(r.earned)}.` : '')) noteSale(a, s.item, r.qty, r.earned, comp); },
|
|
174
174
|
e: (a) => { const r = g.disenchantCompanionBagStack(id, slot); a.result(r, r.ok ? `It breaks into ${r.yielded.map((y) => `${a.itemLabel(y.item)}${y.count > 1 ? ` x${y.count}` : ''}`).join(', ')}.` : ''); },
|
|
175
175
|
b: (a) => a.result(g.companionBankDepositStack(id, slot), `${name} goes into ${comp.name}'s bank.`),
|
|
176
176
|
m: (a) => {
|
|
@@ -191,10 +191,10 @@ export function bankRows(app, comp) {
|
|
|
191
191
|
const rows = [H(''), H(`Their bank ${dim(`${bank.length} stacks Enter: back to their bags`)}`)];
|
|
192
192
|
bank.forEach((s, slot) => {
|
|
193
193
|
const it = ds.item(s.item);
|
|
194
|
-
rows.push({ text: `${qualityPaint(it?.quality)(it?.name ?? s.item)}${s.count > 1 ? ` x${s.count}` : ''}`, detail: () => itemLines(app, it), act: () => app.result(g.companionBankWithdrawStack(id, slot), `${it?.name} comes back out.`) });
|
|
194
|
+
rows.push({ text: `${qualityPaint(it?.quality)(it?.name ?? s.item)}${s.count > 1 ? ` x${s.count}` : ''}`, icon: it?.icon, sub: dim(it?.typeName ?? ''), detail: () => itemLines(app, it), act: () => app.result(g.companionBankWithdrawStack(id, slot), `${it?.name} comes back out.`) });
|
|
195
195
|
});
|
|
196
196
|
const bagSlots = comp.bankBags ?? [];
|
|
197
|
-
rows.push({ text: dim('Buy them another bank bag slot'), detail: () => [bold('Bank bag slot'), '', 'Paid from your purse; the price climbs with each slot like your own.'], act: () => { const r = g.buyCompanionBankSlot(id); app.result(r, r.ok ? `A new bank bag slot for ${comp.name}: ${
|
|
197
|
+
rows.push({ text: dim('Buy them another bank bag slot'), detail: () => [bold('Bank bag slot'), '', 'Paid from your purse; the price climbs with each slot like your own.'], act: () => { const r = g.buyCompanionBankSlot(id); app.result(r, r.ok ? `A new bank bag slot for ${comp.name}: ${coinText(r.cost)}.` : ''); } });
|
|
198
198
|
const bagsInBags = g.state.inventory.stacks.map((s) => ds.item(s.item)).filter((it) => it?.slot === 'bag');
|
|
199
199
|
if (bagsInBags.length) {
|
|
200
200
|
const free = bagSlots.map((b, i) => (b == null ? i : -1)).filter((i) => i >= 0);
|