72 Commits
Author SHA1 Message Date
adman 96481da0a4 refactor: add context propagation to connection and event systems 2026-04-29 23:38:24 +02:00
adman 159437c1ca fix: handle empty kick status message properly in Status protocol state 2026-04-29 18:48:10 +02:00
adman 3d34ad59e9 allow for custom kick status message 2026-04-29 18:31:36 +02:00
adman 4878c6cc7b fix logger not being and other thing maybe 2026-04-28 22:25:36 +02:00
adman 719a3bacd8 Add fuzz testing for Section encoding/decoding with multiple edge cases 2026-04-28 09:31:35 +02:00
adman ce0c58e7c5 refactor shit 2026-04-27 01:50:45 +02:00
adman 8a4e44760e fix: TestRealStorageResizeDirect and TestRealStorageDirectModeNoPalette bugs
- resize() was writing to wrong array (r.data instead of newData) and using
  wrong elementsPerLong during resize loop
- TestRealStorageDirectModeNoPalette had invalid test data (0x1FFFF needs
  bpe>=17 but test used bpe=9)
2026-04-27 01:45:41 +02:00
adman e810a60848 111111 2026-04-27 01:26:56 +02:00
adman 0981339028 1111 2026-04-27 01:03:47 +02:00
adman 7746dd8ee9 real storage 2026-04-27 00:17:56 +02:00
adman 378b78312e storage=????? 2026-04-25 18:00:10 +02:00
adman bd3139754d add import storage and test 2026-04-23 22:36:34 +02:00
adman 841d91b91d storage 2026-04-23 21:30:17 +02:00
adman 895ec53698 parse the world 2026-04-21 22:24:12 +02:00
adman f6001c8b22 mojang cant get their cases straight
i had to implement like 4 cases just  to parse that file
2026-04-21 09:38:14 +02:00
adman d87c362270 anvil testdata 2026-04-21 08:35:40 +02:00
adman 236e414242 anvil start 2026-04-21 08:35:24 +02:00
adman 5222390b65 nbt changes 2026-04-21 08:35:18 +02:00
adman dc04fdc7d0 remove redundant bool arg 2026-04-20 23:38:02 +02:00
adman 8de34dfda1 mapstructure 2026-04-20 23:16:01 +02:00
adman 997594d7c3 fix chunk sections being one 2026-04-20 20:23:25 +02:00
adman c83a777eac mojang is weird i tell them 1 and they turn it into 4 2026-04-20 18:12:25 +02:00
adman 65fea61667 chunk builder 2026-04-19 17:41:13 +02:00
adman c2777f2ca9 chunk serialization fixes and fuzzies 2026-04-19 16:10:09 +02:00
adman dcb42f1970 chunk serialization 2026-04-19 11:04:37 +02:00
adman 7a2081f0a0 support kicker status 2026-04-17 21:51:47 +02:00
adman acc583f694 more logging 2026-04-14 09:20:00 +02:00
adman 76409d07a3 fix smart convert panic 2026-04-12 23:16:36 +02:00
adman bb89730781 fix bug with treshold 0 never compressing instead of always compressing 2026-04-12 23:16:19 +02:00
adman 9f6b1f6bcf more logging for status cli so you can debug address resolution 2026-04-12 23:15:50 +02:00
adman 1b8a9677c6 reduce binary size by not bundling bedrock data 2026-04-12 23:15:16 +02:00
adman 79ab13ca4b server encryption and online mode 2026-04-10 17:28:13 +02:00
adman 3cc414185a implement compression threshold setting 2026-04-10 14:30:35 +02:00
adman fe4a17dce2 Shutdownserver error 2026-04-08 00:31:05 +02:00
adman 891551f9dd simple api cli 2026-04-06 00:34:28 +02:00
adman 8fb48c6ea9 add version flag to status cmd 2026-04-05 23:39:08 +02:00
adman ec25c28061 Remove unused import 2026-04-05 23:38:57 +02:00
adman 45287f47d2 Expose lower register functions
Added RegisterLow and RegisterUntilLow

this gives more power to the user
2026-04-05 16:07:05 +02:00
adman 4cc4b6af5b fix earlies version
It was the latest version i dunno what i was thinking?
2026-04-05 16:05:33 +02:00
adman 09e6760d61 fix timer drifting off and delte old code 2026-04-05 16:04:53 +02:00
adman ca74a3c3d4 more explicit asyncPackets switch 2026-04-04 19:32:46 +02:00
adman fbc3693219 Revert "Send event"
This reverts commit abb0fa4d37.
2026-04-04 15:31:30 +02:00
Admin Else fb69fd93ec my code broke after i changed this last time and this fixed it 2026-04-03 19:01:33 +02:00
Admin Else a3a3c3714e Maybe fix contex done err 2026-04-03 19:00:52 +02:00
Admin Else 9294631c6c fix race condition 2026-04-03 09:05:24 +02:00
Admin Else 293f0b1eed this makes debugging easier trust me 2026-04-03 09:05:10 +02:00
Admin Else 272dcb4d36 warn instead of crash on unexpected packet 2026-04-02 18:49:50 +02:00
Admin Else abb0fa4d37 Send event 2026-04-02 18:49:30 +02:00
Admin Else e5212624af text components prints with tags 2026-04-01 16:43:47 +02:00
Admin Else 35772aca03 Cancle and signal errors shouldnt be voided 2026-03-31 21:57:04 +02:00
Admin Else 819d89b26e Delte schematic server 2026-03-31 17:04:44 +02:00
Admin Else 958102e43c fixes for config in 1.21.11 2026-03-31 16:51:07 +02:00
Admin Else f749d3bfc5 Add support for empty registry serving 2026-03-29 21:16:28 +02:00
Admin Else d207cf52cf 1.21.11 data registry 2026-03-29 21:09:05 +02:00
Admin Else 420ec5611c Fix register(critical)?untilLatest being inverted 2026-03-29 21:08:48 +02:00
Admin Else 4bfaf9b7c5 Remove redundant return 2026-03-29 21:07:23 +02:00
Admin Else 48beb861e8 Add vscode folder to gitignore 2026-03-29 17:50:33 +02:00
Admin Else 9fd2f517d9 Fix SmartConver for slices 2026-03-29 17:34:41 +02:00
Admin Else 1b5af63b75 Fix packet inspector 2026-03-29 17:13:11 +02:00
Admin Else dfb847eb3e Fix registry hunter 2026-03-29 17:11:40 +02:00
Admin Else 322bd77f7f Remove redundant return statement 2026-03-29 14:35:46 +02:00
Admin Else aefe850281 Fix send debug message 2026-03-29 14:21:54 +02:00
Admin Else 57f88f2176 Fix debug message 2026-03-29 14:20:02 +02:00
Admin Else 8dd566c0da Add DebugPrintPackets and fix not translating packet id 2026-03-29 14:15:31 +02:00
Admin Else e386a45d96 Add basic README.md 2026-03-28 18:14:41 +01:00
adman 8f771166a6 better unknown command text 2026-03-27 13:16:16 +01:00
adman 5d6a1a25ea fix packet spy 2026-03-27 13:09:07 +01:00
adman ca33185f20 fix LookupPacketInfoByType for undecoable 2026-03-27 12:59:10 +01:00
adman e921e9e9d2 fix routine thing 2026-03-27 00:03:01 +01:00
adman 7337068d86 more performance 2026-03-26 23:34:30 +01:00
adman 9983062522 fix timer 2026-03-26 22:39:48 +01:00
adman 4fd5324064 new event system (again) 2026-03-26 22:19:03 +01:00
216 changed files with 4853 additions and 589 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
.devres/
.idea/
.failed_packets/
tools/.*/
tools/.*/
.vscode/
+5
View File
@@ -0,0 +1,5 @@
# STROM
# THIS IS WORK IN PROGRESS AND THE API WILL BREAK AS LONG AS THE THE PACKAGE VERSION IS v0.x.x
feel free to contact me on [discord](https://discord.gg/n2Vjc2vcMf)
+130
View File
@@ -0,0 +1,130 @@
package api
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
func BlockedServers() (s []string, err error) {
resp, err := http.Get("https://sessionserver.mojang.com/blockedservers")
if err != nil {
return
}
defer resp.Body.Close()
d, err := io.ReadAll(resp.Body)
if err != nil {
return
}
s = strings.Split(string(d), "\n")
return
}
type ProfileProperty struct {
Name string `json:"name"`
Signature string `json:"signature,omitempty"`
Value string `json:"value"`
}
type ProfileResp struct {
Id string `json:"id"`
Name string `json:"name"`
Legacy bool `json:"legacy"`
Properties []ProfileProperty `json:"properties"`
}
func HasJoined(name, server, ip string) (p ProfileResp, err error) {
url := "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + name + "&serverId=" + server
if ip != "" {
url += "&ip=" + ip
}
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
err = fmt.Errorf("bad status code %v", resp.StatusCode)
return
}
err = json.NewDecoder(resp.Body).Decode(&p)
return
}
type MojangPublicKeysResp struct {
ProfilePropertyKeys []string `json:"profilePropertyKeys"`
PlayerCertificateKeys []string `json:"playerCertificateKeys"`
// AuthenticationKeys are undocumented by wiki.vg but they exist as of the time of writing so ill include them
AuthenticationKeys []string `json:"authenticationKeys"`
}
func MojangPublicKeys() (keys MojangPublicKeysResp, err error) {
resp, err := http.Get("https://sessionserver.mojang.com/session/minecraft/profile/public_key_by_token")
if err != nil {
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&keys)
return
}
func Profile(id string, unsinged bool) (p ProfileResp, err error) {
url := "https://sessionserver.mojang.com/session/minecraft/profile/" + id
if !unsinged {
url += "?unsigned=false"
}
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&p)
return
}
type IdName struct {
Id string `json:"id"`
Name string `json:"name"`
}
func NameToId(name string) (idName IdName, err error) {
// may also use these:
//https://api.mojang.com/users/profiles/minecraft/<player name>
//https://api.minecraftservices.com/minecraft/profile/lookup/name/<player name>
//https://api.mojang.com/minecraft/profile/lookup/name/<player name>
resp, err := http.Get("https://api.minecraftservices.com/minecraft/profile/lookup/name/" + name)
if err != nil {
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&idName)
return
}
func NameToIdBulk(names []string) (idNames []IdName, err error) {
b := bytes.NewBuffer(nil)
err = json.NewEncoder(b).Encode(names)
if err != nil {
return
}
// may also use these:
//https://api.mojang.com/profiles/minecraft
//https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname
//https://api.mojang.com/minecraft/profile/lookup/bulk/byname
r, err := http.NewRequest("POST", "https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname", b)
if err != nil {
return
}
r.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(r)
if err != nil {
return
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&idNames)
return
}
+8 -7
View File
@@ -1,9 +1,9 @@
package client
import (
"context"
"net"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
)
@@ -23,16 +23,17 @@ func Connect(addr string) (ret *proto.Conn, err error) {
return
}
func ConnectVersionLess(addr string) (ret *proto.Conn, err error) {
return ConnectVersionLessCtx(context.Background(), addr)
}
// ConnectVersionLess connects to the given address without setting the version you should set it
// when using this function.
// You must close the connection when you're done with it
func ConnectVersionLess(addr string) (ret *proto.Conn, err error) {
ret = &proto.Conn{}
ret.SetState(proto_base.Handshaking)
ret.SetCompressionThreshold(-1)
func ConnectVersionLessCtx(ctx context.Context, addr string) (ret *proto.Conn, err error) {
ret = proto.NewConnCtx(ctx)
ret.Actor = proto_base.Client
ret.Loop = event.NewLoop()
ret.Conn, err = net.Dial("tcp", addr)
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
if err != nil {
return
}
+1 -5
View File
@@ -3,7 +3,6 @@ package client
import (
"errors"
"fmt"
"log/slog"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
@@ -20,10 +19,7 @@ func (c *ConfigIgnorer) OnStart() (err error) {
}
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
if e.Val == (event.Tick{}) {
return
}
slog.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
c.Log.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
return
}
+8 -11
View File
@@ -6,7 +6,6 @@ import (
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"github.com/admin-else/strom/api"
"github.com/admin-else/strom/crypto"
@@ -42,11 +41,10 @@ func (k KickedDuringLoginErr) Error() string {
}
func (s *LoginClient) OnDefault(event event.Unhandled) (err error) {
err = fmt.Errorf("unexpected event: %#v", event)
return
}
func (s *LoginClient) OnCycle(_ event.Tick) (err error) {
if _, ok := event.Val.(proto_base.EncodeDecodeAble); !ok {
return
}
s.Log.Warn("unexpected packet during login client", "packet", event.Val)
return
}
@@ -133,12 +131,11 @@ func LoginRaw(c *proto.Conn, account *api.Account, addr string) (err error) {
Account: account,
}
lc.RegisterCritical(lc.OnDefault)
lc.RegisterCritical(lc.OnCycle)
lc.RegisterCritical(lc.OnClose)
lc.RegisterUntilLatest(lc.OnCompress)
lc.RegisterUntilLatest(lc.OnEncrypt)
lc.RegisterUntilLatest(lc.OnDisconnect)
lc.RegisterUntilLatest(lc.OnSuccess)
lc.RegisterCriticalUntilLatest(lc.OnCompress)
lc.RegisterCriticalUntilLatest(lc.OnEncrypt)
lc.RegisterCriticalUntilLatest(lc.OnDisconnect)
lc.RegisterCriticalUntilLatest(lc.OnSuccess)
p, err := MakeHandshakePacket(lc.Conn, proto_base.Login)
if err != nil {
+3 -2
View File
@@ -1,6 +1,7 @@
package client
import (
"context"
"encoding/json"
"time"
@@ -39,7 +40,7 @@ func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
// StatusRaw returns a StatusClient that is not connected to a server.
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
// It does not resolve SRV records.
func StatusRaw(addr string) (s *StatusClient, err error) {
func StatusRaw(ctx context.Context, addr string) (s *StatusClient, err error) {
c, err := ConnectVersionLess(addr)
if err != nil {
return
@@ -72,7 +73,7 @@ func StatusRaw(addr string) (s *StatusClient, err error) {
// StatusNoDns is like Status but does not resolve SRV records.
func StatusNoDns(addr string) (status server.StatusResponse, err error) {
s, err := StatusRaw(addr)
s, err := StatusRaw(context.Background(), addr)
if err != nil {
return
}
+60
View File
@@ -0,0 +1,60 @@
package api
import (
"encoding/json"
"errors"
"flag"
"os"
"github.com/admin-else/strom/api"
)
var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
var tokenFlag = cmd.String("token", os.Getenv("MC_TOKEN"), "token to use")
var renameFlag = cmd.String("rename", "", "rename the player")
var uuidFlag = cmd.String("uuid", "", "get data about player with uuid")
var nameFlag = cmd.String("name", "", "get data about player with name")
var unsignedFlag = cmd.Bool("unsigned", true, "use unsigned mode for properties signatures")
var PleaseProvideArgumentErr = errors.New("please provide an argument")
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
a := &api.Account{}
if *tokenFlag != "" {
a, err = api.NewAccountFromYGG(*tokenFlag)
if err != nil {
return
}
}
var data any
if *renameFlag != "" {
data, err = a.ChangeName(*renameFlag)
if err != nil {
return
}
} else if *uuidFlag != "" || *nameFlag != "" {
if *nameFlag != "" {
var idName api.IdName
idName, err = api.NameToId(*nameFlag)
if err != nil {
return
}
*uuidFlag = idName.Id
}
data, err = api.Profile(*uuidFlag, *unsignedFlag)
if err != nil {
return
}
} else {
err = PleaseProvideArgumentErr
return
}
err = json.NewEncoder(os.Stdout).Encode(data)
return
}
+5 -3
View File
@@ -8,6 +8,7 @@ import (
"os"
"slices"
"github.com/admin-else/strom/cmd/strom/api"
"github.com/admin-else/strom/cmd/strom/offline_uuid"
"github.com/admin-else/strom/cmd/strom/packet_inspector"
"github.com/admin-else/strom/cmd/strom/print_nbt"
@@ -15,13 +16,14 @@ import (
)
var ExpectedASubcommandErr = errors.New("expected a subcommand")
var UnknownSubcommandErr = errors.New("unknown subcommand")
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
var subcommands = map[string]func(args []string) error{
"offline-uuid": offline_uuid.Run,
"print-nbt": print_nbt.Run,
"status": status.Run,
"packet-spy": packet_inspector.Run,
"api": api.Run,
}
func Help(args []string) (err error) {
@@ -36,8 +38,8 @@ func mainE() (err error) {
args := os.Args
// Debug stuff
//slog.SetLogLoggerLevel(slog.LevelDebug)
//args = []string{"TEST", "status", "-srv", "-addr", "tcpshield.1f2d.net:25565"}
slog.SetLogLoggerLevel(slog.LevelDebug)
//args = []string{"TEST", "api", "-name", "Admin_Else"}
if len(args) < 2 {
err = ExpectedASubcommandErr
+12 -1
View File
@@ -93,6 +93,7 @@ func Run(args []string) (err error) {
slog.SetLogLoggerLevel(slog.LevelDebug)
slog.Info("Starting packet inspector", "listen", *ListenAddr)
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
serveeConn.DebugPrintPackets = []string{""}
p := &Proxy{Servee: serveeConn, Client: nil}
var acc = api.NewOfflineAccount(*OfflineNameFlag)
if *Token != "" {
@@ -105,7 +106,17 @@ func Run(args []string) (err error) {
if err != nil {
return
}
c, err := client.Login(*TargetAddr, acc)
*TargetAddr, err = client.DoDnsSimple(*TargetAddr)
if err != nil {
return
}
c, err := client.Connect(*TargetAddr)
if err != nil {
return
}
c.DebugPrintPackets = []string{""}
err = client.LoginRaw(c, acc, *TargetAddr)
if err != nil {
return
}
+1 -1
View File
@@ -25,7 +25,7 @@ func Run(args []string) (err error) {
return
}
defer f.Close()
n, err := nbt.ReadFile(f)
n, err := nbt.ReadUnstructuredFile(f)
if err != nil {
return
}
+67 -1
View File
@@ -3,13 +3,78 @@ package status
import (
"flag"
"fmt"
"log/slog"
"time"
"github.com/admin-else/strom/client"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
var cmd = flag.NewFlagSet("status", flag.ContinueOnError)
var addrFlag = cmd.String("addr", "localhost:25565", "address to connect to")
var srvFlag = cmd.Bool("srv", false, "use SRV records to resolve the address")
var versionFlag = cmd.String("version", "1.21.8", "version to use")
type StatusClient struct {
*proto.Conn
Status string
PingSendTime, PingReceiveTime time.Time
DoPingRoundTripTime bool
}
func (s *StatusClient) OnStatus(p *v1_21_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) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
s.PingReceiveTime = time.Now()
err = event.HandlerDoneErr{}
return
}
// StatusRaw returns a StatusClient that is not connected to a server.
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
// It does not resolve SRV records.
func StatusRaw(addr string) (s *StatusClient, err error) {
c, err := client.ConnectVersionLess(addr)
if err != nil {
return
}
defer c.Close()
err = c.SetVersion(*versionFlag)
if err != nil {
return
}
s = &StatusClient{
Conn: c,
}
s.RegisterCriticalUntilLatest(s.OnStatus)
s.RegisterCriticalUntilLatest(s.OnPong)
p, err := client.MakeHandshakePacketAddr(s.Conn, proto_base.Status, addr)
if err != nil {
return
}
err = s.Send(p)
if err != nil {
return
}
s.SetState(proto_base.Status)
err = s.Send(&v1_21_8.StatusToServerPacketPingStart{})
err = s.StartConn()
return
}
func Run(args []string) (err error) {
err = cmd.Parse(args)
@@ -22,8 +87,9 @@ func Run(args []string) (err error) {
return
}
}
slog.Debug("resolved address to ", "addr", *addrFlag)
status, err := client.StatusRaw(*addrFlag)
status, err := StatusRaw(*addrFlag)
if err != nil {
return
}
+21 -6
View File
@@ -1,5 +1,7 @@
package data
import "errors"
// [
//
// {
@@ -14,7 +16,7 @@ package data
// },
type Biome struct {
Id int
Id int32
Name string
Category string
Temperature float64
@@ -24,22 +26,35 @@ type Biome struct {
Color int
}
var BiomesCache = make(map[string][]Biome)
var BiomesCache = make(map[string][]*Biome)
func BiomesForVersion(v string) (ret []Biome) {
func BiomesForVersion(v string) (ret []*Biome) {
var ok bool
if ret, ok = BiomesCache[v]; ok {
return
}
var b []Biome
var b []*Biome
must(LoadVersionedJson(v, "biomes", &b))
BiomesCache[v] = b
return b
}
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) {
if b.Id == int(id) {
if b.Id == id {
biome = b
ok = true
return
}
}
return
}
var UnknownBiomeNameErr = errors.New("unknown biome Name")
func LookupBiomeByName(version string, name string) (biome *Biome, ok bool) {
for _, b := range BiomesForVersion(version) {
if b.Name == name {
biome = b
ok = true
return
+2
View File
@@ -81,6 +81,8 @@ func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool)
return
}
var UnknownBlockNameErr = errors.New("unknown block Name")
func LookupBlockByName(version string, name string) (block *Block, ok bool) {
for _, b := range BlocksForVersion(version) {
if b.Name == name {
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"errors"
)
//go:embed minecraft-data/data
//go:embed minecraft-data/data/pc minecraft-data/data/dataPaths.json
var MinecraftData embed.FS
func LoadJson(path string, data any) (err error) {
+3 -1
View File
@@ -6,6 +6,8 @@ import (
"github.com/admin-else/strom/nbt"
)
// TODO: this is stupid loginPacket.json exists next version migrate to that
//go:embed registry/*
var Registry embed.FS
@@ -15,6 +17,6 @@ func LoadRegistry(name string) (n *nbt.Tag, err error) {
return
}
defer r.Close()
n, err = nbt.ReadFile(r)
n, err = nbt.ReadUnstructuredFile(r)
return
}
Binary file not shown.
+101 -72
View File
@@ -7,20 +7,14 @@ import (
"fmt"
"log/slog"
"reflect"
"time"
)
const (
LoopsPerSecond = 10000 // this is just so the cpu doesn't burn itself in an unlimited infinite loop
TimePerLoop = time.Second / LoopsPerSecond
)
type Loop struct {
HandlerFunctions map[reflect.Type][]reflect.Value
Ctx context.Context
ErrChan chan error
cancel context.CancelFunc
Ctx context.Context
Cancel context.CancelFunc
eventSources []reflect.SelectCase
}
type Unhandled struct {
@@ -31,10 +25,6 @@ type Anything struct {
Val any
}
type (
Tick struct{}
)
// Close is fired when the loop shuts down.
type Close struct {
Reason error
@@ -45,6 +35,14 @@ type HandlerDoneErr struct {
Return error
}
// CallFunc is a special value that when fired with Loop.Fire it calls a function on the main loop
// Its usefull for event event sources to call functions on the main loop
// because event sources dont run on the main loop
type CallFunc struct {
F reflect.Value
Args []reflect.Value
}
func (e HandlerDoneErr) Error() string {
return "HandlerDoneErr"
}
@@ -69,10 +67,8 @@ func (e MainHandlerErr) Unwrap() error {
// DontForwardErr will stop the event from being forwarded to other handlers, including anything handlers.
var DontForwardErr = errors.New("dont forward")
var WhileClosingErr = errors.New("error while closing")
// CloseNonCriticalErr will close even when the event is non-critical
var CloseNonCriticalErr = errors.New("close non-critical")
var ContextDoneErr = errors.New("context done")
var RecvNotOkErr = errors.New("recv not ok")
func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
hv = reflect.ValueOf(h)
@@ -89,44 +85,64 @@ func ValidateHandler(h any) (eventType reflect.Type, hv reflect.Value) {
return
}
// RegisterEventSource registers an event source an event source is a channel this channel along with all other event sources will be reflect.Select 'ed until the loop closes
// here is an example of an event source function
//
// func() (chSending <-chan any) {
// ch := make(chan any)
// go func() {
// ctx := conn.Ctx
// for {
// select {
// case <-ctx.Done():
// return
// case eventt := <-otherSource:
// err = c.DoCommand(eventt)
// }
// }
// }()
// chSending = ch
// return
// }
func (l *Loop) RegisterEventSource(ch <-chan any) {
l.eventSources = append(l.eventSources, reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ch)})
}
// Register registers a handler that will not close the loop if it returns an error.
// Instead, it will log it to slog.Error
// Warning: If a bad handler is passed in the program will panic.
func (l *Loop) Register(h any) {
l.RegisterCustomType(ValidateHandler(h))
eventType, hv := ValidateHandler(h)
l.RegisterLow(eventType, hv, false)
}
// RegisterCustomType registers a handler that will not close the loop if it returns an error.
// RegisterLow registers a handler that will not close the loop if it returns an error.
// Instead, it will log it to slog.Error
// This does not hold true for signaling errors like HandlerDoneErr or DontForwardErr
// Warning: If a bad handler is passed in the program will panic.
func (l *Loop) RegisterCustomType(eventType reflect.Type, hv reflect.Value) {
handleFunc := func(packet any) error {
v := hv.Call([]reflect.Value{reflect.ValueOf(packet)})[0]
if !v.IsNil() {
err := v.Interface().(error)
if errors.Is(err, CloseNonCriticalErr) {
return err
func (l *Loop) RegisterLow(eventType reflect.Type, hv reflect.Value, critical bool) {
handleFunc := hv
if !critical {
handleFunc = reflect.ValueOf(func(packet any) error {
v := hv.Call([]reflect.Value{reflect.ValueOf(packet)})[0]
if !v.IsNil() {
err := v.Interface().(error)
if errors.As(err, &HandlerDoneErr{}) || errors.Is(err, DontForwardErr) {
return err
}
slog.Error("handler failed", "err", err)
}
slog.Error("handler failed", "err", err)
}
return nil
return nil
})
}
l.RegisterDirect(eventType, reflect.ValueOf(handleFunc))
l.HandlerFunctions[eventType] = append(l.HandlerFunctions[eventType], handleFunc)
}
// 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.
func (l *Loop) RegisterCritical(h any) {
eventType, hv := ValidateHandler(h)
l.RegisterDirect(eventType, hv)
}
func (l *Loop) RegisterDirect(k reflect.Type, h reflect.Value) {
l.HandlerFunctions[k] = append(l.HandlerFunctions[k], h)
}
func (l *Loop) RegisterIgnore(t any) {
l.RegisterDirect(reflect.TypeOf(t), reflect.ValueOf(func(any) error { return nil }))
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.
@@ -137,7 +153,7 @@ func (l *Loop) FireFound(event any) (found bool, err error) {
errV := handler.Call([]reflect.Value{reflect.ValueOf(event)})[0]
if !errV.IsNil() {
err = errV.Interface().(error)
err = errors.Join(fmt.Errorf("event handler failed at: %s(%T)", handler.Type().In(0), event), err)
err = errors.Join(fmt.Errorf("event handler failed at: %T", event), err)
return
}
}
@@ -147,6 +163,21 @@ func (l *Loop) FireFound(event any) (found bool, err error) {
// Fire dispatches the provided event to all appropriate handlers and invokes fallback handlers if the event is unhandled.
func (l *Loop) Fire(event any) (err error) {
err, isErr := event.(error)
if isErr {
return
}
err = nil
f, isCallFunc := event.(CallFunc)
if isCallFunc {
errV := f.F.Call(f.Args)[0].Interface()
if errV != nil {
err = errV.(error)
}
return
}
found, err := l.FireFound(event)
if err != nil {
if errors.Is(err, DontForwardErr) {
@@ -155,64 +186,62 @@ func (l *Loop) Fire(event any) (err error) {
return
}
if !found {
_, err = l.FireFound(Unhandled{event})
found, err = l.FireFound(Unhandled{event})
if err != nil {
return
}
}
_, err = l.FireFound(Anything{event})
found, err = l.FireFound(Anything{event})
return
}
func (l *Loop) OnTick(_ Tick) (err error) {
select {
case <-l.Ctx.Done():
err = ContextDoneErr
default:
}
return
func (l *Loop) ContextDoneListener() (chSending <-chan any) {
ch := make(chan any)
go func() {
<-l.Ctx.Done()
ch <- ContextDoneErr
}()
return ch
}
func (l *Loop) startLoop() (err error) {
func (l *Loop) IsTypeRegistered(t reflect.Type) bool {
_, ok := l.HandlerFunctions[t]
return ok
}
func (l *Loop) StartLoop() (err error) {
_ = *l // exit early on nil loop
l.RegisterEventSource(l.ContextDoneListener())
for {
tickStartTime := time.Now()
err = l.Fire(Tick{})
_, v, ok := reflect.Select(l.eventSources)
if !ok {
err = RecvNotOkErr
break
}
err = l.Fire(v.Interface())
if err != nil {
break
}
time.Sleep(TimePerLoop - time.Since(tickStartTime))
}
if errors.Is(err, HandlerDoneErr{}) {
err = errors.Unwrap(err)
var contextDoneErr HandlerDoneErr
if errors.As(err, &contextDoneErr) {
err = contextDoneErr.Return
}
closeErr := l.Fire(Close{err})
if closeErr != nil {
err = errors.Join(err, errors.Join(WhileClosingErr, closeErr))
}
return
}
func (l *Loop) StartLoop() (err error) {
go func() {
l.ErrChan <- MainHandlerErr{l.startLoop()}
}()
err = <-l.ErrChan
l.cancel()
errMaybe := MainHandlerErr{}
if errors.As(err, &errMaybe) {
err = errors.Unwrap(err)
} else {
for !errors.Is(<-l.ErrChan, MainHandlerErr{}) {
}
}
l.Cancel()
return
}
func NewLoop() (l *Loop) {
return NewLoopCtx(context.Background())
}
func NewLoopCtx(ctx context.Context) (l *Loop) {
l = &Loop{}
l.ErrChan = make(chan error)
l.Ctx, l.cancel = context.WithCancel(context.Background())
l.Ctx, l.Cancel = context.WithCancel(ctx)
l.HandlerFunctions = make(map[reflect.Type][]reflect.Value)
return
}
+21 -32
View File
@@ -2,7 +2,6 @@ package event
import (
"bufio"
"context"
"os"
)
@@ -10,36 +9,26 @@ type Stdin struct {
Val string
}
type StdinHandler struct {
*Loop
stdInChan chan Stdin
Ctx context.Context
}
func (s *StdinHandler) OnTick(_ Tick) (err error) {
select {
case stdin := <-s.stdInChan:
err = s.Fire(Stdin{stdin.Val})
default:
}
return
}
func (s *StdinHandler) ScanJob() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
select {
case <-s.Ctx.Done():
return
default:
s.stdInChan <- Stdin{scanner.Text()}
}
}
}
func (s *StdinHandler) OnStart() (err error) {
s.RegisterCritical(s.OnTick)
s.stdInChan = make(chan Stdin)
go s.ScanJob()
func StartListingStdin(l *Loop) {
l.RegisterEventSource(func() (chSending <-chan any) {
ch := make(chan any)
go func() {
scanner := bufio.NewScanner(os.Stdin)
ctx := l.Ctx
for scanner.Scan() {
select {
case <-ctx.Done():
return
default:
ch <- Stdin{scanner.Text()}
}
}
if err := scanner.Err(); err != nil {
ch <- err
}
}()
chSending = ch
return
}())
return
}
+61 -24
View File
@@ -1,6 +1,8 @@
package event
import (
"context"
"reflect"
"slices"
"time"
)
@@ -19,39 +21,54 @@ type OnceTask struct {
type Timer struct {
Once []OnceTask
Interval []IntervalTask
Active bool
}
func (t *Timer) Tick() (err error) {
if !t.Active {
func (t *Timer) Trigger(ch chan any) {
triggerAt := time.Time{}
found := false
iOnce, iInterval := -1, -1
for i, oTask := range t.Once {
if oTask.Trigger.Before(triggerAt) || triggerAt.IsZero() {
found = true
triggerAt = oTask.Trigger
iOnce = i
}
}
for i, iTask := range t.Interval {
if iTask.LastTrigger.Add(iTask.Interval).Before(triggerAt) || triggerAt.IsZero() {
found = true
triggerAt = iTask.LastTrigger.Add(iTask.Interval)
iOnce = -1
iInterval = i
}
}
if !found {
return
}
for i, task := range t.Interval {
if time.Since(task.LastTrigger) >= task.Interval {
task.LastTrigger = time.Now()
err = task.F()
if err != nil {
return
}
}
t.Interval[i] = task
now := time.Now()
sleepFor := triggerAt.Sub(now)
//slog.Debug("sleep next", "dur", sleepFor)
time.Sleep(sleepFor)
if iOnce >= 0 {
ch <- CallFunc{F: reflect.ValueOf(t.Once[iOnce].F), Args: []reflect.Value{}}
t.Once = slices.Delete(t.Once, iOnce, iOnce+1)
return
}
for i, task := range t.Once {
if task.Trigger.After(time.Now()) {
err = task.F()
if err != nil {
return
}
t.Once = slices.Delete(t.Once, i, i)
}
if iInterval >= 0 {
t.Interval[iInterval].LastTrigger = triggerAt
ch <- CallFunc{F: reflect.ValueOf(t.Interval[iInterval].F), Args: []reflect.Value{}}
return
}
return
panic("unreachable")
}
func (t *Timer) Every(interval time.Duration, f func() error) {
t.Interval = append(t.Interval, IntervalTask{
F: f,
Interval: interval,
F: f,
Interval: interval,
LastTrigger: time.Now(),
})
}
@@ -61,3 +78,23 @@ func (t *Timer) In(interval time.Duration, f func() error) {
Trigger: time.Now().Add(interval),
})
}
func (t *Timer) MakeEventSource(ctx context.Context) (chSending <-chan any) {
ch := make(chan any)
go func() {
for {
select {
case <-ctx.Done():
return
default:
t.Trigger(ch)
}
}
}()
chSending = ch
return
}
func (t *Timer) Start(c *Loop) {
c.RegisterEventSource(t.MakeEventSource(c.Ctx))
}
+24
View File
@@ -0,0 +1,24 @@
package anvil
type Chunk struct {
DataVersion int32
Heightmaps map[string][]int64
InhabitedTime int64
XPos, YPos, ZPos int32
Sections []Section
}
type Section struct {
Y int8
BlockStates struct {
Data []int64
Palette []struct {
Name string
Properties map[string]string `nbt:"properties,omitempty"`
}
}
Biomes []struct {
Data []int64
Palette []string
}
}
+185
View File
@@ -0,0 +1,185 @@
package anvil
import (
"io"
"github.com/admin-else/strom/nbt"
)
type Level struct {
ClearWeatherTime int32 `nbt:"clearWeatherTime"`
DataPacks DataPacks
DayTime int64
WanderingTraderSpawnChance int32
GameType int32
WorldGenSettings WorldGenSettings
ScheduledEvents []interface{}
WasModded bool
ThunderTime int32 `nbt:"thunderTime"`
Difficulty int8
DragonFight DragonFight
Initialized bool
AllowCommands bool `nbt:"allowCommands"`
Thundering bool
LastPlayed int64
GameRules map[string]interface{}
RainTime int32 `nbt:"rainTime"`
DataVersion int32
Version Version
WanderingTraderSpawnDelay int32
Hardcore bool
Spawn Spawn
Raining bool
Player Player
DifficultyLocked bool
LevelName string
Time int64
ServerBrands []string
CustomBossEvents map[string]interface{}
}
type DataPacks struct {
Enabled []string
Disabled []string
}
type WorldGenSettings struct {
BonusChest bool
Seed int64
GenerateFeatures bool
Dimensions map[string]Dimension
}
type Dimensions struct {
Overworld Dimension
Nether Dimension
End Dimension
}
type Dimension struct {
Generator Generator
Type string
}
type Generator struct {
Type string
Settings string
BiomeSource BiomeSource
}
type BiomeSource struct {
Preset string `nbt:"preset,omitempty"`
Type string
}
type DragonFight struct {
NeedsStateScanning bool
Gateways []int32
DragonKilled bool
PreviouslyKilled bool
}
type Version struct {
Snapshot bool
Series string
Id int32
Name string
}
type Spawn struct {
Dimension string
Yaw float32
Pos [3]int32
Pitch float32
}
type Player struct {
XpLevel int32
HurtTime int16
DeathTime int16
XpTotal int32
FoodExhaustionLevel float32
RecipeBook RecipeBook
Fire int16
Brain Brain
XpSeed int32
XpP float32
EnderItems []interface{}
Invulnerable bool
Pos [3]float64
Inventory []InventoryItem
SleepTimer int16
Abilities Abilities
PlayerGameType int32
SeenCredits bool
FoodSaturationLevel float32
FallDistance float64
OnGround bool
Dimension string
PortalCooldown int32
Motion [3]float64
IgnoreFallDamageFromCurrentExplosion bool
Rotation [2]float32
CurrentImpulseContextResetGraceTime int32
WardenSpawnTracker WardenSpawnTracker
PreviousPlayerGameType int32
Attributes []Attribute
HurtByTimestamp int32
UUID [4]int32
Score int32
DataVersion int32
FoodLevel int32
SpawnExtraParticlesOnFall bool
SelectedItemSlot int32
FoodTickTimer int32
FallFlying bool
AbsorptionAmount float32
Health float32
Air int16
}
type RecipeBook struct {
Recipes []string
ToBeDisplayed []string
}
type Brain struct {
Memories map[string]interface{}
}
type InventoryItem struct {
Count int32
Slot int8
Id string
}
type Abilities struct {
Invulnerable bool
Mayfly bool
Instabuild bool
WalkSpeed float32
MayBuild bool
Flying bool
FlySpeed float32
}
type WardenSpawnTracker struct {
WarningLevel int32
TicksSinceLastWarning int32
CooldownTicks int32
}
type Attribute struct {
Id string
Base float64
}
func ReadLevelData(r io.Reader) (l Level, err error) {
var lDataWrapped struct{ Data Level }
err = nbt.ReadFile(r, &lDataWrapped)
if err != nil {
return
}
l = lDataWrapped.Data
return
}
+46
View File
@@ -0,0 +1,46 @@
package anvil_test
import (
"encoding/json"
"fmt"
"os"
"testing"
"github.com/admin-else/strom/level/anvil"
"github.com/admin-else/strom/util"
)
func TestLevel(t *testing.T) {
f, err := os.Open("./testdata/testworld/level.dat")
if err != nil {
t.Fatal(err)
}
defer f.Close()
l, err := anvil.ReadLevelData(f)
if err != nil {
t.Fatal(err)
}
t.Log(l)
}
func TestRegionFile(t *testing.T) {
f, err := os.Open("./testdata/testworld/region/r.0.0.mca")
if err != nil {
t.Fatal(err)
}
defer f.Close()
ch, err := anvil.ReadChunkHeader(f)
if err != nil {
t.Fatal(err)
}
n, err := ch.ChunkAt(f, 0, 0)
if err != nil {
return
}
fmt.Println(string(util.MustT(json.MarshalIndent(n, "", " "))))
fmt.Println("Chunk loaded successfully")
}
+103
View File
@@ -0,0 +1,103 @@
package anvil
import (
"compress/gzip"
"compress/zlib"
"encoding/binary"
"fmt"
"io"
"os"
"github.com/admin-else/strom/nbt"
)
type CompressionStrategy uint8
const (
ChunksWidthRegionFile = 32
ChunksRegionFile = ChunksWidthRegionFile * ChunksWidthRegionFile
)
const (
_ CompressionStrategy = iota
CompressionStrategyGZIP
CompressionStrategyZLIB
CompressionStrategyNONE
CompressionStrategyLZ4
)
func (c CompressionStrategy) Wrap(r io.ReadCloser) (rNew io.ReadCloser, shouldClose bool, err error) {
switch c {
case CompressionStrategyGZIP:
shouldClose = true
rNew, err = gzip.NewReader(r)
return
case CompressionStrategyZLIB:
shouldClose = true
rNew, err = zlib.NewReader(r)
return
case CompressionStrategyNONE:
rNew = r
return
case CompressionStrategyLZ4:
err = fmt.Errorf("lz4 compression not supported")
return
default:
err = fmt.Errorf("unknown compression strategy %d", c)
return
}
}
type ChunkHeader struct {
Chunk, Time [ChunksRegionFile]int32
}
func ReadChunkHeader(r *os.File) (ch *ChunkHeader, err error) {
var h ChunkHeader
err = binary.Read(r, binary.BigEndian, &h)
if err != nil {
return
}
ch = &h
return
}
func (c *ChunkHeader) ChunkAt(f *os.File, x, z int32) (n *nbt.Tag, err error) {
off, size := chunkLocation(c.Chunk[chunkXZtoIndex(x, z)])
_, err = f.Seek(int64(off), 0)
if err != nil {
return
}
_ = size // i dont really know why mc has this maybe to point out how much space is wasted?
var realSize int32
err = binary.Read(f, binary.BigEndian, &realSize)
if err != nil {
return
}
var compressionStrategy CompressionStrategy
err = binary.Read(f, binary.BigEndian, &compressionStrategy)
if err != nil {
return
}
r, shouldClose, err := compressionStrategy.Wrap(f)
if err != nil {
return
}
if shouldClose {
defer r.Close()
}
n = &nbt.Tag{}
err = n.Decode(r)
if err != nil {
return
}
return
}
func chunkXZtoIndex(x, z int32) int32 {
return (x % ChunksWidthRegionFile) + (z%ChunksWidthRegionFile)*ChunksWidthRegionFile
}
func chunkLocation(v int32) (offset, l int32) {
return ((v >> 8) & 0xFFFFFF) * 4096, (v & 0xFF) * 4096
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,215 @@
{
"minecraft:recipes/building_blocks/polished_deepslate": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/combat/stone_sword": {
"criteria": {
"has_cobblestone": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_axe": {
"criteria": {
"has_cobblestone": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:story/mine_stone": {
"criteria": {
"get_stone": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/decorations/furnace": {
"criteria": {
"has_cobblestone": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/decorations/crafting_table": {
"criteria": {
"unlock_right_away": "2026-01-18 19:40:40 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
"criteria": {
"has_porkchop": "2026-01-18 19:40:40 +0100"
},
"done": true
},
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop": {
"criteria": {
"has_porkchop": "2026-01-18 19:40:40 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_pickaxe": {
"criteria": {
"has_cobblestone": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/misc/charcoal": {
"criteria": {
"has_log": "2026-01-18 19:41:43 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/oak_planks": {
"criteria": {
"has_logs": "2026-01-18 19:41:43 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
"criteria": {
"has_porkchop": "2026-01-18 19:40:40 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:adventure/adventuring_time": {
"criteria": {
"minecraft:forest": "2026-01-18 19:40:40 +0100",
"minecraft:deep_dark": "2026-01-18 19:41:56 +0100",
"minecraft:stony_shore": "2026-01-18 19:40:46 +0100"
},
"done": false
},
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_hoe": {
"criteria": {
"has_cobblestone": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_shovel": {
"criteria": {
"has_cobblestone": "2026-01-18 19:41:36 +0100"
},
"done": true
},
"DataVersion": 4556
}
@@ -0,0 +1,220 @@
{
"minecraft:recipes/building_blocks/polished_deepslate": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/combat/stone_sword": {
"criteria": {
"has_cobblestone": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_axe": {
"criteria": {
"has_cobblestone": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:story/mine_stone": {
"criteria": {
"get_stone": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/decorations/furnace": {
"criteria": {
"has_cobblestone": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/decorations/crafting_table": {
"criteria": {
"unlock_right_away": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
"criteria": {
"has_porkchop": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop": {
"criteria": {
"has_porkchop": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_pickaxe": {
"criteria": {
"has_cobblestone": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/misc/charcoal": {
"criteria": {
"has_log": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/combat/stone_spear": {
"criteria": {
"has_cobblestone": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/oak_planks": {
"criteria": {
"has_logs": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
"criteria": {
"has_porkchop": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:adventure/adventuring_time": {
"criteria": {
"minecraft:stony_shore": "2026-03-11 19:22:59 +0100",
"minecraft:plains": "2026-03-11 19:22:38 +0100"
},
"done": false
},
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_hoe": {
"criteria": {
"has_cobblestone": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_shovel": {
"criteria": {
"has_cobblestone": "2026-03-11 19:22:37 +0100"
},
"done": true
},
"DataVersion": 4671
}
@@ -0,0 +1,222 @@
{
"minecraft:recipes/building_blocks/polished_deepslate": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/combat/stone_sword": {
"criteria": {
"has_cobblestone": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_axe": {
"criteria": {
"has_cobblestone": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:story/mine_stone": {
"criteria": {
"get_stone": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/decorations/furnace": {
"criteria": {
"has_cobblestone": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/decorations/crafting_table": {
"criteria": {
"unlock_right_away": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
"criteria": {
"has_porkchop": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop": {
"criteria": {
"has_porkchop": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_pickaxe": {
"criteria": {
"has_cobblestone": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/misc/charcoal": {
"criteria": {
"has_log": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/combat/stone_spear": {
"criteria": {
"has_cobblestone": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/oak_planks": {
"criteria": {
"has_logs": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
"criteria": {
"has_porkchop": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:adventure/adventuring_time": {
"criteria": {
"minecraft:forest": "2026-02-07 19:19:04 +0100",
"minecraft:river": "2026-02-07 19:20:26 +0100",
"minecraft:stony_shore": "2026-02-07 19:20:11 +0100",
"minecraft:plains": "2026-02-07 19:20:12 +0100"
},
"done": false
},
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_hoe": {
"criteria": {
"has_cobblestone": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_shovel": {
"criteria": {
"has_cobblestone": "2026-02-07 19:19:03 +0100"
},
"done": true
},
"DataVersion": 4671
}
@@ -0,0 +1,119 @@
{
"minecraft:recipes/decorations/crafting_table": {
"criteria": {
"unlock_right_away": "2025-11-30 09:32:28 +0100"
},
"done": true
},
"minecraft:recipes/transportation/cherry_boat": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"minecraft:adventure/kill_all_mobs": {
"criteria": {
"minecraft:zombie": "2025-11-30 10:06:33 +0100"
},
"done": false
},
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
"criteria": {
"has_porkchop": "2025-11-30 10:06:57 +0100"
},
"done": true
},
"minecraft:recipes/transportation/bamboo_raft": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"minecraft:recipes/transportation/spruce_boat": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"minecraft:recipes/transportation/birch_boat": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"minecraft:recipes/transportation/oak_boat": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"minecraft:recipes/transportation/mangrove_boat": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop": {
"criteria": {
"has_porkchop": "2025-11-30 10:06:57 +0100"
},
"done": true
},
"minecraft:recipes/transportation/acacia_boat": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"minecraft:adventure/root": {
"criteria": {
"killed_something": "2025-11-30 10:06:33 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
"criteria": {
"has_porkchop": "2025-11-30 10:06:57 +0100"
},
"done": true
},
"minecraft:adventure/kill_a_mob": {
"criteria": {
"minecraft:zombie": "2025-11-30 10:06:33 +0100"
},
"done": true
},
"minecraft:recipes/transportation/pale_oak_boat": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"minecraft:adventure/adventuring_time": {
"criteria": {
"minecraft:old_growth_birch_forest": "2025-11-30 10:03:58 +0100",
"minecraft:forest": "2025-11-30 10:04:40 +0100",
"minecraft:taiga": "2025-11-30 09:32:29 +0100",
"minecraft:deep_dark": "2025-11-30 10:05:12 +0100",
"minecraft:river": "2025-11-30 10:09:03 +0100",
"minecraft:grove": "2025-11-30 09:32:32 +0100",
"minecraft:meadow": "2025-11-30 09:32:44 +0100",
"minecraft:birch_forest": "2025-11-30 10:05:33 +0100",
"minecraft:frozen_peaks": "2025-11-30 09:32:55 +0100"
},
"done": false
},
"minecraft:recipes/transportation/jungle_boat": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"minecraft:recipes/transportation/dark_oak_boat": {
"criteria": {
"in_water": "2025-11-30 10:05:13 +0100"
},
"done": true
},
"DataVersion": 4556
}
@@ -0,0 +1,220 @@
{
"minecraft:recipes/building_blocks/polished_deepslate": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_tile_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_brick_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/combat/stone_sword": {
"criteria": {
"has_cobblestone": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_axe": {
"criteria": {
"has_cobblestone": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:story/mine_stone": {
"criteria": {
"get_stone": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/decorations/furnace": {
"criteria": {
"has_cobblestone": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/chiseled_deepslate_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/decorations/crafting_table": {
"criteria": {
"unlock_right_away": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_slab": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/decorations/cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop_from_campfire_cooking": {
"criteria": {
"has_porkchop": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/decorations/cobbled_deepslate_wall": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/decorations/polished_deepslate_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_stairs_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/decorations/deepslate_brick_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/polished_deepslate_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop": {
"criteria": {
"has_porkchop": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_pickaxe": {
"criteria": {
"has_cobblestone": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_brick_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_tile_slab_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/misc/charcoal": {
"criteria": {
"has_log": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/cobbled_deepslate_stairs": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/combat/stone_spear": {
"criteria": {
"has_cobblestone": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/decorations/deepslate_tile_wall_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/oak_planks": {
"criteria": {
"has_logs": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/food/cooked_porkchop_from_smoking": {
"criteria": {
"has_porkchop": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/building_blocks/deepslate_bricks_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:adventure/adventuring_time": {
"criteria": {
"minecraft:stony_shore": "2026-03-12 21:17:48 +0100",
"minecraft:plains": "2026-03-12 21:17:31 +0100"
},
"done": false
},
"minecraft:recipes/building_blocks/deepslate_tiles_from_cobbled_deepslate_stonecutting": {
"criteria": {
"has_cobbled_deepslate": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_hoe": {
"criteria": {
"has_cobblestone": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"minecraft:recipes/tools/stone_shovel": {
"criteria": {
"has_cobblestone": "2026-03-12 21:17:28 +0100"
},
"done": true
},
"DataVersion": 4671
}
+1
View File
@@ -0,0 +1 @@
https://github.com/cabaletta/baritone
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More