mirror of
https://github.com/prosolis/gogobee.git
synced 2026-09-14 10:51:09 +00:00
adventure: run the web's three new verbs through the game's own paths
The game-side half of setting out, going back in, and hiring the sitter from the web. Each is the existing command minus its framing: performExpeditionStart, performResume and performBabysitPurchase now hold the guards and the money, and !expedition start, !resume and !adventure babysit are what is left over. So a departure booked from a phone is the same departure - same eligibility chain, same supply freebies, same opening log line - rather than a second one that drifts. Refusals travel as advRefusal, which wraps a sentinel AND carries the finished sentence. That is what lets the commands keep the exact copy they always sent while the web gets a machine-readable verdict. All three spend coins on a retrying wire, so the debit is keyed to the order guid and a re-offer cannot charge twice. The subtle half is what a re-offer should ANSWER: a settled debit plus an already-started expedition means the order worked and lost its ack, not that the player is busy, so it reports applied instead of refusing the thing it did. Nothing refunds-then-retries - after a refund the keyed debit will not charge again, so a retry would hand over the goods for free, and every failure past the debit is therefore permanent. Also fixes a deadlock that predates all of this: !expedition extract and !expedition resume are aliases for two commands that take the per-user lock themselves, and the alias dispatcher already held it. Since it is a plain sync.Mutex the handler blocked forever and, because the deferred unlock never ran, every later adventure command from that player wedged too. It does not fail loudly on regression - it hangs - so the new test asserts with a timeout. Claude-Session: https://claude.ai/code/session_012bxpQQJDjC1mTtLN3VVtBQ
This commit is contained in:
@@ -540,13 +540,13 @@ type RealmOccupant struct {
|
||||
// recovered by gogobee at push time from the run history, which is why this is
|
||||
// pushed rather than derived on Pete.
|
||||
type RealmFirst struct {
|
||||
Kind string `json:"kind"` // "zone" | "treasure"
|
||||
Target string `json:"target"` // the zone id or treasure key
|
||||
Display string `json:"display"` // the human name for it
|
||||
Tier int `json:"tier,omitempty"` // zone tier, when kind is "zone"
|
||||
Holder string `json:"holder,omitempty"` // character name, empty when unrecoverable
|
||||
Token string `json:"token,omitempty"` // board token; empty when opted out
|
||||
AtUnix int64 `json:"at_unix"` // when the realm first saw it
|
||||
Kind string `json:"kind"` // "zone" | "treasure"
|
||||
Target string `json:"target"` // the zone id or treasure key
|
||||
Display string `json:"display"` // the human name for it
|
||||
Tier int `json:"tier,omitempty"` // zone tier, when kind is "zone"
|
||||
Holder string `json:"holder,omitempty"` // character name, empty when unrecoverable
|
||||
Token string `json:"token,omitempty"` // board token; empty when opted out
|
||||
AtUnix int64 `json:"at_unix"` // when the realm first saw it
|
||||
}
|
||||
|
||||
// RealmStanding is one adventurer's line on the board. Every number here is a
|
||||
@@ -631,6 +631,63 @@ type PlayerDetail struct {
|
||||
// No omitempty: a €0 balance is a real, informative fact (a broke player), not
|
||||
// an absent one — dropping it would let the confirm dialog show a stale amount.
|
||||
Balance float64 `json:"balance"`
|
||||
// Zones is where this adventurer may go right now, priced. It is the offer
|
||||
// list behind the web's "send on expedition" picker: level gating and the T6
|
||||
// postgame gate are resolved here, so a zone the player cannot enter is simply
|
||||
// absent rather than shown and then refused. Empty while they are already out.
|
||||
Zones []ZoneOffer `json:"zones,omitempty"`
|
||||
// Resume is the extracted expedition waiting to be walked back into, priced
|
||||
// the same way. Absent when there is nothing to resume.
|
||||
Resume *ResumeOffer `json:"resume,omitempty"`
|
||||
// Babysit is the pet-care subscription's standing and price. Always present
|
||||
// for a live adventurer: "you already have one" is as useful to the page as a
|
||||
// price is.
|
||||
Babysit *BabysitOffer `json:"babysit,omitempty"`
|
||||
}
|
||||
|
||||
// ZoneOffer is one place the owner may set out for, with what the trip costs.
|
||||
// Pete renders these and does no arithmetic — the prices are the game's, quoted
|
||||
// at push time, and gogobee re-quotes them for real when the order lands.
|
||||
type ZoneOffer struct {
|
||||
ID string `json:"id"`
|
||||
Display string `json:"display"`
|
||||
Tier int `json:"tier"`
|
||||
Hook string `json:"hook,omitempty"`
|
||||
Postgame bool `json:"postgame,omitempty"`
|
||||
Loadouts []LoadoutOffer `json:"loadouts,omitempty"`
|
||||
}
|
||||
|
||||
// LoadoutOffer is one supply preset for a zone: what it is called, what it
|
||||
// costs, and roughly how long it lasts. Key is the token the order carries back.
|
||||
type LoadoutOffer struct {
|
||||
Key string `json:"key"` // lean|balanced|heavy
|
||||
Name string `json:"name"`
|
||||
Blurb string `json:"blurb,omitempty"`
|
||||
Cost int `json:"cost"`
|
||||
Days int `json:"days"` // provisions at the zone's daily burn
|
||||
}
|
||||
|
||||
// ResumeOffer is the extracted expedition the owner can still walk back into,
|
||||
// with the same priced loadouts as a fresh departure. ExpiresAt is the end of
|
||||
// the seven-day window, so the page can say how long is left rather than just
|
||||
// that there is a way back.
|
||||
type ResumeOffer struct {
|
||||
ZoneID string `json:"zone_id"`
|
||||
Display string `json:"display"`
|
||||
Tier int `json:"tier"`
|
||||
Day int `json:"day"`
|
||||
ExpiresAt int64 `json:"expires_at,omitempty"`
|
||||
Loadouts []LoadoutOffer `json:"loadouts,omitempty"`
|
||||
}
|
||||
|
||||
// BabysitOffer is the sitter's standing and price. WeekCost/MonthCost are the
|
||||
// two durations the game sells; they scale with level, which is why they are
|
||||
// pushed rather than hardcoded on Pete.
|
||||
type BabysitOffer struct {
|
||||
Active bool `json:"active"`
|
||||
ExpiresAt int64 `json:"expires_at,omitempty"`
|
||||
WeekCost int `json:"week_cost"`
|
||||
MonthCost int `json:"month_cost"`
|
||||
}
|
||||
|
||||
// EquipSlotView is one of the 5 standard equipment slots, carrying what the web
|
||||
@@ -1007,15 +1064,33 @@ type AdvOrder struct {
|
||||
OwnerLocalpart string `json:"owner_localpart"`
|
||||
Token string `json:"token"`
|
||||
CharacterName string `json:"character_name"`
|
||||
Action string `json:"action"` // extract / siege_join
|
||||
Action string `json:"action"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
// Params is the verb's arguments, and only the verbs that take any carry it:
|
||||
// which zone, which supply loadout, how many days of sitting. It never names
|
||||
// an adventurer — that still comes from the session on Pete's side — and
|
||||
// every field in it is re-resolved against the game's own tables before it
|
||||
// means anything, so a forged zone or a forged price buys nothing.
|
||||
Params *AdvOrderParams `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
// AdvOrderParams is the union of every verb's arguments, flat rather than
|
||||
// per-verb because there are three of them and each reads one or two fields.
|
||||
// Anything a verb does not read is ignored rather than rejected.
|
||||
type AdvOrderParams struct {
|
||||
Zone string `json:"zone,omitempty"` // zone id, for expedition_start
|
||||
Loadout string `json:"loadout,omitempty"` // lean|balanced|heavy, for expedition_start / resume
|
||||
Days int `json:"days,omitempty"` // 7 or 30, for babysit
|
||||
}
|
||||
|
||||
// Action names, the wire contract's half of storage.AdvAction* on Pete.
|
||||
const (
|
||||
AdvOrderExtract = "extract"
|
||||
AdvOrderSiegeJoin = "siege_join"
|
||||
AdvOrderExtract = "extract"
|
||||
AdvOrderSiegeJoin = "siege_join"
|
||||
AdvOrderExpedition = "expedition_start"
|
||||
AdvOrderResume = "expedition_resume"
|
||||
AdvOrderBabysit = "babysit"
|
||||
)
|
||||
|
||||
// PendingOrders asks Pete for web actions waiting on us. A Pete predating the
|
||||
|
||||
Reference in New Issue
Block a user