diff options
| author | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
| commit | 153acb7f644d755c45533b7c5311e3ffa47e8973 (patch) | |
| tree | 7645e6f7b65bc3830b078e3b9336d2e6b199d584 /internal/game/cmd_wear.go | |
| parent | 87d08f0151e643951769e323995f1c5ed89ee200 (diff) | |
| download | thehouseoficarus-153acb7f644d755c45533b7c5311e3ffa47e8973.tar.gz | |
fix: harden engine across casino, economy, concurrency, security, and content
Diffstat (limited to 'internal/game/cmd_wear.go')
| -rw-r--r-- | internal/game/cmd_wear.go | 56 |
1 files changed, 45 insertions, 11 deletions
diff --git a/internal/game/cmd_wear.go b/internal/game/cmd_wear.go index a02fd3b..5a293de 100644 --- a/internal/game/cmd_wear.go +++ b/internal/game/cmd_wear.go @@ -101,7 +101,7 @@ func (g *Game) doWear(sess *net.Session, input string) { sess.WriteLine("Your inventory is full.") return } - p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: existingItemID, Quantity: 1}) + p.SetInvSlot(freeSlot, g.displacedEquipSlot(p, existingItemID)) } if slot.Quantity > 1 { @@ -127,6 +127,24 @@ func (g *Game) doWear(sess *net.Session, input string) { sess.WriteLine(fmt.Sprintf("You %s %s.", verb, g.itemColorize(sess, def, match.Name))) } +// displacedEquipSlot builds the inventory slot that receives an item +// displaced by a swap. The item's equipped quality (EquipQuality, kept +// because the equipment entry alone cannot carry a charge) is stamped onto +// the returned stack and cleared from the map, so the returned item keeps +// merging and charging like one carried normally; leaving the entry behind +// would let act_burn charge a stale record or a zeroed slot. +func (g *Game) displacedEquipSlot(p *player.Player, itemID string) *player.InventorySlot { + slot := &player.InventorySlot{ItemID: itemID, Quantity: 1} + if q, ok := p.EquipQuality[itemID]; ok { + slot.Quality = q + if def, _ := g.ItemStore.Load(itemID); def != nil { + slot.MaxQuality = def.MaxQuality + } + delete(p.EquipQuality, itemID) + } + return slot +} + func (g *Game) equipAmmo(sess *net.Session, p *player.Player, invIdx int, slot *player.InventorySlot, def *item.ItemDef, qty int) { transferQty := slot.Quantity if qty > 0 && qty < transferQty { @@ -135,7 +153,12 @@ func (g *Game) equipAmmo(sess *net.Session, p *player.Player, invIdx int, slot * existingAmmoID, hasAmmo := p.Equipment[item.SlotAmmo] if hasAmmo && existingAmmoID != slot.ItemID { - g.unequipAmmo(p) + // The equipped stack stays equipped when there is nowhere to put + // it; equipping the new stack would otherwise strand the old one. + if !g.unequipAmmo(p) { + sess.WriteLine("Your inventory is full.") + return + } } if slot.Quantity <= transferQty { @@ -156,26 +179,35 @@ func (g *Game) equipAmmo(sess *net.Session, p *player.Player, invIdx int, slot * sess.WriteLine(fmt.Sprintf("You equip %s (x%d).", displayName, p.AmmoQty)) } -func (g *Game) unequipAmmo(p *player.Player) { +// unequipAmmo returns the equipped ammo to the inventory and reports whether +// the equipped stack is now stored. With nowhere to put it the ammo stays +// equipped: the equipment entry is only cleared once the stack has actually +// been merged into an inventory stack or placed in a free slot. +func (g *Game) unequipAmmo(p *player.Player) bool { ammoID, ok := p.Equipment[item.SlotAmmo] if !ok || p.AmmoQty <= 0 { - return + return true } + quality, maxQuality := g.pickedStackProps(ammoID) for i := 0; i < 28; i++ { s := p.InvSlot(i) - if s != nil && s.ItemID == ammoID { + // Only a stack with the properties a fresh stack gets may absorb the + // equipped stack (see pickedStackProps). + if s != nil && s.ItemID == ammoID && s.Quality == quality && s.MaxQuality == maxQuality { s.Quantity += p.AmmoQty delete(p.Equipment, item.SlotAmmo) p.AmmoQty = 0 - return + return true } } freeSlot := p.FirstFreeSlot() - if freeSlot >= 0 { - p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: ammoID, Quantity: p.AmmoQty}) + if freeSlot < 0 { + return false } + p.SetInvSlot(freeSlot, g.newInventorySlot(ammoID, p.AmmoQty)) delete(p.Equipment, item.SlotAmmo) p.AmmoQty = 0 + return true } func (g *Game) doWearAll(sess *net.Session) { @@ -235,8 +267,10 @@ func (g *Game) doWearAll(sess *net.Session) { continue } existingID, hasAmmo := p.Equipment[item.SlotAmmo] - if hasAmmo && existingID != best.itemID { - g.unequipAmmo(p) + // No free slot for the equipped stack: leave it equipped and + // skip this item rather than delete the old stack. + if hasAmmo && existingID != best.itemID && !g.unequipAmmo(p) { + continue } transferQty := invSlot.Quantity p.SetInvSlot(best.invSlot, nil) @@ -256,7 +290,7 @@ func (g *Game) doWearAll(sess *net.Session) { if freeSlot == -1 { continue } - p.SetInvSlot(freeSlot, &player.InventorySlot{ItemID: existing, Quantity: 1}) + p.SetInvSlot(freeSlot, g.displacedEquipSlot(p, existing)) } invSlot := p.InvSlot(best.invSlot) |
