panel — open-source game server manager (public release)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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.<X>.<Z>.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.<X>.<Z>.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 "<X>.<Z>" 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_<cx>_<cz>.comp.bak
|
||||
// error_backup_<cx>_<cz>.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 "<cx>_<cz>" 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 "<a>.<b>" 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
|
||||
}
|
||||
Reference in New Issue
Block a user