From a1b6613c6c6a2f8d6e1ecd47e766bd40f92ac295 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Sun, 28 Jun 2026 02:40:30 -0400 Subject: feat: admin accounts, god mode, OLC commands like dig/room, 'inline' objects changed to 'local' objects --- internal/validate/checks.go | 26 ++++---- internal/validate/inline_test.go | 134 --------------------------------------- internal/validate/local_test.go | 134 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 147 deletions(-) delete mode 100644 internal/validate/inline_test.go create mode 100644 internal/validate/local_test.go (limited to 'internal/validate') diff --git a/internal/validate/checks.go b/internal/validate/checks.go index 7a495a0..7da2e4a 100644 --- a/internal/validate/checks.go +++ b/internal/validate/checks.go @@ -105,13 +105,13 @@ func validateRooms(s Source) []Issue { var collEntries []roomObjEntry for idx, robj := range room.Objects { - if robj.Inline != nil { + if robj.Local != nil { collEntries = append(collEntries, roomObjEntry{ - defKey: fmt.Sprintf("inline:#%d", idx), - displayName: world.NormalizeObjectName(robj.Inline.Name), + defKey: fmt.Sprintf("local:#%d", idx), + displayName: world.NormalizeObjectName(robj.Local.Name), effDefID: robj.ID, }) - issues = append(issues, validateInlineObject(id, robj, itemIDs, roomIndex)...) + issues = append(issues, validateLocalObject(id, robj, itemIDs, roomIndex)...) } else if robj.ID != "" && !objIDs[robj.ID] { issues = append(issues, Issue{ Level: "ERROR", @@ -155,14 +155,14 @@ func validateRooms(s Source) []Issue { return issues } -// validateInlineObject checks a room-inline object definition. Inline objects +// validateLocalObject checks a room-local object definition. Local objects // are restricted to the passive subset (name/aliases/color/hidden/ // inroom_description/description/on_look); interactable or stateful behavior // must be defined as a standalone object file instead. -func validateInlineObject(roomID int, robj world.RoomObject, itemIDs map[string]bool, roomIndex map[int]bool) []Issue { +func validateLocalObject(roomID int, robj world.RoomObject, itemIDs map[string]bool, roomIndex map[int]bool) []Issue { var issues []Issue - def := robj.Inline - prefix := fmt.Sprintf("Room %d: inline object %q", roomID, robj.ID) + def := robj.Local + prefix := fmt.Sprintf("Room %d: local object %q", roomID, robj.ID) if def.Name == "" { issues = append(issues, Issue{ @@ -175,7 +175,7 @@ func validateInlineObject(roomID int, robj world.RoomObject, itemIDs map[string] issues = append(issues, Issue{ Level: "WARN", Type: "reference", - Message: prefix + ": `id:` is ignored on inline objects — identity derives from `name`", + Message: prefix + ": `id:` is ignored on local objects — identity derives from `name`", }) } @@ -208,7 +208,7 @@ func validateInlineObject(roomID int, robj world.RoomObject, itemIDs map[string] issues = append(issues, Issue{ Level: "ERROR", Type: "reference", - Message: prefix + fmt.Sprintf(": inline objects may not define interactable behavior (%s) — define it as an object file instead", + Message: prefix + fmt.Sprintf(": local objects may not define interactable behavior (%s) — define it as an object file instead", strings.Join(bad, ", ")), }) } @@ -230,7 +230,7 @@ func validateInlineObject(roomID int, robj world.RoomObject, itemIDs map[string] // roomObjEntry is a single object slot in a room, reduced to what the per-room // collision check needs. defKey identifies the distinct definition behind the -// slot ("file:" for references, "inline:#" for inline defs), so +// slot ("file:" for references, "local:#" for local defs), so // repeated references to the same file object collapse to one definition. type roomObjEntry struct { defKey string @@ -241,7 +241,7 @@ type roomObjEntry struct { // validateRoomObjectCollisions reports two per-room problems: distinct object // definitions that share a display name (a player typing the exact name could // not disambiguate) and distinct definitions that resolve to the same ObjState -// DefID (their runtime instances would be conflated — e.g. an inline object +// DefID (their runtime instances would be conflated — e.g. a local object // whose name matches a referenced file object's id). Repeated references to the // same file object share a defKey and are allowed (e.g. multiple copper_rock). func validateRoomObjectCollisions(roomID int, entries []roomObjEntry) []Issue { @@ -281,7 +281,7 @@ func validateRoomObjectCollisions(roomID int, entries []roomObjEntry) []Issue { issues = append(issues, Issue{ Level: "ERROR", Type: "duplicate", - Message: fmt.Sprintf("Room %d: object id collision %q (an inline object's name matches another object's id)", + Message: fmt.Sprintf("Room %d: object id collision %q (a local object's name matches another object's id)", roomID, defID), }) } diff --git a/internal/validate/inline_test.go b/internal/validate/inline_test.go deleted file mode 100644 index 5816c26..0000000 --- a/internal/validate/inline_test.go +++ /dev/null @@ -1,134 +0,0 @@ -package validate - -import ( - "os" - "path/filepath" - "testing" - - "thehouseoficarus/internal/behavior" - "thehouseoficarus/internal/item" - "thehouseoficarus/internal/object" - "thehouseoficarus/internal/world" -) - -func TestValidateInlineObjectPassiveOK(t *testing.T) { - robj := world.RoomObject{ID: "window", Inline: &object.ObjectDef{ - Name: "window", - Hidden: true, - Description: behavior.DescList{{Text: "A small window."}}, - }} - issues := validateInlineObject(1001, robj, nil, nil) - if len(issues) != 0 { - t.Errorf("expected no issues for passive inline object, got: %+v", issues) - } -} - -func TestValidateInlineObjectRejectsInteractable(t *testing.T) { - robj := world.RoomObject{ID: "rock", Inline: &object.ObjectDef{ - Name: "rock", - Gather: &behavior.GatherConfig{}, - }} - issues := validateInlineObject(1001, robj, nil, nil) - if !containsMsg(issues, "interactable behavior") { - t.Errorf("expected interactable-behavior error, got: %+v", issues) - } -} - -func TestValidateInlineObjectRequiresName(t *testing.T) { - robj := world.RoomObject{ID: "x", Inline: &object.ObjectDef{ - Description: behavior.DescList{{Text: "no name"}}, - }} - issues := validateInlineObject(1001, robj, nil, nil) - if !containsMsg(issues, "has no name") { - t.Errorf("expected has-no-name error, got: %+v", issues) - } -} - -func TestValidateInlineObjectStrayIDWarns(t *testing.T) { - robj := world.RoomObject{ID: "anvil", Inline: &object.ObjectDef{Name: "anvil", ID: "anvil"}} - issues := validateInlineObject(1001, robj, nil, nil) - if !containsMsg(issues, "ignored on inline objects") { - t.Errorf("expected stray-id warning, got: %+v", issues) - } -} - -func newSource(dir string) Source { - return Source{ - DataDir: dir, - Items: item.NewItemStore(dir), - Objects: object.NewObjectStore(dir), - Mobs: world.NewMobStore(dir), - World: world.New(dir), - } -} - -func writeFile(t *testing.T, path, body string) { - t.Helper() - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(path, []byte(body), 0o644); err != nil { - t.Fatal(err) - } -} - -func TestValidateRoomsSameNameCollision(t *testing.T) { - dir := t.TempDir() - writeFile(t, filepath.Join(dir, "rooms", "1.yaml"), `name: Test Room -objects: - - id: ghost_object - - name: sign - description: "one" - - name: sign - description: "two" -`) - issues := validateRooms(newSource(dir)) - - if !containsMsg(issues, "nonexistent object \"ghost_object\"") { - t.Errorf("expected missing-reference error, got: %+v", issues) - } - if !containsMsg(issues, "objects share the name \"sign\"") { - t.Errorf("expected same-name collision error, got: %+v", issues) - } -} - -func TestValidateRoomsDefIDCollisionAndDuplicateRefs(t *testing.T) { - dir := t.TempDir() - writeFile(t, filepath.Join(dir, "objects", "anvil.yaml"), "name: heavy anvil\n") - writeFile(t, filepath.Join(dir, "objects", "copper_rock.yaml"), "name: copper rock\n") - writeFile(t, filepath.Join(dir, "rooms", "1.yaml"), `name: Test Room -objects: - - id: copper_rock - - id: copper_rock - - id: anvil - - name: anvil - description: "an inline thing that collides with the file id" -`) - issues := validateRooms(newSource(dir)) - - // Inline name "anvil" normalizes to the referenced file id "anvil". - if !containsMsg(issues, "object id collision \"anvil\"") { - t.Errorf("expected defID collision error, got: %+v", issues) - } - // Two references to the same copper_rock file are allowed. - if containsMsg(issues, "name \"copper rock\"") || containsMsg(issues, "collision \"copper_rock\"") { - t.Errorf("repeated references to one file object should not collide, got: %+v", issues) - } -} - -func TestValidateRoomsPartialNameSiblingsOK(t *testing.T) { - dir := t.TempDir() - writeFile(t, filepath.Join(dir, "rooms", "1.yaml"), `name: Test Room -objects: - - name: rusty sign - description: "rusty" - - name: shiny sign - description: "shiny" -`) - issues := validateRooms(newSource(dir)) - for _, iss := range issues { - if iss.Type == "duplicate" { - t.Errorf("partial-name siblings should not collide, got: %+v", iss) - } - } -} diff --git a/internal/validate/local_test.go b/internal/validate/local_test.go new file mode 100644 index 0000000..23bf8ca --- /dev/null +++ b/internal/validate/local_test.go @@ -0,0 +1,134 @@ +package validate + +import ( + "os" + "path/filepath" + "testing" + + "thehouseoficarus/internal/behavior" + "thehouseoficarus/internal/item" + "thehouseoficarus/internal/object" + "thehouseoficarus/internal/world" +) + +func TestValidateLocalObjectPassiveOK(t *testing.T) { + robj := world.RoomObject{ID: "window", Local: &object.ObjectDef{ + Name: "window", + Hidden: true, + Description: behavior.DescList{{Text: "A small window."}}, + }} + issues := validateLocalObject(1001, robj, nil, nil) + if len(issues) != 0 { + t.Errorf("expected no issues for passive local object, got: %+v", issues) + } +} + +func TestValidateLocalObjectRejectsInteractable(t *testing.T) { + robj := world.RoomObject{ID: "rock", Local: &object.ObjectDef{ + Name: "rock", + Gather: &behavior.GatherConfig{}, + }} + issues := validateLocalObject(1001, robj, nil, nil) + if !containsMsg(issues, "interactable behavior") { + t.Errorf("expected interactable-behavior error, got: %+v", issues) + } +} + +func TestValidateLocalObjectRequiresName(t *testing.T) { + robj := world.RoomObject{ID: "x", Local: &object.ObjectDef{ + Description: behavior.DescList{{Text: "no name"}}, + }} + issues := validateLocalObject(1001, robj, nil, nil) + if !containsMsg(issues, "has no name") { + t.Errorf("expected has-no-name error, got: %+v", issues) + } +} + +func TestValidateLocalObjectStrayIDWarns(t *testing.T) { + robj := world.RoomObject{ID: "anvil", Local: &object.ObjectDef{Name: "anvil", ID: "anvil"}} + issues := validateLocalObject(1001, robj, nil, nil) + if !containsMsg(issues, "ignored on local objects") { + t.Errorf("expected stray-id warning, got: %+v", issues) + } +} + +func newSource(dir string) Source { + return Source{ + DataDir: dir, + Items: item.NewItemStore(dir), + Objects: object.NewObjectStore(dir), + Mobs: world.NewMobStore(dir), + World: world.New(dir), + } +} + +func writeFile(t *testing.T, path, body string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(body), 0o644); err != nil { + t.Fatal(err) + } +} + +func TestValidateRoomsSameNameCollision(t *testing.T) { + dir := t.TempDir() + writeFile(t, filepath.Join(dir, "rooms", "1.yaml"), `name: Test Room +objects: + - id: ghost_object + - name: sign + description: "one" + - name: sign + description: "two" +`) + issues := validateRooms(newSource(dir)) + + if !containsMsg(issues, "nonexistent object \"ghost_object\"") { + t.Errorf("expected missing-reference error, got: %+v", issues) + } + if !containsMsg(issues, "objects share the name \"sign\"") { + t.Errorf("expected same-name collision error, got: %+v", issues) + } +} + +func TestValidateRoomsDefIDCollisionAndDuplicateRefs(t *testing.T) { + dir := t.TempDir() + writeFile(t, filepath.Join(dir, "objects", "anvil.yaml"), "name: heavy anvil\n") + writeFile(t, filepath.Join(dir, "objects", "copper_rock.yaml"), "name: copper rock\n") + writeFile(t, filepath.Join(dir, "rooms", "1.yaml"), `name: Test Room +objects: + - id: copper_rock + - id: copper_rock + - id: anvil + - name: anvil + description: "a local thing that collides with the file id" +`) + issues := validateRooms(newSource(dir)) + + // Local name "anvil" normalizes to the referenced file id "anvil". + if !containsMsg(issues, "object id collision \"anvil\"") { + t.Errorf("expected defID collision error, got: %+v", issues) + } + // Two references to the same copper_rock file are allowed. + if containsMsg(issues, "name \"copper rock\"") || containsMsg(issues, "collision \"copper_rock\"") { + t.Errorf("repeated references to one file object should not collide, got: %+v", issues) + } +} + +func TestValidateRoomsPartialNameSiblingsOK(t *testing.T) { + dir := t.TempDir() + writeFile(t, filepath.Join(dir, "rooms", "1.yaml"), `name: Test Room +objects: + - name: rusty sign + description: "rusty" + - name: shiny sign + description: "shiny" +`) + issues := validateRooms(newSource(dir)) + for _, iss := range issues { + if iss.Type == "duplicate" { + t.Errorf("partial-name siblings should not collide, got: %+v", iss) + } + } +} -- cgit v1.2.3