package world import ( "fmt" "os" "path/filepath" "strings" "sync" "gopkg.in/yaml.v3" ) type World struct { dataDir string mu sync.Mutex groundItems map[int][]*groundEntry seeded map[int]bool objStates map[string]*ObjState objMoves []ObjMove } func New(dataDir string) *World { return &World{ dataDir: dataDir, groundItems: make(map[int][]*groundEntry), seeded: make(map[int]bool), objStates: make(map[string]*ObjState), } } 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 { path = filepath.Join(w.dataDir, "rooms", "player_housing", 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]ExitDef) } if room.Spawns == nil { room.Spawns = make([]SpawnDef, 0) } if room.Mobs == nil { room.Mobs = make([]RoomMob, 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) 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 { merged := false for _, e := range w.groundItems[roomID] { if e.quantity > 0 && strings.EqualFold(e.itemID, s.ItemID) && (e.reserveTimer <= 0 || e.reservedFor == "") && e.despawnTimer <= 0 { e.quantity += s.Quantity e.respawnQty += s.Quantity e.isSpawn = true e.respawnDelay = int(s.RespawnTicks) merged = true break } } if !merged { e := &groundEntry{ itemID: s.ItemID, quantity: s.Quantity, isSpawn: true, respawnDelay: int(s.RespawnTicks), respawnQty: s.Quantity, } w.groundItems[roomID] = append(w.groundItems[roomID], e) } } } func (w *World) Tick() { w.mu.Lock() defer w.mu.Unlock() w.tickGroundItemsLocked() w.tickObjStatesLocked() }