panel v0.9.1 — open-source game server manager
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestInitConsistency: every option resolves an enum + a default index, and no
|
||||
// two options share an enum (the 2-char code must be unique per option).
|
||||
func TestInitConsistency(t *testing.T) {
|
||||
if len(options) != 150 {
|
||||
t.Fatalf("expected 150 sandbox options, got %d", len(options))
|
||||
}
|
||||
seen := map[int]string{}
|
||||
for i := range options {
|
||||
o := &options[i]
|
||||
if o.enum == 0 && o.Name != "RangedDamage" {
|
||||
t.Errorf("%s: enum unresolved (0)", o.Name)
|
||||
}
|
||||
if prev, ok := seen[o.enum]; ok {
|
||||
t.Errorf("enum %d shared by %s and %s", o.enum, prev, o.Name)
|
||||
}
|
||||
seen[o.enum] = o.Name
|
||||
if _, ok := defaultIdx[o.Name]; !ok {
|
||||
t.Errorf("%s: no default index", o.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGameDefaultCode decodes the game's own default difficulty (Adventurer)
|
||||
// code from serverconfig.xml and checks the documented meaning, then re-encodes
|
||||
// and confirms it round-trips byte-for-byte.
|
||||
func TestGameDefaultCode(t *testing.T) {
|
||||
const code = "AAAJABJACJADJARFBNC" // shipped in 3.0 serverconfig.xml comment
|
||||
c := V3Codec{}
|
||||
vals, err := c.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
// Triples: AA J / AB J / AC J / AD J / AR F / BN C
|
||||
// AA(0)=RangedDamage idx J=9 -> DamageValues[9]=1.5
|
||||
// AB(1)=MeleeDamage idx 9 -> 1.5
|
||||
// AC(2)=BlockDamage idx 9 -> 1.5
|
||||
// AD(3)=TerrainDamage idx 9 -> 1.5
|
||||
// AR(17)=IncomingDamage idx F=5 -> DamageValues[5]=0.75
|
||||
// BN(39)=AISmellMode idx C=2 -> AISmellMode set Ints[2]=2
|
||||
checks := map[string]string{
|
||||
"RangedDamage": "1.5",
|
||||
"MeleeDamage": "1.5",
|
||||
"BlockDamage": "1.5",
|
||||
"TerrainDamage": "1.5",
|
||||
"IncomingDamage": "0.75",
|
||||
"AISmellMode": "2",
|
||||
}
|
||||
for name, want := range checks {
|
||||
if got := vals[name]; got != want {
|
||||
t.Errorf("%s = %q, want %q", name, got, want)
|
||||
}
|
||||
}
|
||||
// Re-encode: only the 6 non-default options should appear, in enum order →
|
||||
// must reproduce the original code exactly.
|
||||
round, err := c.Encode(vals)
|
||||
if err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
if round != code {
|
||||
t.Errorf("round-trip mismatch:\n have %q\n want %q", round, code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEmptyAndDefaultsEncodeEmpty: the all-defaults map encodes to just the
|
||||
// version char (no triples), and an empty code decodes to all defaults.
|
||||
func TestEmptyAndDefaultsEncodeEmpty(t *testing.T) {
|
||||
c := V3Codec{}
|
||||
def := DefaultsMap()
|
||||
code, err := c.Encode(def)
|
||||
if err != nil {
|
||||
t.Fatalf("encode defaults: %v", err)
|
||||
}
|
||||
if code != "A" {
|
||||
t.Errorf("defaults should encode to bare version char %q, got %q", "A", code)
|
||||
}
|
||||
vals, err := c.Decode("")
|
||||
if err != nil {
|
||||
t.Fatalf("decode empty: %v", err)
|
||||
}
|
||||
if len(vals) != 150 {
|
||||
t.Errorf("empty decode should yield 150 defaults, got %d", len(vals))
|
||||
}
|
||||
for name, v := range vals {
|
||||
if def[name] != v {
|
||||
t.Errorf("%s: empty-decode %q != defaults %q", name, v, def[name])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRoundTripNonDefault sets a spread of options across categories/types and
|
||||
// confirms encode→decode preserves them.
|
||||
func TestRoundTripNonDefault(t *testing.T) {
|
||||
c := V3Codec{}
|
||||
in := DefaultsMap()
|
||||
in["BloodMoonFrequency"] = "14" // int set, non-default
|
||||
in["XPMultiplier"] = "2" // float set
|
||||
in["AllowZombieDigging"] = "false"
|
||||
in["TraderResetInterval"] = "7"
|
||||
in["SillyBigHeads"] = "true"
|
||||
code, err := c.Encode(in)
|
||||
if err != nil {
|
||||
t.Fatalf("encode: %v", err)
|
||||
}
|
||||
out, err := c.Decode(code)
|
||||
if err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
for _, k := range []string{"BloodMoonFrequency", "XPMultiplier", "AllowZombieDigging", "TraderResetInterval", "SillyBigHeads"} {
|
||||
if out[k] != in[k] {
|
||||
t.Errorf("%s: round-trip %q != %q", k, out[k], in[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAlphaHelpers spot-checks the index<->alpha conversions against the C#.
|
||||
func TestAlphaHelpers(t *testing.T) {
|
||||
if v, _ := alpha2ToIndex("BN"); v != 39 {
|
||||
t.Errorf("BN -> %d, want 39", v)
|
||||
}
|
||||
if v, _ := alpha2ToIndex("AA"); v != 0 {
|
||||
t.Errorf("AA -> %d, want 0", v)
|
||||
}
|
||||
if s, _ := indexToAlpha2(39); s != "BN" {
|
||||
t.Errorf("39 -> %q, want BN", s)
|
||||
}
|
||||
if v, _ := alphaToIndex('J'); v != 9 {
|
||||
t.Errorf("J -> %d, want 9", v)
|
||||
}
|
||||
if b, _ := indexToAlpha(9); b != 'J' {
|
||||
t.Errorf("9 -> %c, want J", b)
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidate rejects malformed codes and accepts good ones.
|
||||
func TestValidate(t *testing.T) {
|
||||
if err := Validate(""); err != nil {
|
||||
t.Errorf("empty should be valid: %v", err)
|
||||
}
|
||||
if err := Validate("AAAJABJACJADJARFBNC"); err != nil {
|
||||
t.Errorf("game default should be valid: %v", err)
|
||||
}
|
||||
if err := Validate("ZZZ"); err == nil {
|
||||
t.Error("wrong version char should fail")
|
||||
}
|
||||
if err := Validate("AAB"); err == nil {
|
||||
t.Error("non-multiple-of-3 body should fail")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user