diff options
Diffstat (limited to 'internal/validate')
| -rw-r--r-- | internal/validate/checks.go | 101 |
1 files changed, 71 insertions, 30 deletions
diff --git a/internal/validate/checks.go b/internal/validate/checks.go index aee46f2..1c87c67 100644 --- a/internal/validate/checks.go +++ b/internal/validate/checks.go @@ -56,6 +56,13 @@ func validateRooms(s Source) []Issue { Message: fmt.Sprintf("Room %d: invalid color %q", id, room.Color), }) } + // A conditional description whose condition references a missing room + // or item silently blanks the room text at runtime. + for i := range room.Description { + if c := room.Description[i].Condition; c != nil { + issues = append(issues, validateCondition(fmt.Sprintf("Room %d: description[%d]", id, i), c, roomIndex, itemIDs)...) + } + } for dir, exit := range room.Exits { switch dir { @@ -249,6 +256,14 @@ func validateLocalObject(roomID int, robj world.RoomObject, itemIDs map[string]b }) } + // A conditional description whose condition is malformed silently hides + // the object at runtime; validate it like any other condition. + for i := range def.Description { + if c := def.Description[i].Condition; c != nil { + issues = append(issues, validateCondition(fmt.Sprintf("%s: description[%d]", prefix, i), c, roomIndex, itemIDs)...) + } + } + if len(def.OnLook) > 0 { issues = append(issues, validateTriggerBlock(prefix+": on_look", def.OnLook, itemIDs, roomIndex, mobIDs, dropIDs)...) } @@ -698,6 +713,12 @@ func validateObjects(s Source) []Issue { issues = append(issues, validateGather(fmt.Sprintf("Object %q: gather", id), obj.Gather, itemIDs, dropIDs)...) } + for i := range obj.Description { + if c := obj.Description[i].Condition; c != nil { + issues = append(issues, validateCondition(fmt.Sprintf("Object %q: description[%d]", id, i), c, roomIndex, itemIDs)...) + } + } + if obj.Talk != nil { issues = append(issues, validateTalkConfig(fmt.Sprintf("Object %q: talk", id), obj.Talk, itemIDs, roomIndex, mobIDs, dropIDs)...) } @@ -803,6 +824,17 @@ func validateCraftBlocks(s Source) []Issue { craftLabel = c.Type + "/" + c.Subtype } + // SuccessChance clamps to Cap; an omitted cap makes the recipe + // fail every time (see craftFailureReachable, which would then + // mask it as an intentional always-fail recipe). + if c.Success != nil && c.Success.Cap <= 0 { + issues = append(issues, Issue{ + Level: "WARN", + Type: "config", + Message: fmt.Sprintf("Item %q (craft: %s): success.cap is %v; every success roll clamps to zero", item.ID, craftLabel, c.Success.Cap), + }) + } + for _, e := range c.Ingredients { if len(e.Items) == 0 { issues = append(issues, Issue{ @@ -1367,12 +1399,8 @@ func validateRoomGrid(s Source) []Issue { } } - for _, origin := range seeds { - if placed[origin] { - continue - } - - grid := world.BuildGrid(origin, load, include, nil, func(c world.GridConflict) { + onConf := func(origin int) func(c world.GridConflict) { + return func(c world.GridConflict) { switch c.Kind { case "twist": issues = append(issues, Issue{ @@ -1391,33 +1419,18 @@ func validateRoomGrid(s Source) []Issue { c.Target, c.From, c.Dir, c.Want[0], c.Want[1], c.Want[2], c.Occupier, origin), }) } - }) - - for rid := range grid.Coord { - placed[rid] = true } } - onConf := func(origin int) func(c world.GridConflict) { - return func(c world.GridConflict) { - switch c.Kind { - case "twist": - issues = append(issues, Issue{ - Level: "ERROR", - Type: "integrity", - Message: fmt.Sprintf( - "Grid twist: room %d (via %d %s) maps to (%d,%d,%d) but was already placed at (%d,%d,%d) [origin %d]", - c.Target, c.From, c.Dir, c.Want[0], c.Want[1], c.Want[2], c.Existing[0], c.Existing[1], c.Existing[2], origin), - }) - case "overlap": - issues = append(issues, Issue{ - Level: "ERROR", - Type: "integrity", - Message: fmt.Sprintf( - "Grid overlap: room %d (via %d %s) wants (%d,%d,%d), already used by room %d [origin %d]", - c.Target, c.From, c.Dir, c.Want[0], c.Want[1], c.Want[2], c.Occupier, origin), - }) - } + for _, origin := range seeds { + if placed[origin] { + continue + } + + grid := world.BuildGrid(origin, load, include, nil, onConf(origin)) + + for rid := range grid.Coord { + placed[rid] = true } } @@ -1487,6 +1500,16 @@ func validateTechs(s Source) []Issue { func validateGather(prefix string, cfg *behavior.GatherConfig, itemIDs map[string]bool, dropIDs map[string]bool) []Issue { var issues []Issue + // SuccessChance clamps to Cap, so an omitted (zero) cap makes every roll + // fail forever. Warn instead of letting the content silently never pay. + if cfg.Success.Cap <= 0 { + issues = append(issues, Issue{ + Level: "WARN", + Type: "config", + Message: fmt.Sprintf("%s: success.cap is %v; every success roll clamps to zero (set cap to the intended maximum chance)", prefix, cfg.Success.Cap), + }) + } + if cfg.Bait != "" && !itemIDs[cfg.Bait] { issues = append(issues, Issue{ Level: "ERROR", @@ -1558,6 +1581,9 @@ func validateTalkConfig(prefix string, cfg *behavior.TalkConfig, itemIDs map[str if node.Action != nil { issues = append(issues, validateTalkStep(nodePrefix, node.Action, itemIDs, roomIndex, mobIDs, dropIDs)...) } + if node.Condition != nil { + issues = append(issues, validateCondition(nodePrefix, node.Condition, roomIndex, itemIDs)...) + } if node.Goto != "" { if _, ok := cfg.Nodes[node.Goto]; !ok { issues = append(issues, Issue{ @@ -1572,6 +1598,21 @@ func validateTalkConfig(prefix string, cfg *behavior.TalkConfig, itemIDs map[str if opt.Action != nil { issues = append(issues, validateTalkStep(optPrefix, opt.Action, itemIDs, roomIndex, mobIDs, dropIDs)...) } + if opt.Condition != nil { + issues = append(issues, validateCondition(optPrefix, opt.Condition, roomIndex, itemIDs)...) + } + // An unknown option goto silently ends the conversation at + // runtime (act_talk.advanceTalk), so it is an error here just + // like a node-level goto. + if opt.Goto != "" { + if _, ok := cfg.Nodes[opt.Goto]; !ok { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("%s: goto points to nonexistent node %q", optPrefix, opt.Goto), + }) + } + } } } |
