diff options
| author | historia <[not public]> | 2026-06-10 01:48:28 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-10 01:48:28 -0400 |
| commit | a226d72e51eecb768b13600303f73483118d9104 (patch) | |
| tree | 458d15ef049732c15dacc591a830d3beddbedfde /internal/world | |
| parent | 6a0f3d7a252de4b1741cfe5e1c561412c602becc (diff) | |
| download | thehouseoficarus-a226d72e51eecb768b13600303f73483118d9104.tar.gz | |
feat: implemented janky object interaction model
Diffstat (limited to 'internal/world')
| -rw-r--r-- | internal/world/mob.go | 100 | ||||
| -rw-r--r-- | internal/world/room.go | 55 | ||||
| -rw-r--r-- | internal/world/world.go | 250 |
3 files changed, 324 insertions, 81 deletions
diff --git a/internal/world/mob.go b/internal/world/mob.go index ebf97cf..9dfec59 100644 --- a/internal/world/mob.go +++ b/internal/world/mob.go @@ -8,24 +8,20 @@ import ( "strings" "sync" + "thirdcollapse/internal/action" "gopkg.in/yaml.v3" ) -type LootEntry struct { - ItemID string `yaml:"item_id"` - Weight int `yaml:"weight"` - Quantity int `yaml:"quantity"` -} - type DropTable struct { - Remains string `yaml:"remains"` - Loot []LootEntry `yaml:"loot"` + Remains string `yaml:"remains"` + Loot []action.DropEntry `yaml:"loot"` } type MobDef struct { ID string `yaml:"id"` Name string `yaml:"name"` Description string `yaml:"description"` + BehaviorID string `yaml:"behavior"` IdleDescriptions []string `yaml:"idle_descriptions"` CombatDescriptions []string `yaml:"combat_descriptions"` Attack int `yaml:"attack"` @@ -37,33 +33,32 @@ type MobDef struct { Protected bool `yaml:"protected"` Unique bool `yaml:"unique"` RespawnTicks int `yaml:"respawn_ticks"` - Wander int `yaml:"wander"` - WanderTick int `yaml:"wander_tick"` - WanderChance float64 `yaml:"wander_chance"` + WanderRooms []int `yaml:"wander_rooms"` + WanderInterval int `yaml:"wander_interval"` Drops DropTable `yaml:"drops"` } type MobInstance struct { - InstanceID string - DefID string - Name string - HP int - MaxHP int - Attack int - Strength int - Defense int - Speed int - Aggressive bool - Protected bool - Unique bool - RespawnTicks int - RoomID int - HomeRoomID int - Drops DropTable - IdleDescription string - Wander int - WanderTick int - WanderChance float64 + InstanceID string + DefID string + Name string + BehaviorID string + HP int + MaxHP int + Attack int + Strength int + Defense int + Speed int + Aggressive bool + Protected bool + Unique bool + RespawnTicks int + RoomID int + HomeRoomID int + Drops DropTable + IdleDescription string + WanderRooms []int + WanderInterval int WanderTickCounter int regenerateTick int } @@ -147,44 +142,12 @@ func (s *MobStore) LoadDef(id string) (*MobDef, error) { return &def, nil } -func (s *MobStore) SpawnMob(defID string, roomID int, instanceID string) (*MobInstance, error) { - def, err := s.LoadDef(defID) - if err != nil { - return nil, err - } - s.mu.Lock() - defer s.mu.Unlock() - inst := &MobInstance{ - InstanceID: instanceID, - DefID: defID, - Name: def.Name, - HP: def.HP, - MaxHP: def.HP, - Attack: def.Attack, - Strength: def.Strength, - Defense: def.Defense, - Speed: def.Speed, - Aggressive: def.Aggressive, - RespawnTicks: def.RespawnTicks, - RoomID: roomID, - Drops: def.Drops, - } - s.instances[instanceID] = inst - return inst, nil -} - func (s *MobStore) GetInstance(id string) *MobInstance { s.mu.Lock() defer s.mu.Unlock() return s.instances[id] } -func (s *MobStore) RemoveInstance(id string) { - s.mu.Lock() - defer s.mu.Unlock() - delete(s.instances, id) -} - func (s *MobStore) AllInstances() []*MobInstance { s.mu.Lock() defer s.mu.Unlock() @@ -235,9 +198,8 @@ func (s *MobStore) RollIdleDescription(inst *MobInstance) { return } inst.IdleDescription = pickIdleDescription(def.IdleDescriptions) - inst.Wander = def.Wander - inst.WanderTick = def.WanderTick - inst.WanderChance = def.WanderChance + inst.WanderRooms = def.WanderRooms + inst.WanderInterval = def.WanderInterval inst.WanderTickCounter = 0 } @@ -269,6 +231,7 @@ func (s *MobStore) SeedMobs(roomID int, mobIDs []string) { InstanceID: instID, DefID: defID, Name: dw.def.Name, + BehaviorID: dw.def.BehaviorID, HP: dw.def.HP, MaxHP: dw.def.HP, Attack: dw.def.Attack, @@ -279,9 +242,8 @@ func (s *MobStore) SeedMobs(roomID int, mobIDs []string) { Protected: dw.def.Protected, Unique: dw.def.Unique, RespawnTicks: dw.def.RespawnTicks, - Wander: dw.def.Wander, - WanderTick: dw.def.WanderTick, - WanderChance: dw.def.WanderChance, + WanderRooms: dw.def.WanderRooms, + WanderInterval: dw.def.WanderInterval, RoomID: roomID, HomeRoomID: roomID, Drops: dw.def.Drops, diff --git a/internal/world/room.go b/internal/world/room.go index a92a428..6c2d0e3 100644 --- a/internal/world/room.go +++ b/internal/world/room.go @@ -1,5 +1,7 @@ package world +import "gopkg.in/yaml.v3" + type ExitDir string const ( @@ -41,13 +43,50 @@ type SpawnDef struct { RespawnTicks int `yaml:"respawn_ticks"` } +type ExitDef struct { + Room int `yaml:"room"` + Condition *ExitCondition `yaml:"condition"` + BlockedMessage string `yaml:"blocked_message"` +} + +func (e *ExitDef) UnmarshalYAML(value *yaml.Node) error { + if value.Kind == yaml.ScalarNode { + var n int + if err := value.Decode(&n); err != nil { + return err + } + e.Room = n + return nil + } + type raw ExitDef + return value.Decode((*raw)(e)) +} + +type ExitCondition struct { + Flag string `yaml:"flag"` + Value any `yaml:"value"` + Not bool `yaml:"not"` +} + type Room struct { - ID int `yaml:"id"` - Name string `yaml:"name"` - Description string `yaml:"description"` - MapSymbol string `yaml:"map_symbol"` - Exits map[ExitDir]int `yaml:"exits"` - Objects []string `yaml:"objects"` - Spawns []SpawnDef `yaml:"spawns"` - Mobs []string `yaml:"mobs"` + ID int `yaml:"id"` + Name string `yaml:"name"` + Description string `yaml:"description"` + MapSymbol string `yaml:"map_symbol"` + Exits map[ExitDir]ExitDef `yaml:"exits"` + Objects []RoomObject `yaml:"objects"` + Spawns []SpawnDef `yaml:"spawns"` + Mobs []string `yaml:"mobs"` + OnEnter []EnterStep `yaml:"on_enter"` +} + +type EnterStep struct { + Message string `yaml:"message"` + Condition *ExitCondition `yaml:"condition"` +} + +type RoomObject struct { + ID string `yaml:"id"` + WanderRooms []int `yaml:"wander_rooms"` + WanderInterval int `yaml:"wander_interval"` } diff --git a/internal/world/world.go b/internal/world/world.go index 1429081..71fd518 100644 --- a/internal/world/world.go +++ b/internal/world/world.go @@ -2,8 +2,10 @@ package world import ( "fmt" + "math/rand" "os" "path/filepath" + "sort" "strings" "sync" @@ -37,6 +39,177 @@ type World struct { mu sync.Mutex groundItems map[int][]*groundEntry seeded map[int]bool + objStates map[string]*ObjState + objMoves []ObjMove +} + +type ObjMove struct { + DefID string + Name string + FromRoom int + ToRoom int +} + +type ObjState struct { + Depleted bool + DepleteTimer int + DefID string + Name string + Index int + RoomID int + JustRespawned bool + WanderRooms []int + WanderInterval int + WanderCounter int +} + +func (w *World) ObjStateKey(roomID int, defID string, index int) string { + return fmt.Sprintf("%d:%s:%d", roomID, defID, index) +} + +func (w *World) objStateKey(roomID int, defID string, index int) string { + return w.ObjStateKey(roomID, defID, index) +} + +func (w *World) EnsureObjectStates(roomID int, defIDs []string) { + w.mu.Lock() + defer w.mu.Unlock() + 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) 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 key, 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, + }) + _ = key + } + 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 { + // Check against display name (space-separated words) + for _, word := range strings.Fields(objName) { + if strings.HasPrefix(strings.ToLower(word), lower) { + return true + } + } + // Check defID both as whole and split by underscores + if strings.HasPrefix(strings.ToLower(defID), lower) { + return true + } + for _, part := range strings.Split(defID, "_") { + if strings.HasPrefix(strings.ToLower(part), lower) { + return true + } + } + return false +} + +func (w *World) SetObjName(roomID int, defID string, name string) { + w.mu.Lock() + defer w.mu.Unlock() + for key, st := range w.objStates { + if st.RoomID == roomID && st.DefID == defID { + st.Name = name + } + _ = key + } +} + +func (w *World) SetObjWander(roomID int, defID string, rooms []int, interval int) { + w.mu.Lock() + defer w.mu.Unlock() + for key, st := range w.objStates { + if st.RoomID == roomID && st.DefID == defID { + st.WanderRooms = rooms + st.WanderInterval = interval + } + _ = key + } +} + +func (w *World) SetObjDepleted(roomID int, defID string, index int, delay int) { + 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 New(dataDir string) *World { @@ -44,6 +217,7 @@ func New(dataDir string) *World { dataDir: dataDir, groundItems: make(map[int][]*groundEntry), seeded: make(map[int]bool), + objStates: make(map[string]*ObjState), } } @@ -59,10 +233,7 @@ func (w *World) LoadRoom(id int) (*Room, error) { } room.ID = id if room.Exits == nil { - room.Exits = make(map[ExitDir]int) - } - if room.Objects == nil { - room.Objects = make([]string, 0) + room.Exits = make(map[ExitDir]ExitDef) } if room.Spawns == nil { room.Spawns = make([]SpawnDef, 0) @@ -261,4 +432,75 @@ func (w *World) Tick() { } } } + + for _, st := range w.objStates { + if st.Depleted && st.DepleteTimer > 0 { + st.DepleteTimer-- + if st.DepleteTimer <= 0 { + st.Depleted = false + st.JustRespawned = true + } + } + } +} + +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 st.WanderCounter >= st.WanderInterval { + 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 } |
