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
+165 -4
View File
@@ -7,18 +7,179 @@ import (
)
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
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) {
var lDtatWrapped struct{ Data Level }
err = nbt.ReadFile(r, &lDtatWrapped)
var lDataWrapped struct{ Data Level }
err = nbt.ReadFile(r, &lDataWrapped)
if err != nil {
return
}
l = lDtatWrapped.Data
l = lDataWrapped.Data
return
}
+29
View File
@@ -3,6 +3,8 @@ package mapstructure
import (
"fmt"
"reflect"
"github.com/admin-else/strom/util"
)
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]
if !exists && f.trySnakeCase {
val, exists = m[util.SnakeCase(key)]
}
if !exists && f.tryLowCase {
val, exists = m[util.FirstLetterLower(key)]
}
if !exists {
if tag.Required || (f.requireAll && !tag.OmitEmpty) {
return fmt.Errorf("%w: %s", ErrMissingRequired, key)
}
continue
}
usedKeys[key] = true
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)
}
// 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)
}
+15
View File
@@ -18,6 +18,9 @@ type Format struct {
Name string
requireAll bool
errorOnExtra bool
trySnakeCase bool
tryLowCase bool
decoders 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 {
dt := reflect.TypeOf(decodeFn)
et := reflect.TypeOf(encodeFn)
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"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) {
n, err := ReadUnstructuredFile(file)
-20
View File
@@ -4,28 +4,8 @@ import (
"cmp"
"maps"
"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 {
keysIter := maps.Keys(m)
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) {
tests := []struct {
@@ -17,7 +21,7 @@ func TestCamelCase(t *testing.T) {
}
for _, test := range tests {
result := CamelCase(test.input)
result := util.CamelCase(test.input)
if result != test.expected {
t.Errorf("CamelCase(%q) = %q; expected %q", test.input, result, test.expected)
}