diff --git a/data/blocks.go b/data/blocks.go index 95b6c24..f477cef 100644 --- a/data/blocks.go +++ b/data/blocks.go @@ -1,6 +1,10 @@ package data -// { +import ( + "errors" +) + +// { // "id": 0, // "name": "air", // "displayName": "Air", @@ -35,29 +39,32 @@ type Block struct { DefaultState int32 MinStateId int32 MaxStateId int32 - States []struct { - Name string - Type string - NumValues int - } - Drops []int - BoundingBox string + States []BlockState + Drops []int + BoundingBox string } -var BlocksCache = make(map[string][]Block) +type BlockState struct { + Name string + Type string + NumValues int32 `json:"num_values"` + Values []string +} -func BlocksForVersion(v string) (ret []Block) { +var BlocksCache = make(map[string][]*Block) + +func BlocksForVersion(v string) (ret []*Block) { var ok bool if ret, ok = BlocksCache[v]; ok { return } - var b []Block + var b []*Block must(LoadVersionedJson(v, "blocks", &b)) BlocksCache[v] = b return b } -func LookupBlockByStateId(version string, stateId int32) (block Block, ok bool) { +func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool) { for _, b := range BlocksForVersion(version) { if b.MinStateId <= stateId && stateId <= b.MaxStateId { block = b @@ -67,7 +74,7 @@ func LookupBlockByStateId(version string, stateId int32) (block Block, ok bool) return } -func LookupBlockByName(version string, stateId int32) (block Block, ok bool) { +func LookupBlockByName(version string, stateId int32) (block *Block, ok bool) { for _, b := range BlocksForVersion(version) { if b.MinStateId <= stateId && stateId <= b.MaxStateId { block = b @@ -76,3 +83,103 @@ func LookupBlockByName(version string, stateId int32) (block Block, ok bool) { } return } + +var BlockStateOutOfRangeErr = errors.New("stateId out of range") +var BlockNotFoundErr = errors.New("block not found") +var InvalidBoolValueErr = errors.New("invalid bool value") +var BadBlockStateTypeErr = errors.New("bad block state type") +var BadEnumErr = errors.New("bad enum") +var SupplyAllStatesErr = errors.New("supply all states") + +func FromBlockState(version string, stateId int32) (b *Block, stateData map[string]any, err error) { + b, ok := LookupBlockByStateId(version, stateId) + if !ok { + err = BlockNotFoundErr + } + stateData, err = b.StateMapFromId(stateId) + return +} + +func mcDataBlockStateTypeParse(n int32, s *BlockState) (v any, err error) { + switch s.Type { + case "int": + v = n + case "enum": + v = s.Values[n] + case "bool": + if n == 0 { // MOJANK: 0 is true, 1 is false + v = true + } else if n == 1 { + v = false + } else { + err = InvalidBoolValueErr + } + default: + err = BadBlockStateTypeErr + } + return +} + +func mcDataBlockStateTypeSerialize(v any, s *BlockState) (n int32, err error) { + switch t := v.(type) { + case int32: + n = t + case bool: + if t { + n = 0 + } else { + n = 1 + } + case string: + for i, enumValue := range s.Values { + if enumValue == t { + return int32(i), nil + } + } + err = BadEnumErr + default: + err = BadBlockStateTypeErr + } + return +} + +func (b *Block) StateMapFromId(stateId int32) (ret map[string]any, err error) { + if stateId < b.MinStateId || stateId > b.MaxStateId { + err = BlockStateOutOfRangeErr + return + } + ret = make(map[string]any) + stateId -= b.MinStateId + + a := int32(1) + for i := range len(b.States) { + s := b.States[len(b.States)-1-i] + ret[s.Name], err = mcDataBlockStateTypeParse((stateId/a)%s.NumValues, &s) + if err != nil { + return + } + a *= s.NumValues + } + return +} + +func (b *Block) IdFromStateMap(m map[string]any) (ret int32, err error) { + a := int32(1) + for i := range len(b.States) { + s := b.States[len(b.States)-1-i] + v, found := m[s.Name] + if !found { + err = SupplyAllStatesErr + return + } + var n int32 + n, err = mcDataBlockStateTypeSerialize(v, &s) + if err != nil { + return + } + ret += n * a + a *= s.NumValues + } + ret += b.MinStateId + return +} diff --git a/data/blocks_test.go b/data/blocks_test.go index 9f662e7..726490f 100644 --- a/data/blocks_test.go +++ b/data/blocks_test.go @@ -14,3 +14,21 @@ func TestLookupBlockByStateId(t *testing.T) { } fmt.Println(b) } + +func TestBlockStates(t *testing.T) { + var stateId int32 = 8301 + b, stateMap, err := data.FromBlockState("1.21.8", stateId) + if err != nil { + t.Fatal(err) + return + } + t.Logf("Block state map: %v, block: %v", stateMap, b.Name) + stateId2, err := b.IdFromStateMap(stateMap) + if err != nil { + t.Fatal(err) + return + } + if stateId2 != stateId { + t.Fatal("state id mismatch") + } +} diff --git a/server/login_server.go b/server/login_server.go index d95f2d5..18a7825 100644 --- a/server/login_server.go +++ b/server/login_server.go @@ -15,7 +15,7 @@ import ( "github.com/google/uuid" ) -var StatusServedError = errors.New("status served") +var StatusServedErr = errors.New("status served") var UnexpectedStatusRequest = UnexpectedNextStateError{proto_base.Status} @@ -59,7 +59,7 @@ func (l *LoginServer) OnHandshake(packet *v1_21_8.HandshakingToServerPacketSetPr } if len(l.CompatibleVersions) != 0 && !slices.Contains(l.CompatibleVersions, packet.ProtocolVersion) { - err = fmt.Errorf("incompatible protocol version please use one of %v you can look up the real coresponding version at https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol_version_numbers", l.CompatibleVersions) + err = fmt.Errorf("incompatible protocol version please use one of %v you can look up the real corresponding version at https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol_version_numbers", l.CompatibleVersions) return } @@ -76,7 +76,7 @@ func (l *LoginServer) OnStatusPing(packet *v1_21_8.StatusToServerPacketPing) (er if err != nil { return } - return event.HandlerDoneErr{Return: StatusServedError} + return event.HandlerDoneErr{Return: StatusServedErr} } func (l *LoginServer) SetCompressionThreshold(threshold int32) (err error) { diff --git a/server/server.go b/server/server.go index 229e022..85ede09 100644 --- a/server/server.go +++ b/server/server.go @@ -1,6 +1,7 @@ package server import ( + "errors" "log/slog" "net" @@ -21,13 +22,24 @@ func Servee(c net.Conn) (ret *proto.Conn) { return } -func ServeClient(cNet net.Conn, factory func(c *proto.Conn) (h any, err error)) (err error) { +func ServeClient(cNet net.Conn, factory func(c *proto.Conn) (h any, err error)) { c := Servee(cNet) + defer c.Close() h, err := factory(c) if err != nil { return } err = c.Start(h) + if errors.Is(err, StatusServedErr) { + return + } + if err != nil { + slog.Error("Error while handling client", "error", err, "client", c.Conn.RemoteAddr()) + err = Kick(c, text.Pretty(err.Error())) + if err != nil { + slog.Error("Error while kicking client", "error", err, "client", c.Conn.RemoteAddr()) + } + } return } @@ -42,12 +54,7 @@ func StartServerWithFactory(listenAddr string, factory func(c *proto.Conn) (h an if err != nil { return } - go func() { - err := ServeClient(cNet, factory) - if err != nil { - slog.Error("error while serving client", "error", err, "client", cNet.RemoteAddr()) - } - }() + go ServeClient(cNet, factory) } } diff --git a/tools/schematic_server/main.go b/tools/schematic_server/main.go index e0a505a..ca98ab1 100644 --- a/tools/schematic_server/main.go +++ b/tools/schematic_server/main.go @@ -6,6 +6,7 @@ import ( "time" "github.com/admin-else/strom/client" + "github.com/admin-else/strom/data" "github.com/admin-else/strom/event" "github.com/admin-else/strom/proto" "github.com/admin-else/strom/proto_generated/v1_21_8" @@ -67,10 +68,11 @@ var voidChunk = &v1_21_8.PlayToClientPacketMapChunk{X: 3, Z: 3, Heightmaps: []st func (s *Server) OnStart(_ event.OnStart) (err error) { //goland:noinspection GoResourceLeak captures the conn object which should NOT be closed - _, err = server.ServeLogin(s.Conn, server.WithRawStatus(StatusResponse)) + playerData, err := server.ServeLogin(s.Conn, server.WithRawStatus(StatusResponse), server.WithCompatibleVersions(772)) if err != nil { return } + slog.Info("player logged in", "name", playerData.Requested.Name) err = server.ServeConfig(s.Conn) if err != nil { return @@ -124,7 +126,7 @@ func (s *Server) OnStart(_ event.OnStart) (err error) { return } s.Timer.Every(time.Second*15, s.SendKeepAlive) - s.Timer.Every(time.Second, s.CycleBlockForTesting) + s.Timer.Every(time.Second*20, s.CycleBlockForTesting) s.Timer.Active = true return } @@ -135,6 +137,12 @@ func (s *Server) OnTick(_ event.Tick) (err error) { } func (s *Server) CycleBlockForTesting() (err error) { + _, stateData, err := data.FromBlockState(s.Version, s.Current) + if err != nil { + return + } + slog.Info("cycle block", "state", stateData, "cycle", s.Current) + err = s.Send(&v1_21_8.PlayToClientPacketBlockChange{ Location: v1_21_8.Position{ X: 1, @@ -159,9 +167,9 @@ func (s *Server) SendKeepAlive() (err error) { } func main() { - slog.SetLogLoggerLevel(slog.LevelDebug) + slog.SetLogLoggerLevel(slog.LevelInfo) err := server.StartServerWithFactory(":25565", func(c *proto.Conn) (h any, err error) { - s := &Server{Conn: c, MinCycle: 2938, MaxCycle: 3017, Current: 2938} + s := &Server{Conn: c, MinCycle: 8297, MaxCycle: 8304, Current: 8297} s.Timer = &client.Timer{} h = s return