block state

This commit is contained in:
adman
2026-03-06 21:24:08 +01:00
parent 5dfd8fc499
commit 73d4e07b06
5 changed files with 167 additions and 27 deletions
+117 -10
View File
@@ -1,5 +1,9 @@
package data package data
import (
"errors"
)
// { // {
// "id": 0, // "id": 0,
// "name": "air", // "name": "air",
@@ -35,29 +39,32 @@ type Block struct {
DefaultState int32 DefaultState int32
MinStateId int32 MinStateId int32
MaxStateId int32 MaxStateId int32
States []struct { States []BlockState
Name string
Type string
NumValues int
}
Drops []int Drops []int
BoundingBox string 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 var ok bool
if ret, ok = BlocksCache[v]; ok { if ret, ok = BlocksCache[v]; ok {
return return
} }
var b []Block var b []*Block
must(LoadVersionedJson(v, "blocks", &b)) must(LoadVersionedJson(v, "blocks", &b))
BlocksCache[v] = b BlocksCache[v] = b
return 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) { for _, b := range BlocksForVersion(version) {
if b.MinStateId <= stateId && stateId <= b.MaxStateId { if b.MinStateId <= stateId && stateId <= b.MaxStateId {
block = b block = b
@@ -67,7 +74,7 @@ func LookupBlockByStateId(version string, stateId int32) (block Block, ok bool)
return 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) { for _, b := range BlocksForVersion(version) {
if b.MinStateId <= stateId && stateId <= b.MaxStateId { if b.MinStateId <= stateId && stateId <= b.MaxStateId {
block = b block = b
@@ -76,3 +83,103 @@ func LookupBlockByName(version string, stateId int32) (block Block, ok bool) {
} }
return 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
}
+18
View File
@@ -14,3 +14,21 @@ func TestLookupBlockByStateId(t *testing.T) {
} }
fmt.Println(b) 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")
}
}
+3 -3
View File
@@ -15,7 +15,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
var StatusServedError = errors.New("status served") var StatusServedErr = errors.New("status served")
var UnexpectedStatusRequest = UnexpectedNextStateError{proto_base.Status} 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) { 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 return
} }
@@ -76,7 +76,7 @@ func (l *LoginServer) OnStatusPing(packet *v1_21_8.StatusToServerPacketPing) (er
if err != nil { if err != nil {
return return
} }
return event.HandlerDoneErr{Return: StatusServedError} return event.HandlerDoneErr{Return: StatusServedErr}
} }
func (l *LoginServer) SetCompressionThreshold(threshold int32) (err error) { func (l *LoginServer) SetCompressionThreshold(threshold int32) (err error) {
+14 -7
View File
@@ -1,6 +1,7 @@
package server package server
import ( import (
"errors"
"log/slog" "log/slog"
"net" "net"
@@ -21,13 +22,24 @@ func Servee(c net.Conn) (ret *proto.Conn) {
return 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) c := Servee(cNet)
defer c.Close()
h, err := factory(c) h, err := factory(c)
if err != nil { if err != nil {
return return
} }
err = c.Start(h) 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 return
} }
@@ -42,12 +54,7 @@ func StartServerWithFactory(listenAddr string, factory func(c *proto.Conn) (h an
if err != nil { if err != nil {
return return
} }
go func() { go ServeClient(cNet, factory)
err := ServeClient(cNet, factory)
if err != nil {
slog.Error("error while serving client", "error", err, "client", cNet.RemoteAddr())
}
}()
} }
} }
+12 -4
View File
@@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/admin-else/strom/client" "github.com/admin-else/strom/client"
"github.com/admin-else/strom/data"
"github.com/admin-else/strom/event" "github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto" "github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_generated/v1_21_8" "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) { func (s *Server) OnStart(_ event.OnStart) (err error) {
//goland:noinspection GoResourceLeak captures the conn object which should NOT be closed //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 { if err != nil {
return return
} }
slog.Info("player logged in", "name", playerData.Requested.Name)
err = server.ServeConfig(s.Conn) err = server.ServeConfig(s.Conn)
if err != nil { if err != nil {
return return
@@ -124,7 +126,7 @@ func (s *Server) OnStart(_ event.OnStart) (err error) {
return return
} }
s.Timer.Every(time.Second*15, s.SendKeepAlive) 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 s.Timer.Active = true
return return
} }
@@ -135,6 +137,12 @@ func (s *Server) OnTick(_ event.Tick) (err error) {
} }
func (s *Server) CycleBlockForTesting() (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{ err = s.Send(&v1_21_8.PlayToClientPacketBlockChange{
Location: v1_21_8.Position{ Location: v1_21_8.Position{
X: 1, X: 1,
@@ -159,9 +167,9 @@ func (s *Server) SendKeepAlive() (err error) {
} }
func main() { func main() {
slog.SetLogLoggerLevel(slog.LevelDebug) slog.SetLogLoggerLevel(slog.LevelInfo)
err := server.StartServerWithFactory(":25565", func(c *proto.Conn) (h any, err error) { 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{} s.Timer = &client.Timer{}
h = s h = s
return return