explodorama / files / src / ui / chargeOffer.ts
chargeOffer.ts
bb3823d2 2026-10-31 Pavel Safronov | 1 | import { fireableHeld } from '../sim/actions' |
fcf44f32 2026-10-25 Pavel Safronov | 2 | import { salePrice } from '../sim/charges' |
d3e4ed26 2026-10-25 Pavel Safronov | 3 | import type { GameState } from '../sim/types' |
| 4 | import { CHARGE_ICONS, spriteUrl } from '../render/sprites' | |
| 5 | import { openDialog } from './dialog' | |
| 6 | import { 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 | |
bb3823d2 2026-10-31 Pavel Safronov | 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. | |
d3e4ed26 2026-10-25 Pavel Safronov | 14 | */ |
bb3823d2 2026-10-31 Pavel Safronov | 15 | export function openChargeOffer(s: GameState, answer: (choice: 'keep' | 'swap' | 'fire') => void): void { |
fcf44f32 2026-10-25 Pavel Safronov | 16 | const offered = s.chargeOffer?.kind |
| 17 | const offeredLeft = s.chargeOffer?.left ?? 0 | |
d3e4ed26 2026-10-25 Pavel Safronov | 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>` | |
fcf44f32 2026-10-25 Pavel Safronov | 23 | const newPrice = salePrice(offered, offeredLeft) |
d3e4ed26 2026-10-25 Pavel Safronov | 24 | const oldPrice = salePrice(held.kind, held.left) |
bb3823d2 2026-10-31 Pavel Safronov | 25 | // Fired first, a charge with uses left sells what remains. |
| 26 | const fireLeft = held.left > 1 ? salePrice(held.kind, held.left - 1) : 0 | |
d3e4ed26 2026-10-25 Pavel Safronov | 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>` + | |
fcf44f32 2026-10-25 Pavel Safronov | 32 | card(offered, `new · ${offeredLeft === 1 ? '1 use' : `${offeredLeft} uses`}`) + |
d3e4ed26 2026-10-25 Pavel Safronov | 33 | card(held.kind, `yours · ${held.left} ${held.left === 1 ? 'use' : 'uses'} left`), |
| 34 | buttons: [ | |
bb3823d2 2026-10-31 Pavel Safronov | 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 | : []), | |
d3e4ed26 2026-10-25 Pavel Safronov | 40 | ], |
| 41 | required: true, | |
| 42 | }) | |
| 43 | } |