package world import ( "fmt" "math/rand" "sort" "strings" "thehouseoficarus/internal/object" ) type ObjState struct { Depleted bool DepleteTimer float64 DefID string Name string Index int RoomID int JustRespawned bool WanderRooms []int WanderInterval float64 WanderCounter int SharedMax float64 SharedTimer int Quality float64 SafespotLevel int SafespotTicksAtLevel int SafespotOccupants []string } type ObjMove struct { DefID string Name string FromRoom int ToRoom int } func (w *World) ObjStateKey(roomID int, defID string, index int) string { return fmt.Sprintf("%d:%s:%d", roomID, defID, index) } func (w *World) EnsureObjectStates(roomID int, defIDs []string) { w.mu.Lock() defer w.mu.Unlock() w.ensureObjStates(roomID, defIDs) } func (w *World) ensureObjStates(roomID int, defIDs []string) { if w.objStates == nil { w.objStates = make(map[string]*ObjState) } counts := make(map[string]int) for _, defID := range defIDs { idx := counts[defID] counts[defID]++ key := w.ObjStateKey(roomID, defID, idx) if _, exists := w.objStates[key]; !exists { w.objStates[key] = &ObjState{ DefID: defID, Name: defID, Index: idx, RoomID: roomID, } } } } func (w *World) AddObjInstance(roomID int, defID string, quality float64) { w.mu.Lock() defer w.mu.Unlock() if w.objStates == nil { w.objStates = make(map[string]*ObjState) } idx := 0 for { key := w.ObjStateKey(roomID, defID, idx) if _, exists := w.objStates[key]; !exists { w.objStates[key] = &ObjState{ DefID: defID, Name: defID, Index: idx, RoomID: roomID, Quality: quality, } return } idx++ } } func (w *World) RemoveObjInstance(roomID int, defID string) { w.mu.Lock() defer w.mu.Unlock() idx := 0 for { key := w.ObjStateKey(roomID, defID, idx) if _, exists := w.objStates[key]; !exists { return } delete(w.objStates, key) idx++ } } func (w *World) AllFireStates() []*ObjState { w.mu.Lock() defer w.mu.Unlock() var out []*ObjState for _, st := range w.objStates { if st.DefID == "fire" && st.Quality > 0 { out = append(out, st) } } return out } func (w *World) GetObjState(roomID int, defID string, index int) *ObjState { w.mu.Lock() defer w.mu.Unlock() if w.objStates == nil { return nil } return w.objStates[w.ObjStateKey(roomID, defID, index)] } func (w *World) GetObjStateByKey(key string) *ObjState { w.mu.Lock() defer w.mu.Unlock() if w.objStates == nil { return nil } return w.objStates[key] } func (w *World) AllObjInstances(roomID int) []ObjState { w.mu.Lock() defer w.mu.Unlock() var out []ObjState for _, st := range w.objStates { if st.RoomID == roomID { out = append(out, *st) } } sort.Slice(out, func(i, j int) bool { if out[i].DefID != out[j].DefID { return out[i].DefID < out[j].DefID } return out[i].Index < out[j].Index }) return out } func (w *World) FindObjInstances(roomID int, name string) []ObjState { w.mu.Lock() defer w.mu.Unlock() var out []ObjState lower := strings.ToLower(name) for _, st := range w.objStates { if st.RoomID != roomID { continue } if !wordMatchesObj(lower, st.DefID, st.Name) { continue } out = append(out, ObjState{ DefID: st.DefID, Name: st.Name, Index: st.Index, RoomID: st.RoomID, Depleted: st.Depleted, DepleteTimer: st.DepleteTimer, SharedMax: st.SharedMax, SharedTimer: st.SharedTimer, Quality: st.Quality, }) } sort.Slice(out, func(i, j int) bool { if out[i].DefID != out[j].DefID { return out[i].DefID < out[j].DefID } return out[i].Index < out[j].Index }) return out } func wordMatchesObj(lower, defID, objName string) bool { if object.WordPrefixMatch(lower, objName) { return true } inputWords := strings.Fields(strings.ToLower(lower)) if strings.HasPrefix(strings.ToLower(defID), lower) || strings.HasPrefix(lower, strings.ToLower(defID)) { return true } for _, part := range strings.Split(defID, "_") { for _, iw := range inputWords { pl := strings.ToLower(part) if strings.HasPrefix(pl, iw) || strings.HasPrefix(iw, pl) { return true } } } return false } func (w *World) SetObjName(roomID int, defID string, name string) { w.mu.Lock() defer w.mu.Unlock() for _, st := range w.objStates { if st.RoomID == roomID && st.DefID == defID { st.Name = name } } } func (w *World) SetObjWander(roomID int, defID string, rooms []int, interval float64) { w.mu.Lock() defer w.mu.Unlock() for _, st := range w.objStates { if st.RoomID == roomID && st.DefID == defID { st.WanderRooms = rooms st.WanderInterval = interval } } } func (w *World) SetObjDepleteTimer(roomID int, defID string, max float64) { w.mu.Lock() defer w.mu.Unlock() for _, st := range w.objStates { if st.RoomID == roomID && st.DefID == defID && st.SharedMax == 0 { st.SharedMax = max st.SharedTimer = int(max) } } } func (w *World) AllSharedObjStates() []*ObjState { w.mu.Lock() defer w.mu.Unlock() var out []*ObjState for _, st := range w.objStates { if st.SharedMax > 0 { out = append(out, st) } } return out } func (w *World) SetObjDepleted(roomID int, defID string, index int, delay float64) { w.mu.Lock() defer w.mu.Unlock() key := w.ObjStateKey(roomID, defID, index) if st, ok := w.objStates[key]; ok { st.Depleted = true st.DepleteTimer = delay } } func (w *World) FlushObjRespawns() []ObjState { w.mu.Lock() defer w.mu.Unlock() var out []ObjState for _, st := range w.objStates { if st.JustRespawned { out = append(out, *st) st.JustRespawned = false } } return out } func (w *World) TickObjWander() { w.mu.Lock() defer w.mu.Unlock() var moves []struct { st *ObjState toRoom int } for _, st := range w.objStates { if len(st.WanderRooms) == 0 || st.WanderInterval <= 0 { continue } st.WanderCounter++ if float64(st.WanderCounter) < st.WanderInterval { continue } st.WanderCounter = 0 toRoom := st.WanderRooms[rand.Intn(len(st.WanderRooms))] if toRoom == st.RoomID { continue } moves = append(moves, struct { st *ObjState toRoom int }{st, toRoom}) } for _, m := range moves { oldKey := w.ObjStateKey(m.st.RoomID, m.st.DefID, m.st.Index) fromRoom := m.st.RoomID m.st.RoomID = m.toRoom newKey := w.ObjStateKey(m.st.RoomID, m.st.DefID, m.st.Index) delete(w.objStates, oldKey) w.objStates[newKey] = m.st w.objMoves = append(w.objMoves, ObjMove{ DefID: m.st.DefID, Name: m.st.Name, FromRoom: fromRoom, ToRoom: m.toRoom, }) } } func (w *World) FlushObjMoves() []ObjMove { w.mu.Lock() defer w.mu.Unlock() out := w.objMoves w.objMoves = nil return out } func (w *World) tickObjStatesLocked() { for _, st := range w.objStates { if st.Depleted && st.DepleteTimer > 0 { st.DepleteTimer-- if st.DepleteTimer <= 0 { st.Depleted = false st.JustRespawned = true if st.SharedMax > 0 { st.SharedTimer = int(st.SharedMax) } } } } }