package world import ( "strings" "gopkg.in/yaml.v3" "thehouseoficarus/internal/behavior" "thehouseoficarus/internal/casino" "thehouseoficarus/internal/object" ) type ExitDir string const ( North ExitDir = "north" South ExitDir = "south" East ExitDir = "east" West ExitDir = "west" Northeast ExitDir = "northeast" Northwest ExitDir = "northwest" Southeast ExitDir = "southeast" Southwest ExitDir = "southwest" Up ExitDir = "up" Down ExitDir = "down" ) var ExitAliases = map[string]ExitDir{ "n": North, "s": South, "e": East, "w": West, "ne": Northeast, "nw": Northwest, "se": Southeast, "sw": Southwest, "u": Up, "d": Down, } var OppositeExit = map[ExitDir]ExitDir{ North: South, South: North, East: West, West: East, Northeast: Southwest, Northwest: Southeast, Southeast: Northwest, Southwest: Northeast, Up: Down, Down: Up, } var ExitOrder = []ExitDir{ North, South, East, West, Northeast, Northwest, Southeast, Southwest, Up, Down, } var DirectionDeltas3D = map[ExitDir][3]int{ North: {0, -1, 0}, South: {0, 1, 0}, East: {1, 0, 0}, West: {-1, 0, 0}, Northeast: {1, -1, 0}, Northwest: {-1, -1, 0}, Southeast: {1, 1, 0}, Southwest: {-1, 1, 0}, Up: {0, 0, 1}, Down: {0, 0, -1}, } type SpawnDef struct { ID string `yaml:"id"` Quantity int `yaml:"quantity"` RespawnTicks float64 `yaml:"respawn_ticks"` } type ExitDef struct { Room int `yaml:"room"` Condition *behavior.Condition `yaml:"condition,omitempty"` BlockedMessage string `yaml:"blocked_message,omitempty"` OnTraverse []behavior.Trigger `yaml:"on_traverse,omitempty"` Hidden bool `yaml:"hidden,omitempty"` AlwaysBlocked bool `yaml:"always_blocked,omitempty"` } type Room struct { ID int `yaml:"id"` Name string `yaml:"name"` Color string `yaml:"color"` Description behavior.DescList `yaml:"description"` Exits map[ExitDir]ExitDef `yaml:"exits"` Objects []RoomObject `yaml:"objects"` ItemSpawns []SpawnDef `yaml:"item_spawns"` Mobs []RoomMob `yaml:"mobs"` OnEnter []behavior.Trigger `yaml:"on_enter,omitempty"` OnExit []behavior.Trigger `yaml:"on_exit,omitempty"` Hazard string `yaml:"hazard"` BlockTransport bool `yaml:"block_transport"` CasinoTables []casino.TableConfig `yaml:"casino_tables,omitempty"` CasinoMachines []casino.MachineConfig `yaml:"casino_machines,omitempty"` } type RoomMob struct { ID string `yaml:"id"` WanderRooms []int `yaml:"wander_rooms,omitempty"` WanderInterval float64 `yaml:"wander_interval,omitempty"` } // RoomObject is either a reference to a file-backed object definition (only // `id`/wander fields set) or a fully local object definition (Local != nil). // An entry is treated as local when it carries any passive content field // (name, description, aliases, inroom_description, color, hidden, on_look). // Local objects are room-scoped: their identity (ID, used as the ObjState // DefID) is derived from the normalized name; `id:` is reserved for references. type RoomObject struct { ID string WanderRooms []int WanderInterval float64 Local *object.ObjectDef HiddenOverride bool `yaml:"hidden,omitempty"` } func (ro *RoomObject) UnmarshalYAML(value *yaml.Node) error { type rawRef struct { ID string `yaml:"id"` WanderRooms []int `yaml:"wander_rooms"` WanderInterval float64 `yaml:"wander_interval"` Hidden bool `yaml:"hidden"` } var ref rawRef if err := value.Decode(&ref); err != nil { return err } ro.WanderRooms = ref.WanderRooms ro.WanderInterval = ref.WanderInterval var def object.ObjectDef if err := value.Decode(&def); err != nil { return err } isLocal := def.Name != "" || len(def.Description) > 0 || def.InRoomDescription != "" || len(def.Aliases) > 0 || def.Color != "" || len(def.OnLook) > 0 if isLocal { ro.ID = NormalizeObjectName(def.Name) ro.Local = &def return nil } ro.ID = ref.ID ro.HiddenOverride = ref.Hidden return nil } func (ro RoomObject) MarshalYAML() (interface{}, error) { if ro.Local != nil { type inlineObj struct { Name string `yaml:"name"` Aliases []string `yaml:"aliases,omitempty"` Color string `yaml:"color,omitempty"` Hidden bool `yaml:"hidden,omitempty"` InRoomDescription string `yaml:"inroom_description,omitempty"` RemovalItem string `yaml:"removal_item,omitempty"` Description behavior.DescList `yaml:"description,omitempty"` OnUse []behavior.Trigger `yaml:"on_use,omitempty"` Steal *object.ObjectSteal `yaml:"steal,omitempty"` Gather *behavior.GatherConfig `yaml:"gather,omitempty"` Talk *behavior.TalkConfig `yaml:"talk,omitempty"` Safespot *object.SafespotConfig `yaml:"safespot,omitempty"` OnLook []behavior.Trigger `yaml:"on_look,omitempty"` } def := ro.Local return inlineObj{ Name: def.Name, Aliases: def.Aliases, Color: def.Color, Hidden: def.Hidden, InRoomDescription: def.InRoomDescription, RemovalItem: def.RemovalItem, Description: def.Description, OnUse: def.OnUse, Steal: def.Steal, Gather: def.Gather, Talk: def.Talk, Safespot: def.Safespot, OnLook: def.OnLook, }, nil } type rawRef struct { ID string `yaml:"id"` WanderRooms []int `yaml:"wander_rooms,omitempty"` WanderInterval float64 `yaml:"wander_interval,omitempty"` Hidden bool `yaml:"hidden,omitempty"` } return rawRef{ ID: ro.ID, WanderRooms: ro.WanderRooms, WanderInterval: ro.WanderInterval, Hidden: ro.HiddenOverride, }, nil } // NormalizeObjectName lowercases, trims, and collapses internal whitespace, // keeping spaces (not underscores) so the word-prefix matcher treats the name // as a single token in its `_`-split fallback. It is the canonical derivation // of a local object's ObjState DefID from its name. func NormalizeObjectName(name string) string { return strings.Join(strings.Fields(strings.ToLower(name)), " ") }