aboutsummaryrefslogtreecommitdiff
path: root/internal/validate/checks.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-01 22:30:31 -0400
committerhistoria <[not public]>2026-07-01 22:30:31 -0400
commitc14c2e403c75a913e1a6d962fb6fe64f013adae5 (patch)
tree75070293fe2535b594ac72e501cbf08a3f1146e0 /internal/validate/checks.go
parent8eec7b75ff9c8c368b252373db45fb891732d2ca (diff)
downloadthehouseoficarus-c14c2e403c75a913e1a6d962fb6fe64f013adae5.tar.gz
fix: combat formulas and mob attack types (mobs switch attack styles if safespotting)
Diffstat (limited to 'internal/validate/checks.go')
-rw-r--r--internal/validate/checks.go59
1 files changed, 57 insertions, 2 deletions
diff --git a/internal/validate/checks.go b/internal/validate/checks.go
index a8f973f..ccf996c 100644
--- a/internal/validate/checks.go
+++ b/internal/validate/checks.go
@@ -7,6 +7,7 @@ import (
"thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/combat"
"thehouseoficarus/internal/world"
)
@@ -292,6 +293,57 @@ func validateRoomObjectCollisions(roomID int, entries []roomObjEntry) []Issue {
return issues
}
+// validateMobAttackTypes enforces the mob attack_types spec: a non-empty list
+// containing exactly one melee type (stab/slash/crush) plus at most one each of
+// the optional ranged/science types, with no invalid or duplicate entries.
+func validateMobAttackTypes(id string, types []string) []Issue {
+ var issues []Issue
+
+ if len(types) == 0 {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: missing attack_types (must be a list with exactly one of stab/slash/crush, optionally plus ranged/science)", id),
+ })
+ return issues
+ }
+
+ meleeCount := 0
+ seen := map[string]bool{}
+ for _, t := range types {
+ if !combat.IsValidAttackType(t) {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: invalid attack_types entry %q (must be stab/slash/crush/ranged/science)", id, t),
+ })
+ continue
+ }
+ if seen[t] {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: duplicate attack_types entry %q", id, t),
+ })
+ continue
+ }
+ seen[t] = true
+ if combat.IsMeleeType(t) {
+ meleeCount++
+ }
+ }
+
+ if meleeCount != 1 {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: attack_types must contain exactly one melee type (stab/slash/crush), found %d", id, meleeCount),
+ })
+ }
+
+ return issues
+}
+
func validateMobs(s Source) []Issue {
var issues []Issue
itemIDs := s.Items.IDSet()
@@ -336,6 +388,10 @@ func validateMobs(s Source) []Issue {
})
}
+ if def.Combat != nil && def.Combat.Kind != "task" {
+ issues = append(issues, validateMobAttackTypes(id, def.Combat.AttackTypes)...)
+ }
+
if def.Drops.Remains != "" && !itemIDs[def.Drops.Remains] {
issues = append(issues, Issue{
Level: "ERROR",
@@ -427,7 +483,6 @@ func validateMobs(s Source) []Issue {
func validateHazards(s Source) []Issue {
var issues []Issue
itemIDs := s.Items.IDSet()
- validTypes := map[string]bool{"stab": true, "slash": true, "crush": true, "ranged": true, "science": true}
for id := range s.World.HazardIndex() {
def, err := s.World.LoadHazard(id)
@@ -439,7 +494,7 @@ func validateHazards(s Source) []Issue {
})
continue
}
- if def.AttackType != "" && !validTypes[def.AttackType] {
+ if def.AttackType != "" && !combat.IsValidAttackType(def.AttackType) {
issues = append(issues, Issue{
Level: "ERROR",
Type: "reference",