adventure: let a player leave town from the web, not only read about it
W5a gave the web two verbs that cost nothing. These are the three that take arguments and spend coins: set out for a zone with a supply loadout, walk back into the run you extracted from, hire the pet sitter for a week or a month. Between them they cover the most common thing anybody does in the game, which until now could only be typed into Matrix. Arguments are the new surface, so they are the thing to be careful with. Nothing in a request is trusted: every zone, loadout and duration is looked up in the offer list gogobee pushed onto that owner's own private row, and the order stores what was found there rather than what was sent. A forged zone resolves to nothing and never becomes an order. gogobee then re-resolves all of it anyway, because an offer is a quote and a quote is not a permission. The money confirm is the equip panel's, lifted: cost, balance, and the balance it leaves. When it does not cover, it says so instead of printing a negative. Verified in a browser rather than only in tests, which is where both real defects came from: the confirm box was appending to the whole panel and so appeared at the bottom of the section instead of under the button that raised it, and button prices printed as EUR45000 above a dialog reading EUR45,000. Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
This commit is contained in:
@@ -203,7 +203,8 @@
|
||||
would be the page refusing an action the game would have allowed. gogobee
|
||||
answers rejected_not_running if it really has ended, and that answer shows
|
||||
up in the strip below. -->
|
||||
<section id="adv-actions" class="adv-actions mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete">
|
||||
<section id="adv-actions" class="adv-actions mt-6 rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 p-6 shadow-pete"
|
||||
data-balance="{{.Self.Balance}}">
|
||||
<h2 class="font-display text-xl font-bold mb-1">Your call</h2>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mb-4">
|
||||
Asked for here, done on the game box. It picks these up within a few seconds.
|
||||
@@ -221,6 +222,115 @@
|
||||
data-confirm="Pull out of the dungeon now? You keep the loot, XP and coins you're carrying, and the run waits where you left it: you have seven days to go back in. If you're leading a party, it ends the day for all of you.">Pull out of the run</button>
|
||||
</div>
|
||||
|
||||
{{/* W5b. Everything below is server-rendered from the offer list gogobee
|
||||
pushed onto this owner's own private row, so the page can only ever
|
||||
propose a zone, a loadout and a price the game itself quoted. The order
|
||||
carries those keys back and gogobee re-resolves all of them — an offer is
|
||||
a quote, never a permission. */}}
|
||||
|
||||
{{if .Self.Resume}}
|
||||
{{/* The way back in, first: it is the only offer here with a deadline on it,
|
||||
and it is the answer to the verdict "Pull out" leaves behind, which until
|
||||
now told a web player to go and type !resume in Matrix. */}}
|
||||
<div data-offer class="mt-5 pt-5 border-t border-[color:var(--ink)]/10">
|
||||
<h3 class="font-display text-base font-bold">Go back in</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5 mb-2.5">
|
||||
{{.Self.Resume.Display}}, day {{.Self.Resume.Day}} — waiting where you left it.
|
||||
{{with untilUnix .Self.Resume.ExpiresAt}}<span class="text-[color:var(--warn)] font-semibold">{{.}}</span>.{{end}}
|
||||
You re-stock before you go, so pick a pack.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{{$rz := .Self.Resume}}
|
||||
{{range $rz.Loadouts}}
|
||||
<button type="button"
|
||||
class="adv-action-btn rounded-full border border-theme-adventure/40 text-theme-adventure hover:bg-theme-adventure/10 px-3.5 py-1.5 text-sm font-semibold transition-colors"
|
||||
data-action="expedition_resume"
|
||||
data-loadout="{{.Key}}"
|
||||
data-cost="{{.Cost}}"
|
||||
data-label="{{.Name}} · €{{euro .Cost}}"
|
||||
data-confirm-label="Go back in"
|
||||
data-confirm="Walk back into {{$rz.Display}} on day {{$rz.Day}} with a {{.Name}} pack: {{.Blurb}}. About {{.Days}} days of provisions.">{{.Name}} · €{{euro .Cost}}</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Self.Zones}}
|
||||
{{/* Set out. The zone list is already level-gated and postgame-gated by
|
||||
gogobee, and it arrives EMPTY while this adventurer is already out —
|
||||
which is why there is no "you're on an expedition" branch here. */}}
|
||||
<div data-offer class="mt-5 pt-5 border-t border-[color:var(--ink)]/10">
|
||||
<h3 class="font-display text-base font-bold">Set out</h3>
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5 mb-2.5">
|
||||
Pick where, then pick how much to carry. The pack is what you pay for.
|
||||
</p>
|
||||
<label class="block">
|
||||
<span class="sr-only">Zone</span>
|
||||
<select id="adv-zone-pick"
|
||||
class="w-full rounded-xl bg-[color:var(--ink)]/5 border border-[color:var(--ink)]/15 px-3 py-2 text-sm font-semibold">
|
||||
{{range .Self.Zones}}
|
||||
<option value="{{.ID}}">{{.Display}} · T{{.Tier}}{{if .Postgame}} · mythic{{end}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</label>
|
||||
{{/* One group per zone, all rendered, one shown. The costs differ by tier,
|
||||
so a single shared row of buttons would have to be repriced in the
|
||||
browser — and Pete does no arithmetic on the game's money. */}}
|
||||
{{range .Self.Zones}}
|
||||
{{$z := .}}
|
||||
<div class="adv-zone-loadouts mt-2.5 hidden" data-zone="{{$z.ID}}">
|
||||
{{with $z.Hook}}<p class="text-xs italic text-[color:var(--ink)]/50 mb-2">{{.}}</p>{{end}}
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
{{range $z.Loadouts}}
|
||||
<button type="button"
|
||||
class="adv-action-btn rounded-full border border-theme-adventure/40 text-theme-adventure hover:bg-theme-adventure/10 px-3.5 py-1.5 text-sm font-semibold transition-colors"
|
||||
data-action="expedition_start"
|
||||
data-zone="{{$z.ID}}"
|
||||
data-loadout="{{.Key}}"
|
||||
data-cost="{{.Cost}}"
|
||||
data-label="{{.Name}} · €{{euro .Cost}}"
|
||||
data-confirm-label="Set out"
|
||||
data-confirm="Head for {{$z.Display}} with a {{.Name}} pack: {{.Blurb}}. About {{.Days}} days of provisions.">{{.Name}} · €{{euro .Cost}}</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Self.Babysit}}
|
||||
<div data-offer class="mt-5 pt-5 border-t border-[color:var(--ink)]/10">
|
||||
<h3 class="font-display text-base font-bold">The sitter</h3>
|
||||
{{if .Self.Babysit.Active}}
|
||||
{{/* Engaged: say so and offer nothing. Buying a second one is refused by
|
||||
the game anyway, and a buy button under "already engaged" reads as a
|
||||
page that has not noticed. */}}
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5">
|
||||
Somebody is looking after the camp{{with untilUnix .Self.Babysit.ExpiresAt}} — {{.}}{{end}}.
|
||||
Your pet is being tended daily and standard camps rest like fortified ones.
|
||||
</p>
|
||||
{{else}}
|
||||
<p class="text-sm text-[color:var(--ink)]/60 mt-0.5 mb-2.5">
|
||||
Pet tended daily, standard camps rest like fortified ones, rival duels declined for you.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button type="button"
|
||||
class="adv-action-btn rounded-full border border-theme-adventure/40 text-theme-adventure hover:bg-theme-adventure/10 px-3.5 py-1.5 text-sm font-semibold transition-colors"
|
||||
data-action="babysit" data-days="7" data-cost="{{.Self.Babysit.WeekCost}}"
|
||||
data-label="A week · €{{euro .Self.Babysit.WeekCost}}"
|
||||
data-confirm-label="Hire for a week"
|
||||
data-confirm="Engage the sitter for seven days. No refund if you cancel early.">A week · €{{euro .Self.Babysit.WeekCost}}</button>
|
||||
<button type="button"
|
||||
class="adv-action-btn rounded-full border border-theme-adventure/40 text-theme-adventure hover:bg-theme-adventure/10 px-3.5 py-1.5 text-sm font-semibold transition-colors"
|
||||
data-action="babysit" data-days="30" data-cost="{{.Self.Babysit.MonthCost}}"
|
||||
data-label="A month · €{{euro .Self.Babysit.MonthCost}}"
|
||||
data-confirm-label="Hire for a month"
|
||||
data-confirm="Engage the sitter for thirty days. No refund if you cancel early.">A month · €{{euro .Self.Babysit.MonthCost}}</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- The queue is honest: an action lands on the game box's next poll, so a
|
||||
fresh one reads "asked for", never "done". JS fills this from
|
||||
/api/adventure/orders. -->
|
||||
|
||||
Reference in New Issue
Block a user