diff options
| author | historia <[not public]> | 2026-09-18 17:48:26 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 17:48:26 -0400 |
| commit | 8fe9e3c3fef0049df82281a62afe35dda516dfe2 (patch) | |
| tree | dd269f2c0710f334e19f00a191260ebc46284a90 /internal/validate/checks.go | |
| parent | 194164a77eb4f2479b94c19e6f0e1278ab608781 (diff) | |
| download | thehouseoficarus-8fe9e3c3fef0049df82281a62afe35dda516dfe2.tar.gz | |
fix: close combat ghost, admin traversal, login throttle, journal double-pay, and quality/economy gaps
Diffstat (limited to 'internal/validate/checks.go')
| -rw-r--r-- | internal/validate/checks.go | 64 |
1 files changed, 58 insertions, 6 deletions
diff --git a/internal/validate/checks.go b/internal/validate/checks.go index bfa8342..aee46f2 100644 --- a/internal/validate/checks.go +++ b/internal/validate/checks.go @@ -531,6 +531,10 @@ func validateMobs(s Source) []Issue { } } + if def.Talk != nil { + issues = append(issues, validateTalkConfig(fmt.Sprintf("Mob %q: talk", id), def.Talk, itemIDs, roomIndex, mobIDs, dropIDs)...) + } + if def.Combat != nil && def.Combat.FinishingBlow != "" && !itemIDs[def.Combat.FinishingBlow] { issues = append(issues, Issue{ Level: "ERROR", @@ -709,6 +713,7 @@ func validateObjects(s Source) []Issue { func validateItems(s Source) []Issue { var issues []Issue itemIDs := s.Items.IDSet() + objIDs := s.Objects.IDSet() dropIDs := dropTableIDSet(s.DataDir) roomIndex := s.World.RoomIndex() mobIDs := s.Mobs.AllDefIDs() @@ -744,6 +749,17 @@ func validateItems(s Source) []Issue { id, def.FarmProduct), }) } + + // The runtime derives "<farm_patch_type>_patch" and requires that + // object to exist; a type with no matching patch can never be planted. + if def.FarmPatchType != "" && !objIDs[def.FarmPatchType+"_patch"] { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("Item %q (seed): farm_patch_type %q has no matching %q object", + id, def.FarmPatchType, def.FarmPatchType+"_patch"), + }) + } } return issues @@ -1117,6 +1133,16 @@ func validateCourses(s Source) []Issue { }) } } + for _, rid := range cv.OnFailRooms { + if _, ok := roomIndex[rid]; !ok { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("Course %q: obstacle on_fail room %d does not exist (a failed obstacle would teleport the player into a void)", + cv.ID, rid), + }) + } + } } return issues @@ -1268,7 +1294,7 @@ func validateRoomWiring(s Source) []Issue { // does not exist: the game assigns the id unconditionally, so the player is // saved into a void with no exits. func validateModuleDestinations(s Source) []Issue { - if len(s.ModuleDestinations) == 0 { + if len(s.ModuleDestinations) == 0 && len(s.TransportModules) == 0 { return nil } roomIndex := s.World.RoomIndex() @@ -1289,6 +1315,24 @@ func validateModuleDestinations(s Source) []Issue { Message: fmt.Sprintf("Module %q: destination room %d does not exist", id, dest), }) } + // A transport module whose destination was omitted or <= 0 is not in + // ModuleDestinations (the source only records positive destinations), so + // report it explicitly: the runtime assigns the id unconditionally. + tids := make([]string, 0, len(s.TransportModules)) + for id := range s.TransportModules { + tids = append(tids, id) + } + sort.Strings(tids) + for _, id := range tids { + if dest, ok := s.ModuleDestinations[id]; ok && dest > 0 { + continue + } + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("Module %q: transport module has no valid destination room (the player would be saved into room 0)", id), + }) + } return issues } @@ -1691,14 +1735,14 @@ func validateStep(prefix string, step *behavior.Step, itemIDs map[string]bool, r } if step.Condition != nil { - issues = append(issues, validateCondition(prefix, step.Condition, roomIndex)...) + issues = append(issues, validateCondition(prefix, step.Condition, roomIndex, itemIDs)...) } return issues } // validateCondition checks a Condition tree's Room references (structural room // IDs) and recurses into all_of/any_of. -func validateCondition(prefix string, c *behavior.Condition, roomIndex map[int]bool) []Issue { +func validateCondition(prefix string, c *behavior.Condition, roomIndex map[int]bool, itemIDs map[string]bool) []Issue { if c == nil { return nil } @@ -1713,11 +1757,19 @@ func validateCondition(prefix string, c *behavior.Condition, roomIndex map[int]b }) } } + if c.HasItem != "" && !itemIDs[c.HasItem] { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "reference", + Message: fmt.Sprintf("%s: condition has_item references nonexistent item %q (the gate can never pass)", + prefix, c.HasItem), + }) + } for i := range c.AllOf { - issues = append(issues, validateCondition(prefix, &c.AllOf[i], roomIndex)...) + issues = append(issues, validateCondition(prefix, &c.AllOf[i], roomIndex, itemIDs)...) } for i := range c.AnyOf { - issues = append(issues, validateCondition(prefix, &c.AnyOf[i], roomIndex)...) + issues = append(issues, validateCondition(prefix, &c.AnyOf[i], roomIndex, itemIDs)...) } return issues } @@ -1755,7 +1807,7 @@ func validateTriggerBlock(prefix string, block []behavior.Trigger, itemIDs map[s }) } if t.Condition != nil { - issues = append(issues, validateCondition(entryPrefix, t.Condition, roomIndex)...) + issues = append(issues, validateCondition(entryPrefix, t.Condition, roomIndex, itemIDs)...) } for si := range t.Steps { |
