4ccccc6fe2
Self-hostable game server control panel: Go controller + agent, 26 game modules, embedded web UI. One-line install via install.sh / install.ps1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
104 lines
3.1 KiB
Go
104 lines
3.1 KiB
Go
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
|
||
}
|