package regionmedic import ( "fmt" "strconv" "strings" ) // floorMod returns the mathematical (floored) modulo, always in [0, m). func floorMod(a, m int) int { return ((a % m) + m) % m } // floorDiv returns the floored integer division (rounds toward -inf). func floorDiv(a, m int) int { q := a / m if (a%m != 0) && ((a < 0) != (m < 0)) { q-- } return q } // RegionCoord identifies a region file r...7rg in the 32×32-chunk grid. type RegionCoord struct { X int `json:"x"` Z int `json:"z"` } // FileName returns the on-disk region filename, e.g. "r.-2.0.7rg". func (rc RegionCoord) FileName() string { return fmt.Sprintf("r.%d.%d.7rg", rc.X, rc.Z) } // String returns the canonical region id, e.g. "r.-2.0". func (rc RegionCoord) String() string { return fmt.Sprintf("r.%d.%d", rc.X, rc.Z) } // RegionForChunk maps a global chunk coordinate to its region (floored /32). func RegionForChunk(cx, cz int) RegionCoord { return RegionCoord{X: floorDiv(cx, RegionSide), Z: floorDiv(cz, RegionSide)} } // ParseRegionFileName parses "r...7rg" -> RegionCoord. ok=false otherwise. // Handles negative coordinates, e.g. "r.-2.0.7rg" -> {X:-2, Z:0}. func ParseRegionFileName(name string) (RegionCoord, bool) { if !strings.HasPrefix(name, "r.") || !strings.HasSuffix(name, ".7rg") { return RegionCoord{}, false } mid := strings.TrimSuffix(strings.TrimPrefix(name, "r."), ".7rg") // mid is "." where X and Z may be negative. Split on the dot that // separates them: there is exactly one '.' between two integers. x, z, ok := splitTwoInts(mid) if !ok { return RegionCoord{}, false } return RegionCoord{X: x, Z: z}, true } // ParseErrorBackup parses 7DTD chunk-salvage filenames written on a failed load: // // error_backup__.comp.bak // error_backup__.uncomp.bak // // returning the global chunk coordinates. ok=false if name is not such a file. func ParseErrorBackup(name string) (cx, cz int, ok bool) { const pfx = "error_backup_" if !strings.HasPrefix(name, pfx) { return 0, 0, false } rest := name[len(pfx):] // Strip the known suffixes. switch { case strings.HasSuffix(rest, ".comp.bak"): rest = strings.TrimSuffix(rest, ".comp.bak") case strings.HasSuffix(rest, ".uncomp.bak"): rest = strings.TrimSuffix(rest, ".uncomp.bak") default: return 0, 0, false } // rest is "_" with possibly-negative ints separated by '_'. i := strings.LastIndex(rest, "_") if i <= 0 || i >= len(rest)-1 { return 0, 0, false } a, err1 := strconv.Atoi(rest[:i]) b, err2 := strconv.Atoi(rest[i+1:]) if err1 != nil || err2 != nil { return 0, 0, false } return a, b, true } // splitTwoInts splits "." into two ints where each may be negative. // The separator is the first '.' that is not a leading minus sign position. func splitTwoInts(s string) (int, int, bool) { // Find a '.' that splits into two parseable ints. Scan candidate dots. for i := 1; i < len(s); i++ { if s[i] != '.' { continue } a, err1 := strconv.Atoi(s[:i]) b, err2 := strconv.Atoi(s[i+1:]) if err1 == nil && err2 == nil { return a, b, true } } return 0, 0, false }