diff options
Diffstat (limited to 'internal/world/ground.go')
| -rw-r--r-- | internal/world/ground.go | 59 |
1 files changed, 51 insertions, 8 deletions
diff --git a/internal/world/ground.go b/internal/world/ground.go index 879cc71..2d5ea7b 100644 --- a/internal/world/ground.go +++ b/internal/world/ground.go @@ -87,23 +87,47 @@ func (w *World) AddGroundItem(roomID int, itemID string, qty int) { w.groundItems[roomID] = append(w.groundItems[roomID], e) } +// RemoveReservedItem removes up to qty of itemID from the room's ground, +// refusing entries reserved for another player. It is ALL-OR-NOTHING: +// when any part of the quantity is unobtainable (a reserved-for-others +// entry, or too little accessible stock) nothing is taken and (0, false) +// is returned. Callers must never lose already-removed items to a failed +// pickup, so the removal is verified before any state is touched. func (w *World) RemoveReservedItem(roomID int, itemID string, qty int, owner string) (int, bool) { w.mu.Lock() defer w.mu.Unlock() - removed := 0 - remaining := qty - for _, e := range w.groundItems[roomID] { - if remaining <= 0 { + entries := w.groundItems[roomID] + + // Pass 1: verify the full quantity is takable by this owner without + // mutating anything, so a blocked pickup can never leave the ground + // half-emptied with nothing delivered. + available := 0 + for _, e := range entries { + if available >= qty { break } - if !strings.EqualFold(e.itemID, itemID) { + if e.quantity <= 0 || !strings.EqualFold(e.itemID, itemID) { continue } - if e.quantity <= 0 { + if e.reserveTimer > 0 && e.reservedFor != "" && e.reservedFor != owner { continue } - if e.reserveTimer > 0 && e.reservedFor != "" && e.reservedFor != owner { - return removed, false + available += e.quantity + } + if available < qty { + return 0, false + } + + // Pass 2: take. Every entry reachable here is takeable, so no + // mid-loop abort is possible. + removed := 0 + remaining := qty + for _, e := range entries { + if remaining <= 0 { + break + } + if e.quantity <= 0 || !strings.EqualFold(e.itemID, itemID) { + continue } take := e.quantity if take > remaining { @@ -119,6 +143,25 @@ func (w *World) RemoveReservedItem(roomID int, itemID string, qty int, owner str return removed, true } +// AccessibleGround returns the ground items in roomID this owner may take: +// entries reserved for another player are excluded. Pickup flows use this +// to size requests and match names against takeable stock only. +func (w *World) AccessibleGround(roomID int, owner string) map[string]int { + w.mu.Lock() + defer w.mu.Unlock() + out := make(map[string]int) + for _, e := range w.groundItems[roomID] { + if e.quantity <= 0 { + continue + } + if e.reserveTimer > 0 && e.reservedFor != "" && e.reservedFor != owner { + continue + } + out[e.itemID] += e.quantity + } + return out +} + func (w *World) RemoveGroundItem(roomID int, itemID string, qty int) int { w.mu.Lock() defer w.mu.Unlock() |
