package world import ( "fmt" "os" "path/filepath" "regexp" "strconv" "strings" "gopkg.in/yaml.v3" ) // CountExtraInbound returns the number of exits in rooms OTHER than A and C // that point into B, plus any A/C exits to B other than the expected // (A→dir→B, C→oppositeDir→B). A nonzero result means B has been modified // since it was inserted and a remove would silently orphan edges. It walks // data/rooms and matches \b\b against raw file contents to skip rooms // that don't mention bID at all, then unmarshals only the matching files. // // Shared by the in-game room-remove command and the admin API so the two // removal paths cannot drift. func CountExtraInbound(dataDir string, bID, aID, cID int, dir, oppositeDir ExitDir) int { roomsDir := filepath.Join(dataDir, "rooms") re := regexp.MustCompile(fmt.Sprintf(`\b%d\b`, bID)) var extra int _ = filepath.WalkDir(roomsDir, func(path string, d os.DirEntry, err error) error { if err != nil || d.IsDir() || filepath.Ext(path) != ".yaml" { return nil } data, rerr := os.ReadFile(path) if rerr != nil { return nil } if !re.MatchString(string(data)) { return nil } idStr := strings.TrimSuffix(filepath.Base(path), ".yaml") rid, serr := strconv.Atoi(idStr) if serr != nil || rid == bID { return nil } var room Room if yerr := yaml.Unmarshal(data, &room); yerr != nil { return nil } if room.Exits == nil { return nil } for ed, exitDef := range room.Exits { if exitDef.Room != bID { continue } // Allowed: A→dir→B and C→oppositeDir→B. if rid == aID && ed == dir { continue } if rid == cID && ed == oppositeDir { continue } extra++ } return nil }) return extra }