From 9eb5ab1818b6b19501fd7209db1987c9e06cc919 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Tue, 9 Jun 2026 05:59:20 -0400 Subject: first commit --- internal/world/world.go | 182 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 internal/world/world.go (limited to 'internal/world/world.go') diff --git a/internal/world/world.go b/internal/world/world.go new file mode 100644 index 0000000..265a2f9 --- /dev/null +++ b/internal/world/world.go @@ -0,0 +1,182 @@ +package world + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "sync" + + "gopkg.in/yaml.v3" +) + +const DropDespawnTicks = 1000 + +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) +} + +type World struct { + dataDir string + mu sync.Mutex + groundItems map[int][]*groundEntry + seeded map[int]bool +} + +func New(dataDir string) *World { + return &World{ + dataDir: dataDir, + groundItems: make(map[int][]*groundEntry), + seeded: make(map[int]bool), + } +} + +func (w *World) LoadRoom(id int) (*Room, error) { + path := filepath.Join(w.dataDir, "rooms", fmt.Sprintf("%d.yaml", id)) + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read room %d: %w", id, err) + } + var room Room + if err := yaml.Unmarshal(data, &room); err != nil { + return nil, fmt.Errorf("parse room %d: %w", id, err) + } + room.ID = id + if room.Exits == nil { + room.Exits = make(map[ExitDir]int) + } + if room.Objects == nil { + room.Objects = make([]string, 0) + } + if room.Spawns == nil { + room.Spawns = make([]SpawnDef, 0) + } + if room.Mobs == nil { + room.Mobs = make([]string, 0) + } + return &room, nil +} + +func (w *World) ResolveExit(input string) ExitDir { + if dir, ok := ExitAliases[strings.ToLower(input)]; ok { + return dir + } + canon := ExitDir(strings.ToLower(input)) + switch canon { + case North, South, East, West, Up, Down: + return canon + } + return "" +} + +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) 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) 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) SeedGroundItems(roomID int) { + w.mu.Lock() + if w.seeded[roomID] { + w.mu.Unlock() + return + } + w.seeded[roomID] = true + w.mu.Unlock() + + room, err := w.LoadRoom(roomID) + if err != nil { + return + } + + w.mu.Lock() + defer w.mu.Unlock() + + for _, s := range room.Spawns { + e := &groundEntry{ + itemID: s.ItemID, + quantity: s.Quantity, + isSpawn: true, + respawnDelay: s.RespawnTicks, + respawnQty: s.Quantity, + } + w.groundItems[roomID] = append(w.groundItems[roomID], e) + } +} + +func (w *World) Tick() { + w.mu.Lock() + defer w.mu.Unlock() + + for _, entries := range w.groundItems { + for _, e := range entries { + if e.respawnTimer > 0 { + e.respawnTimer-- + if e.respawnTimer <= 0 { + e.quantity = e.respawnQty + } + } + if e.despawnTimer > 0 { + e.despawnTimer-- + if e.despawnTimer <= 0 { + e.quantity = 0 + } + } + } + } +} -- cgit v1.2.3