diff options
| author | historia <[not public]> | 2026-07-08 19:59:14 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-08 19:59:14 -0400 |
| commit | 09d325dcf5e779eab5550d3fd3377bde50101428 (patch) | |
| tree | a433be903aabbf1d2eadce2aacdac12a9eb8dde0 /internal/validate/checks.go | |
| parent | 9184377301c2604e003f36426788c032fb0ca524 (diff) | |
| download | thehouseoficarus-09d325dcf5e779eab5550d3fd3377bde50101428.tar.gz | |
feat: unify on use, on look, and on kill. all support same conditions/actions now.
Diffstat (limited to 'internal/validate/checks.go')
| -rw-r--r-- | internal/validate/checks.go | 239 |
1 files changed, 129 insertions, 110 deletions
diff --git a/internal/validate/checks.go b/internal/validate/checks.go index 64b1df4..47bbd1a 100644 --- a/internal/validate/checks.go +++ b/internal/validate/checks.go @@ -57,16 +57,20 @@ func validateRooms(s Source) []Issue { }) continue } - if exit.Room <= 0 { - continue + if exit.Room > 0 { + if _, ok := roomIndex[exit.Room]; !ok { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("Room %d: exit %q targets nonexistent room %d", + id, dir, exit.Room), + }) + } } - if _, ok := roomIndex[exit.Room]; !ok { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: exit %q targets nonexistent room %d", - id, dir, exit.Room), - }) + for i, it := range exit.OnTraverse { + issues = append(issues, validateStepAction( + fmt.Sprintf("Room %d: exit %q on_traverse[%d]", id, dir, i), + it.Action, itemIDs, roomIndex, mobIDs)...) } } @@ -114,7 +118,7 @@ func validateRooms(s Source) []Issue { displayName: world.NormalizeObjectName(robj.Local.Name), effDefID: robj.ID, }) - issues = append(issues, validateLocalObject(id, robj, itemIDs, roomIndex)...) + issues = append(issues, validateLocalObject(id, robj, itemIDs, roomIndex, mobIDs)...) } else if robj.ID != "" && !objIDs[robj.ID] { issues = append(issues, Issue{ Level: "ERROR", @@ -162,7 +166,7 @@ func validateRooms(s Source) []Issue { // 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 validateLocalObject(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, mobIDs map[string]bool) []Issue { var issues []Issue def := robj.Local prefix := fmt.Sprintf("Room %d: local object %q", roomID, robj.ID) @@ -192,8 +196,8 @@ func validateLocalObject(roomID int, robj world.RoomObject, itemIDs map[string]b if def.Safespot != nil { bad = append(bad, "safespot") } - if len(def.UseInteractions) > 0 { - bad = append(bad, "use_interactions") + if len(def.OnUse) > 0 { + bad = append(bad, "on_use") } if def.Steal != nil { bad = append(bad, "steal") @@ -218,8 +222,8 @@ func validateLocalObject(roomID int, robj world.RoomObject, itemIDs map[string]b }) } - if def.OnLook != nil { - issues = append(issues, validateNodeAction(prefix+": on_look", def.OnLook, itemIDs, roomIndex)...) + if len(def.OnLook) > 0 { + issues = append(issues, validateOnLook(prefix+": on_look", def.OnLook, itemIDs, roomIndex, mobIDs)...) } return issues @@ -343,6 +347,7 @@ func validateMobs(s Source) []Issue { itemIDs := s.Items.IDSet() dropIDs := dropTableIDSet(s.DataDir) mobIDs := s.Mobs.AllDefIDs() + roomIndex := s.World.RoomIndex() for id := range mobIDs { def, err := s.Mobs.LoadDef(id) @@ -481,6 +486,11 @@ func validateMobs(s Source) []Issue { } } } + + for i, it := range def.OnKill { + issues = append(issues, validateStepAction( + fmt.Sprintf("Mob %q: on_kill[%d]", id, i), it.Action, itemIDs, roomIndex, mobIDs)...) + } } return issues @@ -584,17 +594,17 @@ func validateObjects(s Source) []Issue { } } - for _, ui := range obj.UseInteractions { + for _, ui := range obj.OnUse { if ui.Item != "" && !itemIDs[ui.Item] { issues = append(issues, Issue{ Level: "ERROR", Type: "reference", - Message: fmt.Sprintf("Object %q: use_interaction item %q does not exist", + Message: fmt.Sprintf("Object %q: on_use item %q does not exist", id, ui.Item), }) } if ui.Action != nil { - issues = append(issues, validateNodeAction(fmt.Sprintf("Object %q: use_interaction", id), ui.Action, itemIDs, roomIndex)...) + issues = append(issues, validateStepAction(fmt.Sprintf("Object %q: on_use", id), ui.Action, itemIDs, roomIndex, mobIDs)...) } } @@ -606,9 +616,8 @@ func validateObjects(s Source) []Issue { issues = append(issues, validateTalkConfig(fmt.Sprintf("Object %q: talk", id), obj.Talk, itemIDs, roomIndex)...) } - - if obj.OnLook != nil { - issues = append(issues, validateNodeAction(fmt.Sprintf("Object %q: on_look", id), obj.OnLook, itemIDs, roomIndex)...) + if len(obj.OnLook) > 0 { + issues = append(issues, validateOnLook(fmt.Sprintf("Object %q: on_look", id), obj.OnLook, itemIDs, roomIndex, mobIDs)...) } } @@ -804,49 +813,9 @@ func validateRoomEnterSteps(s Source) []Issue { if err != nil || len(room.OnEnter) == 0 { continue } - for si, step := range room.OnEnter { + for si := range room.OnEnter { stepPrefix := fmt.Sprintf("Room %d: on_enter[%d]", id, si) - if step.SpawnMob != nil && step.SpawnMob.ID != "" { - if _, ok := mobIDs[step.SpawnMob.ID]; !ok { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: spawn_mob %q does not exist", stepPrefix, step.SpawnMob.ID), - }) - } - for _, wr := range step.SpawnMob.DespawnRooms { - if _, ok := roomIndex[wr]; !ok { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: spawn_mob despawn_rooms references nonexistent room %d", stepPrefix, wr), - }) - } - } - } - if step.GiveItem != "" && !itemIDs[step.GiveItem] { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: give_item %q does not exist", stepPrefix, step.GiveItem), - }) - } - if step.TakeItem != "" && !itemIDs[step.TakeItem] { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: take_item %q does not exist", stepPrefix, step.TakeItem), - }) - } - if step.Teleport > 0 { - if _, ok := roomIndex[step.Teleport]; !ok { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: teleport to nonexistent room %d", stepPrefix, step.Teleport), - }) - } - } + issues = append(issues, validateStepAction(stepPrefix, &room.OnEnter[si], itemIDs, roomIndex, mobIDs)...) } } @@ -866,54 +835,9 @@ func validateRoomTriggers(s Source) []Issue { } for ti, trigger := range room.Triggers { prefix := fmt.Sprintf("Room %d: trigger[%d]", id, ti) - for si, step := range trigger.Steps { + for si := range trigger.Steps { stepPrefix := fmt.Sprintf("%s: step[%d]", prefix, si) - if step.SpawnMob != nil && step.SpawnMob.ID != "" { - if _, ok := mobIDs[step.SpawnMob.ID]; !ok { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: spawn_mob %q does not exist", - stepPrefix, step.SpawnMob.ID), - }) - } - for _, wr := range step.SpawnMob.DespawnRooms { - if _, ok := roomIndex[wr]; !ok { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: spawn_mob despawn_rooms references nonexistent room %d", - stepPrefix, wr), - }) - } - } - } - if step.GiveItem != "" && !itemIDs[step.GiveItem] { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: give_item %q does not exist", - stepPrefix, step.GiveItem), - }) - } - if step.TakeItem != "" && !itemIDs[step.TakeItem] { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: take_item %q does not exist", - stepPrefix, step.TakeItem), - }) - } - if step.Teleport > 0 { - if _, ok := roomIndex[step.Teleport]; !ok { - issues = append(issues, Issue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: teleport to nonexistent room %d", - stepPrefix, step.Teleport), - }) - } - } + issues = append(issues, validateStepAction(stepPrefix, &trigger.Steps[si], itemIDs, roomIndex, mobIDs)...) } } } @@ -1309,3 +1233,98 @@ func validateNodeAction(prefix string, na *behavior.NodeAction, itemIDs map[stri return issues } + +// validateStepAction validates the universal effect superset used by on_use, +// on_look, on_kill, on_enter steps, trigger steps, and exit +// traversal. It covers the inline NodeAction fields plus spawn_mob/despawn_mob +// (when mobIDs is non-nil). Returns the list of issues found. +func validateStepAction(prefix string, step *behavior.StepAction, itemIDs map[string]bool, roomIndex map[int]bool, mobIDs map[string]bool) []Issue { + var issues []Issue + if step == nil { + return issues + } + + // Validate the embedded NodeAction subset. + if step.GiveItem != "" && !itemIDs[step.GiveItem] { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("%s: give_item %q does not exist", + prefix, step.GiveItem), + }) + } + if step.TakeItem != "" && !itemIDs[step.TakeItem] { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("%s: take_item %q does not exist", + prefix, step.TakeItem), + }) + } + if step.Teleport > 0 { + if _, ok := roomIndex[step.Teleport]; !ok { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("%s: teleport to nonexistent room %d", + prefix, step.Teleport), + }) + } + } + + // Spawn mob: validate mob def + despawn_rooms (only if mobIDs populated — + // a nil map means the caller doesn't track mob ids, e.g. local objects). + if step.SpawnMob != nil && step.SpawnMob.ID != "" { + if mobIDs != nil && !mobIDs[step.SpawnMob.ID] { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("%s: spawn_mob %q does not exist", + prefix, step.SpawnMob.ID), + }) + } + for _, wr := range step.SpawnMob.DespawnRooms { + if _, ok := roomIndex[wr]; !ok { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("%s: spawn_mob despawn_rooms references nonexistent room %d", + prefix, wr), + }) + } + } + } + + // Despawn mob. + if step.DespawnMob != "" && mobIDs != nil && !mobIDs[step.DespawnMob] { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("%s: despawn_mob %q does not exist", + prefix, step.DespawnMob), + }) + } + + return issues +} + +// validateOnLook validates a list of on_look interactions (now a list, not a +// single NodeAction). For each entry, validates the optional item_id (unused +// on on_look but permitted), the action's effects, and skips (the entry's +// condition is a runtime gate, not a static referential ref). +func validateOnLook(prefix string, list []behavior.Interaction, itemIDs map[string]bool, roomIndex map[int]bool, mobIDs map[string]bool) []Issue { + var issues []Issue + for i, it := range list { + entryPrefix := fmt.Sprintf("%s[%d]", prefix, i) + if it.Item != "" && !itemIDs[it.Item] { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("%s: item_id %q does not exist", + entryPrefix, it.Item), + }) + } + issues = append(issues, validateStepAction(entryPrefix, it.Action, itemIDs, roomIndex, mobIDs)...) + } + return issues +} |
