diff options
| author | historia <[not public]> | 2026-06-25 15:40:48 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-25 15:40:48 -0400 |
| commit | abd612c15799f604e671e83dc7c410ed2b44185f (patch) | |
| tree | 0597ca92350de73aac2a7cf26b2f2d6595dac0cc /internal/game/core_startup.go | |
| parent | 2725e2927a1595c7b100d942d1f14146252adeb7 (diff) | |
| download | thehouseoficarus-abd612c15799f604e671e83dc7c410ed2b44185f.tar.gz | |
slop refactor
Diffstat (limited to 'internal/game/core_startup.go')
| -rw-r--r-- | internal/game/core_startup.go | 887 |
1 files changed, 0 insertions, 887 deletions
diff --git a/internal/game/core_startup.go b/internal/game/core_startup.go deleted file mode 100644 index 2e81eac..0000000 --- a/internal/game/core_startup.go +++ /dev/null @@ -1,887 +0,0 @@ -package game - -import ( - "fmt" - "os" - "path/filepath" - "sort" - "strings" - - "thehouseoficarus/internal/action" - "thehouseoficarus/internal/world" -) - -type StartupIssue struct { - Level string - Type string - Message string -} - -func (g *Game) ValidateStartup() []StartupIssue { - var issues []StartupIssue - - issues = append(issues, validateIDs(g.DataDir, "items", "item")...) - issues = append(issues, validateIDs(g.DataDir, "rooms", "room")...) - issues = append(issues, validateIDs(g.DataDir, "mobs", "mob")...) - issues = append(issues, validateIDs(g.DataDir, "objects", "object")...) - issues = append(issues, validateIDs(g.DataDir, "drops", "drop table")...) - issues = append(issues, validateIDs(g.DataDir, "courses", "course")...) - issues = append(issues, validateIDs(g.DataDir, "modules", "module")...) - issues = append(issues, validateIDs(g.DataDir, "techs", "tech")...) - issues = append(issues, validateIDs(g.DataDir, "help", "help topic")...) - - issues = append(issues, validateRooms(g)...) - issues = append(issues, validateMobs(g)...) - issues = append(issues, validateHazards(g)...) - issues = append(issues, validateObjects(g)...) - issues = append(issues, validateItems(g)...) - issues = append(issues, validateCraftBlocks(g)...) - issues = append(issues, validateDropTables(g)...) - issues = append(issues, validateCourses(g)...) - issues = append(issues, validateTechs(g)...) - issues = append(issues, validateRoomWiring(g)...) - - return issues -} - -func validateIDs(dataDir, subDir, label string) []StartupIssue { - dir := filepath.Join(dataDir, subDir) - dups := action.CheckDuplicateIDs(dir) - var issues []StartupIssue - for _, d := range dups { - short := make([]string, len(d.Paths)) - for i, p := range d.Paths { - short[i] = strings.TrimPrefix(p, dir+"/") - } - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "duplicate", - Message: fmt.Sprintf("Duplicate %s ID %q found in: %s", - label, d.ID, strings.Join(short, ", ")), - }) - } - return issues -} - -func validateRooms(g *Game) []StartupIssue { - var issues []StartupIssue - roomIndex := g.World.RoomIndex() - itemIDs := g.ItemStore.IDSet() - mobIDs := g.MobStore.AllDefIDs() - objIDs := g.ObjectStore.IDSet() - hazardIDs := g.World.HazardIndex() - - for id := range roomIndex { - room, err := g.World.LoadRoom(id) - if err != nil { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: failed to load: %v", id, err), - }) - continue - } - if room.Name == "" { - issues = append(issues, StartupIssue{ - Level: "WARN", - Type: "reference", - Message: fmt.Sprintf("Room %d: has no name", id), - }) - } - - for dir, exit := range room.Exits { - switch dir { - case world.North, world.South, world.East, world.West, world.Up, world.Down: - default: - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: invalid exit direction %q (only north/south/east/west/up/down supported)", id, dir), - }) - continue - } - if exit.Room <= 0 { - continue - } - if _, ok := roomIndex[exit.Room]; !ok { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: exit %q targets nonexistent room %d", - id, dir, exit.Room), - }) - } - } - - for _, rm := range room.Mobs { - if rm.ID == "" { - continue - } - if _, ok := mobIDs[rm.ID]; !ok { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: references nonexistent mob %q", - id, rm.ID), - }) - } - for _, wr := range rm.WanderRooms { - if _, ok := roomIndex[wr]; !ok { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: mob %q wander_rooms references nonexistent room %d", - id, rm.ID, wr), - }) - } - } - } - - for _, s := range room.ItemSpawns { - if s.ID != "" && !itemIDs[s.ID] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: spawns nonexistent item %q", - id, s.ID), - }) - } - } - - for _, robj := range room.Objects { - if robj.ID != "" && !objIDs[robj.ID] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: references nonexistent object %q", - id, robj.ID), - }) - } - for _, wr := range robj.WanderRooms { - if _, ok := roomIndex[wr]; !ok { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: object %q wander_rooms references nonexistent room %d", - id, robj.ID, wr), - }) - } - } - } - - if room.Hazard != "" && !hazardIDs[room.Hazard] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Room %d: references nonexistent hazard %q", - id, room.Hazard), - }) - } - } - - return issues -} - -func validateMobs(g *Game) []StartupIssue { - var issues []StartupIssue - itemIDs := g.ItemStore.IDSet() - dropIDs := dropTableIDSet(g.DataDir) - mobIDs := g.MobStore.AllDefIDs() - - for id := range mobIDs { - def, err := g.MobStore.LoadDef(id) - if err != nil { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Mob %q: failed to load: %v", id, err), - }) - continue - } - if def.Name == "" { - issues = append(issues, StartupIssue{ - Level: "WARN", - Type: "reference", - Message: fmt.Sprintf("Mob %q: has no name", id), - }) - } - - if def.Kind != "" && def.Kind != "combat" && def.Kind != "task" { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Mob %q: invalid kind %q (must be \"combat\" or \"task\")", id, def.Kind), - }) - } - - if def.Drops.Remains != "" && !itemIDs[def.Drops.Remains] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Mob %q: drops remains (item %q) does not exist", - id, def.Drops.Remains), - }) - } - - for _, d := range def.Drops.Loot { - if d.ItemID != "" && !itemIDs[d.ItemID] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Mob %q: loot item %q does not exist", - id, d.ItemID), - }) - } - if d.Table != "" && !dropIDs[d.Table] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Mob %q: loot table %q does not exist", - id, d.Table), - }) - } - } - - if def.StealTable != "" && !dropIDs[def.StealTable] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Mob %q: steal_table %q does not exist", - id, def.StealTable), - }) - } - - if def.FinishingBlow != "" && !itemIDs[def.FinishingBlow] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Mob %q: finishing_blow item %q does not exist", - id, def.FinishingBlow), - }) - } - } - - return issues -} - -func validateHazards(g *Game) []StartupIssue { - var issues []StartupIssue - itemIDs := g.ItemStore.IDSet() - validTypes := map[string]bool{"stab": true, "slash": true, "crush": true, "ranged": true, "science": true} - - for id := range g.World.HazardIndex() { - def, err := g.World.LoadHazard(id) - if err != nil { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Hazard %q: failed to load: %v", id, err), - }) - continue - } - if def.AttackType != "" && !validTypes[def.AttackType] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Hazard %q: invalid attack_type %q (must be stab/slash/crush/ranged/science)", id, def.AttackType), - }) - } - if def.RequiredItem != "" && !itemIDs[def.RequiredItem] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Hazard %q: required_item %q does not exist", id, def.RequiredItem), - }) - } - } - - return issues -} - -func validateObjects(g *Game) []StartupIssue { - var issues []StartupIssue - objIDs := g.ObjectStore.IDSet() - itemIDs := g.ItemStore.IDSet() - mobIDs := g.MobStore.AllDefIDs() - dropIDs := dropTableIDSet(g.DataDir) - roomIndex := g.World.RoomIndex() - - for id := range objIDs { - obj, err := g.ObjectStore.Load(id) - if err != nil { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Object %q: failed to load: %v", id, err), - }) - continue - } - if obj.Name == "" { - issues = append(issues, StartupIssue{ - Level: "WARN", - Type: "reference", - Message: fmt.Sprintf("Object %q: has no name", id), - }) - } - - if obj.RemovalItem != "" && !itemIDs[obj.RemovalItem] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Object %q: removal_item %q does not exist", - id, obj.RemovalItem), - }) - } - - if obj.StealTable != "" && !dropIDs[obj.StealTable] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Object %q: steal_table %q does not exist", - id, obj.StealTable), - }) - } - - if obj.GuardMob != "" && !mobIDs[obj.GuardMob] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Object %q: guard_mob %q does not exist", - id, obj.GuardMob), - }) - } - - for _, ui := range obj.UseInteractions { - if ui.Item != "" && !itemIDs[ui.Item] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Object %q: use_interaction 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)...) - } - } - - if obj.Gather != nil { - issues = append(issues, validateGather(fmt.Sprintf("Object %q: gather", id), obj.Gather, itemIDs, dropIDs)...) - } - - if obj.Talk != nil { - issues = append(issues, validateTalkConfig(fmt.Sprintf("Object %q: talk", id), obj.Talk, itemIDs, roomIndex)...) - } - - if obj.Use != nil { - issues = append(issues, validateUseConfig(fmt.Sprintf("Object %q: use", id), obj.Use, itemIDs)...) - } - } - - return issues -} - -func validateItems(g *Game) []StartupIssue { - var issues []StartupIssue - itemIDs := g.ItemStore.IDSet() - dropIDs := dropTableIDSet(g.DataDir) - - for id := range itemIDs { - def, err := g.ItemStore.Load(id) - if err != nil { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Item %q: failed to load: %v", id, err), - }) - continue - } - if def.Name == "" { - issues = append(issues, StartupIssue{ - Level: "WARN", - Type: "reference", - Message: fmt.Sprintf("Item %q: has no name", id), - }) - } - - if def.SearchTable != "" && !dropIDs[def.SearchTable] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Item %q: search_table %q does not exist", - id, def.SearchTable), - }) - } - - if def.SearchMiscTable != "" && !dropIDs[def.SearchMiscTable] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Item %q: search_misc_table %q does not exist", - id, def.SearchMiscTable), - }) - } - - if def.FarmProduct != "" && !itemIDs[def.FarmProduct] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Item %q (seed): farm_product %q does not exist", - id, def.FarmProduct), - }) - } - } - - return issues -} - -func validateCraftBlocks(g *Game) []StartupIssue { - var issues []StartupIssue - itemIDs := g.ItemStore.IDSet() - objIDs := g.ObjectStore.IDSet() - - allItems, err := g.ItemStore.LoadAll() - if err != nil { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Failed to load items: %v", err), - }) - return issues - } - - for _, item := range allItems { - if len(item.Craft) == 0 { - continue - } - for ci := range item.Craft { - c := &item.Craft[ci] - - for _, e := range c.Consume { - for _, consumeItem := range e.Items { - if consumeItem != "" && !itemIDs[consumeItem] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Item %q (craft: %s): consumes nonexistent item %q", - item.ID, c.Type, consumeItem), - }) - } - } - } - - if c.Fail != "" && !itemIDs[c.Fail] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Item %q (craft: %s): fail product %q does not exist", - item.ID, c.Type, c.Fail), - }) - } - - for _, sid := range c.Station { - if sid != "" && !objIDs[sid] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Item %q (craft: %s): station object %q does not exist", - item.ID, c.Type, sid), - }) - } - } - } - } - - return issues -} - -func validateDropTables(g *Game) []StartupIssue { - var issues []StartupIssue - dropIDs := dropTableIDSet(g.DataDir) - itemIDs := g.ItemStore.IDSet() - - for id := range dropIDs { - dt, err := action.LoadDropTable(g.DataDir, id) - if err != nil { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Drop table %q: failed to load: %v", id, err), - }) - continue - } - for _, d := range dt.Drops { - if d.ItemID != "" && !itemIDs[d.ItemID] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Drop table %q: drops nonexistent item %q", - id, d.ItemID), - }) - } - if d.Table != "" && !dropIDs[d.Table] && d.Table != id { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Drop table %q: references nonexistent sub-table %q", - id, d.Table), - }) - } - } - } - - return issues -} - -func validateCourses(g *Game) []StartupIssue { - var issues []StartupIssue - roomIndex := g.World.RoomIndex() - - courses := g.CourseStore.AllCourses() - for id, cfg := range courses { - if cfg.StartRoom > 0 { - if _, ok := roomIndex[cfg.StartRoom]; !ok { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Course %q: start_room %d does not exist", - id, cfg.StartRoom), - }) - } - } - for _, obs := range cfg.Obstacles { - if _, ok := roomIndex[obs.RoomID]; !ok { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Course %q: obstacle room %d does not exist", - id, obs.RoomID), - }) - } - } - } - - return issues -} - -func validateRoomWiring(g *Game) []StartupIssue { - var issues []StartupIssue - roomIndex := g.World.RoomIndex() - cfg := g.ValidationConfig - - sourceSet := make(map[int]bool) - for _, s := range cfg.CheckSources { - sourceSet[s] = true - } - - ignoreTo := make(map[int]bool) - for _, r := range cfg.IgnoreUnreachable { - ignoreTo[r] = true - } - - hasExitTo := make(map[int]bool) - for id := range roomIndex { - room, err := g.World.LoadRoom(id) - if err != nil { - continue - } - for _, exit := range room.Exits { - if exit.Room > 0 { - hasExitTo[exit.Room] = true - } - } - } - - var orphans []int - for id := range roomIndex { - if ignoreTo[id] { - continue - } - if sourceSet[id] { - continue - } - if !hasExitTo[id] { - orphans = append(orphans, id) - } - } - if len(orphans) > 0 { - sort.Ints(orphans) - var strs []string - for _, id := range orphans { - strs = append(strs, fmt.Sprintf("%d", id)) - } - issues = append(issues, StartupIssue{ - Level: "WARN", - Type: "integrity", - Message: fmt.Sprintf("Orphan rooms (no exit leads to them): %s", - strings.Join(strs, ", ")), - }) - } - - return issues -} - -func validateGather(prefix string, cfg *action.GatherConfig, itemIDs map[string]bool, dropIDs map[string]bool) []StartupIssue { - var issues []StartupIssue - - if cfg.Bait != "" && !itemIDs[cfg.Bait] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: bait item %q does not exist", - prefix, cfg.Bait), - }) - } - - for _, d := range cfg.Drops { - if d.ItemID != "" && !itemIDs[d.ItemID] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: drop item %q does not exist", - prefix, d.ItemID), - }) - } - if d.Table != "" && !dropIDs[d.Table] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: drop table %q does not exist", - prefix, d.Table), - }) - } - } - - return issues -} - -func validateTalkConfig(prefix string, cfg *action.TalkConfig, itemIDs map[string]bool, roomIndex map[int]bool) []StartupIssue { - var issues []StartupIssue - if cfg == nil { - return issues - } - for nodeID, node := range cfg.Nodes { - nodePrefix := fmt.Sprintf("%s: node %q", prefix, nodeID) - if node.Action != nil { - issues = append(issues, validateNodeAction(nodePrefix, node.Action, itemIDs, roomIndex)...) - } - } - return issues -} - -func validateUseConfig(prefix string, cfg *action.UseConfig, itemIDs map[string]bool) []StartupIssue { - var issues []StartupIssue - if cfg == nil { - return issues - } - - for itemID := range cfg.Consume { - if itemID != "" && !itemIDs[itemID] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: consumes nonexistent item %q", - prefix, itemID), - }) - } - } - - if cfg.Reward.ItemID != "" && !itemIDs[cfg.Reward.ItemID] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: reward item %q does not exist", - prefix, cfg.Reward.ItemID), - }) - } - - return issues -} - -func validateNodeAction(prefix string, na *action.NodeAction, itemIDs map[string]bool, roomIndex map[int]bool) []StartupIssue { - var issues []StartupIssue - if na == nil { - return issues - } - - if na.GiveItem != "" && !itemIDs[na.GiveItem] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: give_item %q does not exist", - prefix, na.GiveItem), - }) - } - - if na.TakeItem != "" && !itemIDs[na.TakeItem] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: take_item %q does not exist", - prefix, na.TakeItem), - }) - } - - if na.Teleport > 0 { - if _, ok := roomIndex[na.Teleport]; !ok { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: teleport to nonexistent room %d", - prefix, na.Teleport), - }) - } - } - - if na.Shop != nil { - for _, si := range na.Shop.Items { - if si.ItemID != "" && !itemIDs[si.ItemID] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("%s: shop item %q does not exist", - prefix, si.ItemID), - }) - } - } - } - - return issues -} - -var knownTechCategories = map[string]bool{ - "accuracy": true, - "strength": true, - "defense": true, - "ranged": true, - "science": true, - "protection": true, - "utility": true, - "combo": true, -} - -// Reserved tech IDs whose semantics are coupled to code logic — -// damageAfterTechProtection maps attack types to protect_melee/ranged/science -// and killPlayer checks for retribution. These IDs must exist in YAML. -var reservedTechIDs = map[string]bool{ - "protect_melee": true, - "protect_ranged": true, - "protect_science": true, - "retribution": true, -} - -func validateTechs(g *Game) []StartupIssue { - var issues []StartupIssue - seen := make(map[string]bool) - for _, def := range AllTechs { - if def.ID == "" { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: "Tech has empty id", - }) - continue - } - if seen[def.ID] { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "duplicate", - Message: fmt.Sprintf("Duplicate tech %q", def.ID), - }) - } - seen[def.ID] = true - - if def.Name == "" { - issues = append(issues, StartupIssue{ - Level: "WARN", - Type: "reference", - Message: fmt.Sprintf("Tech %q: has no name", def.ID), - }) - } - if def.Category != "" && !knownTechCategories[def.Category] { - issues = append(issues, StartupIssue{ - Level: "WARN", - Type: "reference", - Message: fmt.Sprintf("Tech %q: unknown category %q", def.ID, def.Category), - }) - } - if def.DrainRate < 0 { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Tech %q: negative drain_rate %.2f", def.ID, def.DrainRate), - }) - } - } - - for id := range reservedTechIDs { - if _, ok := techByID[id]; !ok { - issues = append(issues, StartupIssue{ - Level: "ERROR", - Type: "reference", - Message: fmt.Sprintf("Reserved tech %q does not exist — required by code (damage protection / retribution)", id), - }) - } - } - - return issues -} - -func dropTableIDSet(dataDir string) map[string]bool { - dir := filepath.Join(dataDir, "drops") - ids := make(map[string]bool) - action.WalkYAMLDir(dir, func(path, id string, data []byte) error { - ids[id] = true - return nil - }) - return ids -} - -func (cs *CourseStore) AllCourses() map[string]*CourseConfig { - cs.mu.Lock() - defer cs.mu.Unlock() - if !cs.loaded { - cs.loadAllLocked() - } - return cs.courses -} - -func (g *Game) ValidateAndLog() { - LogStartupIssues(g.ValidateStartup()) -} - -func LogStartupIssues(issues []StartupIssue) { - if len(issues) == 0 { - fmt.Fprintf(os.Stderr, "Startup validation: OK\n") - return - } - - errors := 0 - warns := 0 - for _, issue := range issues { - if issue.Level == "ERROR" { - errors++ - } else { - warns++ - } - } - - fmt.Fprintf(os.Stderr, "\n=== Startup Validation: %d error(s), %d warning(s) ===\n\n", errors, warns) - - sort.Slice(issues, func(i, j int) bool { - if issues[i].Level != issues[j].Level { - return issues[i].Level == "ERROR" - } - return issues[i].Message < issues[j].Message - }) - - for _, issue := range issues { - tag := fmt.Sprintf("[%s]", issue.Level) - fmt.Fprintf(os.Stderr, " %-7s %s\n", tag, issue.Message) - } - fmt.Fprintln(os.Stderr) - - if errors > 0 { - fmt.Fprintf(os.Stderr, "*** %d startup errors detected. The server may behave unexpectedly. ***\n", errors) - } -} |
