adventure: let an owner equip and unequip from their own page

The one adventure ask that carries intent back to the game box, built the
mischief way: no new network route, Pete records the equip/unequip and gogobee
polls it and files a verdict. The who page grows Equip / Take off buttons on the
owner's own worn and backpack panels, and a pending-changes strip that shows
'queued' until the verdict lands, never claiming a change it can't see.

The item handle is the inventory row id, sent only on wearable magic items, so a
non-zero id is also what gates the button. gogobee resolves the owner by
localpart, never a name.
This commit is contained in:
prosolis
2026-07-17 08:44:28 -07:00
parent e90deda498
commit 1589c36e96
10 changed files with 999 additions and 7 deletions

View File

@@ -111,15 +111,35 @@ type whoPage struct {
type itemRow struct {
storage.ItemView
Worn bool
// EquipAction is the control this row offers its owner: "unequip" on anything
// worn, "equip" on a backpack item the magic-item path will accept, "" on
// everything else (vault items, mundane backpack gear). Empty means no button.
EquipAction string
}
func itemRows(items []storage.ItemView, worn bool) []itemRow {
// itemRows wraps gogobee's item views for one panel. panel is "worn", "backpack",
// or "vault"; it decides both the bond wording (only a worn item can be inert) and
// which equip control, if any, the row offers.
func itemRows(items []storage.ItemView, panel string) []itemRow {
if len(items) == 0 {
return nil
}
worn := panel == "worn"
out := make([]itemRow, 0, len(items))
for _, it := range items {
out = append(out, itemRow{ItemView: it, Worn: worn})
row := itemRow{ItemView: it, Worn: worn}
switch {
case worn:
// Everything in magic_item_equipped is a magic item and can come off; the
// slot is the handle gogobee unequips by.
row.EquipAction = storage.EquipActionUnequip
case panel == "backpack" && it.ID != 0:
// Only a wearable magic item carries a row id (gogobee sets it just in the
// magic-item branch), so the id gates Equip to exactly the items the
// magic-item path accepts — never mundane gear, never a vault item.
row.EquipAction = storage.EquipActionEquip
}
out = append(out, row)
}
return out
}
@@ -204,9 +224,9 @@ func (s *Server) handleAdventureWho(w http.ResponseWriter, r *http.Request) {
if self, ok, err := storage.PlayerDetailByOwner(buyerLocalpart(u), token); err == nil && ok {
page.HasSelf = true
page.Self = self
page.Worn = itemRows(self.Equipped, true)
page.Backpack = itemRows(self.Inventory, false)
page.VaultRows = itemRows(self.Vault, false)
page.Worn = itemRows(self.Equipped, "worn")
page.Backpack = itemRows(self.Inventory, "backpack")
page.VaultRows = itemRows(self.Vault, "vault")
for _, it := range self.Equipped {
if it.Attuned {
page.BondsUsed++