aboutsummaryrefslogtreecommitdiff
path: root/internal/world/world.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/world/world.go')
-rw-r--r--internal/world/world.go182
1 files changed, 182 insertions, 0 deletions
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
+ }
+ }
+ }
+ }
+}