aboutsummaryrefslogtreecommitdiff
path: root/internal/world
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-10 01:48:28 -0400
committerhistoria <[not public]>2026-06-10 01:48:28 -0400
commita226d72e51eecb768b13600303f73483118d9104 (patch)
tree458d15ef049732c15dacc591a830d3beddbedfde /internal/world
parent6a0f3d7a252de4b1741cfe5e1c561412c602becc (diff)
downloadthehouseoficarus-a226d72e51eecb768b13600303f73483118d9104.tar.gz
feat: implemented janky object interaction model
Diffstat (limited to 'internal/world')
-rw-r--r--internal/world/mob.go100
-rw-r--r--internal/world/room.go55
-rw-r--r--internal/world/world.go250
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
}