mojang cant get their cases straight

i had to implement like 4 cases just  to parse that file
This commit is contained in:
adman
2026-04-21 09:38:14 +02:00
parent d87c362270
commit f6001c8b22
7 changed files with 265 additions and 33 deletions
+166 -5
View File
@@ -7,18 +7,179 @@ import (
) )
type Level struct { type Level struct {
DataPacks struct { ClearWeatherTime int32 `nbt:"clearWeatherTime"`
DataPacks DataPacks
DayTime int64
WanderingTraderSpawnChance int32
GameType int32
WorldGenSettings WorldGenSettings
ScheduledEvents []interface{}
WasModded bool
ThunderTime int32 `nbt:"thunderTime"`
Difficulty int8
DragonFight DragonFight
Initialized bool
AllowCommands bool `nbt:"allowCommands"`
Thundering bool
LastPlayed int64
GameRules map[string]interface{}
RainTime int32 `nbt:"rainTime"`
DataVersion int32
Version Version
WanderingTraderSpawnDelay int32
Hardcore bool
Spawn Spawn
Raining bool
Player Player
DifficultyLocked bool
LevelName string
Time int64
ServerBrands []string
CustomBossEvents map[string]interface{}
}
type DataPacks struct {
Enabled []string Enabled []string
Disabled []string Disabled []string
} }
type WorldGenSettings struct {
BonusChest bool
Seed int64
GenerateFeatures bool
Dimensions map[string]Dimension
}
type Dimensions struct {
Overworld Dimension
Nether Dimension
End Dimension
}
type Dimension struct {
Generator Generator
Type string
}
type Generator struct {
Type string
Settings string
BiomeSource BiomeSource
}
type BiomeSource struct {
Preset string `nbt:"preset,omitempty"`
Type string
}
type DragonFight struct {
NeedsStateScanning bool
Gateways []int32
DragonKilled bool
PreviouslyKilled bool
}
type Version struct {
Snapshot bool
Series string
Id int32
Name string
}
type Spawn struct {
Dimension string
Yaw float32
Pos [3]int32
Pitch float32
}
type Player struct {
XpLevel int32
HurtTime int16
DeathTime int16
XpTotal int32
FoodExhaustionLevel float32
RecipeBook RecipeBook
Fire int16
Brain Brain
XpSeed int32
XpP float32
EnderItems []interface{}
Invulnerable bool
Pos [3]float64
Inventory []InventoryItem
SleepTimer int16
Abilities Abilities
PlayerGameType int32
SeenCredits bool
FoodSaturationLevel float32
FallDistance float64
OnGround bool
Dimension string
PortalCooldown int32
Motion [3]float64
IgnoreFallDamageFromCurrentExplosion bool
Rotation [2]float32
CurrentImpulseContextResetGraceTime int32
WardenSpawnTracker WardenSpawnTracker
PreviousPlayerGameType int32
Attributes []Attribute
HurtByTimestamp int32
UUID [4]int32
Score int32
DataVersion int32
FoodLevel int32
SpawnExtraParticlesOnFall bool
SelectedItemSlot int32
FoodTickTimer int32
FallFlying bool
AbsorptionAmount float32
Health float32
Air int16
}
type RecipeBook struct {
Recipes []string
ToBeDisplayed []string
}
type Brain struct {
Memories map[string]interface{}
}
type InventoryItem struct {
Count int32
Slot int8
Id string
}
type Abilities struct {
Invulnerable bool
Mayfly bool
Instabuild bool
WalkSpeed float32
MayBuild bool
Flying bool
FlySpeed float32
}
type WardenSpawnTracker struct {
WarningLevel int32
TicksSinceLastWarning int32
CooldownTicks int32
}
type Attribute struct {
Id string
Base float64
} }
func ReadLevelData(r io.Reader) (l Level, err error) { func ReadLevelData(r io.Reader) (l Level, err error) {
var lDtatWrapped struct{ Data Level } var lDataWrapped struct{ Data Level }
err = nbt.ReadFile(r, &lDtatWrapped) err = nbt.ReadFile(r, &lDataWrapped)
if err != nil { if err != nil {
return return
} }
l = lDtatWrapped.Data l = lDataWrapped.Data
return return
} }
+29
View File
@@ -3,6 +3,8 @@ package mapstructure
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/admin-else/strom/util"
) )
func (f *Format) decode(m map[string]any, target reflect.Value) error { func (f *Format) decode(m map[string]any, target reflect.Value) error {
@@ -26,12 +28,19 @@ func (f *Format) decode(m map[string]any, target reflect.Value) error {
} }
val, exists := m[key] val, exists := m[key]
if !exists && f.trySnakeCase {
val, exists = m[util.SnakeCase(key)]
}
if !exists && f.tryLowCase {
val, exists = m[util.FirstLetterLower(key)]
}
if !exists { if !exists {
if tag.Required || (f.requireAll && !tag.OmitEmpty) { if tag.Required || (f.requireAll && !tag.OmitEmpty) {
return fmt.Errorf("%w: %s", ErrMissingRequired, key) return fmt.Errorf("%w: %s", ErrMissingRequired, key)
} }
continue continue
} }
usedKeys[key] = true usedKeys[key] = true
fieldVal := target.Field(i) fieldVal := target.Field(i)
@@ -108,6 +117,26 @@ func (f *Format) setField(target reflect.Value, value any, opts tagOptions) erro
return f.setBool(target, value) return f.setBool(target, value)
} }
// this fixes this edge case
// level_test.go:19: field Data: field Player: field Pos: cannot set [3]float64 from []interface {}
if targetType.Kind() == reflect.Array && src.Kind() == reflect.Slice {
if target.Len() != src.Len() {
return fmt.Errorf("cannot set %s from %T because lengths do not match", targetType, value)
}
for i := 0; i < src.Len(); i++ {
if src.Index(i).Type().AssignableTo(targetType.Elem()) {
target.Index(i).Set(src.Index(i))
continue
}
if src.Index(i).Elem().Type().AssignableTo(targetType.Elem()) {
target.Index(i).Set(src.Index(i).Elem())
continue
}
return fmt.Errorf("cannot set %s from %T because element %d cannot be converted to %s", targetType, value, i, targetType.Elem())
}
return nil
}
return fmt.Errorf("cannot set %s from %T", targetType, value) return fmt.Errorf("cannot set %s from %T", targetType, value)
} }
+15
View File
@@ -18,6 +18,9 @@ type Format struct {
Name string Name string
requireAll bool requireAll bool
errorOnExtra bool errorOnExtra bool
trySnakeCase bool
tryLowCase bool
decoders map[reflect.Type]func(any, string) (any, error) decoders map[reflect.Type]func(any, string) (any, error)
encoders map[reflect.Type]func(any, string) (any, error) encoders map[reflect.Type]func(any, string) (any, error)
} }
@@ -48,6 +51,18 @@ func WithErrorOnExtra() Option {
} }
} }
func WithTrySnakeCase() Option {
return func(f *Format) {
f.trySnakeCase = true
}
}
func WithTryLowCase() Option {
return func(f *Format) {
f.tryLowCase = true
}
}
func WithTypeCodec(decodeFn, encodeFn any) Option { func WithTypeCodec(decodeFn, encodeFn any) Option {
dt := reflect.TypeOf(decodeFn) dt := reflect.TypeOf(decodeFn)
et := reflect.TypeOf(encodeFn) et := reflect.TypeOf(encodeFn)
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/admin-else/strom/mapstructure" "github.com/admin-else/strom/mapstructure"
) )
var Format = mapstructure.NewFormat("nbt", mapstructure.WithRequireAll()) var Format = mapstructure.NewFormat("nbt", mapstructure.WithRequireAll(), mapstructure.WithTrySnakeCase(), mapstructure.WithTryLowCase())
func ReadFile(file io.Reader, data any) (err error) { func ReadFile(file io.Reader, data any) (err error) {
n, err := ReadUnstructuredFile(file) n, err := ReadUnstructuredFile(file)
-20
View File
@@ -4,28 +4,8 @@ import (
"cmp" "cmp"
"maps" "maps"
"slices" "slices"
"unicode"
) )
var boundaries = []rune{' ', '_', '-', '.', ',', ';', ':', '(', ')', '[', ']', '{', '}'}
func CamelCase(s string) string {
ret := ""
atWordBoundary := true
for _, c := range s {
if slices.Contains(boundaries, c) {
atWordBoundary = true
continue
}
if atWordBoundary && unicode.IsLower(c) {
c = unicode.ToUpper(c)
}
atWordBoundary = false
ret += string(c)
}
return ret
}
func OrderedKeys[Map map[K]V, K cmp.Ordered, V any](m Map) []K { func OrderedKeys[Map map[K]V, K cmp.Ordered, V any](m Map) []K {
keysIter := maps.Keys(m) keysIter := maps.Keys(m)
keys := slices.Collect(keysIter) keys := slices.Collect(keysIter)
+43
View File
@@ -0,0 +1,43 @@
package util
import (
"slices"
"unicode"
)
var boundaries = []rune{' ', '_', '-', '.', ',', ';', ':', '(', ')', '[', ']', '{', '}'}
func CamelCase(s string) string {
ret := ""
atWordBoundary := true
for _, c := range s {
if slices.Contains(boundaries, c) {
atWordBoundary = true
continue
}
if atWordBoundary && unicode.IsLower(c) {
c = unicode.ToUpper(c)
}
atWordBoundary = false
ret += string(c)
}
return ret
}
func SnakeCase(s string) string {
ret := ""
for i, c := range s {
if unicode.IsUpper(c) {
if i > 0 {
ret += "_"
}
c = unicode.ToLower(c)
}
ret += string(c)
}
return ret
}
func FirstLetterLower(s string) string {
return string(unicode.ToLower(rune(s[0]))) + s[1:]
}
@@ -1,6 +1,10 @@
package main package util_test
import "testing" import (
"testing"
"github.com/admin-else/strom/util"
)
func TestCamelCase(t *testing.T) { func TestCamelCase(t *testing.T) {
tests := []struct { tests := []struct {
@@ -17,7 +21,7 @@ func TestCamelCase(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
result := CamelCase(test.input) result := util.CamelCase(test.input)
if result != test.expected { if result != test.expected {
t.Errorf("CamelCase(%q) = %q; expected %q", test.input, result, test.expected) t.Errorf("CamelCase(%q) = %q; expected %q", test.input, result, test.expected)
} }