Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acb5fe8d90 | ||
|
|
d9bca7e517 | ||
|
|
b162d0d401 | ||
|
|
3273244c42 | ||
|
|
f579645b88 | ||
|
|
33754d7c71 | ||
|
|
8bd54a04f2 | ||
|
|
492dae8e83 | ||
|
|
d9543de951 | ||
|
|
f1f5374a5c | ||
|
|
0d6b6e1ab9 | ||
|
|
56c97a2b3f | ||
|
|
9063a0328d |
+4
-2
@@ -19,7 +19,8 @@ import (
|
|||||||
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
||||||
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
||||||
"github.com/admin-else/strom/cmd/strom/print_replay"
|
"github.com/admin-else/strom/cmd/strom/print_replay"
|
||||||
"github.com/admin-else/strom/cmd/strom/raw_capture"
|
// "github.com/admin-else/strom/cmd/strom/raw_capture" // removed: use tcpflow instead for raw TCP capture
|
||||||
|
"github.com/admin-else/strom/cmd/strom/raw_to_tmcpr"
|
||||||
"github.com/admin-else/strom/cmd/strom/registry_data_hunter"
|
"github.com/admin-else/strom/cmd/strom/registry_data_hunter"
|
||||||
"github.com/admin-else/strom/cmd/strom/serve_tmcpr"
|
"github.com/admin-else/strom/cmd/strom/serve_tmcpr"
|
||||||
"github.com/admin-else/strom/cmd/strom/serve_world"
|
"github.com/admin-else/strom/cmd/strom/serve_world"
|
||||||
@@ -61,7 +62,8 @@ var subcommands = map[string]func(args []string) error{
|
|||||||
"extract-mca": extract_mca.Run,
|
"extract-mca": extract_mca.Run,
|
||||||
"data": data.Run,
|
"data": data.Run,
|
||||||
"print-replay": print_replay.Run,
|
"print-replay": print_replay.Run,
|
||||||
"raw-capture": raw_capture.Run,
|
// "raw-capture": raw_capture.Run, // removed: use tcpflow instead for raw TCP capture, then raw-to-tmcpr
|
||||||
|
"raw-to-tmcpr": raw_to_tmcpr.Run,
|
||||||
"registry-data-hunter": registry_data_hunter.Run,
|
"registry-data-hunter": registry_data_hunter.Run,
|
||||||
"serve-world": serve_world.Run,
|
"serve-world": serve_world.Run,
|
||||||
"mca-to-tmcpr": mca_to_tmcpr.Run,
|
"mca-to-tmcpr": mca_to_tmcpr.Run,
|
||||||
|
|||||||
+47
-17
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/admin-else/strom/mc/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/mc/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_8"
|
||||||
)
|
)
|
||||||
|
|
||||||
func stror(a, b string) string {
|
func stror(a, b string) string {
|
||||||
@@ -29,12 +30,14 @@ var (
|
|||||||
cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
||||||
connectToFlag = cmd.String("addr", "", "server address to connect to")
|
connectToFlag = cmd.String("addr", "", "server address to connect to")
|
||||||
accFlag = cmd.String("acc", stror(os.Getenv("STROM_ACC"), "strom_messenger"), "the name to use if longer will be treated as ygg token")
|
accFlag = cmd.String("acc", stror(os.Getenv("STROM_ACC"), "strom_messenger"), "the name to use if longer will be treated as ygg token")
|
||||||
|
versionFlag = cmd.String("version", "26.2", "minecraft version to use (e.g. 1.8, 1.21.11)")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Messenger struct {
|
type Messenger struct {
|
||||||
*proto.Conn
|
*proto.Conn
|
||||||
chat *chat.Module
|
chat *chat.Module
|
||||||
world *world.Module
|
world *world.Module
|
||||||
|
legacy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Messenger) OnChat(e *v1_21_11.PlayToClientPacketPlayerChat) (err error) {
|
func (m *Messenger) OnChat(e *v1_21_11.PlayToClientPacketPlayerChat) (err error) {
|
||||||
@@ -52,15 +55,27 @@ func (m *Messenger) OnChatSystem(e *v1_21_11.PlayToClientPacketSystemChat) (err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) OnChatLegacy(e *v1_8.PlayToClientPacketChat) (err error) {
|
||||||
|
m.Log.Info("chat", "m", e.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Messenger) OnStdin(e event.Stdin) (err error) {
|
func (m *Messenger) OnStdin(e event.Stdin) (err error) {
|
||||||
input := strings.TrimSpace(e.Val)
|
input := strings.TrimSpace(e.Val)
|
||||||
if strings.HasPrefix(input, ">say ") {
|
if strings.HasPrefix(input, ">say ") {
|
||||||
return m.chat.SendMessage(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
|
return m.sendChat(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(input, ">getblock ") {
|
if !m.legacy && strings.HasPrefix(input, ">getblock ") {
|
||||||
return m.handleGetBlock(strings.TrimSpace(strings.TrimPrefix(input, ">getblock")))
|
return m.handleGetBlock(strings.TrimSpace(strings.TrimPrefix(input, ">getblock")))
|
||||||
}
|
}
|
||||||
return m.chat.SendMessage(input)
|
return m.sendChat(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Messenger) sendChat(message string) (err error) {
|
||||||
|
if m.legacy {
|
||||||
|
return m.Send(&v1_8.PlayToServerPacketChat{Message: message})
|
||||||
|
}
|
||||||
|
return m.chat.SendMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Messenger) handleGetBlock(args string) (err error) {
|
func (m *Messenger) handleGetBlock(args string) (err error) {
|
||||||
@@ -105,30 +120,45 @@ func Run(args []string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c, err := client.Login(*connectToFlag, acc)
|
c, err := client.Login(*connectToFlag, acc, client.WithVersion(*versionFlag))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
chatMod, err := chat.Start(c, acc)
|
isLegacy := c.ProtocolVersion < 764
|
||||||
if err != nil {
|
|
||||||
return
|
var chatMod *chat.Module
|
||||||
|
if !isLegacy {
|
||||||
|
chatMod, err = chat.Start(c, acc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shareable world storage. Other bots can subscribe to the same World.
|
|
||||||
w := world.NewWorld(c.Version, -64, 384)
|
|
||||||
|
|
||||||
m := &Messenger{
|
m := &Messenger{
|
||||||
Conn: c,
|
Conn: c,
|
||||||
chat: chatMod,
|
chat: chatMod,
|
||||||
world: world.Start(c, w),
|
legacy: isLegacy,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isLegacy {
|
||||||
|
w := world.NewWorld(c.Version, -64, 384)
|
||||||
|
m.world = world.Start(c, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
event.StartListingStdin(m.Loop)
|
event.StartListingStdin(m.Loop)
|
||||||
m.Register(m.OnStdin)
|
m.Register(m.OnStdin)
|
||||||
m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem)
|
|
||||||
keepalive.Start(m.Conn)
|
if isLegacy {
|
||||||
|
m.Register(m.OnChatLegacy)
|
||||||
|
} else {
|
||||||
|
m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isLegacy {
|
||||||
|
keepalive.Start(m.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
return m.StartConn()
|
return m.StartConn()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ var (
|
|||||||
FileFlag = cmd.String("file", "", "The tmcpr file to print")
|
FileFlag = cmd.String("file", "", "The tmcpr file to print")
|
||||||
ProtocolVersion = cmd.Int("protocol", -1, "The protocol version")
|
ProtocolVersion = cmd.Int("protocol", -1, "The protocol version")
|
||||||
CreateTestsPath = cmd.String("create-tests", "", "")
|
CreateTestsPath = cmd.String("create-tests", "", "")
|
||||||
StateFlag = cmd.String("state", "login", "Initial state: login, config, play")
|
StateFlag = cmd.String("state", "login", "Initial state: handshake, login, config, status, play")
|
||||||
DirectionFlag = cmd.String("direction", "clientbound", "Packet direction: clientbound (ToClient) or serverbound (ToServer)")
|
DirectionFlag = cmd.String("direction", "clientbound", "Packet direction: clientbound (ToClient) or serverbound (ToServer)")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,22 +106,6 @@ func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, i proto_base
|
|||||||
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
|
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch direction {
|
|
||||||
case proto_base.ToClient:
|
|
||||||
switch i.Name {
|
|
||||||
case "success":
|
|
||||||
state = proto_base.Configuration
|
|
||||||
case "finish_configuration":
|
|
||||||
state = proto_base.Play
|
|
||||||
}
|
|
||||||
case proto_base.ToServer:
|
|
||||||
switch i.Name {
|
|
||||||
case "login_acknowledged":
|
|
||||||
state = proto_base.Configuration
|
|
||||||
case "finish_configuration":
|
|
||||||
state = proto_base.Play
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packet = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
|
packet = reflect.New(reflect.TypeOf(i.Type).Elem()).Interface().(proto_base.EncodeDecodeAble)
|
||||||
err = packet.Decode(b)
|
err = packet.Decode(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -132,9 +116,40 @@ func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, i proto_base
|
|||||||
packet = &proto.UnCodablePacket{Err: proto.PacketNotFullyDecodedErr, Data: packetData[indexafterid:], Info: i}
|
packet = &proto.UnCodablePacket{Err: proto.PacketNotFullyDecodedErr, Data: packetData[indexafterid:], Info: i}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
trackState(packet, i)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func trackState(packet proto_base.EncodeDecodeAble, i proto_base.PacketInfo) {
|
||||||
|
switch i.Name {
|
||||||
|
case "set_protocol":
|
||||||
|
v := reflect.ValueOf(packet).Elem()
|
||||||
|
nsField := v.FieldByName("NextState")
|
||||||
|
if nsField.IsValid() && nsField.Kind() == reflect.Int32 {
|
||||||
|
switch nsField.Int() {
|
||||||
|
case 1:
|
||||||
|
state = proto_base.Status
|
||||||
|
case 2:
|
||||||
|
state = proto_base.Login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "login_start":
|
||||||
|
if int32(*ProtocolVersion) <= 47 {
|
||||||
|
state = proto_base.Play
|
||||||
|
}
|
||||||
|
case "success":
|
||||||
|
if int32(*ProtocolVersion) >= 764 {
|
||||||
|
state = proto_base.Configuration
|
||||||
|
} else {
|
||||||
|
state = proto_base.Play
|
||||||
|
}
|
||||||
|
case "finish_configuration":
|
||||||
|
state = proto_base.Play
|
||||||
|
case "login_acknowledged":
|
||||||
|
state = proto_base.Configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Run(args []string) (err error) {
|
func Run(args []string) (err error) {
|
||||||
err = cmd.Parse(args)
|
err = cmd.Parse(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -147,12 +162,16 @@ func Run(args []string) (err error) {
|
|||||||
return fmt.Errorf("no file specified")
|
return fmt.Errorf("no file specified")
|
||||||
}
|
}
|
||||||
switch *StateFlag {
|
switch *StateFlag {
|
||||||
|
case "handshake":
|
||||||
|
state = proto_base.Handshaking
|
||||||
case "login":
|
case "login":
|
||||||
state = proto_base.Login
|
state = proto_base.Login
|
||||||
case "config":
|
case "config":
|
||||||
state = proto_base.Configuration
|
state = proto_base.Configuration
|
||||||
case "play":
|
case "play":
|
||||||
state = proto_base.Play
|
state = proto_base.Play
|
||||||
|
case "status":
|
||||||
|
state = proto_base.Status
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown state: %s", *StateFlag)
|
return fmt.Errorf("unknown state: %s", *StateFlag)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,253 +0,0 @@
|
|||||||
package raw_capture
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/admin-else/strom/mc/api"
|
|
||||||
"github.com/admin-else/strom/mc/client"
|
|
||||||
"github.com/admin-else/strom/mc/data"
|
|
||||||
"github.com/admin-else/strom/mc/event"
|
|
||||||
"github.com/admin-else/strom/mc/proto"
|
|
||||||
"github.com/admin-else/strom/mc/proto/replay"
|
|
||||||
"github.com/admin-else/strom/mc/proto_base"
|
|
||||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
|
||||||
"github.com/admin-else/strom/mc/server"
|
|
||||||
"github.com/admin-else/strom/mc/text"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Cmd = flag.NewFlagSet("raw-capture", flag.ContinueOnError)
|
|
||||||
|
|
||||||
var OfflineNameFlag = Cmd.String("name", "raw_capture", "offline player name")
|
|
||||||
var Token = Cmd.String("token", "", "token to use")
|
|
||||||
|
|
||||||
var TargetAddr = Cmd.String("addr", "127.0.0.1:25565", "address to connect to")
|
|
||||||
var ListenAddr = Cmd.String("listen", "127.0.0.1:25566", "address to listen on")
|
|
||||||
var ClientboundFile = Cmd.String("clientbound-file", "clientbound.tmcpr", "file to write clientbound packets to")
|
|
||||||
var ServerboundFile = Cmd.String("serverbound-file", "serverbound.tmcpr", "file to write serverbound packets to")
|
|
||||||
var Version = Cmd.String("version", "", "client protocol version (e.g. 26.1); if empty, detect from server status")
|
|
||||||
|
|
||||||
var StatusResponse = server.StatusResponse{
|
|
||||||
Version: server.StatusResponseVersion{
|
|
||||||
Name: text.Pretty("STROM raw-capture"),
|
|
||||||
Protocol: 0,
|
|
||||||
},
|
|
||||||
Players: server.StatusResponsePlayers{},
|
|
||||||
Description: text.Pretty("STROM raw-capture proxy"),
|
|
||||||
Favicon: "",
|
|
||||||
EnforcesSecureChat: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Proxy struct {
|
|
||||||
Client, Servee *proto.Conn
|
|
||||||
|
|
||||||
clientboundFile *os.File
|
|
||||||
serverboundFile *os.File
|
|
||||||
|
|
||||||
clientboundWriter *replay.Writer
|
|
||||||
serverboundWriter *replay.Writer
|
|
||||||
|
|
||||||
startTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) Start() (err error) {
|
|
||||||
p.startTime = time.Now()
|
|
||||||
|
|
||||||
p.Client.RegisterCritical(p.OnAnything)
|
|
||||||
p.Servee.RegisterCritical(p.OnAnything)
|
|
||||||
|
|
||||||
p.Client.RegisterCriticalUntilLatest(p.OnCompress)
|
|
||||||
p.Servee.RegisterCriticalUntilLatest(p.OnHandshake)
|
|
||||||
p.Servee.RegisterCriticalUntilLatest(p.OnLoginAcknowledged)
|
|
||||||
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
|
|
||||||
|
|
||||||
errChan := make(chan error)
|
|
||||||
go func() {
|
|
||||||
errChan <- p.Client.StartConn()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
errChan <- p.Servee.StartConn()
|
|
||||||
}()
|
|
||||||
err = <-errChan
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) timestamp() uint32 {
|
|
||||||
return uint32(time.Since(p.startTime).Milliseconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) OnAnything(e event.Anything) (err error) {
|
|
||||||
packet, ok := e.Val.(proto_base.EncodeDecodeAble)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
packetInfo, ok := proto.LookupPacketInfoByType(packet)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if packetInfo.Name == "encryption_begin" {
|
|
||||||
return fmt.Errorf("encryption requested by remote server; raw capture cannot continue")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch packetInfo.Direction {
|
|
||||||
case proto_base.ToServer:
|
|
||||||
err = p.serverboundWriter.WritePacket(packet, p.timestamp())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = p.Client.Send(packet)
|
|
||||||
case proto_base.ToClient:
|
|
||||||
err = p.clientboundWriter.WritePacket(packet, p.timestamp())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = p.Servee.Send(packet)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) OnCompress(packet *v1_21_11.LoginToClientPacketCompress) (err error) {
|
|
||||||
err = p.clientboundWriter.WritePacket(packet, p.timestamp())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = p.Servee.Send(packet)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Servee.SetCompressionThreshold(packet.Threshold)
|
|
||||||
p.Client.SetCompressionThreshold(packet.Threshold)
|
|
||||||
return event.DontForwardErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) OnHandshake(packet *v1_21_11.HandshakingToServerPacketSetProtocol) (err error) {
|
|
||||||
state := proto_base.State(packet.NextState)
|
|
||||||
p.Servee.SetState(state)
|
|
||||||
return event.DontForwardErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) OnLoginAcknowledged(packet *v1_21_11.LoginToServerPacketLoginAcknowledged) (err error) {
|
|
||||||
err = p.serverboundWriter.WritePacket(packet, p.timestamp())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = p.Client.Send(packet)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Servee.SetState(proto_base.Configuration)
|
|
||||||
p.Client.SetState(proto_base.Configuration)
|
|
||||||
return event.DontForwardErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPacketFinishConfiguration) (err error) {
|
|
||||||
err = p.serverboundWriter.WritePacket(packet, p.timestamp())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = p.Client.Send(packet)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Servee.SetState(proto_base.Play)
|
|
||||||
p.Client.SetState(proto_base.Play)
|
|
||||||
return event.DontForwardErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run(args []string) (err error) {
|
|
||||||
err = Cmd.Parse(args)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
clientboundFile, err := os.Create(*ClientboundFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create clientbound file: %w", err)
|
|
||||||
}
|
|
||||||
defer clientboundFile.Close()
|
|
||||||
|
|
||||||
serverboundFile, err := os.Create(*ServerboundFile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create serverbound file: %w", err)
|
|
||||||
}
|
|
||||||
defer serverboundFile.Close()
|
|
||||||
|
|
||||||
resolvedAddr, _, _, err := client.DoDNSChecked(nil, *TargetAddr, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetVersion string
|
|
||||||
var targetProtocol int32
|
|
||||||
if *Version == "" {
|
|
||||||
status, err := client.Status(nil, resolvedAddr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to detect server version: %w", err)
|
|
||||||
}
|
|
||||||
targetProtocol = status.Version.Protocol
|
|
||||||
versionInfo, err := data.LookUpVersionByProtocolVersion(targetProtocol)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unknown protocol version %d: %w", targetProtocol, err)
|
|
||||||
}
|
|
||||||
targetVersion = versionInfo.MinecraftVersion
|
|
||||||
} else {
|
|
||||||
versionInfo, err := data.LookUpVersionByName(*Version)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unknown version %s: %w", *Version, err)
|
|
||||||
}
|
|
||||||
targetVersion = versionInfo.MinecraftVersion
|
|
||||||
targetProtocol = versionInfo.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusResponse.Version.Name = text.Pretty("STROM raw-capture " + targetVersion)
|
|
||||||
StatusResponse.Version.Protocol = targetProtocol
|
|
||||||
StatusResponse.Description = text.Pretty(fmt.Sprintf("Raw capture proxy forwards to %s (%s)", resolvedAddr, targetVersion))
|
|
||||||
|
|
||||||
slog.Info("Starting raw capture", "listen", *ListenAddr, "target", resolvedAddr, "version", targetVersion, "protocol", targetProtocol, "clientbound", *ClientboundFile, "serverbound", *ServerboundFile)
|
|
||||||
|
|
||||||
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
|
||||||
proxy := &Proxy{
|
|
||||||
Servee: serveeConn,
|
|
||||||
clientboundFile: clientboundFile,
|
|
||||||
serverboundFile: serverboundFile,
|
|
||||||
clientboundWriter: replay.NewWriter(clientboundFile),
|
|
||||||
serverboundWriter: replay.NewWriter(serverboundFile),
|
|
||||||
}
|
|
||||||
|
|
||||||
acc := api.NewOfflineAccount(*OfflineNameFlag)
|
|
||||||
if *Token != "" {
|
|
||||||
acc, err = api.NewAccountFromYGG(*Token)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse), server.WithoutOnlineMode(), server.WithoutEncryption())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := client.Connect(nil, resolvedAddr, targetVersion)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
err = client.LoginRaw(c, acc)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy.Client = c
|
|
||||||
proxy.Client.AlwaysDecode = true
|
|
||||||
proxy.Servee.AlwaysDecode = true
|
|
||||||
|
|
||||||
return proxy.Start()
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
package raw_to_tmcpr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/admin-else/strom/mc/proto/replay"
|
||||||
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Cmd = flag.NewFlagSet("raw-to-tmcpr", flag.ContinueOnError)
|
||||||
|
|
||||||
|
var ClientboundIn = Cmd.String("clientbound", "", "raw tcpflow file for clientbound (server->client) direction")
|
||||||
|
var ServerboundIn = Cmd.String("serverbound", "", "raw tcpflow file for serverbound (client->server) direction")
|
||||||
|
var ClientboundOut = Cmd.String("clientbound-out", "clientbound.tmcpr", "output TMCPR file for clientbound")
|
||||||
|
var ServerboundOut = Cmd.String("serverbound-out", "serverbound.tmcpr", "output TMCPR file for serverbound")
|
||||||
|
var ClientboundState = Cmd.String("clientbound-state", "login", "initial state for clientbound: handshake, login, status, play")
|
||||||
|
var ServerboundState = Cmd.String("serverbound-state", "handshake", "initial state for serverbound: handshake, login, status, play")
|
||||||
|
|
||||||
|
type captureReader struct {
|
||||||
|
r io.Reader
|
||||||
|
out *replay.Writer
|
||||||
|
state proto_base.State
|
||||||
|
threshold int32
|
||||||
|
startTime time.Time
|
||||||
|
haveStartTime bool
|
||||||
|
direction proto_base.Direction
|
||||||
|
pendingThreshold int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *captureReader) setStartTime() {
|
||||||
|
if !cr.haveStartTime {
|
||||||
|
cr.startTime = time.Now()
|
||||||
|
cr.haveStartTime = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *captureReader) timestamp() uint32 {
|
||||||
|
return uint32(time.Since(cr.startTime).Milliseconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPacketBytes(r io.Reader, threshold int32) ([]byte, error) {
|
||||||
|
packetLen, err := proto_base.DecodeVarInt(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read packet length: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawBytes := make([]byte, packetLen)
|
||||||
|
_, err = io.ReadFull(r, rawBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read packet data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if threshold >= 0 {
|
||||||
|
buf := bytes.NewReader(rawBytes)
|
||||||
|
uncompressedLen, err := proto_base.DecodeVarInt(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read uncompressed length: %w", err)
|
||||||
|
}
|
||||||
|
if uncompressedLen == 0 {
|
||||||
|
return io.ReadAll(buf)
|
||||||
|
} else {
|
||||||
|
zReader, err := zlib.NewReader(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("zlib reader: %w", err)
|
||||||
|
}
|
||||||
|
defer zReader.Close()
|
||||||
|
decompressed, err := io.ReadAll(zReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("decompress: %w", err)
|
||||||
|
}
|
||||||
|
return decompressed, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *captureReader) trackState(packetBytes []byte) {
|
||||||
|
buf := bytes.NewReader(packetBytes)
|
||||||
|
packetId, err := proto_base.DecodeVarInt(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cr.state {
|
||||||
|
case proto_base.Handshaking:
|
||||||
|
if cr.direction == proto_base.ToServer && packetId == 0 {
|
||||||
|
_, err = proto_base.DecodeVarInt(buf) // protocol version
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = proto_base.DecodeString(buf) // server address
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
portBytes := make([]byte, 2)
|
||||||
|
_, err = io.ReadFull(buf, portBytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nextState, err := proto_base.DecodeVarInt(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nextState == 2 {
|
||||||
|
cr.state = proto_base.Login
|
||||||
|
} else if nextState == 1 {
|
||||||
|
cr.state = proto_base.Status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case proto_base.Login:
|
||||||
|
if cr.direction == proto_base.ToClient {
|
||||||
|
if packetId == 0x03 {
|
||||||
|
threshold, err := proto_base.DecodeVarInt(buf)
|
||||||
|
if err == nil {
|
||||||
|
cr.threshold = threshold
|
||||||
|
}
|
||||||
|
} else if packetId == 0x02 {
|
||||||
|
cr.state = proto_base.Play
|
||||||
|
}
|
||||||
|
} else if cr.direction == proto_base.ToServer && packetId == 0 {
|
||||||
|
if cr.pendingThreshold >= 0 {
|
||||||
|
cr.threshold = cr.pendingThreshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case proto_base.Status:
|
||||||
|
case proto_base.Play:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *captureReader) process() (count int, err error) {
|
||||||
|
for {
|
||||||
|
packetBytes, err := readPacketBytes(cr.r, cr.threshold)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cr.setStartTime()
|
||||||
|
cr.trackState(packetBytes)
|
||||||
|
|
||||||
|
err = cr.out.WriteRaw(packetBytes, cr.timestamp())
|
||||||
|
if err != nil {
|
||||||
|
return count, fmt.Errorf("write TMCPR: %w", err)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(args []string) (err error) {
|
||||||
|
err = Cmd.Parse(args)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ClientboundIn == "" && *ServerboundIn == "" {
|
||||||
|
return fmt.Errorf("at least one of -clientbound or -serverbound must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
parseState := func(name string) (proto_base.State, error) {
|
||||||
|
switch name {
|
||||||
|
case "handshake":
|
||||||
|
return proto_base.Handshaking, nil
|
||||||
|
case "login":
|
||||||
|
return proto_base.Login, nil
|
||||||
|
case "play":
|
||||||
|
return proto_base.Play, nil
|
||||||
|
case "status":
|
||||||
|
return proto_base.Status, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown state: %s (valid: handshake, login, status, play)", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var compressionThreshold int32 = -1
|
||||||
|
|
||||||
|
if *ClientboundIn != "" {
|
||||||
|
state, err := parseState(*ClientboundState)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
compressionThreshold, err = processFile(*ClientboundIn, *ClientboundOut, state, proto_base.ToClient, -1)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("clientbound: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ServerboundIn != "" {
|
||||||
|
state, err := parseState(*ServerboundState)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = processFile(*ServerboundIn, *ServerboundOut, state, proto_base.ToServer, compressionThreshold)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("serverbound: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func processFile(inPath, outPath string, initial proto_base.State, dir proto_base.Direction, initialThreshold int32) (finalThreshold int32, err error) {
|
||||||
|
in, err := os.Open(inPath)
|
||||||
|
if err != nil {
|
||||||
|
return initialThreshold, fmt.Errorf("open input: %w", err)
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(outPath)
|
||||||
|
if err != nil {
|
||||||
|
return initialThreshold, fmt.Errorf("create output: %w", err)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
cr := &captureReader{
|
||||||
|
r: in,
|
||||||
|
out: replay.NewWriter(out),
|
||||||
|
state: initial,
|
||||||
|
threshold: -1,
|
||||||
|
direction: dir,
|
||||||
|
pendingThreshold: initialThreshold,
|
||||||
|
}
|
||||||
|
if dir == proto_base.ToClient {
|
||||||
|
cr.threshold = initialThreshold
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := cr.process()
|
||||||
|
if err != nil {
|
||||||
|
return cr.threshold, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s: %d packets written to %s (threshold=%d)\n", inPath, count, outPath, cr.threshold)
|
||||||
|
return cr.threshold, nil
|
||||||
|
}
|
||||||
@@ -289,6 +289,9 @@ func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewAccountFromYGG creates an Account by authenticating the YGG token against
|
||||||
|
// Mojang's API. It makes an HTTP call to fetch the player profile and returns
|
||||||
|
// an Account with name and UUID populated, or an error if authentication fails.
|
||||||
func NewAccountFromYGG(ygg string) (a *Account, err error) {
|
func NewAccountFromYGG(ygg string) (a *Account, err error) {
|
||||||
a = &Account{
|
a = &Account{
|
||||||
Ygg: ygg,
|
Ygg: ygg,
|
||||||
@@ -303,6 +306,8 @@ func NewAccountFromYGG(ygg string) (a *Account, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewAccount creates an Account from explicit name, UUID, and YGG token without
|
||||||
|
// making any HTTP calls. The account uses Mojang's default API servers.
|
||||||
func NewAccount(name string, uuid uuid.UUID, ygg string) (a *Account) {
|
func NewAccount(name string, uuid uuid.UUID, ygg string) (a *Account) {
|
||||||
return &Account{
|
return &Account{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"github.com/admin-else/strom/mc/crypto"
|
"github.com/admin-else/strom/mc/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NewOfflineAccount creates an offline-mode Account with a UUID derived from the
|
||||||
|
// player name. No HTTP calls are made; the UUID is computed locally.
|
||||||
func NewOfflineAccount(name string) *Account {
|
func NewOfflineAccount(name string) *Account {
|
||||||
return &Account{
|
return &Account{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BlockedServers fetches the list of SHA-1 hashes of blocked server IPs from
|
||||||
|
// Mojang's session server. The returned strings are hex-encoded hashes.
|
||||||
func BlockedServers() (s []string, err error) {
|
func BlockedServers() (s []string, err error) {
|
||||||
resp, err := http.Get("https://sessionserver.mojang.com/blockedservers")
|
resp, err := http.Get("https://sessionserver.mojang.com/blockedservers")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -36,6 +38,9 @@ type ProfileResp struct {
|
|||||||
Properties []ProfileProperty `json:"properties"`
|
Properties []ProfileProperty `json:"properties"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasJoined checks with Mojang's session server whether a player has joined a
|
||||||
|
// given server. name is the player name, server is the server hash, and ip is
|
||||||
|
// the optional player IP address. Returns the player's profile on success.
|
||||||
func HasJoined(name, server, ip string) (p ProfileResp, err error) {
|
func HasJoined(name, server, ip string) (p ProfileResp, err error) {
|
||||||
url := "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + name + "&serverId=" + server
|
url := "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + name + "&serverId=" + server
|
||||||
if ip != "" {
|
if ip != "" {
|
||||||
@@ -62,6 +67,8 @@ type MojangPublicKeysResp struct {
|
|||||||
AuthenticationKeys []string `json:"authenticationKeys"`
|
AuthenticationKeys []string `json:"authenticationKeys"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MojangPublicKeys fetches Mojang's public keys for profile property signing and
|
||||||
|
// player certificate verification from the session server.
|
||||||
func MojangPublicKeys() (keys MojangPublicKeysResp, err error) {
|
func MojangPublicKeys() (keys MojangPublicKeysResp, err error) {
|
||||||
resp, err := http.Get("https://sessionserver.mojang.com/session/minecraft/profile/public_key_by_token")
|
resp, err := http.Get("https://sessionserver.mojang.com/session/minecraft/profile/public_key_by_token")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -72,6 +79,8 @@ func MojangPublicKeys() (keys MojangPublicKeysResp, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Profile fetches a player's profile by UUID from Mojang's session server.
|
||||||
|
// If unsigned is false, signed profile properties are included in the response.
|
||||||
func Profile(id string, unsinged bool) (p ProfileResp, err error) {
|
func Profile(id string, unsinged bool) (p ProfileResp, err error) {
|
||||||
url := "https://sessionserver.mojang.com/session/minecraft/profile/" + id
|
url := "https://sessionserver.mojang.com/session/minecraft/profile/" + id
|
||||||
if !unsinged {
|
if !unsinged {
|
||||||
@@ -91,6 +100,8 @@ type IdName struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NameToId resolves a Minecraft player name to a UUID and canonical name by
|
||||||
|
// making an HTTP GET to Mojang's API.
|
||||||
func NameToId(name string) (idName IdName, err error) {
|
func NameToId(name string) (idName IdName, err error) {
|
||||||
// may also use these:
|
// may also use these:
|
||||||
//https://api.mojang.com/users/profiles/minecraft/<player name>
|
//https://api.mojang.com/users/profiles/minecraft/<player name>
|
||||||
@@ -105,6 +116,8 @@ func NameToId(name string) (idName IdName, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NameToIdBulk resolves multiple Minecraft player names to UUIDs and canonical
|
||||||
|
// names in a single HTTP POST to Mojang's API.
|
||||||
func NameToIdBulk(names []string) (idNames []IdName, err error) {
|
func NameToIdBulk(names []string) (idNames []IdName, err error) {
|
||||||
b := bytes.NewBuffer(nil)
|
b := bytes.NewBuffer(nil)
|
||||||
err = json.NewEncoder(b).Encode(names)
|
err = json.NewEncoder(b).Encode(names)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ func (m *mod) onKeepAlive(p *v1_21_8.PlayToClientPacketKeepAlive) (err error) {
|
|||||||
return m.Send(&v1_21_8.PlayToServerPacketKeepAlive{KeepAliveId: p.KeepAliveId})
|
return m.Send(&v1_21_8.PlayToServerPacketKeepAlive{KeepAliveId: p.KeepAliveId})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start begins responding to keep-alive packets on the connection.
|
||||||
func Start(c *proto.Conn) {
|
func Start(c *proto.Conn) {
|
||||||
m := &mod{Conn: c}
|
m := &mod{Conn: c}
|
||||||
m.RegisterUntilLatest(m.onKeepAlive)
|
m.RegisterUntilLatest(m.onKeepAlive)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/admin-else/strom/mc/data"
|
"github.com/admin-else/strom/mc/data"
|
||||||
@@ -21,6 +22,10 @@ func Connect(ctx context.Context, addr string, version string) (ret *proto.Conn,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if s.Version.Protocol == 0 {
|
||||||
|
err = errors.New("server didnt return protocol version")
|
||||||
|
return
|
||||||
|
}
|
||||||
protocolVersion = s.Version.Protocol
|
protocolVersion = s.Version.Protocol
|
||||||
} else {
|
} else {
|
||||||
var v data.VersionInfo
|
var v data.VersionInfo
|
||||||
@@ -55,3 +60,23 @@ func ConnectVersionLess(ctx context.Context, addr string) (ret *proto.Conn, err
|
|||||||
ret.W = ret.Conn
|
ret.W = ret.Conn
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConnectWithConn wraps an existing net.Conn into a proto.Conn with the given
|
||||||
|
// Minecraft version. The version must be non-empty.
|
||||||
|
func ConnectWithConn(ctx context.Context, conn net.Conn, version string) (ret *proto.Conn, err error) {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
ret = proto.NewConnCtx(ctx)
|
||||||
|
ret.Actor = proto_base.Client
|
||||||
|
ret.Conn = conn
|
||||||
|
ret.R = conn
|
||||||
|
ret.W = conn
|
||||||
|
v, err := data.LookUpVersionByName(version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret.ProtocolVersion = v.Version
|
||||||
|
ret.Version = version
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ func (c *ConfigIgnorer) OnKeepAlive(packet *v1_21_8.ConfigurationToClientPacketK
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IgnoreConfig acknowledges configuration packets on the connection and transitions it to the Play state once configuration is complete.
|
||||||
func IgnoreConfig(c *proto.Conn) (err error) {
|
func IgnoreConfig(c *proto.Conn) (err error) {
|
||||||
ci := &ConfigIgnorer{c}
|
ci := &ConfigIgnorer{c}
|
||||||
ci.RegisterCritical(ci.Default)
|
ci.RegisterCritical(ci.Default)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MakeHandshakePacket creates a handshake packet using the connection's remote address as the server host.
|
||||||
func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
|
func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
|
||||||
return MakeHandshakePacketAddr(c, nextState, c.RemoteAddr().String())
|
return MakeHandshakePacketAddr(c, nextState, c.RemoteAddr().String())
|
||||||
}
|
}
|
||||||
|
|||||||
+132
-14
@@ -7,6 +7,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/admin-else/strom/mc/api"
|
"github.com/admin-else/strom/mc/api"
|
||||||
"github.com/admin-else/strom/mc/crypto"
|
"github.com/admin-else/strom/mc/crypto"
|
||||||
@@ -14,8 +15,10 @@ import (
|
|||||||
"github.com/admin-else/strom/mc/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/mc/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_8"
|
||||||
"github.com/admin-else/strom/mc/proto_generated/v26_2"
|
"github.com/admin-else/strom/mc/proto_generated/v26_2"
|
||||||
"github.com/admin-else/strom/mc/text"
|
"github.com/admin-else/strom/mc/text"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginClient struct {
|
type LoginClient struct {
|
||||||
@@ -111,13 +114,42 @@ func (s *LoginClient) OnDisconnect(packet *v1_21_8.LoginToClientPacketDisconnect
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (err error) {
|
func (s *LoginClient) OnDisconnectV1_8(packet *v1_8.LoginToClientPacketDisconnect) (err error) {
|
||||||
s.GivenAccount = success
|
var reason text.Component
|
||||||
err = s.Send(&v1_21_8.LoginToServerPacketLoginAcknowledged{})
|
err = json.Unmarshal([]byte(packet.Reason), &reason)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.SetState(proto_base.Configuration)
|
err = KickedDuringLoginErr{reason}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnCompressV1_8(packet *v1_8.LoginToClientPacketCompress) (err error) {
|
||||||
|
s.SetCompressionThreshold(packet.Threshold)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnSuccessV1_8(success *v1_8.LoginToClientPacketSuccess) (err error) {
|
||||||
|
s.GivenAccount = &v1_21_8.LoginToClientPacketSuccess{
|
||||||
|
Uuid: uuid.MustParse(success.Uuid),
|
||||||
|
Username: success.Username,
|
||||||
|
}
|
||||||
|
s.SetState(proto_base.Play)
|
||||||
|
err = event.HandlerDoneErr{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (err error) {
|
||||||
|
s.GivenAccount = success
|
||||||
|
if s.ProtocolVersion >= 764 {
|
||||||
|
err = s.Send(&v1_21_8.LoginToServerPacketLoginAcknowledged{})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.SetState(proto_base.Configuration)
|
||||||
|
} else {
|
||||||
|
s.SetState(proto_base.Play)
|
||||||
|
}
|
||||||
err = event.HandlerDoneErr{}
|
err = event.HandlerDoneErr{}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -134,7 +166,15 @@ func (s *LoginClient) OnStart() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoginRaw performs the login handshake on an already-established connection without DNS resolution or configuration handling.
|
||||||
|
// The handshake address is derived from the connection's RemoteAddr.
|
||||||
func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
||||||
|
return LoginRawAddr(c, account, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginRawAddr performs the login handshake on an already-established connection using the given address for the handshake packet.
|
||||||
|
// If hostAddr is empty, the handshake address is derived from the connection's RemoteAddr.
|
||||||
|
func LoginRawAddr(c *proto.Conn, account *api.Account, hostAddr string) (err error) {
|
||||||
lc := &LoginClient{
|
lc := &LoginClient{
|
||||||
Conn: c,
|
Conn: c,
|
||||||
Account: account,
|
Account: account,
|
||||||
@@ -146,8 +186,16 @@ func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
|||||||
lc.RegisterCriticalUntilLatest(lc.OnDisconnect)
|
lc.RegisterCriticalUntilLatest(lc.OnDisconnect)
|
||||||
lc.RegisterCriticalUntil("26.1", lc.OnSuccess)
|
lc.RegisterCriticalUntil("26.1", lc.OnSuccess)
|
||||||
lc.RegisterCriticalUntilLatest(lc.OnSuccess26_2)
|
lc.RegisterCriticalUntilLatest(lc.OnSuccess26_2)
|
||||||
|
lc.RegisterCritical(lc.OnCompressV1_8)
|
||||||
|
lc.RegisterCritical(lc.OnDisconnectV1_8)
|
||||||
|
lc.RegisterCritical(lc.OnSuccessV1_8)
|
||||||
|
|
||||||
p, err := MakeHandshakePacket(lc.Conn, proto_base.Login)
|
var p proto_base.EncodeDecodeAble
|
||||||
|
if hostAddr != "" {
|
||||||
|
p, err = MakeHandshakePacketAddr(lc.Conn, proto_base.Login, hostAddr)
|
||||||
|
} else {
|
||||||
|
p, err = MakeHandshakePacket(lc.Conn, proto_base.Login)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -157,7 +205,11 @@ func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
lc.SetState(proto_base.Login)
|
lc.SetState(proto_base.Login)
|
||||||
err = lc.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: lc.Account.Name, PlayerUUID: lc.Account.Uuid})
|
if lc.Conn.ProtocolVersion < 759 {
|
||||||
|
err = lc.Send(&v1_8.LoginToServerPacketLoginStart{Username: lc.Account.Name})
|
||||||
|
} else {
|
||||||
|
err = lc.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: lc.Account.Name, PlayerUUID: lc.Account.Uuid})
|
||||||
|
}
|
||||||
|
|
||||||
err = lc.StartConn()
|
err = lc.StartConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -166,31 +218,67 @@ func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithoutDns returns a login setting that disables SRV DNS resolution during login.
|
||||||
func WithoutDns() func(ls *loginSettings) {
|
func WithoutDns() func(ls *loginSettings) {
|
||||||
return func(ls *loginSettings) {
|
return func(ls *loginSettings) {
|
||||||
ls.NoDns = true
|
ls.NoDns = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithContext returns a login setting that sets the context used for the connection dial and DNS resolution.
|
||||||
func WithContext(ctx context.Context) func(ls *loginSettings) {
|
func WithContext(ctx context.Context) func(ls *loginSettings) {
|
||||||
return func(ls *loginSettings) {
|
return func(ls *loginSettings) {
|
||||||
ls.Ctx = ctx
|
ls.Ctx = ctx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithVersion returns a login setting that forces a specific Minecraft version string for the connection.
|
||||||
func WithVersion(version string) func(ls *loginSettings) {
|
func WithVersion(version string) func(ls *loginSettings) {
|
||||||
return func(ls *loginSettings) {
|
return func(ls *loginSettings) {
|
||||||
ls.Version = version
|
ls.Version = version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithIgnoreConfig() func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.IgnoreConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialFunc is a context-aware dial function used by Login to establish a TCP connection.
|
||||||
|
type DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
|
// WithConn returns a login setting that uses an already-established net.Conn instead of dialling.
|
||||||
|
// WithVersion must also be set; auto-detection is not possible with a pre-established connection.
|
||||||
|
func WithConn(conn net.Conn) func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.Conn = conn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDialer returns a login setting that uses the given dial function instead of net.Dialer.
|
||||||
|
// Auto version detection via status ping is still supported when no explicit version is set.
|
||||||
|
func WithDialer(d DialFunc) func(ls *loginSettings) {
|
||||||
|
return func(ls *loginSettings) {
|
||||||
|
ls.Dialer = d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrWithConnRequiresVersion = errors.New("WithVersion is required when using WithConn")
|
||||||
|
ErrWithConnNoStatus = errors.New("auto version detection is not supported with WithConn")
|
||||||
|
)
|
||||||
|
|
||||||
type loginSettings struct {
|
type loginSettings struct {
|
||||||
NoDns bool
|
NoDns bool
|
||||||
Ctx context.Context
|
Ctx context.Context
|
||||||
Version string
|
Version string
|
||||||
IgnoreConfig bool
|
IgnoreConfig bool
|
||||||
|
Conn net.Conn
|
||||||
|
Dialer DialFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Login resolves the address with SRV DNS, connects, performs the login handshake, and handles the configuration phase, returning an authenticated connection ready for play.
|
||||||
func Login(connectTo string, account *api.Account, settings ...func(ls *loginSettings)) (c *proto.Conn, err error) {
|
func Login(connectTo string, account *api.Account, settings ...func(ls *loginSettings)) (c *proto.Conn, err error) {
|
||||||
ls := &loginSettings{}
|
ls := &loginSettings{}
|
||||||
|
|
||||||
@@ -198,22 +286,52 @@ func Login(connectTo string, account *api.Account, settings ...func(ls *loginSet
|
|||||||
setting(ls)
|
setting(ls)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ls.NoDns {
|
if ls.Conn != nil {
|
||||||
connectTo, _, _, err = DoDNSChecked(ls.Ctx, connectTo, nil)
|
if ls.Version == "" {
|
||||||
|
return nil, ErrWithConnRequiresVersion
|
||||||
|
}
|
||||||
|
c, err = ConnectWithConn(ls.Ctx, ls.Conn, ls.Version)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if ls.Dialer != nil {
|
||||||
|
if ls.Version == "" {
|
||||||
|
return nil, ErrWithConnRequiresVersion
|
||||||
|
}
|
||||||
|
if !ls.NoDns {
|
||||||
|
connectTo, _, _, err = DoDNSChecked(ls.Ctx, connectTo, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var conn net.Conn
|
||||||
|
conn, err = ls.Dialer(ls.Ctx, "tcp", connectTo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c, err = ConnectWithConn(ls.Ctx, conn, ls.Version)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !ls.NoDns {
|
||||||
|
connectTo, _, _, err = DoDNSChecked(ls.Ctx, connectTo, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c, err = Connect(ls.Ctx, connectTo, ls.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = Connect(ls.Ctx, connectTo, ls.Version)
|
err = LoginRawAddr(c, account, connectTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = LoginRaw(c, account)
|
if !ls.IgnoreConfig && c.ProtocolVersion >= 764 {
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !ls.IgnoreConfig {
|
|
||||||
err = IgnoreConfig(c)
|
err = IgnoreConfig(c)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
+30
-4
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/admin-else/strom/mc/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/mc/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
|
"github.com/admin-else/strom/mc/proto_generated/v1_8"
|
||||||
"github.com/admin-else/strom/mc/server"
|
"github.com/admin-else/strom/mc/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,16 +37,38 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StatusClient) OnStatusV1_8(p *v1_8.StatusToClientPacketServerInfo) (err error) {
|
||||||
|
s.Status = p.Response
|
||||||
|
s.PingSendTime = time.Now()
|
||||||
|
if s.DoPingRoundTripTime {
|
||||||
|
err = s.Send(&v1_21_8.StatusToServerPacketPing{Time: s.PingSendTime.UnixMilli()})
|
||||||
|
} else {
|
||||||
|
err = event.HandlerDoneErr{}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatusClient) OnPongV1_8(p *v1_8.StatusToClientPacketPing) (err error) {
|
||||||
|
s.PingReceiveTime = time.Now()
|
||||||
|
err = event.HandlerDoneErr{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// StatusRaw returns a StatusClient that is not connected to a server.
|
// StatusRaw returns a StatusClient that is not connected to a server.
|
||||||
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
||||||
// It does not resolve SRV records.
|
// It does not resolve SRV records.
|
||||||
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
|
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
|
||||||
|
return StatusRawWithVersion(ctx, addr, "1.21.8")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusRawWithVersion is like StatusRaw but uses the given Minecraft version for the handshake.
|
||||||
|
func StatusRawWithVersion(ctx context.Context, addr, version string) (s *StatusClient, err error) {
|
||||||
c, err := ConnectVersionLess(ctx, addr)
|
c, err := ConnectVersionLess(ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
err = c.SetVersion("1.21.8")
|
err = c.SetVersion(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -54,6 +77,8 @@ func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
|
|||||||
}
|
}
|
||||||
s.RegisterCriticalUntilLatest(s.OnStatus)
|
s.RegisterCriticalUntilLatest(s.OnStatus)
|
||||||
s.RegisterCriticalUntilLatest(s.OnPong)
|
s.RegisterCriticalUntilLatest(s.OnPong)
|
||||||
|
s.RegisterCritical(s.OnStatusV1_8)
|
||||||
|
s.RegisterCritical(s.OnPongV1_8)
|
||||||
|
|
||||||
p, err := MakeHandshakePacketAddr(s.Conn, proto_base.Status, addr)
|
p, err := MakeHandshakePacketAddr(s.Conn, proto_base.Status, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,11 +96,12 @@ func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StatusNoDns is like Status but does not resolve SRV records.
|
// StatusNoDns is like Status but does not resolve SRV records.
|
||||||
func StatusNoDns(addr string) (status server.StatusResponse, err error) {
|
func StatusNoDns(ctx context.Context, addr string) (status server.StatusResponse, err error) {
|
||||||
s, err := StatusRaw(context.Background(), addr)
|
s, err := StatusRaw(ctx, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer s.Close()
|
||||||
err = json.Unmarshal([]byte(s.Status), &status)
|
err = json.Unmarshal([]byte(s.Status), &status)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -86,5 +112,5 @@ func Status(ctx context.Context, addr string) (status server.StatusResponse, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return StatusNoDns(addr)
|
return StatusNoDns(ctx, addr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,10 +41,12 @@ type CFB8 struct {
|
|||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCFB8Decrypt creates a new CFB8 stream cipher in decrypt mode.
|
||||||
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8 {
|
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8 {
|
||||||
return newCFB8(c, iv, true)
|
return newCFB8(c, iv, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCFB8Encrypt creates a new CFB8 stream cipher in encrypt mode.
|
||||||
func NewCFB8Encrypt(c cipher.Block, iv []byte) *CFB8 {
|
func NewCFB8Encrypt(c cipher.Block, iv []byte) *CFB8 {
|
||||||
return newCFB8(c, iv, false)
|
return newCFB8(c, iv, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ func uuidV3(space []byte, data []byte) uuid.UUID {
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromOfflinePlayer generates a UUID for an offline-mode (cracked) player from their display name.
|
||||||
func FromOfflinePlayer(displayName string) uuid.UUID {
|
func FromOfflinePlayer(displayName string) uuid.UUID {
|
||||||
return uuidV3([]byte("OfflinePlayer:"), []byte(displayName))
|
return uuidV3([]byte("OfflinePlayer:"), []byte(displayName))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type Biome struct {
|
|||||||
|
|
||||||
var BiomesCache = make(map[string][]*Biome)
|
var BiomesCache = make(map[string][]*Biome)
|
||||||
|
|
||||||
|
// BiomesForVersion returns the biome definitions for the given Minecraft version, caching them after the first load.
|
||||||
func BiomesForVersion(v string) (ret []*Biome) {
|
func BiomesForVersion(v string) (ret []*Biome) {
|
||||||
var ok bool
|
var ok bool
|
||||||
if ret, ok = BiomesCache[v]; ok {
|
if ret, ok = BiomesCache[v]; ok {
|
||||||
@@ -39,6 +40,7 @@ func BiomesForVersion(v string) (ret []*Biome) {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupBiomeById returns the biome with the given numeric ID in the given version.
|
||||||
func LookupBiomeById(version string, id int32) (biome *Biome, ok bool) {
|
func LookupBiomeById(version string, id int32) (biome *Biome, ok bool) {
|
||||||
for _, b := range BiomesForVersion(version) {
|
for _, b := range BiomesForVersion(version) {
|
||||||
if b.Id == id {
|
if b.Id == id {
|
||||||
@@ -52,6 +54,7 @@ func LookupBiomeById(version string, id int32) (biome *Biome, ok bool) {
|
|||||||
|
|
||||||
var UnknownBiomeNameErr = errors.New("unknown biome Name")
|
var UnknownBiomeNameErr = errors.New("unknown biome Name")
|
||||||
|
|
||||||
|
// LookupBiomeByName returns the biome with the given name in the given version.
|
||||||
func LookupBiomeByName(version string, name string) (biome *Biome, ok bool) {
|
func LookupBiomeByName(version string, name string) (biome *Biome, ok bool) {
|
||||||
for _, b := range BiomesForVersion(version) {
|
for _, b := range BiomesForVersion(version) {
|
||||||
if b.Name == name {
|
if b.Name == name {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ type BlockState struct {
|
|||||||
var BlocksCache = make(map[string][]*Block)
|
var BlocksCache = make(map[string][]*Block)
|
||||||
var BlocksCacheIdMap = make(map[string]map[int32]*Block)
|
var BlocksCacheIdMap = make(map[string]map[int32]*Block)
|
||||||
|
|
||||||
|
// BlocksForVersion returns the block definitions for the given Minecraft version, caching them after the first load.
|
||||||
func BlocksForVersion(v string) (ret []*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 {
|
||||||
@@ -84,6 +85,7 @@ func BlocksForVersion(v string) (ret []*Block) {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupBlockByStateId returns the block for the given global palette state ID in the given version.
|
||||||
func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool) {
|
func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool) {
|
||||||
idmap, found := BlocksCacheIdMap[version]
|
idmap, found := BlocksCacheIdMap[version]
|
||||||
if !found {
|
if !found {
|
||||||
@@ -101,6 +103,7 @@ func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool)
|
|||||||
|
|
||||||
var UnknownBlockNameErr = errors.New("unknown block Name")
|
var UnknownBlockNameErr = errors.New("unknown block Name")
|
||||||
|
|
||||||
|
// LookupBlockByName returns the block with the given name in the given version.
|
||||||
func LookupBlockByName(version string, name string) (block *Block, ok bool) {
|
func LookupBlockByName(version string, name string) (block *Block, ok bool) {
|
||||||
for _, b := range BlocksForVersion(version) {
|
for _, b := range BlocksForVersion(version) {
|
||||||
if b.Name == name {
|
if b.Name == name {
|
||||||
@@ -111,6 +114,7 @@ func LookupBlockByName(version string, name string) (block *Block, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StateIdFromBlocKAndStateMap returns the global palette state ID for the given block name and state property map in the given version.
|
||||||
func StateIdFromBlocKAndStateMap(version string, name string, stateMap map[string]string) (stateId int32, err error) {
|
func StateIdFromBlocKAndStateMap(version string, name string, stateMap map[string]string) (stateId int32, err error) {
|
||||||
b, ok := LookupBlockByName(version, name)
|
b, ok := LookupBlockByName(version, name)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -135,6 +139,7 @@ var BadBlockStateTypeErr = errors.New("bad block state type")
|
|||||||
var BadEnumErr = errors.New("bad enum")
|
var BadEnumErr = errors.New("bad enum")
|
||||||
var SupplyAllStatesErr = errors.New("supply all states")
|
var SupplyAllStatesErr = errors.New("supply all states")
|
||||||
|
|
||||||
|
// FromBlockState converts a global palette state ID back into a block definition and a map of state properties.
|
||||||
func FromBlockState(version string, stateId int32) (b *Block, stateData map[string]string, err error) {
|
func FromBlockState(version string, stateId int32) (b *Block, stateData map[string]string, err error) {
|
||||||
b, ok := LookupBlockByStateId(version, stateId)
|
b, ok := LookupBlockByStateId(version, stateId)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
//go:embed minecraft-data/pc minecraft-data/dataPaths.json
|
//go:embed minecraft-data/pc minecraft-data/dataPaths.json
|
||||||
var MinecraftData embed.FS
|
var MinecraftData embed.FS
|
||||||
|
|
||||||
|
// LoadJson reads a JSON file from the embedded minecraft-data FS and unmarshals it into data.
|
||||||
func LoadJson(path string, data any) (err error) {
|
func LoadJson(path string, data any) (err error) {
|
||||||
b, err := MinecraftData.ReadFile(path)
|
b, err := MinecraftData.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -21,6 +22,7 @@ func LoadJson(path string, data any) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadVersionedJson loads a version-specific JSON data file from the embedded minecraft-data FS and unmarshals it into data.
|
||||||
func LoadVersionedJson(version, dataName string, data any) (err error) {
|
func LoadVersionedJson(version, dataName string, data any) (err error) {
|
||||||
b, err := MinecraftData.ReadFile("minecraft-data/" + Paths.Data[version][dataName] + "/" + dataName + ".json")
|
b, err := MinecraftData.ReadFile("minecraft-data/" + Paths.Data[version][dataName] + "/" + dataName + ".json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type Item struct {
|
|||||||
var ItemsCache = make(map[string][]*Item)
|
var ItemsCache = make(map[string][]*Item)
|
||||||
var ItemsCacheMutex = &sync.RWMutex{}
|
var ItemsCacheMutex = &sync.RWMutex{}
|
||||||
|
|
||||||
|
// ItemsForVersion returns the item definitions for the given Minecraft version, caching them after the first load.
|
||||||
func ItemsForVersion(v string) (ret []*Item) {
|
func ItemsForVersion(v string) (ret []*Item) {
|
||||||
var ok bool
|
var ok bool
|
||||||
ItemsCacheMutex.RLock()
|
ItemsCacheMutex.RLock()
|
||||||
@@ -35,6 +36,7 @@ func ItemsForVersion(v string) (ret []*Item) {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupItemById returns the item with the given numeric ID in the given version.
|
||||||
func LookupItemById(version string, id int32) (item *Item, ok bool) {
|
func LookupItemById(version string, id int32) (item *Item, ok bool) {
|
||||||
for _, i := range ItemsForVersion(version) {
|
for _, i := range ItemsForVersion(version) {
|
||||||
if i.Id == id {
|
if i.Id == id {
|
||||||
@@ -46,6 +48,7 @@ func LookupItemById(version string, id int32) (item *Item, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupItemByName returns the item with the given name in the given version.
|
||||||
func LookupItemByName(version string, name string) (item *Item, ok bool) {
|
func LookupItemByName(version string, name string) (item *Item, ok bool) {
|
||||||
for _, i := range ItemsForVersion(version) {
|
for _, i := range ItemsForVersion(version) {
|
||||||
if i.Name == name {
|
if i.Name == name {
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "maxHealth",
|
||||||
|
"resource": "generic.maxHealth",
|
||||||
|
"default": 20,
|
||||||
|
"min": 1,
|
||||||
|
"max": 1024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "followRange",
|
||||||
|
"resource": "generic.followRange",
|
||||||
|
"default": 32,
|
||||||
|
"min": 0,
|
||||||
|
"max": 2048
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "knockbackResistance",
|
||||||
|
"resource": "generic.knockbackResistance",
|
||||||
|
"default": 0,
|
||||||
|
"min": 0,
|
||||||
|
"max": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "movementSpeed",
|
||||||
|
"resource": "generic.movementSpeed",
|
||||||
|
"default": 0.7,
|
||||||
|
"min": 0,
|
||||||
|
"max": 1024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attackDamage",
|
||||||
|
"resource": "generic.attackDamage",
|
||||||
|
"default": 2,
|
||||||
|
"min": 0,
|
||||||
|
"max": 2048
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "horseJumpStrength",
|
||||||
|
"resource": "horse.jumpStrength",
|
||||||
|
"default": 0.7,
|
||||||
|
"min": 0,
|
||||||
|
"max": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "zombieSpawnReinforcements",
|
||||||
|
"resource": "zombie.spawnReinforcements",
|
||||||
|
"default": 0,
|
||||||
|
"min": 0,
|
||||||
|
"max": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attackSpeed",
|
||||||
|
"resource": "generic.attackSpeed",
|
||||||
|
"default": 4,
|
||||||
|
"min": 0,
|
||||||
|
"max": 1024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "flyingSpeed",
|
||||||
|
"resource": "generic.flyingSpeed",
|
||||||
|
"default": 0.4,
|
||||||
|
"min": 0,
|
||||||
|
"max": 1024
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attackKnockback",
|
||||||
|
"resource": "generic.attackKnockback",
|
||||||
|
"default": 0,
|
||||||
|
"min": 0,
|
||||||
|
"max": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "armorHealth",
|
||||||
|
"resource": "generic.armor",
|
||||||
|
"default": 0,
|
||||||
|
"min": 0,
|
||||||
|
"max": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "armorToughness",
|
||||||
|
"resource": "generic.armorToughness",
|
||||||
|
"default": 0,
|
||||||
|
"min": 0,
|
||||||
|
"max": 20
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,817 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "ocean",
|
||||||
|
"name_legacy": "ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Ocean",
|
||||||
|
"color": 112,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "plains",
|
||||||
|
"name_legacy": "plains",
|
||||||
|
"category": "plains",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Plains",
|
||||||
|
"color": 9286496,
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "desert",
|
||||||
|
"name_legacy": "desert",
|
||||||
|
"category": "desert",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Desert",
|
||||||
|
"color": 16421912,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "mountains",
|
||||||
|
"name_legacy": "mountains",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Extreme Hills",
|
||||||
|
"color": 6316128,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "forest",
|
||||||
|
"name_legacy": "forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Forest",
|
||||||
|
"color": 353825,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "taiga",
|
||||||
|
"name_legacy": "taiga",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Taiga",
|
||||||
|
"color": 747097,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "swamp",
|
||||||
|
"name_legacy": "swamp",
|
||||||
|
"category": "swamp",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Swampland",
|
||||||
|
"color": 522674,
|
||||||
|
"rainfall": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "river",
|
||||||
|
"name_legacy": "river",
|
||||||
|
"category": "river",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -0.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "River",
|
||||||
|
"color": 255,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "nether_wastes",
|
||||||
|
"name_legacy": "nether_wastes",
|
||||||
|
"category": "nether",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "nether",
|
||||||
|
"displayName": "Hell",
|
||||||
|
"color": 12532539,
|
||||||
|
"rainfall": 0,
|
||||||
|
"climates": [
|
||||||
|
{
|
||||||
|
"temperature": 0,
|
||||||
|
"humidity": 0,
|
||||||
|
"altitude": 0,
|
||||||
|
"weirdness": 0,
|
||||||
|
"offset": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "the_end",
|
||||||
|
"name_legacy": "the_end",
|
||||||
|
"category": "the_end",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "end",
|
||||||
|
"displayName": "The End",
|
||||||
|
"color": 8421631,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "frozen_ocean",
|
||||||
|
"name_legacy": "frozen_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": -1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Frozen Ocean",
|
||||||
|
"color": 7368918,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "frozen_river",
|
||||||
|
"name_legacy": "frozen_river",
|
||||||
|
"category": "river",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": -0.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Frozen River",
|
||||||
|
"color": 10526975,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"name": "snowy_tundra",
|
||||||
|
"name_legacy": "snowy_tundra",
|
||||||
|
"category": "icy",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Ice Plains",
|
||||||
|
"color": 16777215,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"name": "snowy_mountains",
|
||||||
|
"name_legacy": "snowy_mountains",
|
||||||
|
"category": "icy",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Ice Mountains",
|
||||||
|
"color": 10526880,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"name": "mushroom_fields",
|
||||||
|
"name_legacy": "mushroom_fields",
|
||||||
|
"category": "mushroom",
|
||||||
|
"temperature": 0.9,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mushroom Island",
|
||||||
|
"color": 16711935,
|
||||||
|
"rainfall": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"name": "mushroom_field_shore",
|
||||||
|
"name_legacy": "mushroom_field_shore",
|
||||||
|
"category": "mushroom",
|
||||||
|
"temperature": 0.9,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mushroom Island Shore",
|
||||||
|
"color": 10486015,
|
||||||
|
"rainfall": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"name": "beach",
|
||||||
|
"name_legacy": "beach",
|
||||||
|
"category": "beach",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Beach",
|
||||||
|
"color": 16440917,
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"name": "desert_hills",
|
||||||
|
"name_legacy": "desert_hills",
|
||||||
|
"category": "desert",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Desert Hills",
|
||||||
|
"color": 13786898,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"name": "wooded_hills",
|
||||||
|
"name_legacy": "wooded_hills",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Forest Hills",
|
||||||
|
"color": 2250012,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"name": "taiga_hills",
|
||||||
|
"name_legacy": "taiga_hills",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Taiga Hills",
|
||||||
|
"color": 1456435,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"name": "mountain_edge",
|
||||||
|
"name_legacy": "mountain_edge",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.8,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Extreme Hills Edge",
|
||||||
|
"color": 7501978,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"name": "jungle",
|
||||||
|
"name_legacy": "jungle",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Jungle",
|
||||||
|
"color": 5470985,
|
||||||
|
"rainfall": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"name": "jungle_hills",
|
||||||
|
"name_legacy": "jungle_hills",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Jungle Hills",
|
||||||
|
"color": 2900485,
|
||||||
|
"rainfall": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"name": "jungle_edge",
|
||||||
|
"name_legacy": "jungle_edge",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Jungle Edge",
|
||||||
|
"color": 6458135,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"name": "deep_ocean",
|
||||||
|
"name_legacy": "deep_ocean",
|
||||||
|
"category": "ocean",
|
||||||
|
"temperature": 0.5,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -1.8,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Deep Ocean",
|
||||||
|
"color": 48,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"name": "stone_shore",
|
||||||
|
"name_legacy": "stone_shore",
|
||||||
|
"category": "none",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Stone Beach",
|
||||||
|
"color": 10658436,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"name": "snowy_beach",
|
||||||
|
"name_legacy": "snowy_beach",
|
||||||
|
"category": "beach",
|
||||||
|
"temperature": 0.05,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Cold Beach",
|
||||||
|
"color": 16445632,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"name": "birch_forest",
|
||||||
|
"name_legacy": "birch_forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Birch Forest",
|
||||||
|
"color": 3175492,
|
||||||
|
"rainfall": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"name": "birch_forest_hills",
|
||||||
|
"name_legacy": "birch_forest_hills",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Birch Forest Hills",
|
||||||
|
"color": 2055986,
|
||||||
|
"rainfall": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"name": "dark_forest",
|
||||||
|
"name_legacy": "dark_forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Roofed Forest",
|
||||||
|
"color": 4215066,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"name": "snowy_taiga",
|
||||||
|
"name_legacy": "snowy_taiga",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": -0.5,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Cold Taiga",
|
||||||
|
"color": 3233098,
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"name": "snowy_taiga_hills",
|
||||||
|
"name_legacy": "snowy_taiga_hills",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": -0.5,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Cold Taiga Hills",
|
||||||
|
"color": 2375478,
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"name": "giant_tree_taiga",
|
||||||
|
"name_legacy": "giant_tree_taiga",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.3,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mega Taiga",
|
||||||
|
"color": 5858897,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"name": "giant_tree_taiga_hills",
|
||||||
|
"name_legacy": "giant_tree_taiga_hills",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.3,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mega Taiga Hills",
|
||||||
|
"color": 4542270,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"name": "wooded_mountains",
|
||||||
|
"name_legacy": "wooded_mountains",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Extreme Hills+",
|
||||||
|
"color": 5271632,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 35,
|
||||||
|
"name": "savanna",
|
||||||
|
"name_legacy": "savanna",
|
||||||
|
"category": "savanna",
|
||||||
|
"temperature": 1.2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Savanna",
|
||||||
|
"color": 12431967,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 36,
|
||||||
|
"name": "savanna_plateau",
|
||||||
|
"name_legacy": "savanna_plateau",
|
||||||
|
"category": "savanna",
|
||||||
|
"temperature": 1,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 1.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Savanna Plateau",
|
||||||
|
"color": 10984804,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 37,
|
||||||
|
"name": "badlands",
|
||||||
|
"name_legacy": "badlands",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mesa",
|
||||||
|
"color": 14238997,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 38,
|
||||||
|
"name": "wooded_badlands_plateau",
|
||||||
|
"name_legacy": "wooded_badlands_plateau",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 1.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mesa Plateau F",
|
||||||
|
"color": 11573093,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 39,
|
||||||
|
"name": "badlands_plateau",
|
||||||
|
"name_legacy": "badlands_plateau",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 1.5,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mesa Plateau",
|
||||||
|
"color": 13274213,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 129,
|
||||||
|
"name": "sunflower_plains",
|
||||||
|
"name_legacy": "mutated_plains",
|
||||||
|
"category": "plains",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Plains M",
|
||||||
|
"color": 11918216,
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 130,
|
||||||
|
"name": "desert_lakes",
|
||||||
|
"name_legacy": "mutated_desert",
|
||||||
|
"category": "desert",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.125,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Desert M",
|
||||||
|
"color": 16759872,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 131,
|
||||||
|
"name": "gravelly_mountains",
|
||||||
|
"name_legacy": "mutated_extreme_hills",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Extreme Hills M",
|
||||||
|
"color": 8947848,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 132,
|
||||||
|
"name": "flower_forest",
|
||||||
|
"name_legacy": "mutated_forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Forest M",
|
||||||
|
"color": 2985545,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 133,
|
||||||
|
"name": "taiga_mountains",
|
||||||
|
"name_legacy": "mutated_taiga",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.3,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Taiga M",
|
||||||
|
"color": 3378817,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 134,
|
||||||
|
"name": "swamp_hills",
|
||||||
|
"name_legacy": "mutated_swampland",
|
||||||
|
"category": "swamp",
|
||||||
|
"temperature": 0.8,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": -0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Swampland M",
|
||||||
|
"color": 3145690,
|
||||||
|
"rainfall": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 140,
|
||||||
|
"name": "ice_spikes",
|
||||||
|
"name_legacy": "mutated_ice_flats",
|
||||||
|
"category": "icy",
|
||||||
|
"temperature": 0,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.425,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Ice Plains M",
|
||||||
|
"color": 11853020,
|
||||||
|
"rainfall": 0.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 149,
|
||||||
|
"name": "modified_jungle",
|
||||||
|
"name_legacy": "mutated_jungle",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Jungle M",
|
||||||
|
"color": 8102705,
|
||||||
|
"rainfall": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 151,
|
||||||
|
"name": "modified_jungle_edge",
|
||||||
|
"name_legacy": "mutated_jungle_edge",
|
||||||
|
"category": "jungle",
|
||||||
|
"temperature": 0.95,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "JungleEdge M",
|
||||||
|
"color": 9089855,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 155,
|
||||||
|
"name": "tall_birch_forest",
|
||||||
|
"name_legacy": "mutated_birch_forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Birch Forest M",
|
||||||
|
"color": 5807212,
|
||||||
|
"rainfall": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 156,
|
||||||
|
"name": "tall_birch_hills",
|
||||||
|
"name_legacy": "mutated_birch_forest_hills",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.6,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.55,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Birch Forest Hills M",
|
||||||
|
"color": 4687706,
|
||||||
|
"rainfall": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 157,
|
||||||
|
"name": "dark_forest_hills",
|
||||||
|
"name_legacy": "mutated_roofed_forest",
|
||||||
|
"category": "forest",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Roofed Forest M",
|
||||||
|
"color": 6846786,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 158,
|
||||||
|
"name": "snowy_taiga_mountains",
|
||||||
|
"name_legacy": "mutated_taiga_cold",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": -0.5,
|
||||||
|
"precipitation": "snow",
|
||||||
|
"depth": 0.3,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Cold Taiga M",
|
||||||
|
"color": 5864818,
|
||||||
|
"rainfall": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 160,
|
||||||
|
"name": "giant_spruce_taiga",
|
||||||
|
"name_legacy": "mutated_redwood_taiga",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mega Taiga M",
|
||||||
|
"color": 8490617,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 161,
|
||||||
|
"name": "giant_spruce_taiga_hills",
|
||||||
|
"name_legacy": "mutated_redwood_taiga_hills",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mega Taiga Hills M",
|
||||||
|
"color": 7173990,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 161,
|
||||||
|
"name": "giant_spruce_taiga_hills",
|
||||||
|
"name_legacy": "mutated_redwood_taiga_hills",
|
||||||
|
"category": "taiga",
|
||||||
|
"temperature": 0.25,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 0.2,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Redwood Taiga Hills M",
|
||||||
|
"color": 7173990,
|
||||||
|
"rainfall": 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 162,
|
||||||
|
"name": "modified_gravelly_mountains",
|
||||||
|
"name_legacy": "mutated_extreme_hills_with_trees",
|
||||||
|
"category": "extreme_hills",
|
||||||
|
"temperature": 0.2,
|
||||||
|
"precipitation": "rain",
|
||||||
|
"depth": 1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Extreme Hills+ M",
|
||||||
|
"color": 7903352,
|
||||||
|
"rainfall": 0.3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 163,
|
||||||
|
"name": "shattered_savanna",
|
||||||
|
"name_legacy": "mutated_savanna",
|
||||||
|
"category": "savanna",
|
||||||
|
"temperature": 1.1,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.3625,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Savanna M",
|
||||||
|
"color": 15063687,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 164,
|
||||||
|
"name": "shattered_savanna_plateau",
|
||||||
|
"name_legacy": "mutated_savanna_rock",
|
||||||
|
"category": "savanna",
|
||||||
|
"temperature": 1,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 1.05,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Savanna Plateau M",
|
||||||
|
"color": 13616524,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 165,
|
||||||
|
"name": "eroded_badlands",
|
||||||
|
"name_legacy": "mutated_mesa",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.1,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mesa M",
|
||||||
|
"color": 16739645,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 166,
|
||||||
|
"name": "modified_wooded_badlands_plateau",
|
||||||
|
"name_legacy": "mutated_mesa_rock",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mesa Plateau F M",
|
||||||
|
"color": 14204813,
|
||||||
|
"rainfall": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 167,
|
||||||
|
"name": "modified_badlands_plateau",
|
||||||
|
"name_legacy": "mutated_mesa_clear_rock",
|
||||||
|
"category": "mesa",
|
||||||
|
"temperature": 2,
|
||||||
|
"precipitation": "none",
|
||||||
|
"depth": 0.45,
|
||||||
|
"dimension": "overworld",
|
||||||
|
"displayName": "Mesa Plateau M",
|
||||||
|
"color": 15905933,
|
||||||
|
"rainfall": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
{"blocks":{"air":0,
|
||||||
|
"stone":1,
|
||||||
|
"grass":1,
|
||||||
|
"dirt":1,
|
||||||
|
"cobblestone":1,
|
||||||
|
"planks":1,
|
||||||
|
"sapling":0,
|
||||||
|
"bedrock":1,
|
||||||
|
"flowing_water":0,
|
||||||
|
"water":0,
|
||||||
|
"flowing_lava":0,
|
||||||
|
"lava":0,
|
||||||
|
"sand":1,
|
||||||
|
"gravel":1,
|
||||||
|
"gold_ore":1,
|
||||||
|
"iron_ore":1,
|
||||||
|
"coal_ore":1,
|
||||||
|
"log":1,
|
||||||
|
"leaves":1,
|
||||||
|
"sponge":1,
|
||||||
|
"glass":1,
|
||||||
|
"lapis_ore":1,
|
||||||
|
"lapis_block":1,
|
||||||
|
"dispenser":1,
|
||||||
|
"sandstone":1,
|
||||||
|
"noteblock":1,
|
||||||
|
"bed":2,
|
||||||
|
"golden_rail":0,
|
||||||
|
"detector_rail":0,
|
||||||
|
"sticky_piston":[1,1,1,1,1,1,0,0,3,4,5,6,7,8,0,0],
|
||||||
|
"web":0,
|
||||||
|
"tallgrass":0,
|
||||||
|
"deadbush":0,
|
||||||
|
"piston":[1,1,1,1,1,1,0,0,3,4,5,6,7,8,0,0],
|
||||||
|
"piston_head":[10,11,9,12,13,14,0,0,10,11,9,12,13,14,0,0],
|
||||||
|
"wool":1,
|
||||||
|
"piston_extension":0,
|
||||||
|
"yellow_flower":0,
|
||||||
|
"red_flower":0,
|
||||||
|
"brown_mushroom":0,
|
||||||
|
"red_mushroom":0,
|
||||||
|
"gold_block":1,
|
||||||
|
"iron_block":1,
|
||||||
|
"double_stone_slab":1,
|
||||||
|
"stone_slab":[15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16],
|
||||||
|
"brick_block":1,
|
||||||
|
"tnt":1,
|
||||||
|
"bookshelf":1,
|
||||||
|
"mossy_cobblestone":1,
|
||||||
|
"obsidian":1,
|
||||||
|
"torch":0,
|
||||||
|
"fire":0,
|
||||||
|
"mob_spawner":1,
|
||||||
|
"oak_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"chest":25,
|
||||||
|
"redstone_wire":0,
|
||||||
|
"diamond_ore":1,
|
||||||
|
"diamond_block":1,
|
||||||
|
"crafting_table":1,
|
||||||
|
"wheat":0,
|
||||||
|
"farmland":26,
|
||||||
|
"furnace":1,
|
||||||
|
"lit_furnace":1,
|
||||||
|
"standing_sign":0,
|
||||||
|
"wooden_door":[28,29,30,27,29,30,27,28,28,29,30,27,29,30,27,28],
|
||||||
|
"ladder":[27,27,27,29,30,28,27,27,27,29,30,28,27,27,27,29],
|
||||||
|
"rail":0,
|
||||||
|
"stone_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"wall_sign":0,
|
||||||
|
"lever":0,
|
||||||
|
"stone_pressure_plate":0,
|
||||||
|
"iron_door":[28,29,30,27,29,30,27,28,28,29,30,27,29,30,27,28],
|
||||||
|
"wooden_pressure_plate":0,
|
||||||
|
"redstone_ore":1,
|
||||||
|
"lit_redstone_ore":1,
|
||||||
|
"unlit_redstone_torch":0,
|
||||||
|
"redstone_torch":0,
|
||||||
|
"stone_button":0,
|
||||||
|
"snow_layer":[0,31,32,33,15,34,4,35,0,31,32,33,15,34,4,35],
|
||||||
|
"ice":1,
|
||||||
|
"snow":1,
|
||||||
|
"cactus":36,
|
||||||
|
"clay":1,
|
||||||
|
"reeds":0,
|
||||||
|
"jukebox":1,
|
||||||
|
"fence":37,
|
||||||
|
"pumpkin":1,
|
||||||
|
"netherrack":1,
|
||||||
|
"soul_sand":35,
|
||||||
|
"glowstone":1,
|
||||||
|
"portal":0,
|
||||||
|
"lit_pumpkin":1,
|
||||||
|
"cake":[38,39,40,41,42,43,44,0,0,0,0,0,0,0,0,0],
|
||||||
|
"unpowered_repeater":31,
|
||||||
|
"powered_repeater":31,
|
||||||
|
"stained_glass":1,
|
||||||
|
"trapdoor":[45,45,45,45,27,29,30,28,46,46,46,46,27,29,30,28],
|
||||||
|
"monster_egg":1,
|
||||||
|
"stonebrick":1,
|
||||||
|
"brown_mushroom_block":1,
|
||||||
|
"red_mushroom_block":1,
|
||||||
|
"iron_bars":47,
|
||||||
|
"glass_pane":47,
|
||||||
|
"melon_block":1,
|
||||||
|
"pumpkin_stem":0,
|
||||||
|
"melon_stem":0,
|
||||||
|
"vine":0,
|
||||||
|
"fence_gate":[48,49,48,49,0,0,0,0,48,49,48,49,0,0,0,0],
|
||||||
|
"brick_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"stone_brick_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"mycelium":1,
|
||||||
|
"waterlily":50,
|
||||||
|
"nether_brick":1,
|
||||||
|
"nether_brick_fence":37,
|
||||||
|
"nether_brick_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"nether_wart":0,
|
||||||
|
"enchanting_table":4,
|
||||||
|
"brewing_stand":51,
|
||||||
|
"cauldron":52,
|
||||||
|
"end_portal":0,
|
||||||
|
"end_portal_frame":[81,81,81,81,82,82,82,82,81,81,81,81,82,82,82,82],
|
||||||
|
"end_stone":1,
|
||||||
|
"dragon_egg":53,
|
||||||
|
"redstone_lamp":1,
|
||||||
|
"lit_redstone_lamp":1,
|
||||||
|
"double_wooden_slab":1,
|
||||||
|
"wooden_slab":[15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16],
|
||||||
|
"cocoa":[55,56,54,57,58,59,60,61,62,63,64,65,0,0,0,0],
|
||||||
|
"sandstone_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"emerald_ore":1,
|
||||||
|
"ender_chest":25,
|
||||||
|
"tripwire_hook":0,
|
||||||
|
"tripwire":0,
|
||||||
|
"emerald_block":1,
|
||||||
|
"spruce_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"birch_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"jungle_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"command_block":1,
|
||||||
|
"beacon":1,
|
||||||
|
"cobblestone_wall":66,
|
||||||
|
"flower_pot":67,
|
||||||
|
"carrots":0,
|
||||||
|
"potatoes":0,
|
||||||
|
"wooden_button":0,
|
||||||
|
"skull":[69,69,68,70,71,72,69,69,69,69,68,70,71,72,69,69],
|
||||||
|
"anvil":[73,74,73,74,73,74,73,74,73,74,73,74,0,0,0,0],
|
||||||
|
"trapped_chest":25,
|
||||||
|
"light_weighted_pressure_plate":0,
|
||||||
|
"heavy_weighted_pressure_plate":0,
|
||||||
|
"unpowered_comparator":31,
|
||||||
|
"powered_comparator":31,
|
||||||
|
"daylight_detector":33,
|
||||||
|
"redstone_block":1,
|
||||||
|
"quartz_ore":1,
|
||||||
|
"hopper":75,
|
||||||
|
"quartz_block":1,
|
||||||
|
"quartz_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"activator_rail":0,
|
||||||
|
"dropper":1,
|
||||||
|
"stained_hardened_clay":1,
|
||||||
|
"stained_glass_pane":47,
|
||||||
|
"leaves2":1,"log2":1,"acacia_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"dark_oak_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"slime":1,
|
||||||
|
"barrier":1,
|
||||||
|
"iron_trapdoor":[45,45,45,45,27,29,30,28,46,46,46,46,27,29,30,28],
|
||||||
|
"prismarine":1,
|
||||||
|
"sea_lantern":1,
|
||||||
|
"hay_block":1,
|
||||||
|
"carpet":76,
|
||||||
|
"hardened_clay":1,
|
||||||
|
"coal_block":1,
|
||||||
|
"packed_ice":1,
|
||||||
|
"double_plant":0,
|
||||||
|
"standing_banner":0,
|
||||||
|
"wall_banner":0,
|
||||||
|
"daylight_detector_inverted":33,
|
||||||
|
"red_sandstone":1,
|
||||||
|
"red_sandstone_stairs":[18,19,20,17,21,22,23,24,18,19,20,17,21,22,23,24],
|
||||||
|
"double_stone_slab2":1,"stone_slab2":[15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16],
|
||||||
|
"spruce_fence_gate":[48,49,48,49,0,0,0,0,48,49,48,49,0,0,0,0],
|
||||||
|
"birch_fence_gate":[48,49,48,49,0,0,0,0,48,49,48,49,0,0,0,0],
|
||||||
|
"jungle_fence_gate":[48,49,48,49,0,0,0,0,48,49,48,49,0,0,0,0],
|
||||||
|
"dark_oak_fence_gate":[48,49,48,49,0,0,0,0,48,49,48,49,0,0,0,0],
|
||||||
|
"acacia_fence_gate":[48,49,48,49,0,0,0,0,48,49,48,49,0,0,0,0],
|
||||||
|
"spruce_fence":37,
|
||||||
|
"birch_fence":37,
|
||||||
|
"jungle_fence":37,
|
||||||
|
"dark_oak_fence":37,
|
||||||
|
"acacia_fence":37,
|
||||||
|
"spruce_door":[28,29,30,27,29,30,27,28,28,29,30,27,29,30,27,28],
|
||||||
|
"birch_door":[28,29,30,27,29,30,27,28,28,29,30,27,29,30,27,28],
|
||||||
|
"jungle_door":[28,29,30,27,29,30,27,28,28,29,30,27,29,30,27,28],
|
||||||
|
"acacia_door":[28,29,30,27,29,30,27,28,28,29,30,27,29,30,27,28],
|
||||||
|
"dark_oak_door":[28,29,30,27,29,30,27,28,28,29,30,27,29,30,27,28]},
|
||||||
|
"shapes":{"0":[],
|
||||||
|
"1":[[0.0,0.0,0.0,1.0,1.0,1.0]],
|
||||||
|
"2":[[0.0,0.0,0.0,1.0,0.5625,1.0]],
|
||||||
|
"3":[[0.0,0.25,0.0,1.0,1.0,1.0]],
|
||||||
|
"4":[[0.0,0.0,0.0,1.0,0.75,1.0]],
|
||||||
|
"5":[[0.0,0.0,0.25,1.0,1.0,1.0]],
|
||||||
|
"6":[[0.0,0.0,0.0,1.0,1.0,0.75]],
|
||||||
|
"7":[[0.25,0.0,0.0,1.0,1.0,1.0]],
|
||||||
|
"8":[[0.0,0.0,0.0,0.75,1.0,1.0]],
|
||||||
|
"9":[[0.0,0.0,0.0,1.0,1.0,0.25],[0.375,0.375,0.25,0.625,0.625,1.25]],
|
||||||
|
"10":[[0.0,0.0,0.0,1.0,0.25,1.0],[0.375,0.25,0.375,0.625,1.25,0.625]],
|
||||||
|
"11":[[0.0,0.75,0.0,1.0,1.0,1.0],[0.375,-0.25,0.375,0.625,0.75,0.625]],
|
||||||
|
"12":[[0.0,0.0,0.75,1.0,1.0,1.0],[0.375,0.375,-0.25,0.625,0.625,0.75]],
|
||||||
|
"13":[[0.0,0.0,0.0,0.25,1.0,1.0],[0.25,0.375,0.375,1.25,0.625,0.625]],
|
||||||
|
"14":[[0.75,0.0,0.0,1.0,1.0,1.0],[-0.25,0.375,0.375,0.75,0.625,0.625]],
|
||||||
|
"15":[[0.0,0.0,0.0,1.0,0.5,1.0]],
|
||||||
|
"16":[[0.0,0.5,0.0,1.0,1.0,1.0]],
|
||||||
|
"17":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.0,1.0,1.0,0.5]],
|
||||||
|
"18":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.5,0.5,0.0,1.0,1.0,1.0]],
|
||||||
|
"19":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.0,0.5,1.0,1.0]],
|
||||||
|
"20":[[0.0,0.0,0.0,1.0,0.5,1.0],[0.0,0.5,0.5,1.0,1.0,1.0]],
|
||||||
|
"21":[[0.0,0.5,0.0,1.0,1.0,1.0],[0.5,0.0,0.0,1.0,0.5,1.0]],
|
||||||
|
"22":[[0.0,0.5,0.0,1.0,1.0,1.0],[0.0,0.0,0.0,0.5,0.5,1.0]],
|
||||||
|
"23":[[0.0,0.5,0.0,1.0,1.0,1.0],[0.0,0.0,0.5,1.0,0.5,1.0]],
|
||||||
|
"24":[[0.0,0.5,0.0,1.0,1.0,1.0],[0.0,0.0,0.0,1.0,0.5,0.5]],
|
||||||
|
"25":[[0.0625,0.0,0.0625,0.9375,0.875,0.9375]],
|
||||||
|
"26":[[0.0,0.0,0.0,1.0,0.9375,1.0]],
|
||||||
|
"27":[[0.0,0.0,0.8125,1.0,1.0,1.0]],
|
||||||
|
"28":[[0.0,0.0,0.0,0.1875,1.0,1.0]],
|
||||||
|
"29":[[0.0,0.0,0.0,1.0,1.0,0.1875]],
|
||||||
|
"30":[[0.8125,0.0,0.0,1.0,1.0,1.0]],
|
||||||
|
"31":[[0.0,0.0,0.0,1.0,0.125,1.0]],
|
||||||
|
"32":[[0.0,0.0,0.0,1.0,0.25,1.0]],
|
||||||
|
"33":[[0.0,0.0,0.0,1.0,0.375,1.0]],
|
||||||
|
"34":[[0.0,0.0,0.0,1.0,0.625,1.0]],
|
||||||
|
"35":[[0.0,0.0,0.0,1.0,0.875,1.0]],
|
||||||
|
"36":[[0.0625,0.0,0.0625,0.9375,0.9375,0.9375]],
|
||||||
|
"37":[[0.375,0.0,0.375,0.625,1.5,0.625]],
|
||||||
|
"38":[[0.0625,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"39":[[0.1875,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"40":[[0.3125,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"41":[[0.4375,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"42":[[0.5625,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"43":[[0.6875,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"44":[[0.8125,0.0,0.0625,0.9375,0.5,0.9375]],
|
||||||
|
"45":[[0.0,0.0,0.0,1.0,0.1875,1.0]],
|
||||||
|
"46":[[0.0,0.8125,0.0,1.0,1.0,1.0]],
|
||||||
|
"47":[[0.4375,0.0,0.4375,0.5625,1.0,0.5625]],
|
||||||
|
"48":[[0.0,0.0,0.375,1.0,1.5,0.625]],
|
||||||
|
"49":[[0.375,0.0,0.0,0.625,1.5,1.0]],
|
||||||
|
"50":[[0.0625,0.0,0.0625,0.9375,0.09375,0.9375]],
|
||||||
|
"51":[[0.4375,0.0,0.4375,0.5625,0.875,0.5625],[0.0,0.0,0.0,1.0,0.125,1.0]],
|
||||||
|
"52":[[0.0,0.0,0.0,1.0,0.3125,1.0],[0.0,0.0,0.0,0.125,1.0,1.0],[0.0,0.0,0.0,1.0,1.0,0.125],[0.875,0.0,0.0,1.0,1.0,1.0],[0.0,0.0,0.875,1.0,1.0,1.0]],
|
||||||
|
"53":[[0.0625,0.0,0.0625,0.9375,1.0,0.9375]],
|
||||||
|
"54":[[0.375,0.4375,0.0625,0.625,0.75,0.3125]],
|
||||||
|
"55":[[0.375,0.4375,0.6875,0.625,0.75,0.9375]],
|
||||||
|
"56":[[0.0625,0.4375,0.375,0.3125,0.75,0.625]],
|
||||||
|
"57":[[0.6875,0.4375,0.375,0.9375,0.75,0.625]],
|
||||||
|
"58":[[0.3125,0.3125,0.5625,0.6875,0.75,0.9375]],
|
||||||
|
"59":[[0.0625,0.3125,0.3125,0.4375,0.75,0.6875]],
|
||||||
|
"60":[[0.3125,0.3125,0.0625,0.6875,0.75,0.4375]],
|
||||||
|
"61":[[0.5625,0.3125,0.3125,0.9375,0.75,0.6875]],
|
||||||
|
"62":[[0.25,0.1875,0.4375,0.75,0.75,0.9375]],
|
||||||
|
"63":[[0.0625,0.1875,0.25,0.5625,0.75,0.75]],
|
||||||
|
"64":[[0.25,0.1875,0.0625,0.75,0.75,0.5625]],
|
||||||
|
"65":[[0.4375,0.1875,0.25,0.9375,0.75,0.75]],
|
||||||
|
"66":[[0.25,0.0,0.25,0.75,1.5,0.75]],
|
||||||
|
"67":[[0.3125,0.0,0.3125,0.6875,0.375,0.6875]],
|
||||||
|
"68":[[0.25,0.25,0.5,0.75,0.75,1.0]],
|
||||||
|
"69":[[0.25,0.0,0.25,0.75,0.5,0.75]],
|
||||||
|
"70":[[0.25,0.25,0.0,0.75,0.75,0.5]],
|
||||||
|
"71":[[0.5,0.25,0.25,1.0,0.75,0.75]],
|
||||||
|
"72":[[0.0,0.25,0.25,0.5,0.75,0.75]],
|
||||||
|
"73":[[0.125,0.0,0.0,0.875,1.0,1.0]],
|
||||||
|
"74":[[0.0,0.0,0.125,1.0,1.0,0.875]],
|
||||||
|
"75":[[0.0,0.0,0.0,1.0,0.625,1.0],[0.0,0.0,0.0,0.125,1.0,1.0],[0.875,0.0,0.0,1.0,1.0,1.0],[0.0,0.0,0.0,1.0,1.0,0.125],[0.0,0.0,0.875,1.0,1.0,1.0]],
|
||||||
|
"76":[[0.0,0.0,0.0,1.0,0.0625,1.0]],
|
||||||
|
"81":[[0.0,0.0,0.0,1.0,0.8125,1.0]],
|
||||||
|
"82":[[0.0,0.0,0.0,1.0,0.8125,1.0],[0.3125,0.8125,0.3125,0.6875,1.0,0.6875]]}}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,140 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "Speed",
|
||||||
|
"displayName": "Speed",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Slowness",
|
||||||
|
"displayName": "Slowness",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Haste",
|
||||||
|
"displayName": "Haste",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "MiningFatigue",
|
||||||
|
"displayName": "Mining Fatigue",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Strength",
|
||||||
|
"displayName": "Strength",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "InstantHealth",
|
||||||
|
"displayName": "Instant Health",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "InstantDamage",
|
||||||
|
"displayName": "Instant Damage",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "JumpBoost",
|
||||||
|
"displayName": "Jump Boost",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "Nausea",
|
||||||
|
"displayName": "Nausea",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "Regeneration",
|
||||||
|
"displayName": "Regeneration",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "Resistance",
|
||||||
|
"displayName": "Resistance",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"name": "FireResistance",
|
||||||
|
"displayName": "Fire Resistance",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"name": "WaterBreathing",
|
||||||
|
"displayName": "Water Breathing",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"name": "Invisibility",
|
||||||
|
"displayName": "Invisibility",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"name": "Blindness",
|
||||||
|
"displayName": "Blindness",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"name": "NightVision",
|
||||||
|
"displayName": "Night Vision",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"name": "Hunger",
|
||||||
|
"displayName": "Hunger",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"name": "Weakness",
|
||||||
|
"displayName": "Weakness",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"name": "Poison",
|
||||||
|
"displayName": "Poison",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"name": "Wither",
|
||||||
|
"displayName": "Wither",
|
||||||
|
"type": "bad"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"name": "HealthBoost",
|
||||||
|
"displayName": "Health Boost",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"name": "Absorption",
|
||||||
|
"displayName": "Absorption",
|
||||||
|
"type": "good"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"name": "Saturation",
|
||||||
|
"displayName": "Saturation",
|
||||||
|
"type": "good"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,560 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "protection",
|
||||||
|
"displayName": "Protection",
|
||||||
|
"maxLevel": 4,
|
||||||
|
"minCost": {
|
||||||
|
"a": 11,
|
||||||
|
"b": -10
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 11,
|
||||||
|
"b": 1
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"blast_protection",
|
||||||
|
"fire_protection",
|
||||||
|
"projectile_protection"
|
||||||
|
],
|
||||||
|
"category": "armor",
|
||||||
|
"weight": 10,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "fire_protection",
|
||||||
|
"displayName": "Fire Protection",
|
||||||
|
"maxLevel": 4,
|
||||||
|
"minCost": {
|
||||||
|
"a": 8,
|
||||||
|
"b": 2
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 8,
|
||||||
|
"b": 10
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"blast_protection",
|
||||||
|
"protection",
|
||||||
|
"projectile_protection"
|
||||||
|
],
|
||||||
|
"category": "armor",
|
||||||
|
"weight": 5,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "feather_falling",
|
||||||
|
"displayName": "Feather Falling",
|
||||||
|
"maxLevel": 4,
|
||||||
|
"minCost": {
|
||||||
|
"a": 6,
|
||||||
|
"b": -1
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 6,
|
||||||
|
"b": 5
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "armor_feet",
|
||||||
|
"weight": 5,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "blast_protection",
|
||||||
|
"displayName": "Blast Protection",
|
||||||
|
"maxLevel": 4,
|
||||||
|
"minCost": {
|
||||||
|
"a": 8,
|
||||||
|
"b": -3
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 8,
|
||||||
|
"b": 5
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"fire_protection",
|
||||||
|
"protection",
|
||||||
|
"projectile_protection"
|
||||||
|
],
|
||||||
|
"category": "armor",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "projectile_protection",
|
||||||
|
"displayName": "Projectile Protection",
|
||||||
|
"maxLevel": 4,
|
||||||
|
"minCost": {
|
||||||
|
"a": 6,
|
||||||
|
"b": -3
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 6,
|
||||||
|
"b": 3
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"protection",
|
||||||
|
"blast_protection",
|
||||||
|
"fire_protection"
|
||||||
|
],
|
||||||
|
"category": "armor",
|
||||||
|
"weight": 5,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "respiration",
|
||||||
|
"displayName": "Respiration",
|
||||||
|
"maxLevel": 3,
|
||||||
|
"minCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 0
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 30
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "armor_head",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "aqua_affinity",
|
||||||
|
"displayName": "Aqua Affinity",
|
||||||
|
"maxLevel": 1,
|
||||||
|
"minCost": {
|
||||||
|
"a": 0,
|
||||||
|
"b": 1
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 0,
|
||||||
|
"b": 41
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "armor_head",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "thorns",
|
||||||
|
"displayName": "Thorns",
|
||||||
|
"maxLevel": 3,
|
||||||
|
"minCost": {
|
||||||
|
"a": 20,
|
||||||
|
"b": -10
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "armor_chest",
|
||||||
|
"weight": 1,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "depth_strider",
|
||||||
|
"displayName": "Depth Strider",
|
||||||
|
"maxLevel": 3,
|
||||||
|
"minCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 0
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 15
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"frost_walker"
|
||||||
|
],
|
||||||
|
"category": "armor_feet",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"name": "sharpness",
|
||||||
|
"displayName": "Sharpness",
|
||||||
|
"maxLevel": 5,
|
||||||
|
"minCost": {
|
||||||
|
"a": 11,
|
||||||
|
"b": -10
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 11,
|
||||||
|
"b": 10
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"smite",
|
||||||
|
"bane_of_arthropods"
|
||||||
|
],
|
||||||
|
"category": "weapon",
|
||||||
|
"weight": 10,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"name": "smite",
|
||||||
|
"displayName": "Smite",
|
||||||
|
"maxLevel": 5,
|
||||||
|
"minCost": {
|
||||||
|
"a": 8,
|
||||||
|
"b": -3
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 8,
|
||||||
|
"b": 17
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"sharpness",
|
||||||
|
"bane_of_arthropods"
|
||||||
|
],
|
||||||
|
"category": "weapon",
|
||||||
|
"weight": 5,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"name": "bane_of_arthropods",
|
||||||
|
"displayName": "Bane of Arthropods",
|
||||||
|
"maxLevel": 5,
|
||||||
|
"minCost": {
|
||||||
|
"a": 8,
|
||||||
|
"b": -3
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 8,
|
||||||
|
"b": 17
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"smite",
|
||||||
|
"sharpness"
|
||||||
|
],
|
||||||
|
"category": "weapon",
|
||||||
|
"weight": 5,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"name": "knockback",
|
||||||
|
"displayName": "Knockback",
|
||||||
|
"maxLevel": 2,
|
||||||
|
"minCost": {
|
||||||
|
"a": 20,
|
||||||
|
"b": -15
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "weapon",
|
||||||
|
"weight": 5,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"name": "fire_aspect",
|
||||||
|
"displayName": "Fire Aspect",
|
||||||
|
"maxLevel": 2,
|
||||||
|
"minCost": {
|
||||||
|
"a": 20,
|
||||||
|
"b": -10
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "weapon",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"name": "looting",
|
||||||
|
"displayName": "Looting",
|
||||||
|
"maxLevel": 3,
|
||||||
|
"minCost": {
|
||||||
|
"a": 9,
|
||||||
|
"b": 6
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "weapon",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"name": "efficiency",
|
||||||
|
"displayName": "Efficiency",
|
||||||
|
"maxLevel": 5,
|
||||||
|
"minCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": -9
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "digger",
|
||||||
|
"weight": 10,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"name": "silk_touch",
|
||||||
|
"displayName": "Silk Touch",
|
||||||
|
"maxLevel": 1,
|
||||||
|
"minCost": {
|
||||||
|
"a": 0,
|
||||||
|
"b": 15
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"fortune"
|
||||||
|
],
|
||||||
|
"category": "digger",
|
||||||
|
"weight": 1,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"name": "unbreaking",
|
||||||
|
"displayName": "Unbreaking",
|
||||||
|
"maxLevel": 3,
|
||||||
|
"minCost": {
|
||||||
|
"a": 8,
|
||||||
|
"b": -3
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "breakable",
|
||||||
|
"weight": 5,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 35,
|
||||||
|
"name": "fortune",
|
||||||
|
"displayName": "Fortune",
|
||||||
|
"maxLevel": 3,
|
||||||
|
"minCost": {
|
||||||
|
"a": 9,
|
||||||
|
"b": 6
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"silk_touch"
|
||||||
|
],
|
||||||
|
"category": "digger",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 48,
|
||||||
|
"name": "power",
|
||||||
|
"displayName": "Power",
|
||||||
|
"maxLevel": 5,
|
||||||
|
"minCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": -9
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 6
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "bow",
|
||||||
|
"weight": 10,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 49,
|
||||||
|
"name": "punch",
|
||||||
|
"displayName": "Punch",
|
||||||
|
"maxLevel": 2,
|
||||||
|
"minCost": {
|
||||||
|
"a": 20,
|
||||||
|
"b": -8
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 20,
|
||||||
|
"b": 17
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "bow",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 50,
|
||||||
|
"name": "flame",
|
||||||
|
"displayName": "Flame",
|
||||||
|
"maxLevel": 1,
|
||||||
|
"minCost": {
|
||||||
|
"a": 0,
|
||||||
|
"b": 20
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 0,
|
||||||
|
"b": 50
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "bow",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 51,
|
||||||
|
"name": "infinity",
|
||||||
|
"displayName": "Infinity",
|
||||||
|
"maxLevel": 1,
|
||||||
|
"minCost": {
|
||||||
|
"a": 0,
|
||||||
|
"b": 20
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 0,
|
||||||
|
"b": 50
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"mending"
|
||||||
|
],
|
||||||
|
"category": "bow",
|
||||||
|
"weight": 1,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 61,
|
||||||
|
"name": "luck_of_the_sea",
|
||||||
|
"displayName": "Luck of the Sea",
|
||||||
|
"maxLevel": 3,
|
||||||
|
"minCost": {
|
||||||
|
"a": 9,
|
||||||
|
"b": 6
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "fishing_rod",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 62,
|
||||||
|
"name": "lure",
|
||||||
|
"displayName": "Lure",
|
||||||
|
"maxLevel": 3,
|
||||||
|
"minCost": {
|
||||||
|
"a": 9,
|
||||||
|
"b": 6
|
||||||
|
},
|
||||||
|
"maxCost": {
|
||||||
|
"a": 10,
|
||||||
|
"b": 51
|
||||||
|
},
|
||||||
|
"exclude": [],
|
||||||
|
"category": "fishing_rod",
|
||||||
|
"weight": 2,
|
||||||
|
"treasureOnly": false,
|
||||||
|
"curse": false,
|
||||||
|
"tradeable": true,
|
||||||
|
"discoverable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,580 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 48,
|
||||||
|
"internalId": 48,
|
||||||
|
"name": "Mob",
|
||||||
|
"displayName": "Mob",
|
||||||
|
"type": "mob",
|
||||||
|
"width": null,
|
||||||
|
"height": null,
|
||||||
|
"category": "Generic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 49,
|
||||||
|
"internalId": 49,
|
||||||
|
"name": "Monster",
|
||||||
|
"displayName": "Monster",
|
||||||
|
"type": "mob",
|
||||||
|
"width": null,
|
||||||
|
"height": null,
|
||||||
|
"category": "Generic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 50,
|
||||||
|
"internalId": 50,
|
||||||
|
"name": "Creeper",
|
||||||
|
"displayName": "Creeper",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 1.8,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 51,
|
||||||
|
"internalId": 51,
|
||||||
|
"name": "Skeleton",
|
||||||
|
"displayName": "Skeleton",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 1.95,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 52,
|
||||||
|
"internalId": 52,
|
||||||
|
"name": "Spider",
|
||||||
|
"displayName": "Spider",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 1.4,
|
||||||
|
"height": 0.9,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 53,
|
||||||
|
"internalId": 53,
|
||||||
|
"name": "Giant",
|
||||||
|
"displayName": "Giant",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 3.5999999999999996,
|
||||||
|
"height": 10.8,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 54,
|
||||||
|
"internalId": 54,
|
||||||
|
"name": "Zombie",
|
||||||
|
"displayName": "Zombie",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 1.8,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 55,
|
||||||
|
"internalId": 55,
|
||||||
|
"name": "Slime",
|
||||||
|
"displayName": "Slime",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.51000005,
|
||||||
|
"height": 0.51000005,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 56,
|
||||||
|
"internalId": 56,
|
||||||
|
"name": "Ghast",
|
||||||
|
"displayName": "Ghast",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 4,
|
||||||
|
"height": 4,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 57,
|
||||||
|
"internalId": 57,
|
||||||
|
"name": "PigZombie",
|
||||||
|
"displayName": "Zombie Pigman",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 1.8,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 58,
|
||||||
|
"internalId": 58,
|
||||||
|
"name": "Enderman",
|
||||||
|
"displayName": "Enderman",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 2.9,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 59,
|
||||||
|
"internalId": 59,
|
||||||
|
"name": "CaveSpider",
|
||||||
|
"displayName": "Cave Spider",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.7,
|
||||||
|
"height": 0.5,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 60,
|
||||||
|
"internalId": 60,
|
||||||
|
"name": "Silverfish",
|
||||||
|
"displayName": "Silverfish",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.4,
|
||||||
|
"height": 0.3,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 61,
|
||||||
|
"internalId": 61,
|
||||||
|
"name": "Blaze",
|
||||||
|
"displayName": "Blaze",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 1.8,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 62,
|
||||||
|
"internalId": 62,
|
||||||
|
"name": "LavaSlime",
|
||||||
|
"displayName": "Magma Cube",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.51000005,
|
||||||
|
"height": 0.51000005,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 63,
|
||||||
|
"internalId": 63,
|
||||||
|
"name": "EnderDragon",
|
||||||
|
"displayName": "Ender Dragon",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 16,
|
||||||
|
"height": 8,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 64,
|
||||||
|
"internalId": 64,
|
||||||
|
"name": "WitherBoss",
|
||||||
|
"displayName": "Wither",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.9,
|
||||||
|
"height": 3.5,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 65,
|
||||||
|
"internalId": 65,
|
||||||
|
"name": "Bat",
|
||||||
|
"displayName": "Bat",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.5,
|
||||||
|
"height": 0.9,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 66,
|
||||||
|
"internalId": 66,
|
||||||
|
"name": "Witch",
|
||||||
|
"displayName": "Witch",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 1.8,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 67,
|
||||||
|
"internalId": 67,
|
||||||
|
"name": "Endermite",
|
||||||
|
"displayName": "Endermite",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.4,
|
||||||
|
"height": 0.3,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 68,
|
||||||
|
"internalId": 68,
|
||||||
|
"name": "Guardian",
|
||||||
|
"displayName": "Guardian",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.85,
|
||||||
|
"height": 0.85,
|
||||||
|
"category": "Hostile mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 90,
|
||||||
|
"internalId": 90,
|
||||||
|
"name": "Pig",
|
||||||
|
"displayName": "Pig",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.9,
|
||||||
|
"height": 0.9,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 91,
|
||||||
|
"internalId": 91,
|
||||||
|
"name": "Sheep",
|
||||||
|
"displayName": "Sheep",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.9,
|
||||||
|
"height": 1.3,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 92,
|
||||||
|
"internalId": 92,
|
||||||
|
"name": "Cow",
|
||||||
|
"displayName": "Cow",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.9,
|
||||||
|
"height": 1.3,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 93,
|
||||||
|
"internalId": 93,
|
||||||
|
"name": "Chicken",
|
||||||
|
"displayName": "Chicken",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.4,
|
||||||
|
"height": 0.7,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 94,
|
||||||
|
"internalId": 94,
|
||||||
|
"name": "Squid",
|
||||||
|
"displayName": "Squid",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.95,
|
||||||
|
"height": 0.95,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 95,
|
||||||
|
"internalId": 95,
|
||||||
|
"name": "Wolf",
|
||||||
|
"displayName": "Wolf",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 0.8,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 96,
|
||||||
|
"internalId": 96,
|
||||||
|
"name": "MushroomCow",
|
||||||
|
"displayName": "Mooshroom",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.9,
|
||||||
|
"height": 1.3,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 97,
|
||||||
|
"internalId": 97,
|
||||||
|
"name": "SnowMan",
|
||||||
|
"displayName": "Snow Golem",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.7,
|
||||||
|
"height": 1.9,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 98,
|
||||||
|
"internalId": 98,
|
||||||
|
"name": "Ozelot",
|
||||||
|
"displayName": "Ocelot",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 0.8,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 99,
|
||||||
|
"internalId": 99,
|
||||||
|
"name": "VillagerGolem",
|
||||||
|
"displayName": "Iron Golem",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 1.4,
|
||||||
|
"height": 2.9,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 100,
|
||||||
|
"internalId": 100,
|
||||||
|
"name": "EntityHorse",
|
||||||
|
"displayName": "Horse",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 1.4,
|
||||||
|
"height": 1.6,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 101,
|
||||||
|
"internalId": 101,
|
||||||
|
"name": "Rabbit",
|
||||||
|
"displayName": "Rabbit",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 0.7,
|
||||||
|
"category": "Passive mobs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 120,
|
||||||
|
"internalId": 120,
|
||||||
|
"name": "Villager",
|
||||||
|
"displayName": "Villager",
|
||||||
|
"type": "mob",
|
||||||
|
"width": 0.6,
|
||||||
|
"height": 1.8,
|
||||||
|
"category": "NPCs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"internalId": 41,
|
||||||
|
"name": "Boat",
|
||||||
|
"displayName": "Boat",
|
||||||
|
"type": "object",
|
||||||
|
"width": 1.5,
|
||||||
|
"height": 0.6,
|
||||||
|
"category": "Vehicles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"internalId": 1,
|
||||||
|
"name": "Item",
|
||||||
|
"displayName": "Dropped item",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25,
|
||||||
|
"category": "Drops"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"internalId": 42,
|
||||||
|
"name": "MinecartRideable",
|
||||||
|
"displayName": "Minecart",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.98,
|
||||||
|
"height": 0.7,
|
||||||
|
"category": "Vehicles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"internalId": 42,
|
||||||
|
"name": "MinecartRideable",
|
||||||
|
"displayName": "Minecart",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.98,
|
||||||
|
"height": 0.7,
|
||||||
|
"category": "Vehicles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"internalId": 42,
|
||||||
|
"name": "MinecartRideable",
|
||||||
|
"displayName": "Minecart",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.98,
|
||||||
|
"height": 0.7,
|
||||||
|
"category": "Vehicles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 50,
|
||||||
|
"internalId": 20,
|
||||||
|
"name": "PrimedTnt",
|
||||||
|
"displayName": "Primed TNT",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.98,
|
||||||
|
"height": 0.98,
|
||||||
|
"category": "Blocks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 51,
|
||||||
|
"internalId": 200,
|
||||||
|
"name": "EnderCrystal",
|
||||||
|
"displayName": "Ender Crystal",
|
||||||
|
"type": "object",
|
||||||
|
"width": 2,
|
||||||
|
"height": 2,
|
||||||
|
"category": "Immobile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 60,
|
||||||
|
"internalId": 10,
|
||||||
|
"name": "Arrow",
|
||||||
|
"displayName": "Shot arrow",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.5,
|
||||||
|
"height": 0.5,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 61,
|
||||||
|
"internalId": 11,
|
||||||
|
"name": "Snowball",
|
||||||
|
"displayName": "Thrown snowball",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 62,
|
||||||
|
"internalId": 7,
|
||||||
|
"name": "ThrownEgg",
|
||||||
|
"displayName": "Thrown egg",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 63,
|
||||||
|
"internalId": 12,
|
||||||
|
"name": "Fireball",
|
||||||
|
"displayName": "Ghast fireball",
|
||||||
|
"type": "object",
|
||||||
|
"width": 1,
|
||||||
|
"height": 1,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 64,
|
||||||
|
"internalId": 13,
|
||||||
|
"name": "SmallFireball",
|
||||||
|
"displayName": "Blaze fireball",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.3125,
|
||||||
|
"height": 0.3125,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 65,
|
||||||
|
"internalId": 14,
|
||||||
|
"name": "ThrownEnderpearl",
|
||||||
|
"displayName": "Thrown Ender Pearl",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 66,
|
||||||
|
"internalId": 19,
|
||||||
|
"name": "WitherSkull",
|
||||||
|
"displayName": "Wither Skull",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.3125,
|
||||||
|
"height": 0.3125,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 70,
|
||||||
|
"internalId": 21,
|
||||||
|
"name": "FallingSand",
|
||||||
|
"displayName": "Falling block",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.98,
|
||||||
|
"height": 0.98,
|
||||||
|
"category": "Blocks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 71,
|
||||||
|
"internalId": 18,
|
||||||
|
"name": "ItemFrame",
|
||||||
|
"displayName": "Item Frame",
|
||||||
|
"type": "object",
|
||||||
|
"width": null,
|
||||||
|
"height": null,
|
||||||
|
"category": "Immobile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 72,
|
||||||
|
"internalId": 15,
|
||||||
|
"name": "EyeOfEnderSignal",
|
||||||
|
"displayName": "Thrown Eye of Ender",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 73,
|
||||||
|
"internalId": 16,
|
||||||
|
"name": "ThrownPotion",
|
||||||
|
"displayName": "Thrown splash potion",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 74,
|
||||||
|
"internalId": 21,
|
||||||
|
"name": "FallingSand",
|
||||||
|
"displayName": "Falling block",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.98,
|
||||||
|
"height": 0.98,
|
||||||
|
"category": "Blocks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 75,
|
||||||
|
"internalId": 17,
|
||||||
|
"name": "ThrownExpBottle",
|
||||||
|
"displayName": "Thrown Bottle o' Enchanting",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 76,
|
||||||
|
"internalId": 22,
|
||||||
|
"name": "FireworksRocketEntity",
|
||||||
|
"displayName": "Firework Rocket",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25,
|
||||||
|
"category": "Projectiles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 77,
|
||||||
|
"internalId": 8,
|
||||||
|
"name": "LeashKnot",
|
||||||
|
"displayName": "Lead knot",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.5,
|
||||||
|
"height": 0.5,
|
||||||
|
"category": "Immobile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 78,
|
||||||
|
"internalId": 30,
|
||||||
|
"name": "ArmorStand",
|
||||||
|
"displayName": "Armor Stand",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.5,
|
||||||
|
"height": 2,
|
||||||
|
"category": "Immobile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 90,
|
||||||
|
"name": "Fishing Float",
|
||||||
|
"displayName": "Fishing Float",
|
||||||
|
"type": "object",
|
||||||
|
"width": 0.25,
|
||||||
|
"height": 0.25
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 103,
|
||||||
|
"displayName": "Melon",
|
||||||
|
"name": "melon_block",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 1.2,
|
||||||
|
"effectiveQuality": 3.2,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 141,
|
||||||
|
"displayName": "Carrot",
|
||||||
|
"name": "carrots",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 3,
|
||||||
|
"saturation": 4.8,
|
||||||
|
"effectiveQuality": 7.8,
|
||||||
|
"saturationRatio": 1.5999999999999999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 142,
|
||||||
|
"displayName": "Potato",
|
||||||
|
"name": "potatoes",
|
||||||
|
"stackSize": 64,
|
||||||
|
"foodPoints": 1,
|
||||||
|
"saturation": 0.6,
|
||||||
|
"effectiveQuality": 1.6,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 260,
|
||||||
|
"displayName": "Apple",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "apple",
|
||||||
|
"foodPoints": 4,
|
||||||
|
"saturation": 2.4,
|
||||||
|
"effectiveQuality": 6.4,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 282,
|
||||||
|
"displayName": "Mushroom Stew",
|
||||||
|
"stackSize": 1,
|
||||||
|
"name": "mushroom_stew",
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 7.2,
|
||||||
|
"effectiveQuality": 13.2,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 297,
|
||||||
|
"displayName": "Bread",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "bread",
|
||||||
|
"foodPoints": 5,
|
||||||
|
"saturation": 6,
|
||||||
|
"effectiveQuality": 11,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 319,
|
||||||
|
"displayName": "Raw Porkchop",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "porkchop",
|
||||||
|
"foodPoints": 3,
|
||||||
|
"saturation": 1.8,
|
||||||
|
"effectiveQuality": 4.8,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 320,
|
||||||
|
"displayName": "Cooked Porkchop",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "cooked_porkchop",
|
||||||
|
"foodPoints": 8,
|
||||||
|
"saturation": 12.8,
|
||||||
|
"effectiveQuality": 20.8,
|
||||||
|
"saturationRatio": 1.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 322,
|
||||||
|
"displayName": "Golden Apple",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "golden_apple",
|
||||||
|
"variations": [
|
||||||
|
{
|
||||||
|
"metadata": 0,
|
||||||
|
"displayName": "Golden Apple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": 1,
|
||||||
|
"displayName": "Enchanted Golden Apple"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foodPoints": 4,
|
||||||
|
"saturation": 9.6,
|
||||||
|
"effectiveQuality": 13.6,
|
||||||
|
"saturationRatio": 2.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 349,
|
||||||
|
"displayName": "Raw Fish",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "fish",
|
||||||
|
"variations": [
|
||||||
|
{
|
||||||
|
"metadata": 0,
|
||||||
|
"displayName": "Raw Fish"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": 1,
|
||||||
|
"displayName": "Raw Salmon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": 2,
|
||||||
|
"displayName": "Clownfish"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": 3,
|
||||||
|
"displayName": "Pufferfish"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 0.4,
|
||||||
|
"effectiveQuality": 2.4,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 350,
|
||||||
|
"displayName": "Cooked Fish",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "cooked_fish",
|
||||||
|
"foodPoints": 5,
|
||||||
|
"saturation": 6,
|
||||||
|
"effectiveQuality": 11,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 354,
|
||||||
|
"displayName": "Cake",
|
||||||
|
"stackSize": 1,
|
||||||
|
"name": "cake",
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 0.4,
|
||||||
|
"effectiveQuality": 2.4,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 357,
|
||||||
|
"displayName": "Cookie",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "cookie",
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 0.4,
|
||||||
|
"effectiveQuality": 2.4,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 363,
|
||||||
|
"displayName": "Raw Beef",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "beef",
|
||||||
|
"foodPoints": 3,
|
||||||
|
"saturation": 1.8,
|
||||||
|
"effectiveQuality": 4.8,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 364,
|
||||||
|
"displayName": "Steak",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "cooked_beef",
|
||||||
|
"foodPoints": 8,
|
||||||
|
"saturation": 12.8,
|
||||||
|
"effectiveQuality": 20.8,
|
||||||
|
"saturationRatio": 1.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 365,
|
||||||
|
"displayName": "Raw Chicken",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "chicken",
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 1.2,
|
||||||
|
"effectiveQuality": 3.2,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 366,
|
||||||
|
"displayName": "Cooked Chicken",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "cooked_chicken",
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 7.2,
|
||||||
|
"effectiveQuality": 13.2,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 367,
|
||||||
|
"displayName": "Rotten Flesh",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "rotten_flesh",
|
||||||
|
"foodPoints": 4,
|
||||||
|
"saturation": 0.8,
|
||||||
|
"effectiveQuality": 4.8,
|
||||||
|
"saturationRatio": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 375,
|
||||||
|
"displayName": "Spider Eye",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "spider_eye",
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 3.2,
|
||||||
|
"effectiveQuality": 5.2,
|
||||||
|
"saturationRatio": 1.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 393,
|
||||||
|
"displayName": "Baked Potato",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "baked_potato",
|
||||||
|
"foodPoints": 5,
|
||||||
|
"saturation": 7.2,
|
||||||
|
"effectiveQuality": 12.2,
|
||||||
|
"saturationRatio": 1.44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 394,
|
||||||
|
"displayName": "Poisonous Potato",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "poisonous_potato",
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 1.2,
|
||||||
|
"effectiveQuality": 3.2,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 396,
|
||||||
|
"displayName": "Golden Carrot",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "golden_carrot",
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 14.4,
|
||||||
|
"effectiveQuality": 20.4,
|
||||||
|
"saturationRatio": 2.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 400,
|
||||||
|
"displayName": "Pumpkin Pie",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "pumpkin_pie",
|
||||||
|
"foodPoints": 8,
|
||||||
|
"saturation": 4.8,
|
||||||
|
"effectiveQuality": 12.8,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 411,
|
||||||
|
"displayName": "Raw Rabbit",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "rabbit",
|
||||||
|
"foodPoints": 3,
|
||||||
|
"saturation": 1.8,
|
||||||
|
"effectiveQuality": 4.8,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 412,
|
||||||
|
"displayName": "Cooked Rabbit",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "cooked_rabbit",
|
||||||
|
"foodPoints": 5,
|
||||||
|
"saturation": 6,
|
||||||
|
"effectiveQuality": 11,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 413,
|
||||||
|
"displayName": "Rabbit Stew",
|
||||||
|
"stackSize": 1,
|
||||||
|
"name": "rabbit_stew",
|
||||||
|
"foodPoints": 10,
|
||||||
|
"saturation": 12,
|
||||||
|
"effectiveQuality": 22,
|
||||||
|
"saturationRatio": 1.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 423,
|
||||||
|
"displayName": "Raw Mutton",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "mutton",
|
||||||
|
"foodPoints": 2,
|
||||||
|
"saturation": 1.2,
|
||||||
|
"effectiveQuality": 3.2,
|
||||||
|
"saturationRatio": 0.6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 424,
|
||||||
|
"displayName": "Cooked Mutton",
|
||||||
|
"stackSize": 64,
|
||||||
|
"name": "cooked_mutton",
|
||||||
|
"foodPoints": 6,
|
||||||
|
"saturation": 9.6,
|
||||||
|
"effectiveQuality": 15.6,
|
||||||
|
"saturationRatio": 1.5999999999999999
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "harp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "doubleBass"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "snareDrum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "sticks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "bassDrum"
|
||||||
|
}
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"rock": {
|
||||||
|
"257": 6,
|
||||||
|
"270": 2,
|
||||||
|
"274": 4,
|
||||||
|
"278": 8,
|
||||||
|
"285": 12
|
||||||
|
},
|
||||||
|
"wood": {
|
||||||
|
"258": 6,
|
||||||
|
"271": 2,
|
||||||
|
"275": 4,
|
||||||
|
"279": 8,
|
||||||
|
"286": 12
|
||||||
|
},
|
||||||
|
"plant": {
|
||||||
|
"258": 6,
|
||||||
|
"267": 1.5,
|
||||||
|
"268": 1.5,
|
||||||
|
"271": 2,
|
||||||
|
"272": 1.5,
|
||||||
|
"275": 4,
|
||||||
|
"276": 1.5,
|
||||||
|
"279": 8,
|
||||||
|
"283": 1.5,
|
||||||
|
"286": 12
|
||||||
|
},
|
||||||
|
"melon": {
|
||||||
|
"267": 1.5,
|
||||||
|
"268": 1.5,
|
||||||
|
"272": 1.5,
|
||||||
|
"276": 1.5,
|
||||||
|
"283": 1.5
|
||||||
|
},
|
||||||
|
"leaves": {
|
||||||
|
"267": 1.5,
|
||||||
|
"268": 1.5,
|
||||||
|
"272": 1.5,
|
||||||
|
"276": 1.5,
|
||||||
|
"283": 1.5,
|
||||||
|
"359": 6
|
||||||
|
},
|
||||||
|
"dirt": {
|
||||||
|
"256": 6,
|
||||||
|
"269": 2,
|
||||||
|
"273": 4,
|
||||||
|
"277": 8,
|
||||||
|
"284": 12
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"267": 15,
|
||||||
|
"268": 15,
|
||||||
|
"272": 15,
|
||||||
|
"276": 15,
|
||||||
|
"283": 15,
|
||||||
|
"359": 15
|
||||||
|
},
|
||||||
|
"wool": {
|
||||||
|
"359": 4.8
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "explode"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "largeexplode"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "hugeexplosion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "fireworksSpark"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "bubble"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "splash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "wake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "suspended"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "depthsuspend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "crit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "magicCrit"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"name": "smoke"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"name": "largesmoke"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"name": "spell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"name": "instantSpell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"name": "mobSpell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"name": "mobSpellAmbient"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"name": "witchMagic"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"name": "dripWater"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"name": "dripLava"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"name": "angryVillager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"name": "happyVillager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"name": "townaura"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"name": "note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"name": "portal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"name": "enchantmenttable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"name": "flame"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"name": "lava"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"name": "footstep"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"name": "cloud"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"name": "reddust"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"name": "snowballpoof"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"name": "snowshovel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"name": "slime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"name": "heart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 35,
|
||||||
|
"name": "barrier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 36,
|
||||||
|
"name": "iconcrack_"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 37,
|
||||||
|
"name": "blockcrack_"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 38,
|
||||||
|
"name": "blockdust_"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 39,
|
||||||
|
"name": "droplet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 40,
|
||||||
|
"name": "take"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 41,
|
||||||
|
"name": "mobappearance"
|
||||||
|
}
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"version":47,
|
||||||
|
"minecraftVersion":"1.8.8",
|
||||||
|
"majorVersion":"1.8"
|
||||||
|
}
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"name": "Player",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "craft result",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "craft grid",
|
||||||
|
"index": 1,
|
||||||
|
"size": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "armor",
|
||||||
|
"index": 5,
|
||||||
|
"size": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "helmet",
|
||||||
|
"index": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chestplate",
|
||||||
|
"index": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "leggings",
|
||||||
|
"index": 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "boots",
|
||||||
|
"index": 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "EntityHorse",
|
||||||
|
"name": "Horse",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "saddle",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "armor",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "storage",
|
||||||
|
"index": 2,
|
||||||
|
"size": 15
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openedWith": [
|
||||||
|
{
|
||||||
|
"type": "entity",
|
||||||
|
"id": 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:anvil",
|
||||||
|
"name": "Anvil",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "tool",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "combined",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "result",
|
||||||
|
"index": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": [
|
||||||
|
"repair cost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:beacon",
|
||||||
|
"name": "Beacon",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "input",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": [
|
||||||
|
"level",
|
||||||
|
"effect 1",
|
||||||
|
"effect 2"
|
||||||
|
],
|
||||||
|
"openedWith": [
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"id": 138
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:brewing_stand",
|
||||||
|
"name": "Brewing Stand",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "ingredient",
|
||||||
|
"index": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "result",
|
||||||
|
"index": 0,
|
||||||
|
"size": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": [
|
||||||
|
"brew time"
|
||||||
|
],
|
||||||
|
"openedWith": [
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"id": 117
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:chest",
|
||||||
|
"name": "Chest"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:container",
|
||||||
|
"name": "Container"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:crafting_table",
|
||||||
|
"name": "Workbench",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "craft result",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "craft grid",
|
||||||
|
"index": 1,
|
||||||
|
"size": 9
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:dispenser",
|
||||||
|
"name": "Dispenser"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:dropper",
|
||||||
|
"name": "Dropper"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:enchanting_table",
|
||||||
|
"name": "Enchantment Table",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "enchanted",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lapis",
|
||||||
|
"index": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": [
|
||||||
|
"xp 1",
|
||||||
|
"xp 2",
|
||||||
|
"xp 3",
|
||||||
|
"seed",
|
||||||
|
"tooltip 1",
|
||||||
|
"tooltip 2",
|
||||||
|
"tooltip 3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:furnace",
|
||||||
|
"name": "Furnace",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "smelted",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "fuel",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "result",
|
||||||
|
"index": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": [
|
||||||
|
"fuel",
|
||||||
|
"fuel max",
|
||||||
|
"progress",
|
||||||
|
"progress max"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:hopper",
|
||||||
|
"name": "Hopper"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minecraft:villager",
|
||||||
|
"name": "NPC Trade",
|
||||||
|
"slots": [
|
||||||
|
{
|
||||||
|
"name": "give 1",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "give 2",
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "take",
|
||||||
|
"index": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -20,6 +20,7 @@ type VersionInfo struct {
|
|||||||
|
|
||||||
var ProtocolVersions []VersionInfo
|
var ProtocolVersions []VersionInfo
|
||||||
|
|
||||||
|
// LookUpVersionByName returns the VersionInfo for the given Minecraft version name (e.g. "1.21.11").
|
||||||
func LookUpVersionByName(name string) (ret VersionInfo, err error) {
|
func LookUpVersionByName(name string) (ret VersionInfo, err error) {
|
||||||
for _, v := range ProtocolVersions {
|
for _, v := range ProtocolVersions {
|
||||||
if v.MinecraftVersion == name {
|
if v.MinecraftVersion == name {
|
||||||
@@ -31,6 +32,7 @@ func LookUpVersionByName(name string) (ret VersionInfo, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookUpVersionByProtocolVersion returns the VersionInfo for the given protocol version number.
|
||||||
func LookUpVersionByProtocolVersion(version int32) (ret VersionInfo, err error) {
|
func LookUpVersionByProtocolVersion(version int32) (ret VersionInfo, err error) {
|
||||||
for _, v := range ProtocolVersions {
|
for _, v := range ProtocolVersions {
|
||||||
if v.Version == version {
|
if v.Version == version {
|
||||||
@@ -42,6 +44,7 @@ func LookUpVersionByProtocolVersion(version int32) (ret VersionInfo, err error)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookUpVersionByDataVersion returns the VersionInfo for the given Minecraft data version number.
|
||||||
func LookUpVersionByDataVersion(version int32) (ret VersionInfo, err error) {
|
func LookUpVersionByDataVersion(version int32) (ret VersionInfo, err error) {
|
||||||
for _, v := range ProtocolVersions {
|
for _, v := range ProtocolVersions {
|
||||||
if v.DataVersion == version {
|
if v.DataVersion == version {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
//go:embed registry/*
|
//go:embed registry/*
|
||||||
var Registry embed.FS
|
var Registry embed.FS
|
||||||
|
|
||||||
|
// LoadRegistry loads a registry NBT file by name from the embedded registry data.
|
||||||
func LoadRegistry(name string) (n *nbt2.Tag, err error) {
|
func LoadRegistry(name string) (n *nbt2.Tag, err error) {
|
||||||
r, err := Registry.Open("registry/" + name + ".nbt")
|
r, err := Registry.Open("registry/" + name + ".nbt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ var WhileClosingErr = errors.New("error while closing")
|
|||||||
var ContextDoneErr = errors.New("context done")
|
var ContextDoneErr = errors.New("context done")
|
||||||
var RecvNotOkErr = errors.New("recv not ok")
|
var RecvNotOkErr = errors.New("recv not ok")
|
||||||
|
|
||||||
|
// ValidateHandler validates that h is a function taking a single argument and returning an error.
|
||||||
|
// It panics if the handler does not match the expected signature.
|
||||||
func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
|
func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
|
||||||
hv = reflect.ValueOf(h)
|
hv = reflect.ValueOf(h)
|
||||||
if hv.Kind() != reflect.Func {
|
if hv.Kind() != reflect.Func {
|
||||||
@@ -139,9 +141,11 @@ func (l *Loop) RegisterLow(eventType reflect.Type, hv reflect.Value, critical bo
|
|||||||
|
|
||||||
// RegisterCritical registers a handler that will close the loop if it returns an error.
|
// RegisterCritical registers a handler that will close the loop if it returns an error.
|
||||||
// Warning: If a bad handler is passed in the program will panic.
|
// Warning: If a bad handler is passed in the program will panic.
|
||||||
func (l *Loop) RegisterCritical(h any) {
|
func (l *Loop) RegisterCritical(hs ...any) {
|
||||||
eventType, hv := ValidateHandler(h)
|
for _, h := range hs {
|
||||||
l.HandlerFunctions[eventType] = append(l.HandlerFunctions[eventType], hv)
|
eventType, hv := ValidateHandler(h)
|
||||||
|
l.HandlerFunctions[eventType] = append(l.HandlerFunctions[eventType], hv)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FireFound triggers all handlers registered for the specified event type and returns if handlers were found or an error occurred.
|
// FireFound triggers all handlers registered for the specified event type and returns if handlers were found or an error occurred.
|
||||||
@@ -233,6 +237,7 @@ func (l *Loop) StartLoop() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewLoop creates a new Loop with a background context.
|
||||||
func NewLoop() (l *Loop) {
|
func NewLoop() (l *Loop) {
|
||||||
return NewLoopCtx(context.Background())
|
return NewLoopCtx(context.Background())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ type BiomeNoise struct {
|
|||||||
Temperature, Humidity, ContinentalNess, Erosion, Shift, Weirdness DoublePerlin
|
Temperature, Humidity, ContinentalNess, Erosion, Shift, Weirdness DoublePerlin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BiomeNoiseFromXoroshiro creates a BiomeNoise from the given Xoroshiro source.
|
||||||
func BiomeNoiseFromXoroshiro(x Xoroshiro, large bool) (v BiomeNoise) {
|
func BiomeNoiseFromXoroshiro(x Xoroshiro, large bool) (v BiomeNoise) {
|
||||||
x = x.Split()
|
x = x.Split()
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ type DoublePerlin struct {
|
|||||||
B Octave
|
B Octave
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DoublePerlinFromXoroshiro creates a DoublePerlin from the given Xoroshiro source.
|
||||||
func DoublePerlinFromXoroshiro(x *Xoroshiro, amplitudes []float64, omin int, lenn int, nmax int) (d DoublePerlin) {
|
func DoublePerlinFromXoroshiro(x *Xoroshiro, amplitudes []float64, omin int, lenn int, nmax int) (d DoublePerlin) {
|
||||||
na := -1
|
na := -1
|
||||||
nb := -1
|
nb := -1
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ func (n PerlinNoise) Sample(x, y, z float64, yamp, ymin float64) (v float64) {
|
|||||||
return lerp(t3, l1, l5)
|
return lerp(t3, l1, l5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PerlinNoiseFromXoroshiro initializes a PerlinNoise from the given Xoroshiro source.
|
||||||
func PerlinNoiseFromXoroshiro(x Xoroshiro) (p PerlinNoise) {
|
func PerlinNoiseFromXoroshiro(x Xoroshiro) (p PerlinNoise) {
|
||||||
p.xoffset = x.NextFloat64() * 256.0
|
p.xoffset = x.NextFloat64() * 256.0
|
||||||
p.yoffset = x.NextFloat64() * 256.0
|
p.yoffset = x.NextFloat64() * 256.0
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ var octaveLacunaInits = []float64{1, .5, .25, 1. / 8, 1. / 16, 1. / 32, 1. / 64,
|
|||||||
var octavePersistInits = []float64{0, 1, 2. / 3, 4. / 7, 8. / 15, 16. / 31, 32. / 63, 64. / 127, 128. / 255, 256. / 511, 512. / 1023,
|
var octavePersistInits = []float64{0, 1, 2. / 3, 4. / 7, 8. / 15, 16. / 31, 32. / 63, 64. / 127, 128. / 255, 256. / 511, 512. / 1023,
|
||||||
1024. / 2047, 2048. / 4095, 4096. / 8191, 8192. / 16383, 16384. / 32767, 32768. / 65535}
|
1024. / 2047, 2048. / 4095, 4096. / 8191, 8192. / 16383, 16384. / 32767, 32768. / 65535}
|
||||||
|
|
||||||
|
// OctaveFromXoroshiro creates an Octave (multi-octave Perlin noise) from the given Xoroshiro source.
|
||||||
func OctaveFromXoroshiro(x *Xoroshiro, amplitudes []float64, omin int, lenn int, nmax int) (o Octave) {
|
func OctaveFromXoroshiro(x *Xoroshiro, amplitudes []float64, omin int, lenn int, nmax int) (o Octave) {
|
||||||
if (-omin < 0 || -omin >= len(octaveLacunaInits)) || (lenn < 0 || lenn >= len(octavePersistInits)) {
|
if (-omin < 0 || -omin >= len(octaveLacunaInits)) || (lenn < 0 || lenn >= len(octavePersistInits)) {
|
||||||
panic("octave initialization out of range")
|
panic("octave initialization out of range")
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ func landSpline(f, g, h, i, j, k float32, bl bool) Spline {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateBiomeDepthSpline builds the Minecraft biome depth spline used for terrain generation.
|
||||||
func CreateBiomeDepthSpline() Spline {
|
func CreateBiomeDepthSpline() Spline {
|
||||||
sp1 := landSpline(-0.15, 0.00, 0.0, 0.1, 0.00, -0.03, false)
|
sp1 := landSpline(-0.15, 0.00, 0.0, 0.1, 0.00, -0.03, false)
|
||||||
sp2 := landSpline(-0.10, 0.03, 0.1, 0.1, 0.01, -0.03, false)
|
sp2 := landSpline(-0.10, 0.03, 0.1, 0.1, 0.01, -0.03, false)
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ func (x *Xoroshiro) NextFloat64() float64 {
|
|||||||
return float64(x.Next()>>(64-53)) * 1.1102230246251565e-16
|
return float64(x.Next()>>(64-53)) * 1.1102230246251565e-16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XoroshiroFromString creates an Xoroshiro from an MD5 hash of the given string.
|
||||||
func XoroshiroFromString(s string) (x Xoroshiro) {
|
func XoroshiroFromString(s string) (x Xoroshiro) {
|
||||||
sum := md5.Sum([]byte(s))
|
sum := md5.Sum([]byte(s))
|
||||||
x.L = binary.BigEndian.Uint64(sum[:8])
|
x.L = binary.BigEndian.Uint64(sum[:8])
|
||||||
@@ -49,6 +50,7 @@ func XoroshiroFromString(s string) (x Xoroshiro) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XoroshiroFromSeed creates an Xoroshiro from an int64 seed.
|
||||||
func XoroshiroFromSeed(seed int64) (x Xoroshiro) {
|
func XoroshiroFromSeed(seed int64) (x Xoroshiro) {
|
||||||
x.L = uint64(seed) ^ 0x6a09e667f3bcc909
|
x.L = uint64(seed) ^ 0x6a09e667f3bcc909
|
||||||
x.H = x.L + 0x9e3779b97f4a7c15
|
x.H = x.L + 0x9e3779b97f4a7c15
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ type Attribute struct {
|
|||||||
Base float64
|
Base float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadLevelData reads and decodes the level.dat NBT file from a reader.
|
||||||
func ReadLevelData(r io.Reader) (l Level, err error) {
|
func ReadLevelData(r io.Reader) (l Level, err error) {
|
||||||
var lDataWrapped struct{ Data Level }
|
var lDataWrapped struct{ Data Level }
|
||||||
err = nbt.ReadFile(r, &lDataWrapped)
|
err = nbt.ReadFile(r, &lDataWrapped)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type ChunkHeader struct {
|
|||||||
Chunk, Time [ChunksRegionFile]int32
|
Chunk, Time [ChunksRegionFile]int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadChunkHeader reads the chunk header from an MCA region file.
|
||||||
func ReadChunkHeader(r *os.File) (ch *ChunkHeader, err error) {
|
func ReadChunkHeader(r *os.File) (ch *ChunkHeader, err error) {
|
||||||
var h ChunkHeader
|
var h ChunkHeader
|
||||||
err = binary.Read(r, binary.BigEndian, &h)
|
err = binary.Read(r, binary.BigEndian, &h)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type Chunk struct {
|
|||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadChunkFromChunkPacketData decodes chunk packet data from a reader into a Chunk.
|
||||||
func ReadChunkFromChunkPacketData(r io.Reader, version string, worldHeight int) (c *Chunk, err error) {
|
func ReadChunkFromChunkPacketData(r io.Reader, version string, worldHeight int) (c *Chunk, err error) {
|
||||||
c = &Chunk{}
|
c = &Chunk{}
|
||||||
c.Version = version
|
c.Version = version
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ type Structure struct {
|
|||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadFromPath loads a litematic file from disk.
|
||||||
func LoadFromPath(path string) (s *Structure, err error) {
|
func LoadFromPath(path string) (s *Structure, err error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -104,6 +105,7 @@ func LoadFromPath(path string) (s *Structure, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadFromFile reads and parses a litematic file from a reader.
|
||||||
func LoadFromFile(r io.Reader) (s *Structure, err error) {
|
func LoadFromFile(r io.Reader) (s *Structure, err error) {
|
||||||
n, err := nbt.ReadUnstructuredFile(r)
|
n, err := nbt.ReadUnstructuredFile(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -112,6 +114,7 @@ func LoadFromFile(r io.Reader) (s *Structure, err error) {
|
|||||||
return Load(n.Value)
|
return Load(n.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load decodes raw NBT data into a litematic Structure.
|
||||||
func Load(v any) (s *Structure, err error) {
|
func Load(v any) (s *Structure, err error) {
|
||||||
s = &Structure{}
|
s = &Structure{}
|
||||||
err = mapstructure.Decode(v, &s.File)
|
err = mapstructure.Decode(v, &s.File)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type Section struct {
|
|||||||
Blocks, Biomes *Storage
|
Blocks, Biomes *Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeBiomeFormat returns the StorageFormat for biome data at the given version.
|
||||||
func MakeBiomeFormat(version string) StorageFormat {
|
func MakeBiomeFormat(version string) StorageFormat {
|
||||||
directBpe := uint8(math.Ceil(math.Log2(float64(len(data2.BiomesForVersion(version))))))
|
directBpe := uint8(math.Ceil(math.Log2(float64(len(data2.BiomesForVersion(version))))))
|
||||||
return StorageFormat{
|
return StorageFormat{
|
||||||
@@ -39,6 +40,7 @@ func MakeBiomeFormat(version string) StorageFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeBlockFormat returns the StorageFormat for block data at the given version.
|
||||||
func MakeBlockFormat(version string) StorageFormat {
|
func MakeBlockFormat(version string) StorageFormat {
|
||||||
directBpe := uint8(math.Ceil(math.Log2(float64(len(data2.BlocksForVersion(version))))))
|
directBpe := uint8(math.Ceil(math.Log2(float64(len(data2.BlocksForVersion(version))))))
|
||||||
return StorageFormat{
|
return StorageFormat{
|
||||||
@@ -51,6 +53,7 @@ func MakeBlockFormat(version string) StorageFormat {
|
|||||||
|
|
||||||
var BadPaletteLenErr = errors.New("bad palette length")
|
var BadPaletteLenErr = errors.New("bad palette length")
|
||||||
|
|
||||||
|
// ReadSectionStorage reads a packed section storage from a reader.
|
||||||
func ReadSectionStorage(r io.Reader, format StorageFormat) (s *Storage, err error) {
|
func ReadSectionStorage(r io.Reader, format StorageFormat) (s *Storage, err error) {
|
||||||
var bpe uint8
|
var bpe uint8
|
||||||
err = binary.Read(r, binary.BigEndian, &bpe)
|
err = binary.Read(r, binary.BigEndian, &bpe)
|
||||||
@@ -97,6 +100,7 @@ func ReadSectionStorage(r io.Reader, format StorageFormat) (s *Storage, err erro
|
|||||||
return format.ImportFromReader(r, bpe, pallette)
|
return format.ImportFromReader(r, bpe, pallette)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteSectionStorage writes a packed section storage to a writer.
|
||||||
func WriteSectionStorage(w io.Writer, s *Storage) (err error) {
|
func WriteSectionStorage(w io.Writer, s *Storage) (err error) {
|
||||||
err = binary.Write(w, binary.BigEndian, s.bpe)
|
err = binary.Write(w, binary.BigEndian, s.bpe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -122,6 +126,7 @@ func WriteSectionStorage(w io.Writer, s *Storage) (err error) {
|
|||||||
return binary.Write(w, binary.BigEndian, s.data)
|
return binary.Write(w, binary.BigEndian, s.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SectionDecodePacket decodes a chunk section from the Minecraft packet wire format.
|
||||||
func SectionDecodePacket(r io.Reader, version string) (s Section, err error) {
|
func SectionDecodePacket(r io.Reader, version string) (s Section, err error) {
|
||||||
s = Section{}
|
s = Section{}
|
||||||
err = binary.Read(r, binary.BigEndian, &s.BlockCount)
|
err = binary.Read(r, binary.BigEndian, &s.BlockCount)
|
||||||
@@ -136,6 +141,7 @@ func SectionDecodePacket(r io.Reader, version string) (s Section, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SectionEncodePacket encodes a chunk section to the Minecraft packet wire format.
|
||||||
func SectionEncodePacket(w io.Writer, s Section) (err error) {
|
func SectionEncodePacket(w io.Writer, s Section) (err error) {
|
||||||
err = binary.Write(w, binary.BigEndian, s.BlockCount)
|
err = binary.Write(w, binary.BigEndian, s.BlockCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -149,6 +155,7 @@ func SectionEncodePacket(w io.Writer, s Section) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SectionEquals returns whether two sections are equal.
|
||||||
func SectionEquals(a, b Section) bool {
|
func SectionEquals(a, b Section) bool {
|
||||||
if a.BlockCount != b.BlockCount {
|
if a.BlockCount != b.BlockCount {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -250,6 +250,7 @@ func (r *Storage) Equals(other *Storage) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CountStorage counts the number of elements in the storage that match the given value.
|
||||||
func CountStorage(s *Storage, v int32) (a int) {
|
func CountStorage(s *Storage, v int32) (a int) {
|
||||||
for i := range s.format.Len {
|
for i := range s.format.Len {
|
||||||
if w, _ := s.Get(i); w == v {
|
if w, _ := s.Get(i); w == v {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/go-viper/mapstructure/v2"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Load reads a structure NBT file and returns the decoded Structure.
|
||||||
func Load(fileName string) (s *Structure, err error) {
|
func Load(fileName string) (s *Structure, err error) {
|
||||||
n, err := nbt.ReadUnstructuredFilePath(fileName)
|
n, err := nbt.ReadUnstructuredFilePath(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package mapstructure
|
|||||||
|
|
||||||
var DefaultFormat = NewFormat("mapstructure", WithRequireAll(), WithTrySnakeCase(), WithTryLowCase())
|
var DefaultFormat = NewFormat("mapstructure", WithRequireAll(), WithTrySnakeCase(), WithTryLowCase())
|
||||||
|
|
||||||
|
// Decode is a stub placeholder. Use Format.Decode instead.
|
||||||
func Decode(data any, v any) error {
|
func Decode(data any, v any) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type Format struct {
|
|||||||
encoders map[reflect.Type]func(any, string) (any, error)
|
encoders map[reflect.Type]func(any, string) (any, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFormat creates a new Format with the given name and options.
|
||||||
func NewFormat(name string, opts ...Option) *Format {
|
func NewFormat(name string, opts ...Option) *Format {
|
||||||
f := &Format{
|
f := &Format{
|
||||||
Name: name,
|
Name: name,
|
||||||
@@ -39,30 +40,35 @@ func NewFormat(name string, opts ...Option) *Format {
|
|||||||
|
|
||||||
type Option func(*Format)
|
type Option func(*Format)
|
||||||
|
|
||||||
|
// WithRequireAll returns an Option that requires all struct fields to be present in the map.
|
||||||
func WithRequireAll() Option {
|
func WithRequireAll() Option {
|
||||||
return func(f *Format) {
|
return func(f *Format) {
|
||||||
f.requireAll = true
|
f.requireAll = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithErrorOnExtra returns an Option that causes an error when the map contains unmatched keys.
|
||||||
func WithErrorOnExtra() Option {
|
func WithErrorOnExtra() Option {
|
||||||
return func(f *Format) {
|
return func(f *Format) {
|
||||||
f.errorOnExtra = true
|
f.errorOnExtra = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTrySnakeCase returns an Option that tries to match struct fields using snake_case names.
|
||||||
func WithTrySnakeCase() Option {
|
func WithTrySnakeCase() Option {
|
||||||
return func(f *Format) {
|
return func(f *Format) {
|
||||||
f.trySnakeCase = true
|
f.trySnakeCase = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTryLowCase returns an Option that tries to match struct fields using lowercase names.
|
||||||
func WithTryLowCase() Option {
|
func WithTryLowCase() Option {
|
||||||
return func(f *Format) {
|
return func(f *Format) {
|
||||||
f.tryLowCase = true
|
f.tryLowCase = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTypeCodec returns an Option that registers custom decode/encode functions for a type.
|
||||||
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)
|
||||||
@@ -94,6 +100,7 @@ func WithTypeCodec(decodeFn, encodeFn any) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode populates target (a pointer to a struct) from data (a map[string]any).
|
||||||
func (f *Format) Decode(data any, target any) error {
|
func (f *Format) Decode(data any, target any) error {
|
||||||
v := reflect.ValueOf(target)
|
v := reflect.ValueOf(target)
|
||||||
if v.Kind() != reflect.Pointer || v.IsNil() {
|
if v.Kind() != reflect.Pointer || v.IsNil() {
|
||||||
@@ -110,6 +117,7 @@ func (f *Format) Decode(data any, target any) error {
|
|||||||
return f.decode(m, v)
|
return f.decode(m, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encode converts source (a struct or pointer to a struct) into a map[string]any.
|
||||||
func (f *Format) Encode(source any) (map[string]any, error) {
|
func (f *Format) Encode(source any) (map[string]any, error) {
|
||||||
v := reflect.ValueOf(source)
|
v := reflect.ValueOf(source)
|
||||||
if v.Kind() == reflect.Pointer {
|
if v.Kind() == reflect.Pointer {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
var Format = mapstructure.NewFormat("nbt", mapstructure.WithRequireAll(), mapstructure.WithTrySnakeCase(), mapstructure.WithTryLowCase())
|
var Format = mapstructure.NewFormat("nbt", mapstructure.WithRequireAll(), mapstructure.WithTrySnakeCase(), mapstructure.WithTryLowCase())
|
||||||
|
|
||||||
|
// ReadFile reads a gzipped NBT file from r and decodes it into data using the package-level Format.
|
||||||
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -18,6 +19,7 @@ func ReadFile(file io.Reader, data any) (err error) {
|
|||||||
return Format.Decode(n.Value, data)
|
return Format.Decode(n.Value, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteUnstructuredFilePath writes an NBT tag as a gzipped file to the given filesystem path.
|
||||||
func WriteUnstructuredFilePath(path string, n *Tag) (err error) {
|
func WriteUnstructuredFilePath(path string, n *Tag) (err error) {
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -27,6 +29,7 @@ func WriteUnstructuredFilePath(path string, n *Tag) (err error) {
|
|||||||
return WriteUnstructuredFile(f, n)
|
return WriteUnstructuredFile(f, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WriteUnstructuredFile writes an NBT tag as a gzipped file to w.
|
||||||
func WriteUnstructuredFile(file io.Writer, n *Tag) (err error) {
|
func WriteUnstructuredFile(file io.Writer, n *Tag) (err error) {
|
||||||
gzw := gzip.NewWriter(file)
|
gzw := gzip.NewWriter(file)
|
||||||
defer gzw.Close()
|
defer gzw.Close()
|
||||||
@@ -34,6 +37,7 @@ func WriteUnstructuredFile(file io.Writer, n *Tag) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadUnstructuredFile reads a gzipped NBT file from r and returns the root Tag.
|
||||||
func ReadUnstructuredFile(file io.Reader) (n *Tag, err error) {
|
func ReadUnstructuredFile(file io.Reader) (n *Tag, err error) {
|
||||||
r, err := gzip.NewReader(file)
|
r, err := gzip.NewReader(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -45,6 +49,7 @@ func ReadUnstructuredFile(file io.Reader) (n *Tag, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadUnstructuredFilePath reads a gzipped NBT file from the given filesystem path and returns the root Tag.
|
||||||
func ReadUnstructuredFilePath(path string) (n *Tag, err error) {
|
func ReadUnstructuredFilePath(path string) (n *Tag, err error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ type Conn struct {
|
|||||||
Log *slog.Logger
|
Log *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartConn starts the connection event loop, registering the receive routine and login tick handler.
|
||||||
func (c *Conn) StartConn() (err error) {
|
func (c *Conn) StartConn() (err error) {
|
||||||
c.RegisterEventSource(c.ActivateReceiveRoutine())
|
c.RegisterEventSource(c.ActivateReceiveRoutine())
|
||||||
c.RegisterCritical(c.OnLoginTick)
|
c.RegisterCritical(c.OnLoginTick)
|
||||||
@@ -81,6 +82,7 @@ func (c *Conn) StartConn() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnLoginTick handles a login tick by receiving a packet and firing it as an event.
|
||||||
func (c *Conn) OnLoginTick(_ LoginTick) (err error) {
|
func (c *Conn) OnLoginTick(_ LoginTick) (err error) {
|
||||||
packet, err := c.Receive()
|
packet, err := c.Receive()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -90,30 +92,35 @@ func (c *Conn) OnLoginTick(_ LoginTick) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetState sets the connection's protocol state.
|
||||||
func (c *Conn) SetState(state proto_base.State) {
|
func (c *Conn) SetState(state proto_base.State) {
|
||||||
c.stateMutex.Lock()
|
c.stateMutex.Lock()
|
||||||
defer c.stateMutex.Unlock()
|
defer c.stateMutex.Unlock()
|
||||||
c.state = state
|
c.state = state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// State returns the current connection protocol state.
|
||||||
func (c *Conn) State() proto_base.State {
|
func (c *Conn) State() proto_base.State {
|
||||||
c.stateMutex.RLock()
|
c.stateMutex.RLock()
|
||||||
defer c.stateMutex.RUnlock()
|
defer c.stateMutex.RUnlock()
|
||||||
return c.state
|
return c.state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetAsyncPackets enables or disables asynchronous packet handling in the receive routine.
|
||||||
func (c *Conn) SetAsyncPackets(asyncPackets bool) {
|
func (c *Conn) SetAsyncPackets(asyncPackets bool) {
|
||||||
c.asyncPacketsMutex.Lock()
|
c.asyncPacketsMutex.Lock()
|
||||||
defer c.asyncPacketsMutex.Unlock()
|
defer c.asyncPacketsMutex.Unlock()
|
||||||
c.asyncPackets = asyncPackets
|
c.asyncPackets = asyncPackets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAsyncPackets returns whether asynchronous packet handling is enabled.
|
||||||
func (c *Conn) GetAsyncPackets() bool {
|
func (c *Conn) GetAsyncPackets() bool {
|
||||||
c.asyncPacketsMutex.RLock()
|
c.asyncPacketsMutex.RLock()
|
||||||
defer c.asyncPacketsMutex.RUnlock()
|
defer c.asyncPacketsMutex.RUnlock()
|
||||||
return c.asyncPackets
|
return c.asyncPackets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetVersion sets the connection's Minecraft version by version name and looks up the corresponding protocol version.
|
||||||
func (c *Conn) SetVersion(version string) (err error) {
|
func (c *Conn) SetVersion(version string) (err error) {
|
||||||
versionData, err := data.LookUpVersionByName(version)
|
versionData, err := data.LookUpVersionByName(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,6 +131,7 @@ func (c *Conn) SetVersion(version string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetProtocolVersion sets the connection's version by protocol version number and looks up the corresponding version name.
|
||||||
func (c *Conn) SetProtocolVersion(version int32) (err error) {
|
func (c *Conn) SetProtocolVersion(version int32) (err error) {
|
||||||
versionData, err := data.LookUpVersionByProtocolVersion(version)
|
versionData, err := data.LookUpVersionByProtocolVersion(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -202,6 +210,7 @@ func SimplePacketToBytes(packet proto_base.EncodeDecodeAble) (b []byte, err erro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SimplePacketToBytesLenPrefix is like SimplePacketToBytes but also prepends the length-prefix framing.
|
||||||
func SimplePacketToBytesLenPrefix(packet proto_base.EncodeDecodeAble) (b []byte, err error) {
|
func SimplePacketToBytesLenPrefix(packet proto_base.EncodeDecodeAble) (b []byte, err error) {
|
||||||
b, err = SimplePacketToBytes(packet)
|
b, err = SimplePacketToBytes(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -282,6 +291,7 @@ func (c *Conn) sendRegisteredPacket(packet proto_base.EncodeDecodeAble) (err err
|
|||||||
return c.SendRaw(packetBuff.Bytes())
|
return c.SendRaw(packetBuff.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send encodes and sends a packet to the remote peer, translating protocol versions if needed.
|
||||||
func (c *Conn) Send(packet proto_base.EncodeDecodeAble) (err error) {
|
func (c *Conn) Send(packet proto_base.EncodeDecodeAble) (err error) {
|
||||||
switch packet := packet.(type) {
|
switch packet := packet.(type) {
|
||||||
case *UnCodablePacket:
|
case *UnCodablePacket:
|
||||||
@@ -292,6 +302,7 @@ func (c *Conn) Send(packet proto_base.EncodeDecodeAble) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Receive reads and decodes a raw packet from the remote peer.
|
||||||
func (c *Conn) Receive() (packet proto_base.EncodeDecodeAble, err error) {
|
func (c *Conn) Receive() (packet proto_base.EncodeDecodeAble, err error) {
|
||||||
if c.R == nil {
|
if c.R == nil {
|
||||||
return
|
return
|
||||||
@@ -335,10 +346,13 @@ func (c *Conn) Receive() (packet proto_base.EncodeDecodeAble, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConn creates a new Conn with default settings and a background context.
|
||||||
func NewConn() (ret *Conn) {
|
func NewConn() (ret *Conn) {
|
||||||
return NewConnCtx(context.Background())
|
return NewConnCtx(context.Background())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConnCtx creates a new Conn with the given context, setting the handshaking state and latest version.
|
||||||
func NewConnCtx(ctx context.Context) (ret *Conn) {
|
func NewConnCtx(ctx context.Context) (ret *Conn) {
|
||||||
ret = &Conn{}
|
ret = &Conn{}
|
||||||
ret.SetState(proto_base.Handshaking)
|
ret.SetState(proto_base.Handshaking)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookUpTypeByPacketInfo looks up a packet's type information by direction, state, packet id, and protocol version.
|
||||||
func LookUpTypeByPacketInfo(direction proto_base.Direction, state proto_base.State, pid, version int32) (p proto_base.PacketInfo, ok bool) {
|
func LookUpTypeByPacketInfo(direction proto_base.Direction, state proto_base.State, pid, version int32) (p proto_base.PacketInfo, ok bool) {
|
||||||
pP, ok := pidMap[PidEtc{direction, state, pid, version}]
|
pP, ok := pidMap[PidEtc{direction, state, pid, version}]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -39,6 +40,7 @@ func LookUpTypeByPacketInfo(direction proto_base.Direction, state proto_base.Sta
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookUpTypeByPacketInfoAndCopyType looks up a packet and returns a newly allocated instance of its type.
|
||||||
func LookUpTypeByPacketInfoAndCopyType(direction proto_base.Direction, state proto_base.State, pid, version int32) (p proto_base.EncodeDecodeAble, ok bool) {
|
func LookUpTypeByPacketInfoAndCopyType(direction proto_base.Direction, state proto_base.State, pid, version int32) (p proto_base.EncodeDecodeAble, ok bool) {
|
||||||
i, ok := LookUpTypeByPacketInfo(direction, state, pid, version)
|
i, ok := LookUpTypeByPacketInfo(direction, state, pid, version)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -48,6 +50,7 @@ func LookUpTypeByPacketInfoAndCopyType(direction proto_base.Direction, state pro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupPacketInfoByType looks up a packet's type information by matching against the type of the given packet value.
|
||||||
func LookupPacketInfoByType(packet proto_base.EncodeDecodeAble) (p proto_base.PacketInfo, ok bool) {
|
func LookupPacketInfoByType(packet proto_base.EncodeDecodeAble) (p proto_base.PacketInfo, ok bool) {
|
||||||
undecodedPacket, isUndecoable := packet.(*UnCodablePacket)
|
undecodedPacket, isUndecoable := packet.(*UnCodablePacket)
|
||||||
if isUndecoable {
|
if isUndecoable {
|
||||||
@@ -64,6 +67,7 @@ func LookupPacketInfoByType(packet proto_base.EncodeDecodeAble) (p proto_base.Pa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupPacketInfoByTypeAndState looks up a packet's type information by type and state.
|
||||||
func LookupPacketInfoByTypeAndState(packet proto_base.EncodeDecodeAble, state proto_base.State) (p proto_base.PacketInfo, ok bool) {
|
func LookupPacketInfoByTypeAndState(packet proto_base.EncodeDecodeAble, state proto_base.State) (p proto_base.PacketInfo, ok bool) {
|
||||||
for _, p := range proto_generated.Packets {
|
for _, p := range proto_generated.Packets {
|
||||||
if state == p.State && reflect.TypeOf(p.Type) == reflect.TypeOf(packet) {
|
if state == p.State && reflect.TypeOf(p.Type) == reflect.TypeOf(packet) {
|
||||||
@@ -73,6 +77,7 @@ func LookupPacketInfoByTypeAndState(packet proto_base.EncodeDecodeAble, state pr
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupPacketInfoByNameProtocolVersionAndState looks up a packet's type information by name, protocol version, and state.
|
||||||
func LookupPacketInfoByNameProtocolVersionAndState(name string, version int32, state proto_base.State) (p proto_base.PacketInfo, ok bool) {
|
func LookupPacketInfoByNameProtocolVersionAndState(name string, version int32, state proto_base.State) (p proto_base.PacketInfo, ok bool) {
|
||||||
for _, p := range proto_generated.Packets {
|
for _, p := range proto_generated.Packets {
|
||||||
if state == p.State && p.Name == name && p.ProtocolVersion == version {
|
if state == p.State && p.Name == name && p.ProtocolVersion == version {
|
||||||
@@ -82,6 +87,7 @@ func LookupPacketInfoByNameProtocolVersionAndState(name string, version int32, s
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LookupPacketInfoByNameProtocolVersionStateAndDirection looks up a packet's type information by name, protocol version, state, and direction.
|
||||||
func LookupPacketInfoByNameProtocolVersionStateAndDirection(name string, version int32, state proto_base.State, direction proto_base.Direction) (p proto_base.PacketInfo, ok bool) {
|
func LookupPacketInfoByNameProtocolVersionStateAndDirection(name string, version int32, state proto_base.State, direction proto_base.Direction) (p proto_base.PacketInfo, ok bool) {
|
||||||
for _, p := range proto_generated.Packets {
|
for _, p := range proto_generated.Packets {
|
||||||
if state == p.State && p.Name == name && p.ProtocolVersion == version && p.Direction == direction {
|
if state == p.State && p.Name == name && p.ProtocolVersion == version && p.Direction == direction {
|
||||||
|
|||||||
@@ -27,18 +27,21 @@ type RawConn struct {
|
|||||||
MaxPacketSize int32
|
MaxPacketSize int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCompressionThreshold sets the packet compression threshold. Values below 0 disable compression.
|
||||||
func (c *RawConn) SetCompressionThreshold(threshold int32) {
|
func (c *RawConn) SetCompressionThreshold(threshold int32) {
|
||||||
c.CompressionThresholdMut.Lock()
|
c.CompressionThresholdMut.Lock()
|
||||||
defer c.CompressionThresholdMut.Unlock()
|
defer c.CompressionThresholdMut.Unlock()
|
||||||
c.compressionThreshold = threshold
|
c.compressionThreshold = threshold
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCompressionThreshold returns the current compression threshold.
|
||||||
func (c *RawConn) GetCompressionThreshold() int32 {
|
func (c *RawConn) GetCompressionThreshold() int32 {
|
||||||
c.CompressionThresholdMut.RLock()
|
c.CompressionThresholdMut.RLock()
|
||||||
defer c.CompressionThresholdMut.RUnlock()
|
defer c.CompressionThresholdMut.RUnlock()
|
||||||
return c.compressionThreshold
|
return c.compressionThreshold
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendRaw sends raw packet bytes over the connection, applying compression if the threshold is met.
|
||||||
func (c *RawConn) SendRaw(rawPacketBytes []byte) (err error) {
|
func (c *RawConn) SendRaw(rawPacketBytes []byte) (err error) {
|
||||||
if c.MaxPacketSize > 0 && int32(len(rawPacketBytes)) > c.MaxPacketSize {
|
if c.MaxPacketSize > 0 && int32(len(rawPacketBytes)) > c.MaxPacketSize {
|
||||||
err = errors.New("packet too large")
|
err = errors.New("packet too large")
|
||||||
@@ -90,6 +93,7 @@ func (c *RawConn) SendRaw(rawPacketBytes []byte) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReceiveRaw reads and decompresses (if needed) a raw packet from the connection.
|
||||||
func (c *RawConn) ReceiveRaw() (packetBytes []byte, err error) {
|
func (c *RawConn) ReceiveRaw() (packetBytes []byte, err error) {
|
||||||
rawPacketLen, err := proto_base.DecodeVarInt(c.R)
|
rawPacketLen, err := proto_base.DecodeVarInt(c.R)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -142,6 +146,7 @@ func (c *RawConn) ReceiveRaw() (packetBytes []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSecret enables AES/CFB8 encryption on the connection using the shared secret.
|
||||||
func (c *RawConn) SetSecret(sharedSecret []byte) (err error) {
|
func (c *RawConn) SetSecret(sharedSecret []byte) (err error) {
|
||||||
var b cipher.Block
|
var b cipher.Block
|
||||||
b, err = aes.NewCipher(sharedSecret)
|
b, err = aes.NewCipher(sharedSecret)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type Reader struct {
|
|||||||
Version int32
|
Version int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewReader creates a new replay Reader from an io.Reader.
|
||||||
func NewReader(r io.Reader) *Reader {
|
func NewReader(r io.Reader) *Reader {
|
||||||
return &Reader{r: r}
|
return &Reader{r: r}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type Writer struct {
|
|||||||
w io.Writer
|
w io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWriter creates a new replay Writer from an io.Writer.
|
||||||
func NewWriter(w io.Writer) *Writer {
|
func NewWriter(w io.Writer) *Writer {
|
||||||
return &Writer{w: w}
|
return &Writer{w: w}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/admin-else/strom/mc/proto_generated"
|
"github.com/admin-else/strom/mc/proto_generated"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SmartConvertibleTo reports whether a value of type from can be recursively converted to type to.
|
||||||
func SmartConvertibleTo(from, to reflect.Type) bool {
|
func SmartConvertibleTo(from, to reflect.Type) bool {
|
||||||
switch from.Kind() {
|
switch from.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
@@ -36,6 +37,7 @@ func SmartConvertibleTo(from, to reflect.Type) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SmartConvert recursively converts a reflect.Value from one type to another compatible type.
|
||||||
func SmartConvert(from reflect.Value, to reflect.Type) (ret reflect.Value) {
|
func SmartConvert(from reflect.Value, to reflect.Type) (ret reflect.Value) {
|
||||||
switch from.Kind() {
|
switch from.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UnimplementedErr is returned when a code path is not yet implemented.
|
||||||
var (
|
var (
|
||||||
UnimplementedErr = errors.New("unimplemented")
|
UnimplementedErr = errors.New("unimplemented")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Direction indicates whether a packet is sent to the server or to the client.
|
||||||
type Direction int
|
type Direction int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -18,6 +20,7 @@ const (
|
|||||||
ToClient
|
ToClient
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String returns a human-readable representation of the Direction.
|
||||||
func (d Direction) String() string {
|
func (d Direction) String() string {
|
||||||
switch d {
|
switch d {
|
||||||
case ToServer:
|
case ToServer:
|
||||||
@@ -29,10 +32,12 @@ func (d Direction) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Opposite returns the reverse Direction.
|
||||||
func (d Direction) Opposite() Direction {
|
func (d Direction) Opposite() Direction {
|
||||||
return d ^ 1
|
return d ^ 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// State represents the Minecraft connection state.
|
||||||
type State int32
|
type State int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -43,6 +48,7 @@ const (
|
|||||||
Play
|
Play
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String returns a human-readable representation of the State.
|
||||||
func (s State) String() string {
|
func (s State) String() string {
|
||||||
switch s {
|
switch s {
|
||||||
case Handshaking:
|
case Handshaking:
|
||||||
@@ -60,13 +66,16 @@ func (s State) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actor represents a participant in a Minecraft connection.
|
||||||
type Actor int
|
type Actor int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Servee Actor = iota // Servee is the one being served
|
// Servee is the one being served (e.g. the backend server in a proxy).
|
||||||
|
Servee Actor = iota
|
||||||
Client
|
Client
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String returns a human-readable representation of the Actor.
|
||||||
func (a Actor) String() string {
|
func (a Actor) String() string {
|
||||||
switch a {
|
switch a {
|
||||||
case Servee:
|
case Servee:
|
||||||
@@ -78,6 +87,7 @@ func (a Actor) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendDirection returns the Direction in which this Actor sends packets.
|
||||||
func (a Actor) SendDirection() Direction {
|
func (a Actor) SendDirection() Direction {
|
||||||
switch a {
|
switch a {
|
||||||
case Servee:
|
case Servee:
|
||||||
@@ -89,6 +99,7 @@ func (a Actor) SendDirection() Direction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReceiveDirection returns the Direction from which this Actor receives packets.
|
||||||
func (a Actor) ReceiveDirection() Direction {
|
func (a Actor) ReceiveDirection() Direction {
|
||||||
switch a {
|
switch a {
|
||||||
case Servee:
|
case Servee:
|
||||||
@@ -101,13 +112,19 @@ func (a Actor) ReceiveDirection() Direction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ToDo struct{}
|
// ToDo is a placeholder type for unimplemented packet fields.
|
||||||
|
ToDo struct{}
|
||||||
|
// RestBuffer represents the remaining unread bytes in a packet buffer.
|
||||||
RestBuffer []byte
|
RestBuffer []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ToDoError is returned when a ToDo placeholder is used.
|
||||||
var ToDoError = errors.New("to do")
|
var ToDoError = errors.New("to do")
|
||||||
|
|
||||||
|
// BadTypeError is returned when an unexpected type is encountered.
|
||||||
var BadTypeError = errors.New("bad type")
|
var BadTypeError = errors.New("bad type")
|
||||||
|
|
||||||
|
// Encode writes the RestBuffer to w.
|
||||||
func (b *RestBuffer) Encode(w io.Writer) (err error) {
|
func (b *RestBuffer) Encode(w io.Writer) (err error) {
|
||||||
n, err := w.Write(*b)
|
n, err := w.Write(*b)
|
||||||
if n != len(*b) {
|
if n != len(*b) {
|
||||||
@@ -116,11 +133,13 @@ func (b *RestBuffer) Encode(w io.Writer) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode reads all remaining bytes from r into the RestBuffer.
|
||||||
func (b *RestBuffer) Decode(r io.Reader) (err error) {
|
func (b *RestBuffer) Decode(r io.Reader) (err error) {
|
||||||
*b, err = io.ReadAll(r)
|
*b, err = io.ReadAll(r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeString encodes a length-prefixed UTF-8 string in Minecraft varint format.
|
||||||
func EncodeString(w io.Writer, s string) (err error) {
|
func EncodeString(w io.Writer, s string) (err error) {
|
||||||
err = EncodeVarInt(w, int32(len(s)))
|
err = EncodeVarInt(w, int32(len(s)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -130,6 +149,7 @@ func EncodeString(w io.Writer, s string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeString decodes a length-prefixed UTF-8 string in Minecraft varint format.
|
||||||
func DecodeString(r io.Reader) (ret string, err error) {
|
func DecodeString(r io.Reader) (ret string, err error) {
|
||||||
l, err := DecodeVarInt(r)
|
l, err := DecodeVarInt(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -146,16 +166,19 @@ func DecodeString(r io.Reader) (ret string, err error) {
|
|||||||
return string(rawString), nil
|
return string(rawString), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encode is a no-op stub that always returns ToDoError.
|
||||||
func (t ToDo) Encode(_ io.Writer) (err error) {
|
func (t ToDo) Encode(_ io.Writer) (err error) {
|
||||||
err = ToDoError
|
err = ToDoError
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode is a no-op stub that always returns ToDoError.
|
||||||
func (t ToDo) Decode(_ io.Reader) (ret ToDo, err error) {
|
func (t ToDo) Decode(_ io.Reader) (ret ToDo, err error) {
|
||||||
err = ToDoError
|
err = ToDoError
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeVarInt encodes a signed 32-bit integer in Minecraft varint format.
|
||||||
func EncodeVarInt(w io.Writer, v int32) (err error) {
|
func EncodeVarInt(w io.Writer, v int32) (err error) {
|
||||||
uv := uint32(v)
|
uv := uint32(v)
|
||||||
for {
|
for {
|
||||||
@@ -175,6 +198,7 @@ func EncodeVarInt(w io.Writer, v int32) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeVarInt decodes a signed 32-bit integer in Minecraft varint format.
|
||||||
func DecodeVarInt(r io.Reader) (ret int32, err error) {
|
func DecodeVarInt(r io.Reader) (ret int32, err error) {
|
||||||
for i := range 5 { // 32/7
|
for i := range 5 { // 32/7
|
||||||
var b uint8 = 0
|
var b uint8 = 0
|
||||||
@@ -191,6 +215,7 @@ func DecodeVarInt(r io.Reader) (ret int32, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeVarLong encodes a signed 64-bit integer in Minecraft varlong format.
|
||||||
func EncodeVarLong(w io.Writer, v int64) (err error) {
|
func EncodeVarLong(w io.Writer, v int64) (err error) {
|
||||||
uv := uint64(v)
|
uv := uint64(v)
|
||||||
for {
|
for {
|
||||||
@@ -210,6 +235,7 @@ func EncodeVarLong(w io.Writer, v int64) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecodeVarLong decodes a signed 64-bit integer in Minecraft varlong format.
|
||||||
func DecodeVarLong(r io.Reader) (ret int64, err error) {
|
func DecodeVarLong(r io.Reader) (ret int64, err error) {
|
||||||
for i := range 10 { // ceil(64/7)
|
for i := range 10 { // ceil(64/7)
|
||||||
var b uint8
|
var b uint8
|
||||||
@@ -226,6 +252,7 @@ func DecodeVarLong(r io.Reader) (ret int64, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErroringIndex looks up key k in map m and returns an error if the key is not found.
|
||||||
func ErroringIndex[K comparable, V any, M map[K]V](m M, i K) (v V, err error) {
|
func ErroringIndex[K comparable, V any, M map[K]V](m M, i K) (v V, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
v, ok = m[i]
|
v, ok = m[i]
|
||||||
@@ -235,11 +262,14 @@ func ErroringIndex[K comparable, V any, M map[K]V](m M, i K) (v V, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeDecodeAble is implemented by types that can be encoded and decoded for
|
||||||
|
// Minecraft protocol packets.
|
||||||
type EncodeDecodeAble interface {
|
type EncodeDecodeAble interface {
|
||||||
Encode(w io.Writer) (err error)
|
Encode(w io.Writer) (err error)
|
||||||
Decode(r io.ReadSeeker) (err error)
|
Decode(r io.ReadSeeker) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PacketInfo holds metadata about a Minecraft protocol packet.
|
||||||
type PacketInfo struct {
|
type PacketInfo struct {
|
||||||
Type EncodeDecodeAble
|
Type EncodeDecodeAble
|
||||||
Name string
|
Name string
|
||||||
@@ -249,6 +279,7 @@ type PacketInfo struct {
|
|||||||
ProtocolVersion int32
|
ProtocolVersion int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bool2uint8 converts a bool to a uint8 (1 for true, 0 for false).
|
||||||
func Bool2uint8(b bool) uint8 {
|
func Bool2uint8(b bool) uint8 {
|
||||||
if b {
|
if b {
|
||||||
return 1
|
return 1
|
||||||
@@ -256,6 +287,7 @@ func Bool2uint8(b bool) uint8 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bool2uint16 converts a bool to a uint16 (1 for true, 0 for false).
|
||||||
func Bool2uint16(b bool) uint16 {
|
func Bool2uint16(b bool) uint16 {
|
||||||
if b {
|
if b {
|
||||||
return 1
|
return 1
|
||||||
@@ -263,6 +295,7 @@ func Bool2uint16(b bool) uint16 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bool2uint32 converts a bool to a uint32 (1 for true, 0 for false).
|
||||||
func Bool2uint32(b bool) uint32 {
|
func Bool2uint32(b bool) uint32 {
|
||||||
if b {
|
if b {
|
||||||
return 1
|
return 1
|
||||||
@@ -270,6 +303,7 @@ func Bool2uint32(b bool) uint32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bool2uint64 converts a bool to a uint64 (1 for true, 0 for false).
|
||||||
func Bool2uint64(b bool) uint64 {
|
func Bool2uint64(b bool) uint64 {
|
||||||
if b {
|
if b {
|
||||||
return 1
|
return 1
|
||||||
@@ -277,13 +311,17 @@ func Bool2uint64(b bool) uint64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LpVec3d is a compressed 3D velocity vector used in Minecraft packets.
|
||||||
type LpVec3d struct {
|
type LpVec3d struct {
|
||||||
X, Y, Z float64
|
X, Y, Z float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encode encodes the LpVec3d as a compressed velocity triple into w.
|
||||||
func (v *LpVec3d) Encode(w io.Writer) error {
|
func (v *LpVec3d) Encode(w io.Writer) error {
|
||||||
return writeVelocity(w, *v)
|
return writeVelocity(w, *v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decode decodes a compressed velocity triple from r into the LpVec3d.
|
||||||
func (v *LpVec3d) Decode(r io.ReadSeeker) error {
|
func (v *LpVec3d) Decode(r io.ReadSeeker) error {
|
||||||
v3, err := readVelocity(r)
|
v3, err := readVelocity(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+1134
-571
File diff suppressed because it is too large
Load Diff
+1054
-531
File diff suppressed because it is too large
Load Diff
+1134
-571
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1144
-576
File diff suppressed because it is too large
Load Diff
+1146
-577
File diff suppressed because it is too large
Load Diff
@@ -429,7 +429,7 @@ func GenerateVersions(versions []string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
err := GenerateVersions([]string{"1.21.8", "1.21.9", "1.21.11", "26.1", "26.2"})
|
err := GenerateVersions([]string{"1.8", "1.21.8", "1.21.9", "1.21.11", "26.1", "26.2"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (g *Generator) ParseCompareTo(compareTo string) (e ast.Expr, cet CaseExprType, err error) {
|
func (g *Generator) ParseCompareTo(compareTo string) (e ast.Expr, cet CaseExprType, err error) {
|
||||||
|
if strings.HasPrefix(compareTo, "$") {
|
||||||
|
err = fmt.Errorf("parameterized type placeholder: %s", compareTo)
|
||||||
|
return
|
||||||
|
}
|
||||||
parts := strings.Split(compareTo, "/")
|
parts := strings.Split(compareTo, "/")
|
||||||
downPrefixCount := 0
|
downPrefixCount := 0
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
@@ -124,6 +128,12 @@ func (g *Generator) RegisterCompareToNatives() {
|
|||||||
"bitfield": BitfieldCompareTo,
|
"bitfield": BitfieldCompareTo,
|
||||||
"u8": ReturnInputCompareTo,
|
"u8": ReturnInputCompareTo,
|
||||||
"i8": ReturnInputCompareTo,
|
"i8": ReturnInputCompareTo,
|
||||||
|
"i16": ReturnInputCompareTo,
|
||||||
|
"i32": ReturnInputCompareTo,
|
||||||
|
"i64": ReturnInputCompareTo,
|
||||||
|
"u16": ReturnInputCompareTo,
|
||||||
|
"u32": ReturnInputCompareTo,
|
||||||
|
"string": ReturnInputCompareTo,
|
||||||
"bitflags": BitflagsCompareTo,
|
"bitflags": BitflagsCompareTo,
|
||||||
"switch": ReturnInputCompareTo,
|
"switch": ReturnInputCompareTo,
|
||||||
"varlong": ReturnInputCompareTo,
|
"varlong": ReturnInputCompareTo,
|
||||||
|
|||||||
@@ -634,6 +634,8 @@ func (g *Generator) RegisterDecoderNatives() {
|
|||||||
"varlong": VarLongDecoder,
|
"varlong": VarLongDecoder,
|
||||||
"anonymousNbt": DefaultDecoder,
|
"anonymousNbt": DefaultDecoder,
|
||||||
"anonOptionalNbt": DefaultDecoder, // I have no idea what the difference is between these two
|
"anonOptionalNbt": DefaultDecoder, // I have no idea what the difference is between these two
|
||||||
|
"optionalNbt": DefaultDecoder,
|
||||||
|
"nbt": DefaultDecoder,
|
||||||
"lpVec3": DefaultDecoder,
|
"lpVec3": DefaultDecoder,
|
||||||
"UUID": UUIDDecoder,
|
"UUID": UUIDDecoder,
|
||||||
"restBuffer": DefaultDecoder,
|
"restBuffer": DefaultDecoder,
|
||||||
|
|||||||
@@ -11,15 +11,16 @@ import (
|
|||||||
"github.com/go-viper/mapstructure/v2"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DefaultEncoder(_ *Generator, varToSet ast.Expr, _ any, _ string) (s []ast.Stmt, err error) {
|
func DefaultEncoder(_ *Generator, varToSet ast.Expr, _ any, name string) (s []ast.Stmt, err error) {
|
||||||
|
tmpName := name + "EncTmp"
|
||||||
s = Stmts(
|
s = Stmts(
|
||||||
|
Define121(Ident(tmpName), varToSet),
|
||||||
Assign121(
|
Assign121(
|
||||||
Ident("err"),
|
Ident("err"),
|
||||||
Call(SelectorExprAndStr(varToSet, "Encode"), Ident("w")),
|
Call(SelectorExprAndStr(Ident(tmpName), "Encode"), Ident("w")),
|
||||||
),
|
),
|
||||||
IfErrNil(),
|
IfErrNil(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,6 +403,8 @@ func (g *Generator) RegisterEncoderNatives() {
|
|||||||
"varlong": VarLongEncoder,
|
"varlong": VarLongEncoder,
|
||||||
"anonymousNbt": DefaultEncoder,
|
"anonymousNbt": DefaultEncoder,
|
||||||
"anonOptionalNbt": DefaultEncoder, // I have no idea what the difference is between these two
|
"anonOptionalNbt": DefaultEncoder, // I have no idea what the difference is between these two
|
||||||
|
"optionalNbt": DefaultEncoder,
|
||||||
|
"nbt": DefaultEncoder,
|
||||||
"UUID": UUIDEncoder,
|
"UUID": UUIDEncoder,
|
||||||
"restBuffer": DefaultEncoder,
|
"restBuffer": DefaultEncoder,
|
||||||
"lpVec3": DefaultEncoder,
|
"lpVec3": DefaultEncoder,
|
||||||
|
|||||||
@@ -200,6 +200,8 @@ func (g *Generator) RegisterNatives() {
|
|||||||
"native": VisitDontGenerateType,
|
"native": VisitDontGenerateType,
|
||||||
"anonymousNbt": MakeSelectorVisitor("nbt", "Anon"),
|
"anonymousNbt": MakeSelectorVisitor("nbt", "Anon"),
|
||||||
"anonOptionalNbt": MakeSelectorVisitor("nbt", "Anon"), // I have no idea what the difference is between these two
|
"anonOptionalNbt": MakeSelectorVisitor("nbt", "Anon"), // I have no idea what the difference is between these two
|
||||||
|
"optionalNbt": MakeSelectorVisitor("nbt", "Tag"),
|
||||||
|
"nbt": MakeSelectorVisitor("nbt", "Tag"),
|
||||||
"void": VoidType,
|
"void": VoidType,
|
||||||
"bool": MakeIdentVisitor("bool"),
|
"bool": MakeIdentVisitor("bool"),
|
||||||
"u8": MakeIdentVisitor("uint8"),
|
"u8": MakeIdentVisitor("uint8"),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type ConfigServer struct {
|
|||||||
registryData map[string]any
|
registryData map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeConfig runs the configuration phase: sends known packs, registry data, and tags, then transitions to play.
|
||||||
func ServeConfig(c *proto.Conn) (err error) {
|
func ServeConfig(c *proto.Conn) (err error) {
|
||||||
cs := &ConfigServer{Conn: c}
|
cs := &ConfigServer{Conn: c}
|
||||||
n, err := data.LoadRegistry(c.Version)
|
n, err := data.LoadRegistry(c.Version)
|
||||||
@@ -26,13 +27,15 @@ func ServeConfig(c *proto.Conn) (err error) {
|
|||||||
}
|
}
|
||||||
cs.registryData = n.Value.(map[string]any)
|
cs.registryData = n.Value.(map[string]any)
|
||||||
packet := &v1_21_8.ConfigurationToClientPacketCommonSelectKnownPacks{}
|
packet := &v1_21_8.ConfigurationToClientPacketCommonSelectKnownPacks{}
|
||||||
for _, pAny := range cs.registryData["KnownPacks"].([]any) {
|
if knownPacks, ok := cs.registryData["KnownPacks"]; ok {
|
||||||
p := pAny.(map[string]any)
|
for _, pAny := range knownPacks.([]any) {
|
||||||
packet.Packs = append(packet.Packs, struct {
|
p := pAny.(map[string]any)
|
||||||
Namespace string
|
packet.Packs = append(packet.Packs, struct {
|
||||||
Id string
|
Namespace string
|
||||||
Version string
|
Id string
|
||||||
}{p["Namespace"].(string), p["Id"].(string), p["Version"].(string)})
|
Version string
|
||||||
|
}{p["Namespace"].(string), p["Id"].(string), p["Version"].(string)})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err = c.Send(packet)
|
err = c.Send(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ var (
|
|||||||
CantKickInStateErr = errors.New("cant kick in this state")
|
CantKickInStateErr = errors.New("cant kick in this state")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Kick sends a disconnect/kick packet appropriate for the connection's current state.
|
||||||
func Kick(c *proto.Conn, reason *text.Component, status string) (err error) {
|
func Kick(c *proto.Conn, reason *text.Component, status string) (err error) {
|
||||||
if c.Actor != proto_base.Servee {
|
if c.Actor != proto_base.Servee {
|
||||||
err = NotClientErr
|
err = NotClientErr
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ func (l *LoginServer) FinishLogin() (err error) {
|
|||||||
|
|
||||||
type LoginServerSetting func(*LoginServer)
|
type LoginServerSetting func(*LoginServer)
|
||||||
|
|
||||||
|
// WithOtherAccount returns a setting that overrides the login identity with the given account.
|
||||||
func WithOtherAccount(a *api2.Account) LoginServerSetting {
|
func WithOtherAccount(a *api2.Account) LoginServerSetting {
|
||||||
return func(s *LoginServer) {
|
return func(s *LoginServer) {
|
||||||
s.Given = &NameAndUUID{
|
s.Given = &NameAndUUID{
|
||||||
@@ -213,12 +214,14 @@ func WithOtherAccount(a *api2.Account) LoginServerSetting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithRawStatus returns a setting that sets the raw JSON status response payload.
|
||||||
func WithRawStatus(status []byte) LoginServerSetting {
|
func WithRawStatus(status []byte) LoginServerSetting {
|
||||||
return func(s *LoginServer) {
|
return func(s *LoginServer) {
|
||||||
s.Status = status
|
s.Status = status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithStatus marshals a StatusResponse to JSON and passes it to WithRawStatus.
|
||||||
func WithStatus(response StatusResponse) LoginServerSetting {
|
func WithStatus(response StatusResponse) LoginServerSetting {
|
||||||
var status []byte
|
var status []byte
|
||||||
status, err := json.Marshal(response)
|
status, err := json.Marshal(response)
|
||||||
@@ -229,6 +232,7 @@ func WithStatus(response StatusResponse) LoginServerSetting {
|
|||||||
return WithRawStatus(status)
|
return WithRawStatus(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCompatibleVersionsRange returns a setting that restricts the server to protocol versions in [start, end].
|
||||||
func WithCompatibleVersionsRange(start, end int32) LoginServerSetting {
|
func WithCompatibleVersionsRange(start, end int32) LoginServerSetting {
|
||||||
var versions []int32
|
var versions []int32
|
||||||
for i := start; i <= end; i++ {
|
for i := start; i <= end; i++ {
|
||||||
@@ -237,24 +241,28 @@ func WithCompatibleVersionsRange(start, end int32) LoginServerSetting {
|
|||||||
return WithCompatibleVersions(versions...)
|
return WithCompatibleVersions(versions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCompatibleVersions returns a setting that restricts the server to the given protocol versions.
|
||||||
func WithCompatibleVersions(versions ...int32) LoginServerSetting {
|
func WithCompatibleVersions(versions ...int32) LoginServerSetting {
|
||||||
return func(s *LoginServer) {
|
return func(s *LoginServer) {
|
||||||
s.CompatibleVersions = versions
|
s.CompatibleVersions = versions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithoutOnlineMode returns a setting that disables online-mode authentication.
|
||||||
func WithoutOnlineMode() LoginServerSetting {
|
func WithoutOnlineMode() LoginServerSetting {
|
||||||
return func(s *LoginServer) {
|
return func(s *LoginServer) {
|
||||||
s.OnlineMode = false
|
s.OnlineMode = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithoutEncryption returns a setting that disables connection encryption.
|
||||||
func WithoutEncryption() LoginServerSetting {
|
func WithoutEncryption() LoginServerSetting {
|
||||||
return func(s *LoginServer) {
|
return func(s *LoginServer) {
|
||||||
s.Encryption = false
|
s.Encryption = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeLogin runs the full login sequence (handshake, encryption, auth, and configuration handoff) on a connection.
|
||||||
func ServeLogin(c *proto.Conn, settings ...LoginServerSetting) (ret *LoginServer, err error) {
|
func ServeLogin(c *proto.Conn, settings ...LoginServerSetting) (ret *LoginServer, err error) {
|
||||||
ret = &LoginServer{Conn: c}
|
ret = &LoginServer{Conn: c}
|
||||||
ret.OnlineMode = true
|
ret.OnlineMode = true
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/admin-else/strom/mc/text"
|
"github.com/admin-else/strom/mc/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Servee wraps a net.Conn in a new proto.Conn configured for the servee side.
|
||||||
func Servee(c net.Conn) (ret *proto.Conn) {
|
func Servee(c net.Conn) (ret *proto.Conn) {
|
||||||
ret = proto.NewConn()
|
ret = proto.NewConn()
|
||||||
ret.Actor = proto_base.Servee
|
ret.Actor = proto_base.Servee
|
||||||
@@ -24,6 +25,7 @@ type ConnAcceptor func(c net.Conn) (err error)
|
|||||||
|
|
||||||
type Factory func(c *proto.Conn) (h ConnAcceptor, err error)
|
type Factory func(c *proto.Conn) (h ConnAcceptor, err error)
|
||||||
|
|
||||||
|
// ServeClient accepts a single client connection, creates a handler via the factory, and runs it.
|
||||||
func ServeClient(cNet net.Conn, factory Factory) {
|
func ServeClient(cNet net.Conn, factory Factory) {
|
||||||
c := Servee(cNet)
|
c := Servee(cNet)
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
@@ -40,6 +42,7 @@ func ServeClient(cNet net.Conn, factory Factory) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartServerWithFactory listens on the given address and spawns ServeClient for every accepted connection.
|
||||||
func StartServerWithFactory(listenAddr string, factory Factory) (err error) {
|
func StartServerWithFactory(listenAddr string, factory Factory) (err error) {
|
||||||
l, err := net.Listen("tcp", listenAddr)
|
l, err := net.Listen("tcp", listenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -57,6 +60,8 @@ func StartServerWithFactory(listenAddr string, factory Factory) (err error) {
|
|||||||
|
|
||||||
var ShutDownServerErr = errors.New("server shutting down")
|
var ShutDownServerErr = errors.New("server shutting down")
|
||||||
|
|
||||||
|
// StartServerWithOnConn listens on the given address and calls onConn for every accepted connection.
|
||||||
|
// The server loop stops when onConn returns ShutDownServerErr.
|
||||||
func StartServerWithOnConn(listenAddr string, onConn func(c *proto.Conn) (err error)) (err error) {
|
func StartServerWithOnConn(listenAddr string, onConn func(c *proto.Conn) (err error)) (err error) {
|
||||||
l, err := net.Listen("tcp", listenAddr)
|
l, err := net.Listen("tcp", listenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ func (p *StatusServer) OnStatusPing(packet *v1_21_8.StatusToServerPacketPing) (e
|
|||||||
return p.Send(&v1_21_8.StatusToClientPacketPing{Time: packet.Time})
|
return p.Send(&v1_21_8.StatusToClientPacketPing{Time: packet.Time})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServeStatus runs the server list ping sequence, responding with the given StatusResponse.
|
||||||
func ServeStatus(c *proto.Conn, s StatusResponse) (err error) {
|
func ServeStatus(c *proto.Conn, s StatusResponse) (err error) {
|
||||||
status, err := json.Marshal(s)
|
status, err := json.Marshal(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -526,6 +526,7 @@ func replacePlaceholders(s string, values map[string]any) string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrettyPlaceholders parses a legacy formatted string with named placeholders replaced from the given map, returning a Component.
|
||||||
func PrettyPlaceholders(format string, placeholders map[string]any) *Component {
|
func PrettyPlaceholders(format string, placeholders map[string]any) *Component {
|
||||||
s := replacePlaceholders(format, placeholders)
|
s := replacePlaceholders(format, placeholders)
|
||||||
return Pretty(s)
|
return Pretty(s)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
var boundaries = []rune{' ', '_', '-', '.', ',', ';', ':', '(', ')', '[', ']', '{', '}'}
|
var boundaries = []rune{' ', '_', '-', '.', ',', ';', ':', '(', ')', '[', ']', '{', '}'}
|
||||||
|
|
||||||
|
// CamelCase converts a string with word boundaries to CamelCase.
|
||||||
func CamelCase(s string) string {
|
func CamelCase(s string) string {
|
||||||
ret := ""
|
ret := ""
|
||||||
atWordBoundary := true
|
atWordBoundary := true
|
||||||
@@ -24,6 +25,7 @@ func CamelCase(s string) string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SnakeCase converts a CamelCase string to snake_case.
|
||||||
func SnakeCase(s string) string {
|
func SnakeCase(s string) string {
|
||||||
ret := ""
|
ret := ""
|
||||||
for i, c := range s {
|
for i, c := range s {
|
||||||
@@ -38,6 +40,7 @@ func SnakeCase(s string) string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FirstLetterLower returns the string with the first rune lowercased.
|
||||||
func FirstLetterLower(s string) string {
|
func FirstLetterLower(s string) string {
|
||||||
return string(unicode.ToLower(rune(s[0]))) + s[1:]
|
return string(unicode.ToLower(rune(s[0]))) + s[1:]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package util
|
|||||||
|
|
||||||
import "math"
|
import "math"
|
||||||
|
|
||||||
|
// FloorDiv returns floor division (rounds toward negative infinity).
|
||||||
func FloorDiv(a, b int32) int32 {
|
func FloorDiv(a, b int32) int32 {
|
||||||
return (a - ((a%b + b) % b)) / b
|
return (a - ((a%b + b) % b)) / b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BpeByNum returns the minimum bits per element needed to represent x distinct values.
|
||||||
func BpeByNum(x float64) uint8 {
|
func BpeByNum(x float64) uint8 {
|
||||||
return uint8(math.Ceil(math.Log2(x)))
|
return uint8(math.Ceil(math.Log2(x)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package util
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
|
// MustOk returns v if ok is true, otherwise panics.
|
||||||
func MustOk[T any](v T, ok bool) T {
|
func MustOk[T any](v T, ok bool) T {
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("not ok")
|
panic("not ok")
|
||||||
@@ -9,6 +10,7 @@ func MustOk[T any](v T, ok bool) T {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustT returns v if err is nil, otherwise panics.
|
||||||
func MustT[T any](v T, err error) T {
|
func MustT[T any](v T, err error) T {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -16,12 +18,14 @@ func MustT[T any](v T, err error) T {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must panics if err is non-nil.
|
||||||
func Must(err error) {
|
func Must(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertTypeError asserts that v is of type T and returns it, or returns an error.
|
||||||
func AssertTypeError[T any](v any) (T, error) {
|
func AssertTypeError[T any](v any) (T, error) {
|
||||||
var zero T
|
var zero T
|
||||||
r, ok := v.(T)
|
r, ok := v.(T)
|
||||||
@@ -31,6 +35,7 @@ func AssertTypeError[T any](v any) (T, error) {
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MapGetError returns the value for key k from map m, or an error if not present.
|
||||||
func MapGetError[K comparable, V any, M map[K]V](m M, k K) (V, error) {
|
func MapGetError[K comparable, V any, M map[K]V](m M, k K) (V, error) {
|
||||||
v, ok := m[k]
|
v, ok := m[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// OrderedKeys returns the keys of the map sorted in ascending order.
|
||||||
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)
|
||||||
@@ -13,6 +14,7 @@ func OrderedKeys[Map map[K]V, K cmp.Ordered, V any](m Map) []K {
|
|||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertAndConvertMapValues asserts and converts all values in a map[string]any to type T.
|
||||||
func AssertAndConvertMapValues[T any, M map[K]any, K cmp.Ordered](m M) map[K]T {
|
func AssertAndConvertMapValues[T any, M map[K]any, K cmp.Ordered](m M) map[K]T {
|
||||||
ret := map[K]T{}
|
ret := map[K]T{}
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
@@ -21,6 +23,7 @@ func AssertAndConvertMapValues[T any, M map[K]any, K cmp.Ordered](m M) map[K]T {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReverseMap returns a new map with keys and values swapped.
|
||||||
func ReverseMap[M map[K]V, K cmp.Ordered, V cmp.Ordered](m M) map[V]K {
|
func ReverseMap[M map[K]V, K cmp.Ordered, V cmp.Ordered](m M) map[V]K {
|
||||||
ret := map[V]K{}
|
ret := map[V]K{}
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
@@ -29,6 +32,7 @@ func ReverseMap[M map[K]V, K cmp.Ordered, V cmp.Ordered](m M) map[V]K {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PopFront removes and returns the first element of the slice.
|
||||||
func PopFront[T any](s []T) (T, []T, bool) {
|
func PopFront[T any](s []T) (T, []T, bool) {
|
||||||
if len(s) == 0 {
|
if len(s) == 0 {
|
||||||
var zero T
|
var zero T
|
||||||
@@ -37,6 +41,7 @@ func PopFront[T any](s []T) (T, []T, bool) {
|
|||||||
return s[0], s[1:], true
|
return s[0], s[1:], true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CombineNamAndData returns a slice containing name and data.
|
||||||
func CombineNamAndData(name string, data any) []any {
|
func CombineNamAndData(name string, data any) []any {
|
||||||
return []any{name, data}
|
return []any{name, data}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user