mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-21 03:12:41 +00:00
adventure: ask 7 — apply web equipment management (poll half)
Mirror of Pete's ask 7. gogobee polls the equip queue and applies the new
actions against the five standard gear slots:
- equip: routes MasterworkGear/ArenaGear to applyMasterworkEquip (evicts
any special occupant back to the pack; downgrade-blocked), else the
existing applyMagicEquip.
- unequip: EquipmentSlot vocabulary -> applyMasterworkUnequip (resets the
slot to its tier-0 default, keeps the row), else applyMagicUnequip.
- upgrade: purchaseEquipmentTier, euro-idempotent (DebitIdem keyed on the
order GUID), downgrade + max-tier guarded.
- repair: repair(), euro-idempotent, recomputes blacksmithRepairCost.
Detail push now carries Slots (EquipSlotView x5) + Balance; itemViews gives
masterwork/arena backpack rows an equip id; the compare decorator is guarded
to magic-only. buildDetailSnapshot is a method so it can read the euro balance.
Retry-safety: no CreditIdem refund on a later save fault (would double-pay a
guid-guarded retry) — we return retry=true and let the next poll re-run, since
the debit is guid-idempotent and the slot write is idempotent. Matches the
casino escrow precedent. Unit tests cover downgrade block, max-tier,
insufficient funds, idempotent replay, eviction, and take-off reset.
Deploy AFTER Pete: Pete's ingest must accept the new verdict strings before
this side emits them.
This commit is contained in:
@@ -97,7 +97,7 @@ func (p *AdventurePlugin) pushRoster() {
|
||||
var detailPushOK bool
|
||||
|
||||
func (p *AdventurePlugin) pushDetails() {
|
||||
snap, err := buildDetailSnapshot(time.Now().UTC())
|
||||
snap, err := p.buildDetailSnapshot(time.Now().UTC())
|
||||
if err != nil {
|
||||
slog.Error("roster: build detail snapshot failed", "err", err)
|
||||
return
|
||||
@@ -158,7 +158,7 @@ func rosterDetail(uid id.UserID, c *DnDCharacter) *peteclient.RosterDetail {
|
||||
// owner it belongs to, so hiding a player from it would only deny them their own
|
||||
// sheet. The board token rides along so Pete can match owner↔page without ever
|
||||
// reversing the one-way token.
|
||||
func buildDetailSnapshot(now time.Time) (peteclient.DetailSnapshot, error) {
|
||||
func (p *AdventurePlugin) buildDetailSnapshot(now time.Time) (peteclient.DetailSnapshot, error) {
|
||||
snap := peteclient.DetailSnapshot{SnapshotAt: now.Unix()}
|
||||
rows, err := db.Get().Query(`SELECT user_id FROM player_meta WHERE alive = 1`)
|
||||
if err != nil {
|
||||
@@ -208,6 +208,13 @@ func buildDetailSnapshot(now time.Time) (peteclient.DetailSnapshot, error) {
|
||||
pd.Vault = itemViews(items)
|
||||
}
|
||||
pd.Equipped = equippedViews(uid)
|
||||
// Ask 7: the 5 standard slots for the web management panel, plus the euro
|
||||
// balance the upgrade/repair confirm dialogs show. Balance is nil-guarded so
|
||||
// the free-standing tests (which build no euro plugin) still run.
|
||||
pd.Slots = buildEquipSlotViews(uid)
|
||||
if p.euro != nil {
|
||||
pd.Balance = p.euro.GetBalance(uid)
|
||||
}
|
||||
snap.Players = append(snap.Players, pd)
|
||||
}
|
||||
return snap, nil
|
||||
@@ -256,6 +263,12 @@ func itemViews(items []AdvItem) []peteclient.ItemView {
|
||||
} else if it.Slot != "" {
|
||||
// Shop equipment resolves by (slot, tier) — Name is decorative.
|
||||
v.Desc = equipmentDefByTier(it.Slot, it.Tier).Description
|
||||
// A masterwork/arena piece carries a real slot, so it can be worn from the
|
||||
// web (into a standard slot) — give it the equip handle. Plain shop gear in
|
||||
// the pack stays button-less: its slot is just a category, not a wearable.
|
||||
if it.Type == "MasterworkGear" || it.Type == "ArenaGear" {
|
||||
v.ID = it.ID
|
||||
}
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user