diff --git a/level/anvil/level.go b/level/anvil/level.go index 833109b..6bc84a7 100644 --- a/level/anvil/level.go +++ b/level/anvil/level.go @@ -7,18 +7,179 @@ import ( ) type Level struct { - DataPacks struct { - Enabled []string - Disabled []string - } + 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 } diff --git a/mapstructure/decode.go b/mapstructure/decode.go index 9c9688b..c627c3e 100644 --- a/mapstructure/decode.go +++ b/mapstructure/decode.go @@ -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) } diff --git a/mapstructure/format.go b/mapstructure/format.go index c5af16b..dd5881a 100644 --- a/mapstructure/format.go +++ b/mapstructure/format.go @@ -18,8 +18,11 @@ type Format struct { Name string requireAll bool errorOnExtra bool - decoders map[reflect.Type]func(any, string) (any, error) - encoders map[reflect.Type]func(any, string) (any, error) + trySnakeCase bool + tryLowCase bool + + decoders map[reflect.Type]func(any, string) (any, error) + encoders map[reflect.Type]func(any, string) (any, error) } func NewFormat(name string, opts ...Option) *Format { @@ -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) diff --git a/nbt/file.go b/nbt/file.go index ee49d38..d1b054f 100644 --- a/nbt/file.go +++ b/nbt/file.go @@ -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) diff --git a/proto_generator/utils.go b/proto_generator/utils.go index 618cc30..310d275 100644 --- a/proto_generator/utils.go +++ b/proto_generator/utils.go @@ -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) diff --git a/util/case.go b/util/case.go new file mode 100644 index 0000000..1c460b9 --- /dev/null +++ b/util/case.go @@ -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:] +} diff --git a/proto_generator/utils_test.go b/util/case_test.go similarity index 80% rename from proto_generator/utils_test.go rename to util/case_test.go index de0321e..f031a52 100644 --- a/proto_generator/utils_test.go +++ b/util/case_test.go @@ -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) }