fuzzyworld

explodorama / files / src / ui / chargeOffer.ts

chargeOffer.ts

2.3 KB · history · blame

1import { fireableHeld } from '../sim/actions'
2import { salePrice } from '../sim/charges'
3import type { GameState } from '../sim/types'
4import { CHARGE_ICONS, spriteUrl } from '../render/sprites'
5import { openDialog } from './dialog'
6import { CHARGE_TEXT } from './info'
7
8/**
9 * A charge picked up while carrying another (PLAN.md §19.7; user request, 2026-10-24): the player
10 * carries one at a time. Keep the one held and sell the new one, or swap: take the new one, and
11 * the one held is sold for what is left of it; or, for a charge that needs no aiming (Freeze),
12 * use the one held now and then take the new one (user request, 2026-10-31). It must be answered;
13 * it takes no turn.
14 */
15export function openChargeOffer(s: GameState, answer: (choice: 'keep' | 'swap' | 'fire') => void): void {
16 const offered = s.chargeOffer?.kind
17 const offeredLeft = s.chargeOffer?.left ?? 0
18 const held = s.charge
19 if (!offered || !held) return
20 const card = (kind: typeof offered, note: string) =>
21 `<div class="almanac-entry"><img src="${spriteUrl(CHARGE_ICONS[kind])}" alt="" draggable="false">` +
22 `<div><strong>${CHARGE_TEXT[kind].name}</strong> <small>${note}</small><p>${CHARGE_TEXT[kind].text}</p></div></div>`
23 const newPrice = salePrice(offered, offeredLeft)
24 const oldPrice = salePrice(held.kind, held.left)
25 // Fired first, a charge with uses left sells what remains.
26 const fireLeft = held.left > 1 ? salePrice(held.kind, held.left - 1) : 0
27 openDialog({
28 icon: spriteUrl(CHARGE_ICONS[offered]),
29 title: `${CHARGE_TEXT[offered].name}!`,
30 body:
31 `<p>You can carry one charge at a time. The one you don’t keep is sold for coins.</p>` +
32 card(offered, `new · ${offeredLeft === 1 ? '1 use' : `${offeredLeft} uses`}`) +
33 card(held.kind, `yours · ${held.left} ${held.left === 1 ? 'use' : 'uses'} left`),
34 buttons: [
35 { label: `Keep ${CHARGE_TEXT[held.kind].name} · +${newPrice}`, kind: 'plain', onClick: () => answer('keep') },
36 { label: `Take ${CHARGE_TEXT[offered].name} · +${oldPrice}`, kind: 'primary', onClick: () => answer('swap') },
37 ...(fireableHeld(s)
38 ? [{ label: `Use ${CHARGE_TEXT[held.kind].name}, then take ${CHARGE_TEXT[offered].name}${fireLeft ? ` · +${fireLeft}` : ''}`, kind: 'primary' as const, onClick: () => answer('fire') }]
39 : []),
40 ],
41 required: true,
42 })
43}