package world import ( "strings" ) const DropDespawnTicks = 1000 const ReserveTicks = 100 type GroundItemInfo struct { ItemID string Quantity int ReservedFor string ReserveTimer int DespawnTimer int IsSpawn bool } type groundEntry struct { itemID string quantity int isSpawn bool respawnTimer int // >0 = counting down to respawn respawnQty int respawnDelay int despawnTimer int // >0 = counting down to despawn (dropped items) reservedFor string reserveTimer int // >0 = counting down reservation } func (w *World) GroundItemsDetailed(roomID int) []GroundItemInfo { w.mu.Lock() defer w.mu.Unlock() var out []GroundItemInfo for _, e := range w.groundItems[roomID] { if e.quantity <= 0 { continue } info := GroundItemInfo{ ItemID: e.itemID, Quantity: e.quantity, DespawnTimer: e.despawnTimer, IsSpawn: e.isSpawn, } if e.reserveTimer > 0 && e.reservedFor != "" { info.ReservedFor = e.reservedFor info.ReserveTimer = e.reserveTimer } out = append(out, info) } return out } func (w *World) GroundItems(roomID int) 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 { out[e.itemID] += e.quantity } } return out } func (w *World) AddReservedItem(roomID int, itemID string, qty int, owner string) { w.mu.Lock() defer w.mu.Unlock() e := &groundEntry{ itemID: itemID, quantity: qty, despawnTimer: DropDespawnTicks, reservedFor: owner, reserveTimer: ReserveTicks, } w.groundItems[roomID] = append(w.groundItems[roomID], e) } func (w *World) AddGroundItem(roomID int, itemID string, qty int) { w.mu.Lock() defer w.mu.Unlock() e := &groundEntry{ itemID: itemID, quantity: qty, despawnTimer: DropDespawnTicks, } w.groundItems[roomID] = append(w.groundItems[roomID], e) } 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 { break } if !strings.EqualFold(e.itemID, itemID) { continue } if e.quantity <= 0 { continue } if e.reserveTimer > 0 && e.reservedFor != "" && e.reservedFor != owner { return removed, false } take := e.quantity if take > remaining { take = remaining } e.quantity -= take removed += take remaining -= take if e.isSpawn && e.quantity <= 0 && e.respawnDelay > 0 { e.respawnTimer = e.respawnDelay } } return removed, true } func (w *World) RemoveGroundItem(roomID int, itemID string, qty int) int { w.mu.Lock() defer w.mu.Unlock() removed := 0 remaining := qty for _, e := range w.groundItems[roomID] { if remaining <= 0 { break } if !strings.EqualFold(e.itemID, itemID) { continue } if e.quantity <= 0 { continue } take := e.quantity if take > remaining { take = remaining } e.quantity -= take removed += take remaining -= take if e.isSpawn && e.quantity <= 0 && e.respawnDelay > 0 { e.respawnTimer = e.respawnDelay } } return removed } func (w *World) tickGroundItems() { for _, entries := range w.groundItems { for _, e := range entries { if e.respawnTimer > 0 { e.respawnTimer-- if e.respawnTimer <= 0 { e.quantity = e.respawnQty w.mergeNearbyItems(entries, e) } } if e.despawnTimer > 0 { e.despawnTimer-- if e.despawnTimer <= 0 { e.quantity = 0 } } if e.reserveTimer > 0 { e.reserveTimer-- if e.reserveTimer <= 0 { e.reservedFor = "" } } } } } func (w *World) mergeNearbyItems(entries []*groundEntry, target *groundEntry) { for _, other := range entries { if other != target && other.quantity > 0 && strings.EqualFold(other.itemID, target.itemID) && (other.reserveTimer <= 0 || other.reservedFor == "") && other.despawnTimer <= 0 { target.quantity += other.quantity other.quantity = 0 } } }