227 Commits
Author SHA1 Message Date
adman 30cfc507ba less pollution 2026-07-27 15:05:50 +02:00
adman d84618c116 update minecraft data to fix bundle delimiter 2026-07-23 22:32:59 +02:00
adman 5a55520126 migrate proto_generated to be runable from repo root 2026-07-23 22:26:23 +02:00
adman 8ac5e00c8e regenerate protocol 2026-07-23 22:23:22 +02:00
adman d46c678b89 make mincraft data sync scripts executable from anywhere 2026-07-23 22:20:53 +02:00
adman 0b714edcf5 Sync minecraft-data 2026-07-23 22:14:19 +02:00
adman 45151add64 Remove proto_base.Void 2026-07-23 22:11:29 +02:00
adman b90830881f Fix 26.2 login_success protocol handling and add convertibility tests
- Server sends v26_2 login_success with SessionId for protocol >= 776

- Client adds v26_2 login_success handler and scopes v1_21_8 handler to <= 26.1

- Fix WithOtherAccount guard in login server

- Add smart-convert assumption tests for login server/client

- Improve translatePacketVersion error messages
2026-07-23 17:00:13 +02:00
adman 94301a4a50 Add 26.2 protocol support to strom 2026-07-23 15:06:49 +02:00
adman aa5d15a4dc Regenerate 26.1 protocol with corrected play packet IDs (clientbound + serverbound) 2026-07-23 00:09:43 +02:00
adman d388e2c350 add 26.1 support, raw-capture subcommand, and local minecraft-data copy script 2026-07-22 23:22:14 +02:00
adman 126ff5bca0 improve packet replay things 2026-07-20 12:56:19 +02:00
adman a347aedfc2 untrack big testdata 2026-07-16 22:56:44 +02:00
adman e324a95727 cmd take account with env var 2026-07-16 22:16:04 +02:00
adman 7f878f40a1 add world module for in-memory block storage and integrate getblock command in messenger 2026-07-16 16:05:42 +02:00
adman 7baa9990cb yes222 2026-07-16 14:02:28 +02:00
adman 5c5c391aa6 yes 2026-07-16 01:13:31 +02:00
adman b966925212 move data stuff 2026-07-11 23:08:03 +02:00
adman efd7049859 biome map stuff 2026-07-11 22:08:29 +02:00
adman 3ef9446f50 biome depth stuff 2026-07-06 11:49:45 +02:00
adman 68612969c9 fix spline test 2026-07-05 22:32:39 +02:00
adman b4712ff5c6 spline 2026-07-05 20:53:59 +02:00
adman eab050e291 fixes 2026-07-05 10:49:45 +02:00
adman a8b090227b mc gen start 2026-07-05 10:25:43 +02:00
adman 201e0dfa44 fix moving pains 2026-06-27 14:10:31 +02:00
adman 666f250b3e mc root source folder cleanup 2026-06-27 13:54:54 +02:00
adman b2784574bd move tools to strom cli 2026-06-27 13:06:05 +02:00
adman c0b1501f23 add packet spy json logging 2026-06-17 11:45:54 +02:00
adman 4de76d8023 regenerate proto 2026-06-16 23:08:00 +02:00
adman f08684fb67 fix bug in entity metadata encoding 2026-06-16 23:07:33 +02:00
adman 9e4f9409c5 add TestEntityMetadata 2026-06-16 23:06:58 +02:00
adman 4d3fbed957 add SimplePacketToBytesLenPrefix 2026-06-16 23:06:42 +02:00
adman b338888b58 add SimpleBytesToPacket utility function for decoding packets 2026-06-16 22:29:26 +02:00
adman 4956c1d874 add OnFinishLogin handling to packet inspector 2026-06-16 22:28:01 +02:00
adman d4fe362d9f add MustLookupProtocolVersion utility function for testing purposes 2026-06-16 22:27:41 +02:00
adman 5d8ff870df add true-forward mode to packet inspector 2026-06-02 21:06:41 +02:00
adman 58c9a60236 add optional encryption flag to login server and update ServeLogin behavior 2026-05-30 15:53:35 +02:00
adman 3e5b141ec7 add ToolSum function for build info and integrate it into Version outputs 2026-05-29 01:27:35 +02:00
adman efeb5ecda6 add file comment generation and hashing for reproducible output in proto_generator 2026-05-29 01:14:56 +02:00
adman 624e8ad4de add SimplePacketToBytes utility function for encoding packets 2026-05-28 23:59:54 +02:00
adman 764cfd8c4d lpvec3d 2026-05-14 10:42:38 +02:00
adman d43ff7f83d add seek-back handling in entity metadata decoding 2026-05-14 00:06:45 +02:00
adman a39999b344 update Decode function signatures to use io.ReadSeeker instead of io.Reader 2026-05-13 23:54:06 +02:00
adman 8ee48e1a45 add EntityMetadataLoop handling 2026-05-13 23:21:31 +02:00
adman bf6affeb97 remove unused protodef_types file from proto_generator 2026-05-13 22:20:32 +02:00
adman ede9a47ca2 add placeholder replacement and formatting utility functions 2026-05-12 16:31:00 +02:00
adman 2fb290572a add mutex for thread-safe XORKeyStream in CFB8 2026-05-11 23:27:14 +02:00
adman 021b8ddc1a add support for initial state configuration in print_replay tool 2026-05-10 20:35:58 +02:00
adman 05e8da2ff4 add packet id lookup and write to replay writer 2026-05-10 20:35:49 +02:00
adman a8936d92a5 add replay reader and writer for handling tmcpr files 2026-05-10 18:41:04 +02:00
adman c56898e787 refactor print_replay to improve unprocessable packet test creation 2026-05-10 17:29:52 +02:00
adman c483bf9553 if ctx is nil, set it to background context 2026-05-10 14:30:28 +02:00
adman b5ee17d1ae remove uncodeable_packet_hunter_proxy tool and integrate its functionality into print_replay 2026-05-10 14:09:24 +02:00
adman 57cef25e27 manage state transitions in print_replay tool 2026-05-10 13:01:40 +02:00
adman 148302c94d medium sized replay test data 2026-05-09 23:07:27 +02:00
adman 02c402251d add packet-info subcommand 2026-05-09 21:56:50 +02:00
adman c800c5d471 improve chunk section palette handling for blocks 2026-05-09 16:34:31 +02:00
adman 477f51cce7 fix biome palette initialization in chunk sections 2026-05-08 22:17:32 +02:00
adman 60103ebf47 enable Git LFS in checkout step 2026-05-08 21:41:46 +02:00
adman 21eeebf0f7 fix lighting 2026-05-08 21:33:05 +02:00
adman bb6f6548d5 serve world tool 2026-05-08 20:58:08 +02:00
adman d6c07cf428 replay testdata 2026-05-08 20:50:42 +02:00
adman e98921755d replay testdata 2026-05-08 20:50:37 +02:00
adman 60383fede1 replay testdata 2026-05-08 20:50:34 +02:00
adman 3bfa70d384 replay testdata 2026-05-08 19:17:09 +02:00
adman 60147fd4b6 correct "Properties" field case 2026-05-08 19:16:42 +02:00
adman fe51287cc6 add FIXME comments for mapstructure usage in go.mod and text.go 2026-05-08 12:23:27 +02:00
adman a44c5f8606 ci(workflows): add go vet step to CI pipeline 2026-05-08 12:11:36 +02:00
adman d5979b2c3d feat(util, anvil): add FloorDiv utility and nil-check in ChunkAt 2026-05-06 22:38:24 +02:00
admin-elseandGitHub 4b95ea9ae2 Merge pull request #1 from admin-else/feat/mca2packet
start work on mca to packet conversion
2026-05-06 21:14:53 +02:00
admin-elseandGitHub a80fd29297 Merge branch 'master' into feat/mca2packet 2026-05-06 21:13:49 +02:00
adman 589f35d147 delete the failing test because it works now i tested in game 2026-05-06 21:13:02 +02:00
adman c7b5f6ba0f refactor(util): remove unused utility functions 2026-05-06 18:47:18 +02:00
adman 0600315e69 refactor: move utility functions to dedicated util package and adjust references 2026-05-06 16:51:45 +02:00
adman 12005df68b Merge remote-tracking branch 'origin/feat/mca2packet' into feat/mca2packet 2026-05-06 16:48:48 +02:00
adman 600842a679 refactor storage format and bpe handling; add redirecting bpe support and utility function for next largest number 2026-05-06 16:48:39 +02:00
adman c93d907d46 refactor storage format and bpe handling; add redirecting bpe support and utility function for next largest number 2026-05-06 16:47:50 +02:00
adman be6d2fb461 update Go version to 1.26.2 in go.mod and CI workflow 2026-05-06 15:55:54 +02:00
adman b2df88203d fix block count calculation in chunk sections; improve variable naming in tests 2026-05-05 23:12:02 +02:00
admin-elseandGitHub 2ed4498181 Merge pull request #2 from admin-else/feat/data-cli
add `data` command and utility functions for type assertion and map k…
2026-05-05 15:32:03 +02:00
adman 0316b0aa09 add data command and utility functions for type assertion and map key access 2026-05-05 15:28:40 +02:00
adman 62d12386e1 improve chunk comparison by adding custom storage comparer and ignoring unexported fields 2026-05-05 13:57:02 +02:00
adman 00ea02a529 refactor chunk version handling; improve logging in equality checks and storage import logic 2026-05-05 13:30:21 +02:00
adman e5231dfdb6 improve chunk palette handling and logging; fix biome format and storage comparison logic 2026-05-04 00:27:00 +02:00
adman 3b1b436ecf refactor block state handling and storage initialization in litematic structures 2026-05-03 22:26:02 +02:00
adman 47180bf650 fix incorrect equality check in storage comparison logic 2026-05-03 19:02:06 +02:00
adman 325db98b99 add fuzz test data for chunk reading validation 2026-05-03 18:59:49 +02:00
adman ceae543352 read chunk section pallet fix 2026-05-03 18:59:42 +02:00
adman aef9615476 add equality checks and fuzz testing for chunk and storage data comparison 2026-05-03 18:34:21 +02:00
adman 5d3860b91e start work on mca to packet conversion 2026-05-03 18:20:36 +02:00
adman 94d15258d8 change Proxy.Data to pointer for consistency with nbt.Tag usage 2026-05-03 16:31:14 +02:00
adman ab03b36b4f add bounds checking for ChunkAt and add TimeAt function to ChunkHeader 2026-05-03 16:28:50 +02:00
adman da0c3c6f14 add bounds checking for ChunkAt and add TimeAt function to ChunkHeader 2026-05-03 16:28:37 +02:00
adman b01e482bc5 add file validation to print_nbt command 2026-05-03 16:23:01 +02:00
adman c560966177 add WriteUnstructuredFilePath function to simplify file writing logic 2026-05-03 16:22:17 +02:00
adman 66f86c5599 update Go version to 1.25 in GitHub Actions workflow 2026-05-03 15:32:58 +02:00
adman 5a2bfcb1c1 Merge remote-tracking branch 'origin/master' 2026-05-03 15:30:06 +02:00
admin-elseandGitHub b34f8d68ff Create Go CI workflow in GitHub Actions
Add GitHub Actions workflow for Go project build and test
2026-05-03 15:29:52 +02:00
adman c98472165b update connection initialization logic 2026-05-03 15:14:47 +02:00
adman 4888a07ebb update connection initialization to use LatestVersion instead of EarliestVersion 2026-05-03 15:11:08 +02:00
adman a9bf30a609 remove unused TestStorageImportBpeNotAvailable test because there is resizing for mca level storage to block format 2026-05-03 15:00:05 +02:00
adman 35030601e1 fix PrettyToNbt test to include empty text field and improve error formatting 2026-05-03 14:58:04 +02:00
adman 66f32b4cd2 update minecraft data binding to make binary smaller 2026-05-03 14:39:31 +02:00
adman e6d08dccbb working on disk to chunk packet test 2026-05-03 14:33:13 +02:00
adman 8a4f203621 add WithoutOnlineMode setting to login server and update usage in packet inspector 2026-05-02 16:16:54 +02:00
adman 69e230a79c add 'version' subcommand to display build information 2026-05-02 16:07:31 +02:00
adman 1a5f4f4f8e remove unused address parameter from LoginRaw and fix biome palette length calculation 2026-05-02 15:52:06 +02:00
adman d0844dd6b5 anvil chunk to real chunk conversion things 2026-05-02 01:36:29 +02:00
adman d8e36aa6fa make login server respect online mode settings 2026-05-02 00:21:26 +02:00
adman 6781e4c16d support upsizing storage on import if bpe not available 2026-05-02 00:21:17 +02:00
adman fccba0248a this makes errors that happen during login display 2026-05-01 22:17:51 +02:00
adman 52b3c1ce17 Revert "add export-ignore rules for proto_generator and tools directories in .gitattributes"
This reverts commit 3511cd2699.
2026-05-01 16:49:10 +02:00
adman 3511cd2699 add export-ignore rules for proto_generator and tools directories in .gitattributes 2026-05-01 16:48:26 +02:00
adman 6dddccab7f fix gitattributes 2026-05-01 16:45:58 +02:00
adman 9109d77f98 add export-ignore rules for test files in .gitattributes 2026-05-01 16:44:08 +02:00
adman 9eec1e8370 clone only needed minecraft-data 2026-05-01 16:33:28 +02:00
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
adman df0e5e354b more srv stuff and packet inspetor 2026-03-25 19:00:18 +01:00
adman 23b5a5d9b7 move cmd to more sensible location 2026-03-25 14:53:38 +01:00
adman 46770ed980 Account changes
- now supports custom enviorments/servers
- cleaner code imo
2026-03-25 14:48:42 +01:00
adman 6179c5f6b6 fix proxy 2026-03-24 22:36:14 +01:00
adman f758a10575 performance fix 2026-03-24 21:50:52 +01:00
adman 41d1ab4c45 performance 2026-03-24 17:46:23 +01:00
adman 032fa26927 fix some race conditions 2026-03-22 22:51:29 +01:00
adman f7401a287e minecraft data raw code 2026-03-22 19:49:37 +01:00
adman 2d1e8ef4f6 go get does not support git submodules 2026-03-22 19:48:21 +01:00
adman 2802cc36ce litematic format 2026-03-22 19:45:05 +01:00
adman ea024fe808 look at the code if you want to know what i did 2026-03-22 00:35:07 +01:00
adman a6102b7ec9 working srv status 2026-03-20 22:19:07 +01:00
adman 254b75e76b remove onstart handler thing in favor of the user doing that themselves 2026-03-20 22:10:40 +01:00
adman 33cec6ecc9 mark lpvec3d as todo 2026-03-20 18:53:51 +01:00
adman fbf9bacd05 go vet fixup 2026-03-20 18:51:17 +01:00
adman 29baf8da9f test fixup 2026-03-20 17:57:03 +01:00
adman 43fea40d08 dns fix 2026-03-20 17:08:54 +01:00
adman b107eba37c dns 2026-03-19 23:50:28 +01:00
adman abc0f6aebb move some stuff and add registercritical 2026-03-19 22:28:20 +01:00
adman 8c6172dcda cleanup protodef type locations 2026-03-19 21:57:05 +01:00
adman 57e78478cd fix status cli 2026-03-18 21:23:48 +01:00
adman f554e91354 move a few tools to a more central utility cli 2026-03-18 20:45:54 +01:00
adman e0cf1834f1 items and struture loading 2026-03-12 23:13:25 +01:00
adman 047c4d02ff version compat system 2026-03-11 09:18:58 +01:00
adman c516cc5283 version compat system start 2026-03-09 21:30:31 +01:00
adman 165075ea02 Refactor event system 2026-03-08 13:56:36 +01:00
adman 2eee7bde24 refactor event systems 2026-03-08 11:52:25 +01:00
adman 73d4e07b06 block state 2026-03-06 21:24:08 +01:00
adman 5dfd8fc499 accidentally regen again and block state early work 2026-03-03 09:31:44 +01:00
adman 1b36949316 regenerate proto 2026-03-02 22:33:17 +01:00
adman 5c455bd2c8 regenerate proto 2026-03-02 22:26:40 +01:00
adman 8d1a4b581b shitty bitfield packing code 2026-03-02 22:24:26 +01:00
adman 085b83cb71 fix stupid protodef stuff 2026-03-02 21:40:36 +01:00
adman 65bf63d67e Update unCodable packet hunter to new scheme 2026-03-02 11:08:22 +01:00
adman d8cc9cab48 stuff 2026-03-02 00:14:57 +01:00
adman f7d7a72b7c nbt files 2026-03-01 13:28:49 +01:00
adman 29b1b92113 Add server infrastructure with login, status, and kick handlers 2026-02-24 23:28:25 +01:00
adman bd83b9689e Refactor server modules into main package and improve error handling 2026-02-24 23:27:56 +01:00
adman 85529b965b egg 2026-01-25 12:55:27 +01:00
275 changed files with 2204403 additions and 12795 deletions
+6
View File
@@ -0,0 +1,6 @@
**/*_test.go export-ignore
**/testdata export-ignore
*.zip filter=lfs diff=lfs merge=lfs -text
*.tmcpr filter=lfs diff=lfs merge=lfs -text
*.mcpr filter=lfs diff=lfs merge=lfs -text
*.mca filter=lfs diff=lfs merge=lfs -text
+35
View File
@@ -0,0 +1,35 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout with LFS
uses: actions/checkout@v4
with:
lfs: true
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.26.2'
- name: Build
run: go build -v ./...
- name: Vet
run: go vet -v ./...
- name: Test
run: go test -v ./...
+1
View File
@@ -1,3 +1,4 @@
.devres/
.idea/
.failed_packets/
.vscode/
-3
View File
@@ -1,3 +0,0 @@
[submodule "data/minecraft-data"]
path = data/minecraft-data
url = git@github.com:PrismarineJS/minecraft-data.git
+37 -178
View File
@@ -1,202 +1,61 @@
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
0. Additional Definitions.
1. Definitions.
As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
3. Object Code Incorporating Material from Library Header Files.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license document.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Combined Works.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
5. Combined Libraries.
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
6. Revised Versions of the GNU Lesser General Public License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
+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)
-26
View File
@@ -1,26 +0,0 @@
package client
import (
"net"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
)
func Connect(addr string) (ret *proto.Conn, err error) {
ret = &proto.Conn{}
err = ret.SetVersion("1.21.8")
if err != nil {
return
}
ret.SetState(proto_base.Handshaking)
ret.CompressionThreshold = -1
ret.Actor = proto_base.Client
ret.Conn, err = net.Dial("tcp", addr)
if err != nil {
return
}
ret.R = ret.Conn
ret.W = ret.Conn
return
}
-22
View File
@@ -1,22 +0,0 @@
package client
import (
"net"
"strings"
)
func LookupMinecraftSRV(host string, port uint16) (hostRet string, portRet uint16) {
hostRet = host // in case of error
portRet = port
if port != 25565 {
return
}
_, srvs, err := net.LookupSRV("minecraft", "tcp", host)
if err != nil || len(srvs) == 0 {
return
}
target := strings.TrimSuffix(srvs[0].Target, ".")
hostRet = target
portRet = srvs[0].Port
return
}
-172
View File
@@ -1,172 +0,0 @@
package modules
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"net"
"strconv"
"github.com/admin-else/strom/api"
"github.com/admin-else/strom/client"
"github.com/admin-else/strom/crypto"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/text"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
type LoginClient struct {
*proto.Conn
Account *api.Account
ServerHost string
ServerPort uint16
GivenAccount *v1_21_8.LoginToClientPacketSuccess
}
type KickedDuringLoginErr struct {
text.Component
}
func (k KickedDuringLoginErr) Error() string {
return k.String()
}
func (s *LoginClient) Default(event event.Unhandled) (err error) {
err = fmt.Errorf("unexpected event: %#v", event)
return
}
func (s *LoginClient) OnCycle(_ event.Tick) (err error) {
return
}
func (s *LoginClient) OnStart(_ event.OnStart) (err error) {
if s.ServerHost == "" && s.ServerPort == 0 {
var portStr string
s.ServerHost, portStr, err = net.SplitHostPort(s.RemoteAddr().String())
if err != nil {
return
}
var portUint uint64
portUint, err = strconv.ParseUint(portStr, 10, 16)
if err != nil {
return
}
s.ServerPort = uint16(portUint)
}
err = s.Send(&v1_21_8.HandshakingToServerPacketSetProtocol{
ProtocolVersion: s.ProtocolVersion,
ServerHost: s.ServerHost,
ServerPort: s.ServerPort,
NextState: int32(proto_base.Login),
})
if err != nil {
return
}
s.SetState(proto_base.Login)
err = s.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: s.Account.Name, PlayerUUID: s.Account.Uuid})
return
}
func (s *LoginClient) OnCompress(compress *v1_21_8.LoginToClientPacketCompress) (err error) {
s.CompressionThreshold = compress.Threshold
return
}
func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBegin) (err error) {
sharedSecret := make([]byte, 16)
_, _ = rand.Read(sharedSecret) //never fails
if packet.ShouldAuthenticate {
if s.Account.Ygg == "" {
err = fmt.Errorf("the account has no token so we cant join online servers")
return
}
serverId := crypto.AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey)
err = s.Account.JoinServer(serverId)
if err != nil {
return
}
}
pubAny, err := x509.ParsePKIXPublicKey(packet.PublicKey)
if err != nil {
return
}
pub, ok := pubAny.(*rsa.PublicKey)
if !ok {
err = fmt.Errorf("public key is not rsa")
return
}
verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, packet.VerifyToken)
if err != nil {
return
}
sharedSecretEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, sharedSecret)
if err != nil {
return
}
err = s.Send(&v1_21_8.LoginToServerPacketEncryptionBegin{SharedSecret: sharedSecretEnc, VerifyToken: verifyTokenEnc})
if err != nil {
return
}
err = s.SetSecret(sharedSecret)
return
}
func (s *LoginClient) OnDisconnect(packet *v1_21_8.LoginToClientPacketDisconnect) (err error) {
var reason text.Component
err = json.Unmarshal([]byte(packet.Reason), &reason)
if err != nil {
return
}
err = KickedDuringLoginErr{reason}
return
}
func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (err error) {
s.GivenAccount = success
err = s.Send(&v1_21_8.LoginToServerPacketLoginAcknowledged{})
if err != nil {
return
}
s.SetState(proto_base.Configuration)
err = event.HandlerDoneErr{}
return
}
func Login(c *proto.Conn, account *api.Account) (err error) {
err = c.Start(&LoginClient{
Conn: c,
Account: account,
})
if err != nil {
err = errors.Join(err, errors.New("failed to login"))
}
return
}
func ConnectAndLogin(connectTo string, account *api.Account) (c *proto.Conn, err error) {
c, err = client.Connect(connectTo)
if err != nil {
return
}
err = c.Start(&LoginClient{
Conn: c,
Account: account,
})
if err != nil {
err = errors.Join(errors.New("failed to login"), err)
}
return
}
-54
View File
@@ -1,54 +0,0 @@
package modules
import (
"fmt"
"strconv"
"strings"
"github.com/admin-else/strom/event"
"github.com/admin-else/strom/proto"
"github.com/admin-else/strom/proto_base"
"github.com/admin-else/strom/data"
"github.com/admin-else/strom/proto_generated/v1_21_8"
)
type StatusClient struct {
*proto.Conn
Status string
}
func (s *StatusClient) Default(event any) (err error) {
err = fmt.Errorf("unexpected event: %v", event)
return
}
func (s *StatusClient) OnStart(_ event.OnStart) (err error) {
parts := strings.Split(s.RemoteAddr().String(), ":")
versionData, err := data.LookUpProtocolVersionByName(s.Version)
if err != nil {
return
}
port, err := strconv.Atoi(parts[1])
if err != nil {
return
}
err = s.Send(&v1_21_8.HandshakingToServerPacketSetProtocol{
ProtocolVersion: int32(versionData.Version),
ServerHost: parts[0],
ServerPort: uint16(port),
NextState: int32(proto_base.Status),
})
if err != nil {
return
}
s.SetState(proto_base.Status)
err = s.Send(&v1_21_8.StatusToServerPacketPingStart{})
return
}
func (s *StatusClient) OnStatus(p v1_21_8.StatusToClientPacketServerInfo) (err error) {
s.Status = p.Response
err = event.HandlerDoneErr{}
return
}
-63
View File
@@ -1,63 +0,0 @@
package modules
import "time"
type IntervalTask struct {
F func() error
LastTrigger time.Time
Interval time.Duration
}
type OnceTask struct {
F func() error
Trigger time.Time
}
type Timer struct {
Once []OnceTask
Interval []IntervalTask
Active bool
}
func (t *Timer) Tick() (err error) {
if !t.Active {
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
}
var triggerLater []OnceTask
for _, task := range t.Once {
if task.Trigger.After(time.Now()) {
err = task.F()
if err != nil {
return
}
} else {
triggerLater = append(triggerLater, task)
}
}
t.Once = triggerLater
return
}
func (t *Timer) Every(interval time.Duration, f func() error) {
t.Interval = append(t.Interval, IntervalTask{
F: f,
Interval: interval,
})
}
func (t *Timer) In(interval time.Duration, f func() error) {
t.Once = append(t.Once, OnceTask{
F: f,
Trigger: time.Now().Add(interval),
})
}
+61
View File
@@ -0,0 +1,61 @@
package api
import (
"encoding/json"
"errors"
"flag"
"log/slog"
"os"
"github.com/admin-else/strom/mc/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 {
slog.Info("account", "name", a.Name, "uuid", a.Uuid)
return
}
err = json.NewEncoder(os.Stdout).Encode(data)
return
}
+11
View File
@@ -0,0 +1,11 @@
package cmd_util
import "github.com/admin-else/strom/mc/api"
func Account(s string) (a *api.Account, err error) {
if len(s) > 16 {
return api.NewAccountFromYGG(s)
}
a = api.NewOfflineAccount(s)
return
}
+118
View File
@@ -0,0 +1,118 @@
package data
import (
"encoding/json"
"fmt"
"maps"
"os"
"slices"
mcdata "github.com/admin-else/strom/mc/data"
"github.com/admin-else/strom/mc/proto_generated"
"github.com/admin-else/strom/mc/util"
)
// strom data 1.21.11 blocks id 0 name => air
// {
// "id": 0,
// "name": "air",
// "displayName": "Air",
// "hardness": 0.0,
// "resistance": 0.0,
// "stackSize": 64,
// "diggable": false,
// "material": "default",
// "transparent": true,
// "emitLight": 0,
// "filterLight": 0,
// "defaultState": 0,
// "minStateId": 0,
// "maxStateId": 0,
// "states": [],
// "drops": [],
// "boundingBox": "empty"
// },
func Run(args []string) (err error) {
var dataValues any
switch len(args) {
case 0: // print available versions
dataValues = proto_generated.SupportedVersions
case 1: // print available data for version
dataValues = slices.Collect(maps.Keys(mcdata.Paths.Data[args[0]]))
case 2:
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
case 3:
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
if err != nil {
return
}
var dataSlices []any
dataSlices, err = util.AssertTypeError[[]any](dataValues)
if err != nil {
return
}
var result = make([]any, len(dataSlices))
for _, v := range dataSlices {
var dataMap map[string]any
dataMap, err = util.AssertTypeError[map[string]any](v)
if err != nil {
return
}
var vToAppend any
vToAppend, err = util.MapGetError(dataMap, args[2])
if err != nil {
return
}
result = append(result, vToAppend)
}
dataValues = result
case 4, 5:
err = mcdata.LoadVersionedJson(args[0], args[1], &dataValues)
if err != nil {
return
}
var dataSlices []any
dataSlices, err = util.AssertTypeError[[]any](dataValues)
if err != nil {
return
}
found := false
for _, v := range dataSlices {
var dataMap map[string]any
dataMap, err = util.AssertTypeError[map[string]any](v)
if err != nil {
return
}
if v2, ok := dataMap[args[2]]; ok {
if args[3] != fmt.Sprint(v2) {
continue
}
dataValues = dataMap
found = true
if len(args) == 5 {
dataValues, err = util.MapGetError(dataMap, args[4])
if err != nil {
return
}
}
break
}
}
if !found {
err = fmt.Errorf("not found")
return
}
default:
err = fmt.Errorf("too many arguments")
}
if err == nil {
e := json.NewEncoder(os.Stdout)
e.SetIndent("", " ")
err = e.Encode(dataValues)
return
}
return
}
+52
View File
@@ -0,0 +1,52 @@
package extract_mca
import (
"flag"
"fmt"
"log/slog"
"os"
"github.com/admin-else/strom/mc/level/anvil"
"github.com/admin-else/strom/mc/nbt"
)
var (
cmd = flag.NewFlagSet("print-nbt", flag.ContinueOnError)
FileFlag = cmd.String("file", "", "The MCA file to extract")
IndexXFlag = cmd.Int("x", 0, "The x chunk coordinate")
IndexZFlag = cmd.Int("z", 0, "The z chunk coordinate")
ToFlag = cmd.String("to", "mca-chunk.nbt", "The output file")
)
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
if *FileFlag == "" {
return fmt.Errorf("no file specified")
}
f, err := os.Open(*FileFlag)
if err != nil {
return
}
defer f.Close()
mca, err := anvil.ReadChunkHeader(f)
if err != nil {
return
}
t, err := mca.TimeAt(f, int32(*IndexXFlag), int32(*IndexZFlag))
if err != nil {
return
}
slog.Info("Chunk last written", "time", t)
n, err := mca.ChunkAt(f, int32(*IndexXFlag), int32(*IndexZFlag))
if err != nil {
return
}
err = nbt.WriteUnstructuredFilePath(*ToFlag, n)
if err != nil {
return
}
return
}
+103
View File
@@ -0,0 +1,103 @@
package main
import (
"errors"
"fmt"
"log/slog"
"maps"
"os"
"runtime/debug"
"slices"
"github.com/admin-else/strom/cmd/strom/api"
"github.com/admin-else/strom/cmd/strom/data"
"github.com/admin-else/strom/cmd/strom/extract_mca"
"github.com/admin-else/strom/cmd/strom/mca_to_tmcpr"
"github.com/admin-else/strom/cmd/strom/messenger"
"github.com/admin-else/strom/cmd/strom/offline_uuid"
"github.com/admin-else/strom/cmd/strom/packet_info"
"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_replay"
"github.com/admin-else/strom/cmd/strom/raw_capture"
"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_world"
"github.com/admin-else/strom/cmd/strom/status"
)
var ExpectedASubcommandErr = errors.New("expected a subcommand")
var UnknownSubcommandErr = errors.New("unknown subcommand try 'strom help' for a list of subcommands")
func ToolVersion() string {
info, ok := debug.ReadBuildInfo()
if !ok {
return "devel"
}
return info.Main.Version
}
func ToolSum() string {
info, ok := debug.ReadBuildInfo()
if !ok {
return ""
}
return info.Main.Sum
}
func Version(args []string) error {
fmt.Println("strom cli ", ToolVersion(), " (", ToolSum(), ")")
return nil
}
var subcommands = map[string]func(args []string) error{
"offline-uuid": offline_uuid.Run,
"packet-info": packet_info.Run,
"print-nbt": print_nbt.Run,
"status": status.Run,
"packet-spy": packet_inspector.Run,
"api": api.Run,
"version": Version,
"extract-mca": extract_mca.Run,
"data": data.Run,
"print-replay": print_replay.Run,
"raw-capture": raw_capture.Run,
"registry-data-hunter": registry_data_hunter.Run,
"serve-world": serve_world.Run,
"mca-to-tmcpr": mca_to_tmcpr.Run,
"serve-tmcpr": serve_tmcpr.Run,
"messenger": messenger.Run,
}
func Help(args []string) (err error) {
subcommandNames := slices.Collect(maps.Keys(subcommands))
slices.Sort(subcommandNames)
fmt.Println("Available subcommands", subcommandNames)
return
}
func mainE() (err error) {
subcommands["help"] = Help
args := os.Args
//args = []string{"TEST", "messenger", "-addr", "localhost"}
if len(args) < 2 {
err = ExpectedASubcommandErr
return
}
f, ok := subcommands[args[1]]
if !ok {
err = UnknownSubcommandErr
return
}
err = f(args[2:])
return
}
func main() {
err := mainE()
if err != nil {
slog.Error("exiting", "err", err)
}
}
+211
View File
@@ -0,0 +1,211 @@
package mca_to_tmcpr
import (
"bytes"
"flag"
"fmt"
"log/slog"
"os"
"github.com/admin-else/strom/mc/level"
"github.com/admin-else/strom/mc/level/anvil"
"github.com/admin-else/strom/mc/nbt"
"github.com/admin-else/strom/mc/proto/replay"
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
"github.com/admin-else/strom/mc/util"
)
var (
cmd = flag.NewFlagSet("mca-to-tmcpr", flag.ContinueOnError)
worldPath = cmd.String("world", "", "Path to world directory")
output = cmd.String("output", "", "Output .tmcpr file path")
radius = cmd.Int("radius", 14, "Chunk radius around spawn")
spawnX = cmd.Float64("spawn-x", 0.5, "Spawn X coordinate")
spawnY = cmd.Float64("spawn-y", 100, "Spawn Y coordinate")
spawnZ = cmd.Float64("spawn-z", 0.5, "Spawn Z coordinate")
)
var FullLight [][]uint8
func init() {
light := make([]uint8, 2048)
for i := range light {
light[i] = 0xFF
}
sectionsPerChunk := 384 / 16
FullLight = make([][]uint8, sectionsPerChunk)
for i := range sectionsPerChunk {
FullLight[i] = light
}
}
func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
xmca := util.FloorDiv(x, anvil.ChunksWidthRegionFile)
zmca := util.FloorDiv(z, anvil.ChunksWidthRegionFile)
x = x % anvil.ChunksWidthRegionFile
if x < 0 {
x += anvil.ChunksWidthRegionFile
}
z = z % anvil.ChunksWidthRegionFile
if z < 0 {
z += anvil.ChunksWidthRegionFile
}
f, err := os.Open(fmt.Sprintf("./.devres/anvil_testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
if err != nil {
return
}
defer f.Close()
mca, err := anvil.ReadChunkHeader(f)
if err != nil {
return
}
var n *nbt.Tag
n, err = mca.ChunkAt(f, x, z)
if err != nil {
return
}
if n == nil {
return
}
var chunk anvil.Chunk
err = nbt.Format.Decode(n.Value, &chunk)
if err != nil {
return
}
var packetChunk *level.Chunk
packetChunk, err = chunk.ToStorage(version)
if err != nil {
return
}
buffer := bytes.NewBuffer(nil)
err = packetChunk.WriteChunkData(buffer)
if err != nil {
return
}
b = buffer.Bytes()
return
}
func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug)
err := cmd.Parse(args)
if err != nil {
return err
}
if *worldPath == "" {
slog.Error("no world path specified")
return nil
}
if *output == "" {
slog.Error("no output path specified")
return nil
}
outFile, err := os.Create(*output)
if err != nil {
slog.Error("failed to create output file", "error", err)
return err
}
defer outFile.Close()
w := replay.NewWriter(outFile)
err = w.WritePacket(&v1_21_11.PlayToClientPacketLogin{
EntityId: 73,
IsHardcore: false,
WorldNames: []string{"minecraft:overworld", "minecraft:the_end", "minecraft:the_nether"},
MaxPlayers: 100,
ViewDistance: 10,
SimulationDistance: 10,
ReducedDebugInfo: false,
EnableRespawnScreen: true,
DoLimitedCrafting: false,
WorldState: v1_21_11.PlayToClientSpawnInfo{
Dimension: 0,
Name: "minecraft:overworld",
HashedSeed: -8365436745629239539,
Gamemode: "survival",
PreviousGamemode: 0xff,
IsDebug: false,
IsFlat: false,
Death: nil,
PortalCooldown: 0,
SeaLevel: 63,
},
EnforcesSecureChat: false,
}, 0)
if err != nil {
slog.Error("failed to write login packet", "error", err)
return err
}
err = w.WritePacket(&v1_21_11.PlayToClientPacketPosition{
TeleportId: 0,
X: *spawnX,
Y: *spawnY,
Z: *spawnZ,
Dx: 0,
Dy: 0,
Dz: 0,
Yaw: 0,
Pitch: 0,
Flags: v1_21_11.PlayToClientPositionUpdateRelatives{},
}, 0)
if err != nil {
slog.Error("failed to write position packet", "error", err)
return err
}
chunkX := int32(*spawnX) >> 4
chunkZ := int32(*spawnZ) >> 4
err = w.WritePacket(&v1_21_11.PlayToClientPacketUpdateViewPosition{
ChunkX: chunkX,
ChunkZ: chunkZ,
}, 0)
if err != nil {
slog.Error("failed to write view position packet", "error", err)
return err
}
err = w.WritePacket(&v1_21_11.PlayToClientPacketGameStateChange{
Reason: "level_chunks_load_start",
}, 0)
if err != nil {
slog.Error("failed to write game state packet", "error", err)
return err
}
radius32 := int32(*radius)
for x := -radius32; x < radius32; x++ {
for z := -radius32; z < radius32; z++ {
var chunkData []byte
chunkData, err = GetChunkFromMca(x, z, "1.21.11")
if err != nil {
return err
}
if chunkData == nil {
continue
}
OLDpacket := &v1_21_11.PlayToClientPacketMapChunk{}
OLDpacket.X = x
OLDpacket.Z = z
OLDpacket.ChunkData = v1_21_11.ByteArray{Val: chunkData}
OLDpacket.BlockLight = FullLight
OLDpacket.BlockLightMask = []int64{(1 << 24) - 1}
err = w.WritePacket(OLDpacket, 0)
if err != nil {
return err
}
}
}
slog.Info("conversion complete", "output", *output)
return nil
}
+134
View File
@@ -0,0 +1,134 @@
package messenger
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"github.com/admin-else/strom/cmd/strom/cmd_util"
"github.com/admin-else/strom/mc/bot/chat"
"github.com/admin-else/strom/mc/bot/keepalive"
"github.com/admin-else/strom/mc/bot/world"
"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_generated/v1_21_11"
)
func stror(a, b string) string {
if a != "" {
return a
}
return b
}
var (
cmd = flag.NewFlagSet("status", flag.ContinueOnError)
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")
)
type Messenger struct {
*proto.Conn
chat *chat.Module
world *world.Module
}
func (m *Messenger) OnChat(e *v1_21_11.PlayToClientPacketPlayerChat) (err error) {
m.Log.Info("chat", "author", e.SenderUuid, "m", e.PlainMessage)
return
}
func (m *Messenger) OnChatUnsigned(e *v1_21_11.PlayToClientPacketProfilelessChat) (err error) {
m.Log.Info("chatP", "author", e.Name, "m", e.Message)
return
}
func (m *Messenger) OnChatSystem(e *v1_21_11.PlayToClientPacketSystemChat) (err error) {
m.Log.Info("chatS", "m", e.Content)
return
}
func (m *Messenger) OnStdin(e event.Stdin) (err error) {
input := strings.TrimSpace(e.Val)
if strings.HasPrefix(input, ">say ") {
return m.chat.SendMessage(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
}
if strings.HasPrefix(input, ">getblock ") {
return m.handleGetBlock(strings.TrimSpace(strings.TrimPrefix(input, ">getblock")))
}
return m.chat.SendMessage(input)
}
func (m *Messenger) handleGetBlock(args string) (err error) {
fields := strings.Fields(args)
if len(fields) != 3 {
m.Log.Info("getblock", "error", "expected >getblock <x> <y> <z>")
return nil
}
coords := make([]int64, 3)
for i, f := range fields {
coords[i], err = strconv.ParseInt(f, 10, 32)
if err != nil {
m.Log.Info("getblock", "error", fmt.Sprintf("bad coordinate %q", f))
return nil
}
}
stateId, err := m.world.GetBlock(int32(coords[0]), int32(coords[1]), int32(coords[2]))
if err != nil {
m.Log.Info("getblock", "error", err.Error())
return nil
}
block, ok := data.LookupBlockByStateId(m.Version, stateId)
if !ok {
m.Log.Info("getblock", "stateId", stateId, "name", "unknown")
return nil
}
m.Log.Info("getblock", "x", coords[0], "y", coords[1], "z", coords[2], "name", block.Name, "stateId", stateId)
return nil
}
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
acc, err := cmd_util.Account(*accFlag)
if err != nil {
return
}
c, err := client.Login(*connectToFlag, acc)
if err != nil {
return
}
defer c.Close()
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{
Conn: c,
chat: chatMod,
world: world.Start(c, w),
}
event.StartListingStdin(m.Loop)
m.Register(m.OnStdin)
m.RegisterUntilLatest(m.OnChat, m.OnChatUnsigned, m.OnChatSystem)
keepalive.Start(m.Conn)
return m.StartConn()
}
+20
View File
@@ -0,0 +1,20 @@
package offline_uuid
import (
"flag"
"fmt"
"github.com/admin-else/strom/mc/crypto"
)
var cmd = flag.NewFlagSet("offline-uuid", flag.ContinueOnError)
var nameFlag = cmd.String("name", "Notch", "name of the player")
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
fmt.Println(crypto.FromOfflinePlayer(*nameFlag))
return
}
+169
View File
@@ -0,0 +1,169 @@
package packet_info
import (
"flag"
"fmt"
"strconv"
"github.com/admin-else/strom/mc/data"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated"
)
var cmd = flag.NewFlagSet("packet-info", flag.ContinueOnError)
var versionFlag = cmd.String("version", "", "protocol version or minecraft version name")
var stateFlag = cmd.String("state", "", "handshaking, status, login, configuration, play")
var directionFlag = cmd.String("direction", "", "toserver, toclient")
var idFlag = cmd.Int("id", -1, "packet id")
var nameFlag = cmd.String("name", "", "packet name")
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
var version int32
if *versionFlag != "" {
v, err := data.LookUpVersionByName(*versionFlag)
if err != nil {
v2, err := parseVersion(*versionFlag)
if err != nil {
return fmt.Errorf("invalid version: %s", *versionFlag)
}
v, err = data.LookUpVersionByProtocolVersion(v2)
if err != nil {
return err
}
}
version = v.Version
}
var state proto_base.State
if *stateFlag != "" {
state, err = parseState(*stateFlag)
if err != nil {
return
}
}
var direction proto_base.Direction
if *directionFlag != "" {
direction, err = parseDirection(*directionFlag)
if err != nil {
return
}
}
if *idFlag != -1 && *nameFlag != "" {
err = fmt.Errorf("cannot specify both id and name")
return
}
if *idFlag != -1 {
if *versionFlag == "" || *stateFlag == "" || *directionFlag == "" {
err = fmt.Errorf("version, state, and direction are required when looking up by id")
return
}
p, ok := proto.LookUpTypeByPacketInfo(direction, state, int32(*idFlag), version)
if !ok {
err = fmt.Errorf("packet not found")
return
}
printPacketInfo(p)
return
}
if *nameFlag != "" {
if *versionFlag == "" {
err = fmt.Errorf("version is required when looking up by name")
return
}
if state != 0 || direction != 0 {
p, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(*nameFlag, version, state, direction)
if !ok {
err = fmt.Errorf("packet not found")
return
}
printPacketInfo(p)
} else {
var found bool
for _, p := range proto_generated.Packets {
if p.Name == *nameFlag && p.ProtocolVersion == version {
printPacketInfo(p)
found = true
}
}
if !found {
err = fmt.Errorf("packet not found")
}
}
return
}
err = fmt.Errorf("must specify either -id or -name")
return
}
func parseState(s string) (proto_base.State, error) {
switch s {
case "handshaking":
return proto_base.Handshaking, nil
case "status":
return proto_base.Status, nil
case "login":
return proto_base.Login, nil
case "configuration":
return proto_base.Configuration, nil
case "play":
return proto_base.Play, nil
default:
return 0, fmt.Errorf("unknown state: %s", s)
}
}
func parseDirection(s string) (proto_base.Direction, error) {
switch s {
case "toserver":
return proto_base.ToServer, nil
case "toclient":
return proto_base.ToClient, nil
default:
return 0, fmt.Errorf("unknown direction: %s", s)
}
}
func parseVersion(val string) (int32, error) {
v, err := strconv.ParseInt(val, 10, 32)
if err != nil {
return 0, err
}
return int32(v), nil
}
func printPacketInfo(p proto_base.PacketInfo) {
v, _ := data.LookUpVersionByProtocolVersion(p.ProtocolVersion)
fmt.Printf("Name: %s\n", p.Name)
fmt.Printf("ID: %d\n", p.PacketId)
fmt.Printf("State: %s\n", stateToString(p.State))
fmt.Printf("Direction: %s\n", p.Direction)
fmt.Printf("Version: %d (%s)\n", p.ProtocolVersion, v.MinecraftVersion)
}
func stateToString(s proto_base.State) string {
switch s {
case proto_base.Handshaking:
return "handshaking"
case proto_base.Status:
return "status"
case proto_base.Login:
return "login"
case proto_base.Configuration:
return "configuration"
case proto_base.Play:
return "play"
default:
return "unknown"
}
}
+203
View File
@@ -0,0 +1,203 @@
package packet_inspector
import (
"encoding/json"
"flag"
"log/slog"
"os"
"time"
"github.com/admin-else/strom/mc/api"
"github.com/admin-else/strom/mc/client"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/nbt"
"github.com/admin-else/strom/mc/proto"
"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("packet-inspector", flag.ContinueOnError)
var OfflineNameFlag = Cmd.String("name", "packet_inspector", "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 TrueForward = Cmd.Bool("true-forward", false, "forward packets without modification -name and custom server status wont work")
type Proxy struct {
Client, Servee *proto.Conn
Data nbt.Tag
}
func (p *Proxy) Start() (err error) {
p.Client.RegisterCritical(p.OnAnything)
p.Servee.RegisterCritical(p.OnAnything)
p.Servee.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
p.Servee.RegisterCriticalUntilLatest(p.OnFinishLogin)
p.Servee.RegisterCriticalUntilLatest(p.OnHandshake)
//p.Servee.RegisterCriticalUntilLatest(p.OnCompress)
p.Client.RegisterCriticalUntilLatest(p.OnCompress)
errChan := make(chan error)
go func() {
errChan <- p.Client.StartConn()
}()
go func() {
errChan <- p.Servee.StartConn()
}()
err = <-errChan
return
}
type PacketLogEvent struct {
Time time.Time
Packet proto_base.EncodeDecodeAble
Info PacketInfoNoTypeJson
ClientAddr string
}
type PacketInfoNoTypeJson struct {
Type proto_base.EncodeDecodeAble `json:"-"`
Name string
Direction proto_base.Direction
State proto_base.State
PacketId int32
ProtocolVersion int32
}
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
}
jsonBytes, err := json.Marshal(PacketLogEvent{
Time: time.Now(),
Packet: packet,
ClientAddr: p.Client.RemoteAddr().String(),
Info: PacketInfoNoTypeJson(packetInfo),
})
if err != nil {
return
}
// :pray:
_, _ = os.Stdout.Write(jsonBytes)
_, _ = os.Stdout.Write([]byte("\n"))
switch packetInfo.Direction {
case proto_base.ToServer:
err = p.Client.Send(packet)
case proto_base.ToClient:
err = p.Servee.Send(packet)
}
return
}
func (p *Proxy) OnFinishConfiguration(packet *v1_21_11.ConfigurationToServerPacketFinishConfiguration) (err error) {
err = p.Client.Send(packet)
if err != nil {
return
}
p.Servee.SetState(proto_base.Play)
p.Client.SetState(proto_base.Play)
err = event.DontForwardErr
return
}
func (p *Proxy) OnFinishLogin(packet *v1_21_11.LoginToServerPacketLoginAcknowledged) (err error) {
err = p.Client.Send(packet)
if err != nil {
return
}
p.Servee.SetState(proto_base.Configuration)
p.Client.SetState(proto_base.Configuration)
err = event.DontForwardErr
return
}
func (p *Proxy) OnHandshake(packet *v1_21_11.HandshakingToServerPacketSetProtocol) (err error) {
err = p.Client.Send(packet)
state := proto_base.State(packet.NextState)
if err != nil {
return
}
p.Servee.SetState(state)
p.Client.SetState(state)
err = event.DontForwardErr
return
}
func (p *Proxy) OnCompress(packet *v1_21_11.LoginToClientPacketCompress) (err error) {
err = p.Servee.Send(packet)
if err != nil {
return
}
p.Servee.SetCompressionThreshold(packet.Threshold)
p.Client.SetCompressionThreshold(packet.Threshold)
err = event.DontForwardErr
return
}
var StatusResponse = server.StatusResponse{
Version: server.StatusResponseVersion{
Name: text.Pretty("STROM"),
Protocol: 772,
},
Players: server.StatusResponsePlayers{},
Description: text.Pretty("Packet spy forwards to " + *TargetAddr),
Favicon: "",
EnforcesSecureChat: false,
}
func Run(args []string) (err error) {
err = Cmd.Parse(args)
if err != nil {
return
}
slog.Info("Starting packet inspector", "listen", *ListenAddr)
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
p := &Proxy{Servee: serveeConn, Client: nil}
var acc = api.NewOfflineAccount(*OfflineNameFlag)
if !*TrueForward {
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
}
}
*TargetAddr, _, _, err = client.DoDNSChecked(nil, *TargetAddr, nil)
if err != nil {
return
}
c, err := client.Connect(nil, *TargetAddr, "")
if err != nil {
return
}
defer c.Close()
if !*TrueForward {
err = client.LoginRaw(c, acc)
if err != nil {
return
}
}
p.Client = c
p.Client.AlwaysDecode = true
p.Servee.AlwaysDecode = true
err = p.Start()
return
})
return
}
+53
View File
@@ -0,0 +1,53 @@
package print_nbt
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"os"
"github.com/admin-else/strom/mc/nbt"
)
var (
cmd = flag.NewFlagSet("print-nbt", flag.ContinueOnError)
FileFlag = cmd.String("file", "", "The NBT file to print")
HashFormatFlag = cmd.Bool("snbt", false, "will print with go %#v")
)
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
if *FileFlag == "" {
return fmt.Errorf("no file specified")
}
f, err := os.Open(*FileFlag)
if err != nil {
return
}
defer f.Close()
n, err := nbt.ReadUnstructuredFile(f)
if err != nil {
return
}
var b []byte
if *HashFormatFlag {
bb := bytes.NewBuffer(nil)
err = nbt.PrintSNBTAny(n.Value, bb)
if err != nil {
return
}
b = bb.Bytes()
//b = []byte(strings.ReplaceAll(string(b), ",", ",\n"))
} else {
b, err = json.MarshalIndent(n, "", " ")
}
if err != nil {
return
}
_, err = os.Stdout.Write(b)
return
}
@@ -0,0 +1,18 @@
package failed_packets_test
import (
"bytes"
"testing"
generated "github.com/admin-else/strom/mc/proto_generated/%v"
)
// Error: %v
func TestFailedPacket%10X(t *testing.T) {
p := &generated.%v{}
b := bytes.NewBuffer(%#v)
err := p.Decode(b)
if err != nil {
t.Fatal(err)
}
}
+198
View File
@@ -0,0 +1,198 @@
package print_replay
import (
"bytes"
"crypto/sha256"
_ "embed"
"encoding/binary"
"errors"
"flag"
"fmt"
"go/format"
"io"
"log/slog"
"os"
"reflect"
"strings"
"github.com/admin-else/strom/mc/data"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
)
var (
cmd = flag.NewFlagSet("print-replay", flag.ContinueOnError)
FileFlag = cmd.String("file", "", "The tmcpr file to print")
ProtocolVersion = cmd.Int("protocol", -1, "The protocol version")
CreateTestsPath = cmd.String("create-tests", "", "")
StateFlag = cmd.String("state", "login", "Initial state: login, config, play")
DirectionFlag = cmd.String("direction", "clientbound", "Packet direction: clientbound (ToClient) or serverbound (ToServer)")
)
// TODO: maybe use the proto replay api
//go:embed failed_packet.go.tmpl
var TestSrcF string
func SaveUnCodeAbleAsTest(packet *proto.UnCodablePacket) (err error) {
if packet.Info.Type == nil {
slog.Warn("skipping test for unknown packet id", "packet_id", packet.Info.PacketId, "state", packet.Info.State, "error", packet.Err)
return
}
hUntrimmed := sha256.Sum256(packet.Data)
h := hUntrimmed[:8]
if err = os.MkdirAll(*CreateTestsPath, 0755); err != nil {
return
}
ret, err := data.LookUpVersionByProtocolVersion(packet.Info.ProtocolVersion)
if err != nil {
return
}
goPackage := "v" + strings.ReplaceAll(ret.MinecraftVersion, ".", "_")
typeName := reflect.TypeOf(packet.Info.Type).Elem().Name()
src := fmt.Sprintf(TestSrcF, goPackage, packet.Err, h, typeName, packet.Data)
formatted, formatErr := format.Source([]byte(src))
if formatErr != nil {
formatted = []byte(src)
}
filePath := fmt.Sprintf(*CreateTestsPath+"/%v_%10x_test.go", len(packet.Data), h)
if err = os.WriteFile(filePath, formatted, 0644); err != nil {
return
}
slog.Info("Packet failed to parse", "error", packet.Err, "saved", filePath, "data", fmt.Sprintf("%q", packet.Data))
return
}
var state = proto_base.Login
var direction = proto_base.ToClient
func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, i proto_base.PacketInfo, err error) {
var timestamp, length uint32
err = binary.Read(r, binary.BigEndian, &timestamp)
if err != nil {
return
}
err = binary.Read(r, binary.BigEndian, &length)
if err != nil {
return
}
// Read the packet data into a byte slice
packetData := make([]byte, length)
_, err = io.ReadFull(r, packetData)
if err != nil {
return
}
// Use bytes.Reader instead of bytes.Buffer
b := bytes.NewReader(packetData)
packetId, err := proto_base.DecodeVarInt(b)
if err != nil {
return
}
// Calculate position after reading the packet ID
// indexafterid represents how many bytes we've read so far
indexafterid := int(b.Size()) - b.Len()
var ok bool
i, ok = proto.LookUpTypeByPacketInfo(direction, state, packetId, int32(*ProtocolVersion))
if !ok {
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
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)
err = packet.Decode(b)
if err != nil {
packet = &proto.UnCodablePacket{Err: err, Data: packetData[indexafterid:], Info: i}
return
}
if b.Len() != 0 {
packet = &proto.UnCodablePacket{Err: proto.PacketNotFullyDecodedErr, Data: packetData[indexafterid:], Info: i}
return
}
return
}
func Run(args []string) (err error) {
err = cmd.Parse(args)
if err != nil {
return
}
if *ProtocolVersion == -1 {
return fmt.Errorf("no protocol version specified")
}
if *FileFlag == "" {
return fmt.Errorf("no file specified")
}
switch *StateFlag {
case "login":
state = proto_base.Login
case "config":
state = proto_base.Configuration
case "play":
state = proto_base.Play
default:
return fmt.Errorf("unknown state: %s", *StateFlag)
}
switch *DirectionFlag {
case "clientbound", "to-client":
direction = proto_base.ToClient
case "serverbound", "to-server":
direction = proto_base.ToServer
default:
return fmt.Errorf("unknown direction: %s", *DirectionFlag)
}
f, err := os.Open(*FileFlag)
if err != nil {
return
}
defer f.Close()
var totalPackets, failedPackets int
for {
var packet proto_base.EncodeDecodeAble
var info proto_base.PacketInfo
packet, info, err = UnpackPacket(f)
unCodablePacket, ok := packet.(*proto.UnCodablePacket)
if ok {
slog.Error("failed to decode packet", "error", err, "packet_name", info.Name)
if *CreateTestsPath != "" {
err = SaveUnCodeAbleAsTest(unCodablePacket)
if err != nil {
slog.Error("failed to save uncodable packet as test", "error", err)
}
}
failedPackets++
}
fmt.Printf("Packet: %#v\n", packet)
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
break
}
totalPackets++
}
fmt.Printf("Total packets: %d, failed packets: %d, %%failed: %.2f%%\n", totalPackets, failedPackets, float64(failedPackets)/float64(totalPackets)*100)
return
}
+253
View File
@@ -0,0 +1,253 @@
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
}
+152
View File
@@ -0,0 +1,152 @@
package registry_data_hunter
import (
"encoding/json"
"errors"
"log/slog"
"os"
"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/nbt"
"github.com/admin-else/strom/mc/proto"
"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/server"
"github.com/admin-else/strom/mc/text"
)
func wontFail[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
var (
Version = "1.21.11"
SaveAs = "data/registry/" + Version + ".nbt"
CompatibleProtocolVersion = wontFail(data.LookUpVersionByName(Version)).Version
FinishedConfigErr = errors.New("finished configuration")
)
var Status = wontFail(json.Marshal(server.StatusResponse{
Version: server.StatusResponseVersion{
Name: nil,
Protocol: CompatibleProtocolVersion,
},
Description: text.Pretty("Registry data hunter"),
}))
type Proxy struct {
Client, Servee *proto.Conn
Data *nbt.Tag
}
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
}
switch packetInfo.Direction {
case proto_base.ToServer:
err = p.Client.Send(packet)
case proto_base.ToClient:
err = p.Servee.Send(packet)
}
return
}
func (p *Proxy) OnFinishConfiguration(_ *v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
err = FinishedConfigErr
return
}
func (p *Proxy) OnSelectKnownPacks(data *v1_21_8.ConfigurationToClientPacketCommonSelectKnownPacks) (err error) {
var entries []any
for _, entry := range data.Packs {
e := map[string]any{"Namespace": entry.Namespace, "Id": entry.Id, "Version": entry.Version}
entries = append(entries, e)
}
p.Data.Value.(map[string]any)["KnownPacks"] = entries
return
}
func (p *Proxy) OnData(data *v1_21_8.ConfigurationToClientPacketRegistryData) (err error) {
d := p.Data.Value.(map[string]any)["Registries"].([]any)
var entries []any
for _, entry := range data.Entries {
if entry.Value != nil {
entries = append(entries, map[string]any{"TagType": entry.Key, "Value": entry.Value.Value})
} else {
entries = append(entries, map[string]any{"TagType": entry.Key})
}
}
d = append(d, map[string]any{"Id": data.Id, "Entries": entries})
p.Data.Value.(map[string]any)["Registries"] = d
return
}
func (p *Proxy) OnTags(data *v1_21_8.ConfigurationToClientPacketTags) (err error) {
var tags []any
for _, tag := range data.Tags {
var entries []any
for _, entry := range tag.Tags.Val {
entries = append(entries, map[string]any{"TagName": entry.TagName, "Entries": entry.Entries})
}
tags = append(tags, map[string]any{"TagType": tag.TagType, "Entries": entries})
}
p.Data.Value.(map[string]any)["Tags"] = tags
return
}
func handleClient(c *proto.Conn) (err error) {
acc := api.NewOfflineAccount("RegistryHunter")
_, err = server.ServeLogin(c, server.WithOtherAccount(acc), server.WithCompatibleVersions(CompatibleProtocolVersion), server.WithRawStatus(Status))
if err != nil {
return
}
sockPuppet, err := client.Login(":25565", acc)
if err != nil {
return
}
defer sockPuppet.Close()
errChan := make(chan error)
p := &Proxy{Client: sockPuppet, Servee: c}
p.Data.Value = map[string]any{"Registries": []any{}}
p.Client.RegisterCritical(p.OnAnything)
p.Servee.RegisterCritical(p.OnAnything)
p.Client.RegisterCriticalUntilLatest(p.OnData)
p.Client.RegisterCriticalUntilLatest(p.OnTags)
p.Client.RegisterCriticalUntilLatest(p.OnSelectKnownPacks)
p.Client.RegisterCriticalUntilLatest(p.OnFinishConfiguration)
go func() {
errChan <- p.Client.StartConn()
}()
go func() {
errChan <- p.Servee.StartConn()
}()
err = <-errChan
f, err := os.Create(SaveAs)
if err != nil {
return
}
defer f.Close()
err = nbt.WriteUnstructuredFile(f, p.Data)
slog.Info("saved", "file", SaveAs)
return
}
func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug)
return server.StartServerWithOnConn(":25566", handleClient)
}
+97
View File
@@ -0,0 +1,97 @@
package serve_tmcpr
import (
"errors"
"flag"
"io"
"log/slog"
"math/rand/v2"
"os"
"time"
"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_generated/v1_21_8"
"github.com/admin-else/strom/mc/server"
)
type Server struct {
*proto.Conn
*event.Timer
}
var (
cmd = flag.NewFlagSet("serve-tmcpr", flag.ContinueOnError)
tmcprFile = cmd.String("file", "/tmp/world.tmcpr", "Path to .tmcpr replay file")
bindAddr = cmd.String("bind", ":25566", "Address to listen on")
)
func Limbo(c *proto.Conn, tmcprPath string) (err error) {
s := &Server{Conn: c}
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
if err != nil {
return
}
err = server.ServeConfig(s.Conn)
if err != nil {
return
}
f, err := os.Open(tmcprPath)
if err != nil {
return
}
defer f.Close()
r := replay.NewReader(f)
r.Version = c.ProtocolVersion
start := time.Now()
for {
var packetData []byte
var timestamp uint32
packetData, timestamp, _, err = r.ReadRaw()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return err
}
time.Sleep(time.Duration(timestamp)*time.Millisecond - time.Since(start))
err = s.SendRaw(packetData)
if err != nil {
return err
}
}
s.Timer = &event.Timer{}
s.Timer.Every(time.Second*15, s.SendKeepAlive)
s.Timer.Start(c.Loop)
return s.StartConn()
}
func (s *Server) SendKeepAlive() error {
return s.Send(&v1_21_8.PlayToClientPacketKeepAlive{KeepAliveId: rand.Int64()})
}
func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug)
err := cmd.Parse(args)
if err != nil {
return err
}
if *tmcprFile == "" {
slog.Error("no file specified")
return nil
}
return server.StartServerWithOnConn(*bindAddr, func(c *proto.Conn) error {
return Limbo(c, *tmcprFile)
})
}
+180
View File
@@ -0,0 +1,180 @@
package serve_world
import (
"bytes"
"fmt"
"log/slog"
"math/rand/v2"
"os"
"time"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/level"
"github.com/admin-else/strom/mc/level/anvil"
"github.com/admin-else/strom/mc/nbt"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
"github.com/admin-else/strom/mc/server"
"github.com/admin-else/strom/mc/util"
)
type Server struct {
*proto.Conn
*event.Timer
reconnectCalled bool
}
var FullLight [][]uint8
func init() {
light := make([]uint8, 2048)
for i := range light {
light[i] = 0xFF
}
sectionsPerChunk := 384 / 16
FullLight = make([][]uint8, sectionsPerChunk)
for i := range sectionsPerChunk {
FullLight[i] = light
}
}
func GetChunkFromMca(x, z int32, version string) (b []byte, err error) {
xmca := util.FloorDiv(x, anvil.ChunksWidthRegionFile)
zmca := util.FloorDiv(z, anvil.ChunksWidthRegionFile)
x = x % anvil.ChunksWidthRegionFile
if x < 0 {
x += anvil.ChunksWidthRegionFile
}
z = z % anvil.ChunksWidthRegionFile
if z < 0 {
z += anvil.ChunksWidthRegionFile
}
f, err := os.Open(fmt.Sprintf("./.devres/anvil_testdata/securechattestworld/region/r.%v.%v.mca", xmca, zmca))
if err != nil {
return
}
defer f.Close()
mca, err := anvil.ReadChunkHeader(f)
if err != nil {
return
}
var n *nbt.Tag
n, err = mca.ChunkAt(f, x, z)
if err != nil {
return
}
if n == nil {
return
}
var chunk anvil.Chunk
err = nbt.Format.Decode(n.Value, &chunk)
if err != nil {
return
}
var packetChunk *level.Chunk
packetChunk, err = chunk.ToStorage(version)
if err != nil {
return
}
buffer := bytes.NewBuffer(nil)
err = packetChunk.WriteChunkData(buffer)
if err != nil {
return
}
b = buffer.Bytes()
return
}
func Limbo(c *proto.Conn) (err error) {
s := &Server{
Conn: c,
}
_, err = server.ServeLogin(s.Conn, server.WithoutOnlineMode())
if err != nil {
return
}
err = server.ServeConfig(s.Conn)
if err != nil {
return
}
login := &v1_21_8.PlayToClientPacketLogin{EntityId: 73, IsHardcore: false, WorldNames: []string{"minecraft:overworld", "minecraft:the_end", "minecraft:the_nether"}, MaxPlayers: 100, ViewDistance: 10, SimulationDistance: 10, ReducedDebugInfo: false, EnableRespawnScreen: true, DoLimitedCrafting: false, WorldState: v1_21_8.PlayToClientSpawnInfo{Dimension: 0, Name: "minecraft:overworld", HashedSeed: -8365436745629239539, Gamemode: "survival", PreviousGamemode: 0xff, IsDebug: false, IsFlat: false, Death: nil, PortalCooldown: 0, SeaLevel: 63}, EnforcesSecureChat: false}
err = s.Send(login)
if err != nil {
return
}
err = s.Send(&v1_21_8.PlayToClientPacketPosition{
TeleportId: 0,
X: 0.5,
Y: 100,
Z: 0.5,
Dx: 0,
Dy: 0,
Dz: 0,
Yaw: 0,
Pitch: 0,
Flags: v1_21_8.PlayToClientPositionUpdateRelatives{},
})
if err != nil {
return
}
err = s.Send(&v1_21_8.PlayToClientPacketUpdateViewPosition{
ChunkX: 0,
ChunkZ: 0,
})
if err != nil {
return
}
err = s.Send(&v1_21_8.PlayToClientPacketGameStateChange{
Reason: "level_chunks_load_start",
})
radius := int32(10)
for x := -radius; x < radius; x++ {
for z := -radius; z < radius; z++ {
var chunkData []byte
chunkData, err = GetChunkFromMca(x, z, c.Version)
if err != nil {
return
}
if chunkData == nil {
continue
}
OLDpacket := &v1_21_8.PlayToClientPacketMapChunk{}
OLDpacket.X = x
OLDpacket.Z = z
OLDpacket.ChunkData = v1_21_8.ByteArray{Val: chunkData}
OLDpacket.BlockLight = FullLight
OLDpacket.BlockLightMask = []int64{(1 << 24) - 1}
err = s.Send(OLDpacket)
if err != nil {
return
}
}
}
s.Timer = &event.Timer{}
s.Timer.Every(time.Second*15, s.SendKeepAlive)
s.Timer.Start(c.Loop)
return s.StartConn()
}
func (s *Server) SendKeepAlive() (err error) {
err = s.Send(&v1_21_8.PlayToClientPacketKeepAlive{KeepAliveId: rand.Int64()})
if err != nil {
return
}
return
}
func Run(args []string) error {
slog.SetLogLoggerLevel(slog.LevelDebug)
return server.StartServerWithOnConn(":25566", Limbo)
}
+98
View File
@@ -0,0 +1,98 @@
package status
import (
"flag"
"fmt"
"log/slog"
"time"
"github.com/admin-else/strom/mc/client"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/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(nil, 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)
if err != nil {
return
}
if *srvFlag {
*addrFlag, _, _, err = client.DoDNSChecked(nil, *addrFlag, nil)
if err != nil {
return
}
}
slog.Debug("resolved address to ", "addr", *addrFlag)
status, err := StatusRaw(*addrFlag)
if err != nil {
return
}
fmt.Println(status.Status)
return
}
-68
View File
@@ -1,68 +0,0 @@
package data
// {
// "id": 0,
// "name": "air",
// "displayName": "Air",
// "hardness": 0.0,
// "resistance": 0.0,
// "stackSize": 64,
// "diggable": false,
// "material": "default",
// "transparent": true,
// "emitLight": 0,
// "filterLight": 0,
// "defaultState": 0,
// "minStateId": 0,
// "maxStateId": 0,
// "states": [],
// "drops": [],
// "boundingBox": "empty"
// },
type Block struct {
Id int
Name string
DisplayName string
Hardness float64
Resistance float64
StackSize int
Diggable bool
Material string
Transparent bool
EmitLight int
FilterLight int
DefaultState int32
MinStateId int32
MaxStateId int32
States []struct {
Name string
Type string
NumValues int
}
Drops []int
BoundingBox string
}
var BlocksCache = make(map[string][]Block)
func BlocksForVersion(v string) (ret []Block) {
var ok bool
if ret, ok = BlocksCache[v]; ok {
return
}
var b []Block
must(LoadVersionedJson(v, "blocks", &b))
BlocksCache[v] = b
return b
}
func LookupBlockByStateId(version string, stateId int32) (block Block, ok bool) {
for _, b := range BlocksForVersion(version) {
if b.MinStateId <= stateId && stateId <= b.MaxStateId {
block = b
ok = true
}
}
return
}
-16
View File
@@ -1,16 +0,0 @@
package data_test
import (
"fmt"
"testing"
"github.com/admin-else/strom/data"
)
func TestLookupBlockByStateId(t *testing.T) {
b, ok := data.LookupBlockByStateId("1.21.8", 0)
if !ok {
t.Fatal("Block not found")
}
fmt.Println(b)
}
-47
View File
@@ -1,47 +0,0 @@
package data
// {
// "minecraftVersion": "1.21.10-rc1",
// "version": 1073742098,
// "dataVersion": 4555,
// "usesNetty": true,
// "majorVersion": "1.21",
// "releaseType": "snapshot"
// },
type ProtocolVersion struct {
MinecraftVersion string
Version int
DataVersion int
UsesNetty bool
MajorVersion string
ReleaseType string
}
var ProtocolVersions []ProtocolVersion
func LookUpProtocolVersionByName(name string) (ret ProtocolVersion, err error) {
for _, v := range ProtocolVersions {
if v.MinecraftVersion == name {
ret = v
return
}
}
err = UnknownMinecraftVersionError
return
}
func LookUpProtocolVersionByVersion(version int) (ret ProtocolVersion, err error) {
for _, v := range ProtocolVersions {
if v.Version == version {
ret = v
return
}
}
err = UnknownMinecraftVersionError
return
}
func init() {
must(LoadJson("minecraft-data/data/pc/common/protocolVersions.json", &ProtocolVersions))
}
-118
View File
@@ -1,118 +0,0 @@
// Package event is the event handler
package event
import (
"errors"
"fmt"
"reflect"
"strings"
)
type Loop struct {
HandlerFunctions map[reflect.Type][]reflect.Value
Handlers []any
HandlerIndex int
}
type Unhandled struct {
Val any
}
type Anything struct {
Val any
}
type (
OnStart struct{}
Tick struct{}
)
// HandlerDoneErr will stop the event loop and return the error if provided.
type HandlerDoneErr struct {
Return error
}
func (e HandlerDoneErr) Error() string {
return "HandlerDoneErr"
}
func (e HandlerDoneErr) Unwrap() error {
return e.Return
}
// ErrDontForward will stop the event from being forwarded to other handlers, including anything handlers.
var ErrDontForward = errors.New("dont forward")
// FindHandlers initializes and populates the HandlerFunctions map by reflecting over methods of registered handlers.
func (l *Loop) FindHandlers() {
l.HandlerFunctions = make(map[reflect.Type][]reflect.Value)
for _, inst := range l.Handlers {
t := reflect.TypeOf(inst)
v := reflect.ValueOf(inst)
for i := 0; i < t.NumMethod(); i++ {
method := t.Method(i)
if strings.HasPrefix(method.Name, "On") {
if method.Type.NumIn() != 2 || method.Type.NumOut() != 1 || method.Type.Out(0) != reflect.TypeFor[error]() {
fmt.Println("Invalid event handler signature:", t.String(), ".", method.Name)
continue
}
} else {
continue
}
eventType := method.Type.In(1)
l.HandlerFunctions[eventType] = append(l.HandlerFunctions[eventType], v.Method(i))
//fmt.Println("Registered Handler:", t.String(), ".", method.Name, "(", eventType.String(), ")")
}
}
}
// FireFound triggers all handlers registered for the specified event type and returns if handlers were found or an error occurred.
func (l *Loop) FireFound(event any) (found bool, err error) {
var handlers []reflect.Value
if handlers, found = l.HandlerFunctions[reflect.TypeOf(event)]; found {
for _, handler := range handlers {
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)
return
}
}
}
return
}
// 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) {
found, err := l.FireFound(event)
if err != nil {
if errors.Is(err, ErrDontForward) {
err = nil
}
return
}
if !found {
_, err = l.FireFound(Unhandled{event})
if err != nil {
return
}
}
_, err = l.FireFound(Anything{event})
return
}
func (l *Loop) Start() (err error) {
_ = *l // exit early on nil connection
l.FindHandlers()
err = l.Fire(OnStart{})
for err == nil {
err = l.Fire(Tick{})
if err != nil {
break
}
}
if errors.Is(err, HandlerDoneErr{}) {
err = errors.Unwrap(err)
}
return
}
+2 -2
View File
@@ -1,9 +1,9 @@
module github.com/admin-else/strom
go 1.24
go 1.26.2
require (
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/go-viper/mapstructure/v2 v2.4.0 // FIXME: this should be replaced with my version
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
)
-149
View File
@@ -1,149 +0,0 @@
package level
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"github.com/admin-else/strom/proto_base"
)
const (
BlocksPerChunkSection = 4096
BiomesPerChunkSection = 64
)
func UnpackArrayPalette(r io.Reader, bitsPerEntry uint8, numberOfEntries int) (data []int32, err error) {
palletLen, err := proto_base.DecodeVarInt(r)
if err != nil {
return
}
var pallet []int32 // remember we can't use make because this is user-controlled data
for range palletLen {
entry, err := proto_base.DecodeVarInt(r)
if err != nil {
return
}
pallet = append(pallet, entry)
}
data, err = UnpackLongData(r, bitsPerEntry, numberOfEntries)
for i, b := range data {
data[i] = pallet[b] // maybe check bounds?
}
return
}
func UnpackLongData(r io.Reader, bitsPerEntry uint8, numberOfEntries int) (data []int32, err error) {
entriesPerLong := int(64 / bitsPerEntry)
numberOfLongs := int(math.Ceil(float64(numberOfEntries) / float64(entriesPerLong)))
var dataL []uint64
for range numberOfLongs {
var entry uint64
err = binary.Read(r, binary.BigEndian, &entry)
if err != nil {
return
}
dataL = append(dataL, entry)
}
data = LongsToData(dataL, int32(numberOfEntries), int32(bitsPerEntry))
return
}
func UnpackSingleValuePalette(r io.Reader, numberOfEntries int) (data []int32, err error) {
entry, err := proto_base.DecodeVarInt(r)
if err != nil {
return
}
data = make([]int32, numberOfEntries)
for i := range data {
data[i] = int32(entry)
}
return
}
func LongsToData(data []uint64, n, bpe int32) (ret []int32) {
mask := (uint64(1) << uint32(bpe)) - 1
elementsPerLong := 64 / bpe
ret = make([]int32, n)
for i := range n {
ret[i] = int32((data[i/elementsPerLong] >> ((i % elementsPerLong) * bpe)) & mask)
}
return
}
func UnpackBlockData(r io.Reader) (blocks [BlocksPerChunkSection]int32, err error) {
var bitsPerEntry uint8
err = binary.Read(r, binary.BigEndian, &bitsPerEntry)
if err != nil {
return
}
var blocksSlice []int32
fmt.Println("blocks bpe", bitsPerEntry)
switch bitsPerEntry {
case 0:
blocksSlice, err = UnpackSingleValuePalette(r, BlocksPerChunkSection)
case 1, 2, 3, 4:
blocksSlice, err = UnpackArrayPalette(r, bitsPerEntry, BlocksPerChunkSection)
case 5, 6, 7, 8:
err = errors.New("unimplemented")
default:
blocksSlice, err = UnpackLongData(r, bitsPerEntry, BlocksPerChunkSection)
}
if err != nil {
return
}
blocks = [BlocksPerChunkSection]int32(blocksSlice)
return
}
func UnpackBiomeData(r io.Reader) (biomes [BiomesPerChunkSection]int32, err error) {
// net.minecraft.world.chunk.PaletteProvider#forBiomes
var bitsPerEntry uint8
err = binary.Read(r, binary.BigEndian, &bitsPerEntry)
if err != nil {
return
}
var biomesSlice []int32
fmt.Println("biome bpe", bitsPerEntry)
switch bitsPerEntry {
case 0:
biomesSlice, err = UnpackSingleValuePalette(r, BiomesPerChunkSection)
case 1, 2, 3:
biomesSlice, err = UnpackArrayPalette(r, bitsPerEntry, BiomesPerChunkSection)
default:
biomesSlice, err = UnpackLongData(r, bitsPerEntry, BiomesPerChunkSection)
}
if err != nil {
return
}
biomes = [BiomesPerChunkSection]int32(biomesSlice)
return
}
type ChunkSection struct {
BlockData [BlocksPerChunkSection]int32
BiomeData [BiomesPerChunkSection]int32
}
func UnpackSection(r io.Reader) (s ChunkSection, err error) {
var blockCount int16
err = binary.Read(r, binary.BigEndian, &blockCount)
if err != nil {
return
}
s.BlockData, err = UnpackBlockData(r)
if err != nil {
return
}
s.BiomeData, err = UnpackBiomeData(r)
if err != nil {
return
}
return
}
+64 -37
View File
@@ -9,32 +9,40 @@ import (
"strings"
"time"
"github.com/admin-else/strom/crypto"
"github.com/google/uuid"
)
type Servers struct {
ServicesHost string
SessionHost string
ProfilesHost string
}
var DefaultServers = &Servers{
ServicesHost: "https://api.minecraftservices.com",
SessionHost: "https://sessionserver.mojang.com",
ProfilesHost: "https://api.mojang.com",
}
type Account struct {
*Servers
Name string
Uuid uuid.UUID
Ygg string
}
const (
BaseUrlApi = "https://api.minecraftservices.com"
)
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
var b []byte
if method != "GET" {
b, err = json.Marshal(from)
if err != nil {
return err
return
}
}
r, err := http.NewRequest(method, url, bytes.NewReader(b))
if err != nil {
return err
return
}
r.Header.Set("Content-Type", "application/json")
@@ -44,21 +52,22 @@ func (a *Account) doMcApiRequest(method string, url string, from any, to any) (e
resp, err := http.DefaultClient.Do(r)
if err != nil {
return err
return
}
defer resp.Body.Close()
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
return fmt.Errorf("bad status code %v", resp.StatusCode)
}
b, err = io.ReadAll(resp.Body)
if err != nil {
return err
return
}
err = json.Unmarshal(b, to)
if err != nil {
return err
return
}
return nil
return
}
type PlayerKeys struct {
@@ -73,7 +82,7 @@ type PlayerKeys struct {
}
func (a *Account) FetchKeys() (keys PlayerKeys, err error) {
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/certificates", nil, &keys)
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/certificates", nil, &keys)
return
}
@@ -90,10 +99,12 @@ func (a *Account) JoinServer(serverId string) (err error) {
if err != nil {
return
}
resp, err := http.Post("https://sessionserver.mojang.com/session/minecraft/join", "application/json", bytes.NewReader(body))
resp, err := http.Post(a.SessionHost+"/session/minecraft/join", "application/json", bytes.NewReader(body))
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode != 204 {
body, err = io.ReadAll(resp.Body)
if err != nil {
@@ -112,17 +123,10 @@ type NameChangResp struct {
}
func (a *Account) NameChangeInfo() (r NameChangResp, err error) {
err = a.doMcApiRequest("GET", "https://api.minecraftservices.com/minecraft/profile/namechange", nil, &r)
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/namechange", nil, &r)
return
}
func NewOfflineAccount(name string) *Account {
return &Account{
Name: name,
Uuid: crypto.FromOfflinePlayer(name),
}
}
// PlayerProfile represents the player's profile information
type PlayerProfile struct {
ID string `json:"id"`
@@ -143,7 +147,7 @@ type PlayerProfile struct {
// QueryProfile queries the player's profile
func (a *Account) QueryProfile() (profile PlayerProfile, err error) {
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile", nil, &profile)
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile", nil, &profile)
return
}
@@ -181,7 +185,7 @@ type PlayerAttributes struct {
// QueryAttributes queries the player's attributes
func (a *Account) QueryAttributes() (attrs PlayerAttributes, err error) {
err = a.doMcApiRequest("GET", BaseUrlApi+"/player/attributes", nil, &attrs)
err = a.doMcApiRequest("GET", a.ServicesHost+"/player/attributes", nil, &attrs)
return
}
@@ -194,7 +198,7 @@ type ModifyAttributesRequest struct {
// ModifyAttributes modifies the player's attributes
func (a *Account) ModifyAttributes(req ModifyAttributesRequest) (attrs PlayerAttributes, err error) {
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/attributes", req, &attrs)
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/attributes", req, &attrs)
return
}
@@ -205,24 +209,23 @@ type BlockedUsers struct {
// GetBlocklist gets the list of blocked users
func (a *Account) GetBlocklist() (blocklist BlockedUsers, err error) {
err = a.doMcApiRequest("GET", BaseUrlApi+"/privacy/blocklist", nil, &blocklist)
err = a.doMcApiRequest("GET", a.ServicesHost+"/privacy/blocklist", nil, &blocklist)
return
}
// CheckNameAvailability checks if a name is available
type NameAvailability struct {
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
}
// CheckNameAvailability checks if a name is available
func (a *Account) CheckNameAvailability(name string) (avail NameAvailability, err error) {
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile/name/"+name+"/available", nil, &avail)
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/name/"+name+"/available", nil, &avail)
return
}
// ChangeName changes the player's name
func (a *Account) ChangeName(newName string) (profile PlayerProfile, err error) {
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/name/"+newName, nil, &profile)
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/name/"+newName, nil, &profile)
return
}
@@ -234,19 +237,19 @@ type ChangeSkinRequest struct {
// ChangeSkin changes the player's skin
func (a *Account) ChangeSkin(req ChangeSkinRequest) (profile PlayerProfile, err error) {
err = a.doMcApiRequest("POST", BaseUrlApi+"/minecraft/profile/skins", req, &profile)
err = a.doMcApiRequest("POST", a.ServicesHost+"/minecraft/profile/skins", req, &profile)
return
}
// ResetSkin resets the player's skin to default
func (a *Account) ResetSkin() (profile PlayerProfile, err error) {
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/skins/active", nil, &profile)
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/skins/active", nil, &profile)
return
}
// HideCape hides the player's active cape
func (a *Account) HideCape() (profile PlayerProfile, err error) {
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/capes/active", nil, &profile)
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/capes/active", nil, &profile)
return
}
@@ -258,29 +261,53 @@ type ShowCapeRequest struct {
// ShowCape shows a specific cape
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
req := ShowCapeRequest{CapeID: capeID}
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/capes/active", req, &profile)
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/capes/active", req, &profile)
return
}
// CheckGiftCodeValidity checks if a gift code is valid
func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error) {
url := BaseUrlApi + "/productvoucher/giftcode"
url := a.ServicesHost + "/productvoucher/" + giftCode
r, err := http.NewRequest("GET", url, nil)
if err != nil {
return false, err
return
}
r.Header.Set("Authorization", "Bearer "+a.Ygg)
resp, err := http.DefaultClient.Do(r)
if err != nil {
return false, err
return
}
defer resp.Body.Close()
// Returns HTTP 200 or 204 if valid, 404 if invalid
if resp.StatusCode == 200 || resp.StatusCode == 204 {
return true, nil
valid = true
return
}
return
}
func NewAccountFromYGG(ygg string) (a *Account, err error) {
a = &Account{
Ygg: ygg,
Servers: DefaultServers,
}
p, err := a.QueryProfile()
if err != nil {
return
}
a.Name = p.Name
a.Uuid, err = uuid.Parse(strings.ReplaceAll(p.ID, "-", ""))
return
}
func NewAccount(name string, uuid uuid.UUID, ygg string) (a *Account) {
return &Account{
Name: name,
Uuid: uuid,
Ygg: ygg,
Servers: DefaultServers,
}
return false, nil
}
+12
View File
@@ -0,0 +1,12 @@
package api
import (
"github.com/admin-else/strom/mc/crypto"
)
func NewOfflineAccount(name string) *Account {
return &Account{
Name: name,
Uuid: crypto.FromOfflinePlayer(name),
}
}
+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
}
+207
View File
@@ -0,0 +1,207 @@
package chat
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/pem"
"fmt"
"time"
"github.com/admin-else/strom/mc/api"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
"github.com/google/uuid"
)
type Module struct {
*proto.Conn
Account *api.Account
// Chat signing state
canSign bool
privKey *rsa.PrivateKey
sessionUUID uuid.UUID
msgIndex int32
}
// Start creates a chat module for the connection. If acc is an online account,
// it fetches the profile key pair from Mojang and enables signed chat.
func Start(c *proto.Conn, acc *api.Account) (*Module, error) {
m := &Module{Conn: c, Account: acc}
if acc != nil && acc.Ygg != "" {
if err := m.initSigning(acc); err != nil {
return nil, err
}
}
return m, nil
}
func (m *Module) initSigning(acc *api.Account) (err error) {
keys, err := acc.FetchKeys()
if err != nil {
return fmt.Errorf("fetch profile keys: %w", err)
}
priv, err := parsePrivateKey(keys.KeyPair.PrivateKey)
if err != nil {
return fmt.Errorf("parse private key: %w", err)
}
m.privKey = priv
m.sessionUUID = uuid.New()
pubDer, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
if err != nil {
return fmt.Errorf("marshal public key: %w", err)
}
sigBytes, err := base64.StdEncoding.DecodeString(keys.PublicKeySignatureV2)
if err != nil {
return fmt.Errorf("decode key signature: %w", err)
}
if err = m.Conn.Send(&v1_21_11.PlayToServerPacketChatSessionUpdate{
SessionUUID: m.sessionUUID,
ExpireTime: keys.ExpiresAt.UnixMilli(),
PublicKey: v1_21_11.ByteArray{Val: pubDer},
Signature: v1_21_11.ByteArray{Val: sigBytes},
}); err != nil {
return fmt.Errorf("send chat session update: %w", err)
}
m.canSign = true
return nil
}
// SendMessage sends a chat message. If signing is enabled, the message is signed;
// otherwise it is sent unsigned.
func (m *Module) SendMessage(message string) (err error) {
if m.canSign {
return m.sendSigned(message)
}
return m.Conn.Send(&v1_21_11.PlayToServerPacketChatMessage{Message: message})
}
func (m *Module) sendSigned(message string) (err error) {
now := time.Now()
saltBytes := make([]byte, 8)
if _, err = rand.Read(saltBytes); err != nil {
return
}
salt := int64(binary.BigEndian.Uint64(saltBytes))
timestamp := now.UnixMilli()
payload, err := m.buildSignaturePayload(message, salt, timestamp)
if err != nil {
return
}
hashed := sha256.Sum256(payload)
sigBytes, err := rsa.SignPKCS1v15(rand.Reader, m.privKey, crypto.SHA256, hashed[:])
if err != nil {
return
}
if len(sigBytes) != 256 {
return fmt.Errorf("unexpected signature length: %d", len(sigBytes))
}
var signature [256]byte
copy(signature[:], sigBytes)
m.msgIndex++
return m.Conn.Send(&v1_21_11.PlayToServerPacketChatMessage{
Message: message,
Timestamp: timestamp,
Salt: salt,
Signature: &signature,
Offset: 0,
Acknowledged: [3]byte{},
Checksum: 0,
})
}
func (m *Module) buildSignaturePayload(content string, salt, timestampMillis int64) ([]byte, error) {
buf := make([]byte, 0, 64+len(content))
w := func(b []byte) { buf = append(buf, b...) }
// Header
w(int32Bytes(1))
// SignedMessageLink: sender UUID, session UUID, index
w(uuidBytes(m.Account.Uuid))
w(uuidBytes(m.sessionUUID))
w(int32Bytes(m.msgIndex))
// SignedMessageBody: salt, timestamp seconds, content
w(int64Bytes(uint64(salt)))
w(int64Bytes(uint64(timestampMillis / 1000)))
contentBytes := []byte(content)
w(int32Bytes(int32(len(contentBytes))))
w(contentBytes)
// LastSeenMessages: empty
w(int32Bytes(0))
return buf, nil
}
func parsePrivateKey(s string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(s))
if block != nil {
k, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
k, err = x509.ParsePKCS1PrivateKey(block.Bytes)
}
if err != nil {
return nil, err
}
if key, ok := k.(*rsa.PrivateKey); ok {
return key, nil
}
return nil, fmt.Errorf("not an RSA private key")
}
// Some implementations return raw base64 DER instead of PEM
raw, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
}
k, err := x509.ParsePKCS8PrivateKey(raw)
if err != nil {
k, err = x509.ParsePKCS1PrivateKey(raw)
}
if err != nil {
return nil, err
}
key, ok := k.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("not an RSA private key")
}
return key, nil
}
func int32Bytes(v int32) []byte {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(v))
return b
}
func int64Bytes(v uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, v)
return b
}
func uuidBytes(u uuid.UUID) []byte {
b := make([]byte, 16)
copy(b, u[:])
return b
}
+19
View File
@@ -0,0 +1,19 @@
package keepalive
import (
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
)
type mod struct {
*proto.Conn
}
func (m *mod) onKeepAlive(p *v1_21_8.PlayToClientPacketKeepAlive) (err error) {
return m.Send(&v1_21_8.PlayToServerPacketKeepAlive{KeepAliveId: p.KeepAliveId})
}
func Start(c *proto.Conn) {
m := &mod{Conn: c}
m.RegisterUntilLatest(m.onKeepAlive)
}
+178
View File
@@ -0,0 +1,178 @@
package world
import (
"bytes"
"errors"
"sync"
"github.com/admin-else/strom/mc/level"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
)
var (
ChunkNotLoadedErr = errors.New("chunk not loaded")
OutOfBoundsErr = errors.New("coordinates out of bounds")
)
// ChunkPos identifies a chunk column.
type ChunkPos struct {
X, Z int32
}
// World stores a shareable in-memory view of the Minecraft world.
// Multiple bots can subscribe to the same World and update it from their
// respective connections.
type World struct {
mu sync.RWMutex
chunks map[ChunkPos]*level.Chunk
version string
minY int
height int
}
// NewWorld creates a new World for the given protocol version and vertical
// bounds. For a 1.21 overworld use minY=-64 and height=384.
func NewWorld(version string, minY, height int) *World {
return &World{
chunks: make(map[ChunkPos]*level.Chunk),
version: version,
minY: minY,
height: height,
}
}
// GetBlock returns the global block state ID at the given world coordinates.
func (w *World) GetBlock(x, y, z int32) (stateId int32, err error) {
w.mu.RLock()
defer w.mu.RUnlock()
return w.getBlockLocked(x, y, z)
}
func (w *World) getBlockLocked(x, y, z int32) (stateId int32, err error) {
chunkX := floorDiv(x, level.ChunkWidth)
chunkZ := floorDiv(z, level.ChunkWidth)
chunk, ok := w.chunks[ChunkPos{chunkX, chunkZ}]
if !ok {
err = ChunkNotLoadedErr
return
}
sectionIndex := (int(y) - w.minY) / level.ChunkWidth
if sectionIndex < 0 || sectionIndex >= len(chunk.Sections) {
err = OutOfBoundsErr
return
}
lx, ly, lz := blockToLocal(x, y, z)
index := ly*level.ChunkWidth*level.ChunkWidth + lz*level.ChunkWidth + lx
return chunk.Sections[sectionIndex].Blocks.Get(index)
}
// SetBlock updates the global block state ID at the given world coordinates.
func (w *World) SetBlock(x, y, z int32, stateId int32) (err error) {
w.mu.Lock()
defer w.mu.Unlock()
chunkX := floorDiv(x, level.ChunkWidth)
chunkZ := floorDiv(z, level.ChunkWidth)
chunk, ok := w.chunks[ChunkPos{chunkX, chunkZ}]
if !ok {
return ChunkNotLoadedErr
}
sectionIndex := (int(y) - w.minY) / level.ChunkWidth
if sectionIndex < 0 || sectionIndex >= len(chunk.Sections) {
return OutOfBoundsErr
}
lx, ly, lz := blockToLocal(x, y, z)
index := ly*level.ChunkWidth*level.ChunkWidth + lz*level.ChunkWidth + lx
return chunk.Sections[sectionIndex].Blocks.Set(index, stateId)
}
// Module subscribes to chunk and block-change packets and updates a shared
// World. Multiple Modules can point to the same World.
type Module struct {
*proto.Conn
world *World
}
// GetBlock returns the block state ID at the given world coordinates via the
// shared World.
func (m *Module) GetBlock(x, y, z int32) (int32, error) {
return m.world.GetBlock(x, y, z)
}
// Start registers packet handlers on c that update the shared World w.
func Start(c *proto.Conn, w *World) *Module {
m := &Module{Conn: c, world: w}
m.RegisterUntilLatest(m.onMapChunk)
m.RegisterUntilLatest(m.onBlockChange)
m.RegisterUntilLatest(m.onMultiBlockChange)
return m
}
func (m *Module) onMapChunk(p *v1_21_11.PlayToClientPacketMapChunk) (err error) {
chunk, err := level.ReadChunkFromChunkPacketData(
bytes.NewReader(p.ChunkData.Val),
m.world.version,
m.world.height,
)
if err != nil {
return
}
m.world.mu.Lock()
m.world.chunks[ChunkPos{p.X, p.Z}] = chunk
m.world.mu.Unlock()
return nil
}
func (m *Module) onBlockChange(p *v1_21_11.PlayToClientPacketBlockChange) (err error) {
return m.world.SetBlock(p.Location.X, int32(p.Location.Y), p.Location.Z, p.Type)
}
func (m *Module) onMultiBlockChange(p *v1_21_11.PlayToClientPacketMultiBlockChange) (err error) {
chunkX := p.ChunkCoordinates.X
chunkZ := p.ChunkCoordinates.Z
sectionY := p.ChunkCoordinates.Y
for _, record := range p.Records {
packedPos := record & 0xFFF
stateId := record >> 12
localX := (packedPos >> 8) & 0xF
localY := (packedPos >> 4) & 0xF
localZ := packedPos & 0xF
x := chunkX*level.ChunkWidth + int32(localX)
y := sectionY*level.ChunkWidth + int32(localY)
z := chunkZ*level.ChunkWidth + int32(localZ)
if err = m.world.SetBlock(x, y, z, stateId); err != nil {
return
}
}
return nil
}
func floorDiv(a, b int32) int32 {
return (a - ((a%b + b) % b)) / b
}
func blockToLocal(x, y, z int32) (lx, ly, lz int32) {
lx = x % level.ChunkWidth
if lx < 0 {
lx += level.ChunkWidth
}
ly = y % level.ChunkWidth
if ly < 0 {
ly += level.ChunkWidth
}
lz = z % level.ChunkWidth
if lz < 0 {
lz += level.ChunkWidth
}
return
}
+57
View File
@@ -0,0 +1,57 @@
package client
import (
"context"
"net"
"github.com/admin-else/strom/mc/data"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/server"
)
// Connect connects to the given address note that it does not resolve the SRV records it's just a raw net.Dial
// You must close the connection when you're done with it
// version must be a vaild minecraft version eg 1.21.11 if left empty it will be detected with a status-request.
func Connect(ctx context.Context, addr string, version string) (ret *proto.Conn, err error) {
var protocolVersion int32
if version == "" {
var s server.StatusResponse
s, err = Status(ctx, addr)
if err != nil {
return
}
protocolVersion = s.Version.Protocol
} else {
var v data.VersionInfo
v, err = data.LookUpVersionByName(version)
if err != nil {
return
}
protocolVersion = v.Version
}
ret, err = ConnectVersionLess(ctx, addr)
if err != nil {
return
}
err = ret.SetProtocolVersion(protocolVersion)
return
}
// 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(ctx context.Context, addr string) (ret *proto.Conn, err error) {
if ctx == nil {
ctx = context.Background()
}
ret = proto.NewConnCtx(ctx)
ret.Actor = proto_base.Client
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
if err != nil {
return
}
ret.R = ret.Conn
ret.W = ret.Conn
return
}
@@ -1,21 +1,25 @@
package modules
package client
import (
"errors"
"fmt"
"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"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
)
type ConfigIgnorer struct {
*proto.Conn
}
func (c *ConfigIgnorer) Default(event any) (err error) {
fmt.Printf("%#v\n", event)
func (c *ConfigIgnorer) OnStart() (err error) {
return
}
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
c.Log.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
return
}
@@ -23,7 +27,7 @@ func (c *ConfigIgnorer) OnKnownPacks(packet *v1_21_8.ConfigurationToClientPacket
return c.Send(&v1_21_8.ConfigurationToServerPacketCommonSelectKnownPacks{Packs: packet.Packs})
}
func (c *ConfigIgnorer) OnFinish(_ v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
func (c *ConfigIgnorer) OnFinish(_ *v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
err = c.Send(&v1_21_8.ConfigurationToServerPacketFinishConfiguration{})
if err != nil {
return
@@ -44,7 +48,14 @@ func (c *ConfigIgnorer) OnKeepAlive(packet *v1_21_8.ConfigurationToClientPacketK
}
func IgnoreConfig(c *proto.Conn) (err error) {
err = c.Start(&ConfigIgnorer{c})
ci := &ConfigIgnorer{c}
ci.RegisterCritical(ci.Default)
ci.RegisterUntilLatest(ci.OnKnownPacks)
ci.RegisterUntilLatest(ci.OnFinish)
ci.RegisterUntilLatest(ci.OnPing)
ci.RegisterUntilLatest(ci.OnKeepAlive)
err = ci.StartConn()
if err != nil {
err = errors.Join(err, errors.New("failed to ignore config"))
}
+80
View File
@@ -0,0 +1,80 @@
package client
import (
"context"
"errors"
"net"
"strconv"
"strings"
"time"
)
type DNSCheckFunc func(addr string) error
// DoDNSChecked resolves a DNS name to an IP address
// It should resolve a minecraft compatible address into something net.Dial
// It also optionally accepts a function to "check" the address
func DoDNSChecked(ctx context.Context, in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) {
if f == nil {
f = func(addr string) error { return nil }
}
if ctx == nil {
ctx = context.Background()
}
// https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34
host, port, err := net.SplitHostPort(in)
if err != nil {
if addrErr, ok := errors.AsType[*net.AddrError](err); ok && addrErr.Err == "missing port in address" {
err = nil
host = in
port = "25565"
} else {
return
}
}
porti, err := strconv.Atoi(port)
if err != nil {
return
}
err = f(host)
if err != nil {
return
}
portN = uint16(porti)
outIP, err := net.ResolveIPAddr("ip", host)
if err != nil {
return
}
err = f(outIP.String())
if err != nil {
return
}
connectTo = outIP.String() + ":" + port
if portN != 25565 {
return
}
ctx, cancel := context.WithTimeout(ctx, time.Second) // otherwise this takes 10+ seconds
defer cancel()
r := &net.Resolver{}
_, srvs, _ := r.LookupSRV(ctx, "minecraft", "tcp", host)
if len(srvs) == 0 {
return
}
host = strings.TrimSuffix(srvs[0].Target, ".")
portN = srvs[0].Port
outIP, err = net.ResolveIPAddr("ip", host)
if err != nil {
return
}
err = f(outIP.String())
if err != nil {
return
}
connectTo = outIP.String() + ":" + port
return
}
+27
View File
@@ -0,0 +1,27 @@
package client
import "testing"
func TestDoDNS(t *testing.T) {
type Case struct {
Input string
Host string
portN uint16
}
cases := []Case{
{"localhost", "localhost", 25565},
{"play.hypixel.net", "play.hypixel.net", 25565},
{"1f2d.net", "tcpshield.1f2d.net", 25565},
}
for _, c := range cases {
t.Logf("Testing %s", c.Input)
_, host, portN, err := DoDNSChecked(nil, c.Input, nil)
if err != nil {
t.Error(err)
continue
}
if host != c.Host || portN != c.portN {
t.Errorf("expected %s:%d, got %s:%d", c.Host, c.portN, host, portN)
}
}
}
+31
View File
@@ -0,0 +1,31 @@
package client
import (
"net"
"strconv"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
)
func MakeHandshakePacket(c *proto.Conn, nextState proto_base.State) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
return MakeHandshakePacketAddr(c, nextState, c.RemoteAddr().String())
}
// MakeHandshakePacketAddr gets a custom addr
func MakeHandshakePacketAddr(s *proto.Conn, nextState proto_base.State, addr string) (p *v1_21_8.HandshakingToServerPacketSetProtocol, err error) {
p = &v1_21_8.HandshakingToServerPacketSetProtocol{NextState: int32(nextState), ProtocolVersion: s.ProtocolVersion}
var portStr string
p.ServerHost, portStr, err = net.SplitHostPort(addr)
if err != nil {
return
}
var portUint uint64
portUint, err = strconv.ParseUint(portStr, 10, 16)
if err != nil {
return
}
p.ServerPort = uint16(portUint)
return
}
+220
View File
@@ -0,0 +1,220 @@
package client
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"errors"
"github.com/admin-else/strom/mc/api"
"github.com/admin-else/strom/mc/crypto"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/proto"
"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/v26_2"
"github.com/admin-else/strom/mc/text"
)
type LoginClient struct {
*proto.Conn
Account *api.Account
ServerHost string
ServerPort uint16
GivenAccount *v1_21_8.LoginToClientPacketSuccess
}
var (
NoTokenErr = errors.New("the account has no token so we cant join online servers")
BadPublicKeyTypeErr = errors.New("the public key is not rsa")
FailedToLoginErr = errors.New("failed to login")
)
type KickedDuringLoginErr struct {
text.Component
}
func (k KickedDuringLoginErr) Error() string {
return k.String()
}
func (s *LoginClient) OnDefault(event event.Unhandled) (err error) {
if _, ok := event.Val.(proto_base.EncodeDecodeAble); !ok {
return
}
s.Log.Warn("unexpected packet during login client", "packet", event.Val)
return
}
func (s *LoginClient) OnClose(_ event.Close) (err error) {
return
}
func (s *LoginClient) OnCompress(compress *v1_21_8.LoginToClientPacketCompress) (err error) {
s.SetCompressionThreshold(compress.Threshold)
return
}
func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBegin) (err error) {
sharedSecret := make([]byte, 16)
_, _ = rand.Read(sharedSecret) //never fails
if packet.ShouldAuthenticate {
if s.Account.Ygg == "" {
err = NoTokenErr
return
}
serverId := crypto.AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey)
err = s.Account.JoinServer(serverId)
if err != nil {
return
}
}
pubAny, err := x509.ParsePKIXPublicKey(packet.PublicKey)
if err != nil {
return
}
pub, ok := pubAny.(*rsa.PublicKey)
if !ok {
err = BadPublicKeyTypeErr
return
}
verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, packet.VerifyToken)
if err != nil {
return
}
sharedSecretEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, sharedSecret)
if err != nil {
return
}
err = s.Send(&v1_21_8.LoginToServerPacketEncryptionBegin{SharedSecret: sharedSecretEnc, VerifyToken: verifyTokenEnc})
if err != nil {
return
}
err = s.SetSecret(sharedSecret)
return
}
func (s *LoginClient) OnDisconnect(packet *v1_21_8.LoginToClientPacketDisconnect) (err error) {
var reason text.Component
err = json.Unmarshal([]byte(packet.Reason), &reason)
if err != nil {
return
}
err = KickedDuringLoginErr{reason}
return
}
func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (err error) {
s.GivenAccount = success
err = s.Send(&v1_21_8.LoginToServerPacketLoginAcknowledged{})
if err != nil {
return
}
s.SetState(proto_base.Configuration)
err = event.HandlerDoneErr{}
return
}
func (s *LoginClient) OnSuccess26_2(success *v26_2.LoginToClientPacketSuccess) (err error) {
return s.OnSuccess(&v1_21_8.LoginToClientPacketSuccess{
Uuid: success.Uuid,
Username: success.Username,
Properties: success.Properties,
})
}
func (s *LoginClient) OnStart() (err error) {
return
}
func LoginRaw(c *proto.Conn, account *api.Account) (err error) {
lc := &LoginClient{
Conn: c,
Account: account,
}
lc.RegisterCritical(lc.OnDefault)
lc.RegisterCritical(lc.OnClose)
lc.RegisterCriticalUntilLatest(lc.OnCompress)
lc.RegisterCriticalUntilLatest(lc.OnEncrypt)
lc.RegisterCriticalUntilLatest(lc.OnDisconnect)
lc.RegisterCriticalUntil("26.1", lc.OnSuccess)
lc.RegisterCriticalUntilLatest(lc.OnSuccess26_2)
p, err := MakeHandshakePacket(lc.Conn, proto_base.Login)
if err != nil {
return
}
err = lc.Send(p)
if err != nil {
return
}
lc.SetState(proto_base.Login)
err = lc.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: lc.Account.Name, PlayerUUID: lc.Account.Uuid})
err = lc.StartConn()
if err != nil {
err = errors.Join(FailedToLoginErr, err)
}
return
}
func WithoutDns() func(ls *loginSettings) {
return func(ls *loginSettings) {
ls.NoDns = true
}
}
func WithContext(ctx context.Context) func(ls *loginSettings) {
return func(ls *loginSettings) {
ls.Ctx = ctx
}
}
func WithVersion(version string) func(ls *loginSettings) {
return func(ls *loginSettings) {
ls.Version = version
}
}
type loginSettings struct {
NoDns bool
Ctx context.Context
Version string
IgnoreConfig bool
}
func Login(connectTo string, account *api.Account, settings ...func(ls *loginSettings)) (c *proto.Conn, err error) {
ls := &loginSettings{}
for _, setting := range settings {
setting(ls)
}
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 {
return
}
err = LoginRaw(c, account)
if err != nil {
return
}
if !ls.IgnoreConfig {
err = IgnoreConfig(c)
}
return
}
+55
View File
@@ -0,0 +1,55 @@
package client_test
import (
"reflect"
"testing"
"github.com/admin-else/strom/mc/data"
"github.com/admin-else/strom/mc/proto"
"github.com/admin-else/strom/mc/proto_base"
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
)
func assertClientSendConvertible(t *testing.T, srcType reflect.Type, name string, state proto_base.State, direction proto_base.Direction, targetVersion int32) {
t.Helper()
targetInfo, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(name, targetVersion, state, direction)
if !ok {
t.Fatalf("packet %s not found for version %d", name, targetVersion)
}
targetType := reflect.TypeOf(targetInfo.Type)
if !proto.SmartConvertibleTo(srcType, targetType) {
t.Fatalf("%s is not smart-convertible from v1_21_8 to version %d", name, targetVersion)
}
}
func assertClientReceiveConvertible(t *testing.T, dstType reflect.Type, name string, state proto_base.State, direction proto_base.Direction, targetVersion int32) {
t.Helper()
targetInfo, ok := proto.LookupPacketInfoByNameProtocolVersionStateAndDirection(name, targetVersion, state, direction)
if !ok {
t.Fatalf("packet %s not found for version %d", name, targetVersion)
}
targetType := reflect.TypeOf(targetInfo.Type)
if !proto.SmartConvertibleTo(targetType, dstType) {
t.Fatalf("%s is not smart-convertible from version %d to v1_21_8", name, targetVersion)
}
}
func TestLoginClientSendAssumptions(t *testing.T) {
v26_1Version := data.MustLookupProtocolVersion("26.1")
// Packets sent by LoginClient as v1_21_8.
assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.HandshakingToServerPacketSetProtocol](), "set_protocol", proto_base.Handshaking, proto_base.ToServer, v26_1Version)
assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketLoginStart](), "login_start", proto_base.Login, proto_base.ToServer, v26_1Version)
assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketLoginAcknowledged](), "login_acknowledged", proto_base.Login, proto_base.ToServer, v26_1Version)
assertClientSendConvertible(t, reflect.TypeFor[*v1_21_8.LoginToServerPacketEncryptionBegin](), "encryption_begin", proto_base.Login, proto_base.ToServer, v26_1Version)
}
func TestLoginClientReceiveAssumptions(t *testing.T) {
v26_1Version := data.MustLookupProtocolVersion("26.1")
// Packets received by LoginClient via RegisterCriticalUntilLatest.
// login_success is excluded because 26.2 is not convertible.
assertClientReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketCompress](), "compress", proto_base.Login, proto_base.ToClient, v26_1Version)
assertClientReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketEncryptionBegin](), "encryption_begin", proto_base.Login, proto_base.ToClient, v26_1Version)
assertClientReceiveConvertible(t, reflect.TypeFor[*v1_21_8.LoginToClientPacketDisconnect](), "disconnect", proto_base.Login, proto_base.ToClient, v26_1Version)
}
+90
View File
@@ -0,0 +1,90 @@
package client
import (
"context"
"encoding/json"
"time"
"github.com/admin-else/strom/mc/event"
"github.com/admin-else/strom/mc/proto"
"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/server"
)
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(ctx context.Context, addr string) (s *StatusClient, err error) {
c, err := ConnectVersionLess(ctx, addr)
if err != nil {
return
}
defer c.Close()
err = c.SetVersion("1.21.8")
if err != nil {
return
}
s = &StatusClient{
Conn: c,
}
s.RegisterCriticalUntilLatest(s.OnStatus)
s.RegisterCriticalUntilLatest(s.OnPong)
p, err := 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
}
// StatusNoDns is like Status but does not resolve SRV records.
func StatusNoDns(addr string) (status server.StatusResponse, err error) {
s, err := StatusRaw(context.Background(), addr)
if err != nil {
return
}
err = json.Unmarshal([]byte(s.Status), &status)
return
}
// Status resolves the status of the minecraft server at the given address.
func Status(ctx context.Context, addr string) (status server.StatusResponse, err error) {
addr, _, _, err = DoDNSChecked(ctx, addr, nil)
if err != nil {
return
}
return StatusNoDns(addr)
}
+4
View File
@@ -28,6 +28,7 @@ package crypto
import (
"crypto/cipher"
"crypto/subtle"
"sync"
"unsafe"
)
@@ -37,6 +38,7 @@ type CFB8 struct {
ivPos int
iv []byte
de bool
mutex sync.Mutex
}
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8 {
@@ -59,6 +61,8 @@ func newCFB8(c cipher.Block, iv []byte, de bool) *CFB8 {
}
func (cf *CFB8) XORKeyStream(dst, src []byte) {
cf.mutex.Lock()
defer cf.mutex.Unlock()
if len(src) == 0 {
return
}
+3 -3
View File
@@ -6,9 +6,9 @@ import (
"github.com/google/uuid"
)
// UUIDv3 Variation of google.uuid.NewHash
// uuidV3 Variation of google.uuid.NewHash
// We need this because we want a 14-byte namespace and Google forces 16
func UUIDv3(space []byte, data []byte) uuid.UUID {
func uuidV3(space []byte, data []byte) uuid.UUID {
h := md5.New()
h.Reset()
h.Write(space) //nolint:errcheck
@@ -22,5 +22,5 @@ func UUIDv3(space []byte, data []byte) uuid.UUID {
}
func FromOfflinePlayer(displayName string) uuid.UUID {
return UUIDv3([]byte("OfflinePlayer:"), []byte(displayName))
return uuidV3([]byte("OfflinePlayer:"), []byte(displayName))
}
+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
@@ -3,7 +3,7 @@ package data_test
import (
"testing"
"github.com/admin-else/strom/data"
"github.com/admin-else/strom/mc/data"
)
func TestLookupBiomeById(t *testing.T) {
+226
View File
@@ -0,0 +1,226 @@
package data
import (
"errors"
"slices"
"strconv"
"github.com/admin-else/strom/mc/util"
)
// {
// "id": 0,
// "name": "air",
// "displayName": "Air",
// "hardness": 0.0,
// "resistance": 0.0,
// "stackSize": 64,
// "diggable": false,
// "material": "default",
// "transparent": true,
// "emitLight": 0,
// "filterLight": 0,
// "defaultState": 0,
// "minStateId": 0,
// "maxStateId": 0,
// "states": [],
// "drops": [],
// "boundingBox": "empty"
// },
type Block struct {
Id int
Name string
DisplayName string
Hardness float64
Resistance float64
StackSize int
Diggable bool
Material string
Transparent bool
EmitLight int
FilterLight int
DefaultState int32
MinStateId int32
MaxStateId int32
States []BlockState
Drops []int
BoundingBox string
}
type BlockState struct {
Name string
Type string
MinValue int32 // this is generated on load and used for state value calculations
NumValues int32 `json:"num_values"`
Values []string
}
var BlocksCache = make(map[string][]*Block)
var BlocksCacheIdMap = make(map[string]map[int32]*Block)
func BlocksForVersion(v string) (ret []*Block) {
var ok bool
if ret, ok = BlocksCache[v]; ok {
return
}
var b []*Block
must(LoadVersionedJson(v, "blocks", &b))
BlocksCache[v] = b
for _, block := range b {
for i, state := range block.States {
if state.Type != "int" {
continue
}
values := make([]int32, state.NumValues)
for j, stateValue := range state.Values {
n := util.MustT(strconv.Atoi(stateValue))
values[j] = int32(n)
}
block.States[i].MinValue = slices.Min(values)
}
}
return b
}
func LookupBlockByStateId(version string, stateId int32) (block *Block, ok bool) {
idmap, found := BlocksCacheIdMap[version]
if !found {
idmap = make(map[int32]*Block)
for _, b := range BlocksForVersion(version) {
for i := range b.MaxStateId - b.MinStateId + 1 {
idmap[b.MinStateId+i] = b
}
}
BlocksCacheIdMap[version] = idmap
}
block, ok = idmap[stateId]
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 {
block = b
ok = true
}
}
return
}
func StateIdFromBlocKAndStateMap(version string, name string, stateMap map[string]string) (stateId int32, err error) {
b, ok := LookupBlockByName(version, name)
if !ok {
err = BlockNotFoundErr
return
}
if stateMap == nil {
stateId = b.DefaultState
} else {
stateId, err = b.IdFromStateMap(stateMap)
}
if b.MinStateId > stateId || b.MaxStateId < stateId {
err = BlockStateOutOfRangeErr
}
return
}
var BlockStateOutOfRangeErr = errors.New("stateId out of range")
var BlockNotFoundErr = errors.New("block not found")
var InvalidBoolValueErr = errors.New("invalid bool value")
var BadBlockStateTypeErr = errors.New("bad block state type")
var BadEnumErr = errors.New("bad enum")
var SupplyAllStatesErr = errors.New("supply all states")
func FromBlockState(version string, stateId int32) (b *Block, stateData map[string]string, err error) {
b, ok := LookupBlockByStateId(version, stateId)
if !ok {
err = BlockNotFoundErr
}
stateData, err = b.StateMapFromId(stateId)
return
}
func mcDataBlockStateTypeParse(n int32, s *BlockState) (v string, err error) {
switch s.Type {
case "int":
v = strconv.Itoa(int(n))
case "enum":
v = s.Values[n]
case "bool":
if n == 0 { // MOJANK: 0 is true, 1 is false
v = "true"
} else if n == 1 {
v = "false"
} else {
err = InvalidBoolValueErr
}
default:
err = BadBlockStateTypeErr
}
return
}
func mcDataBlockStateTypeSerialize(v string, s *BlockState) (n int32, err error) {
if v == "true" {
return 0, nil
}
if v == "false" {
return 1, nil
}
n1, errMabye := strconv.Atoi(v)
if errMabye == nil {
return int32(n1) - s.MinValue, nil
}
for i, enumValue := range s.Values {
if enumValue == v {
return int32(i), nil
}
}
err = BadEnumErr
return
}
func (b *Block) StateMapFromId(stateId int32) (ret map[string]string, err error) {
if stateId < b.MinStateId || stateId > b.MaxStateId {
err = BlockStateOutOfRangeErr
return
}
ret = make(map[string]string)
stateId -= b.MinStateId
a := int32(1)
for i := range len(b.States) {
s := b.States[len(b.States)-1-i]
ret[s.Name], err = mcDataBlockStateTypeParse((stateId/a)%s.NumValues, &s)
if err != nil {
return
}
a *= s.NumValues
}
return
}
func (b *Block) IdFromStateMap(m map[string]string) (ret int32, err error) {
a := int32(1)
for i := range len(b.States) {
s := b.States[len(b.States)-1-i]
v, found := m[s.Name]
if !found {
err = SupplyAllStatesErr
return
}
var n int32
n, err = mcDataBlockStateTypeSerialize(v, &s)
if err != nil {
return
}
ret += n * a
a *= s.NumValues
}
ret += b.MinStateId
return
}
+42
View File
@@ -0,0 +1,42 @@
package data_test
import (
"fmt"
"testing"
"github.com/admin-else/strom/mc/data"
)
func TestLookupBlockByStateId(t *testing.T) {
b, ok := data.LookupBlockByStateId("1.21.8", 0)
if !ok {
t.Fatal("Block not found")
}
fmt.Println(b)
}
func TestBlockStates(t *testing.T) {
var stateId int32 = 8301
b, stateMap, err := data.FromBlockState("1.21.8", stateId)
if err != nil {
t.Fatal(err)
return
}
t.Logf("Block state map: %v, block: %v", stateMap, b.Name)
stateId2, err := b.IdFromStateMap(stateMap)
if err != nil {
t.Fatal(err)
return
}
if stateId2 != stateId {
t.Fatal("state id mismatch")
}
}
func TestBlockStateOutOfRangeBug(t *testing.T) {
id, err := data.StateIdFromBlocKAndStateMap("1.21.11", "wildflowers", map[string]string{"facing": "east", "flower_amount": "4"})
if err != nil {
t.Fatal(err)
}
t.Logf("State id: %d", id)
}
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/sh
set -xeuo pipefail
# Copy minecraft-data from the local AnyGate checkout instead of cloning upstream.
# This is used when upstream does not yet have the target version or when we want
# to test local changes before pushing.
LOCAL_MINECRAFT_DATA="${LOCAL_MINECRAFT_DATA:-$HOME/src/anygate/minecraft-data}"
cd "$(dirname "$0")"
rm -rf minecraft-data/
mkdir -p minecraft-data/pc
cp "$LOCAL_MINECRAFT_DATA/data/dataPaths.json" minecraft-data/
copy-mc() {
cp -r "$LOCAL_MINECRAFT_DATA/data/pc/$1" minecraft-data/pc
}
copy-mc common
copy-mc 1.21.8
copy-mc 1.21.9
copy-mc 1.21.11
copy-mc 26.1
copy-mc 26.2
+7 -3
View File
@@ -4,9 +4,12 @@ import (
"embed"
"encoding/json"
"errors"
"fmt"
)
//go:embed minecraft-data/data
//go:generate ./download_minecraft_data.sh
//go:embed minecraft-data/pc minecraft-data/dataPaths.json
var MinecraftData embed.FS
func LoadJson(path string, data any) (err error) {
@@ -19,9 +22,10 @@ func LoadJson(path string, data any) (err error) {
}
func LoadVersionedJson(version, dataName string, data any) (err error) {
b, err := MinecraftData.ReadFile("minecraft-data/data/" + Paths.Data[version][dataName] + "/" + dataName + ".json")
b, err := MinecraftData.ReadFile("minecraft-data/" + Paths.Data[version][dataName] + "/" + dataName + ".json")
if err != nil {
return
// If you get this error, you need to probably need to update the download script
return fmt.Errorf("minecraft-data/%s/%s/%s.json: %w", version, Paths.Data[version][dataName], dataName, err)
}
err = json.Unmarshal(b, data)
return
+1 -1
View File
@@ -9,5 +9,5 @@ var Paths struct {
}
func init() {
must(LoadJson("minecraft-data/data/dataPaths.json", &Paths))
must(LoadJson("minecraft-data/dataPaths.json", &Paths))
}
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/sh
set -xeuo pipefail
# Go get / install does not clone git submodules so we have to do it like this
cd "$(dirname "$0")"
rm -rf minecraft-data/
git clone --depth 1 git@github.com:PrismarineJS/minecraft-data.git
mv minecraft-data minecraft-data-untrimmed
mkdir minecraft-data
mkdir minecraft-data/pc
mv minecraft-data-untrimmed/data/dataPaths.json minecraft-data
copy-mc() {
mv minecraft-data-untrimmed/data/pc/$1 minecraft-data/pc
}
copy-mc common
copy-mc 1.21.8
copy-mc 1.21.9
copy-mc 1.21.11
rm -rf minecraft-data-untrimmed
+57
View File
@@ -0,0 +1,57 @@
package data
import "sync"
// {
// "id": 2,
// "name": "granite",
// "displayName": "Granite",
// "stackSize": 64
// },
type Item struct {
Id int32
Name string
DisplayName string
StackSize int32
}
var ItemsCache = make(map[string][]*Item)
var ItemsCacheMutex = &sync.RWMutex{}
func ItemsForVersion(v string) (ret []*Item) {
var ok bool
ItemsCacheMutex.RLock()
if ret, ok = ItemsCache[v]; ok {
ItemsCacheMutex.RUnlock()
return
}
ItemsCacheMutex.RUnlock()
var b []*Item
must(LoadVersionedJson(v, "items", &b))
ItemsCacheMutex.Lock()
ItemsCache[v] = b
ItemsCacheMutex.Unlock()
return b
}
func LookupItemById(version string, id int32) (item *Item, ok bool) {
for _, i := range ItemsForVersion(version) {
if i.Id == id {
item = i
ok = true
return
}
}
return
}
func LookupItemByName(version string, name string) (item *Item, ok bool) {
for _, i := range ItemsForVersion(version) {
if i.Name == name {
item = i
ok = true
}
}
return
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,247 @@
[
{
"name": "armor",
"resource": "minecraft:armor",
"min": 0.0,
"max": 30.0,
"default": 0.0
},
{
"name": "armorToughness",
"resource": "minecraft:armor_toughness",
"min": 0.0,
"max": 20.0,
"default": 0.0
},
{
"name": "attackDamage",
"resource": "minecraft:attack_damage",
"min": 0.0,
"max": 2048.0,
"default": 2.0
},
{
"name": "attackKnockback",
"resource": "minecraft:attack_knockback",
"min": 0.0,
"max": 5.0,
"default": 0.0
},
{
"name": "attackSpeed",
"resource": "minecraft:attack_speed",
"min": 0.0,
"max": 1024.0,
"default": 4.0
},
{
"name": "blockBreakSpeed",
"resource": "minecraft:block_break_speed",
"min": 0.0,
"max": 1024.0,
"default": 1.0
},
{
"name": "blockInteractionRange",
"resource": "minecraft:block_interaction_range",
"min": 0.0,
"max": 64.0,
"default": 4.5
},
{
"name": "burningTime",
"resource": "minecraft:burning_time",
"min": 0.0,
"max": 1024.0,
"default": 1.0
},
{
"name": "cameraDistance",
"resource": "minecraft:camera_distance",
"min": 0.0,
"max": 32.0,
"default": 4.0
},
{
"name": "explosionKnockbackResistance",
"resource": "minecraft:explosion_knockback_resistance",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "entityInteractionRange",
"resource": "minecraft:entity_interaction_range",
"min": 0.0,
"max": 64.0,
"default": 3.0
},
{
"name": "fallDamageMultiplier",
"resource": "minecraft:fall_damage_multiplier",
"min": 0.0,
"max": 100.0,
"default": 1.0
},
{
"name": "flyingSpeed",
"resource": "minecraft:flying_speed",
"min": 0.0,
"max": 1024.0,
"default": 0.4
},
{
"name": "followRange",
"resource": "minecraft:follow_range",
"min": 0.0,
"max": 2048.0,
"default": 32.0
},
{
"name": "gravity",
"resource": "minecraft:gravity",
"min": -1.0,
"max": 1.0,
"default": 0.08
},
{
"name": "jumpStrength",
"resource": "minecraft:jump_strength",
"min": 0.0,
"max": 32.0,
"default": 0.41999998688697815
},
{
"name": "knockbackResistance",
"resource": "minecraft:knockback_resistance",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "luck",
"resource": "minecraft:luck",
"min": -1024.0,
"max": 1024.0,
"default": 0.0
},
{
"name": "maxAbsorption",
"resource": "minecraft:max_absorption",
"min": 0.0,
"max": 2048.0,
"default": 0.0
},
{
"name": "maxHealth",
"resource": "minecraft:max_health",
"min": 1.0,
"max": 1024.0,
"default": 20.0
},
{
"name": "miningEfficiency",
"resource": "minecraft:mining_efficiency",
"min": 0.0,
"max": 1024.0,
"default": 0.0
},
{
"name": "movementEfficiency",
"resource": "minecraft:movement_efficiency",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "movementSpeed",
"resource": "minecraft:movement_speed",
"min": 0.0,
"max": 1024.0,
"default": 0.7
},
{
"name": "oxygenBonus",
"resource": "minecraft:oxygen_bonus",
"min": 0.0,
"max": 1024.0,
"default": 0.0
},
{
"name": "safeFallDistance",
"resource": "minecraft:safe_fall_distance",
"min": -1024.0,
"max": 1024.0,
"default": 3.0
},
{
"name": "scale",
"resource": "minecraft:scale",
"min": 0.0625,
"max": 16.0,
"default": 1.0
},
{
"name": "sneakingSpeed",
"resource": "minecraft:sneaking_speed",
"min": 0.0,
"max": 1.0,
"default": 0.3
},
{
"name": "spawnReinforcements",
"resource": "minecraft:spawn_reinforcements",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "stepHeight",
"resource": "minecraft:step_height",
"min": 0.0,
"max": 10.0,
"default": 0.6
},
{
"name": "submergedMiningSpeed",
"resource": "minecraft:submerged_mining_speed",
"min": 0.0,
"max": 20.0,
"default": 0.2
},
{
"name": "sweepingDamageRatio",
"resource": "minecraft:sweeping_damage_ratio",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "temptRange",
"resource": "minecraft:tempt_range",
"min": 0.0,
"max": 2048.0,
"default": 10.0
},
{
"name": "waterMovementEfficiency",
"resource": "minecraft:water_movement_efficiency",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "waypointTransmitRange",
"resource": "minecraft:waypoint_transmit_range",
"min": 0.0,
"max": 6.0E7,
"default": 0.0
},
{
"name": "waypointReceiveRange",
"resource": "minecraft:waypoint_receive_range",
"min": 0.0,
"max": 6.0E7,
"default": 0.0
}
]
@@ -0,0 +1,652 @@
[
{
"id": 0,
"name": "badlands",
"category": "mesa",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Badlands",
"color": 7254527
},
{
"id": 1,
"name": "bamboo_jungle",
"category": "jungle",
"temperature": 0.95,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Bamboo Jungle",
"color": 7842047
},
{
"id": 2,
"name": "basalt_deltas",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Basalt Deltas",
"color": 0
},
{
"id": 3,
"name": "beach",
"category": "beach",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Beach",
"color": 7907327
},
{
"id": 4,
"name": "birch_forest",
"category": "forest",
"temperature": 0.6,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Birch Forest",
"color": 8037887
},
{
"id": 5,
"name": "cherry_grove",
"category": "forest",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Cherry Grove",
"color": 8103167
},
{
"id": 6,
"name": "cold_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Cold Ocean",
"color": 8103167
},
{
"id": 7,
"name": "crimson_forest",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Crimson Forest",
"color": 0
},
{
"id": 8,
"name": "dark_forest",
"category": "forest",
"temperature": 0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Dark Forest",
"color": 7972607
},
{
"id": 9,
"name": "deep_cold_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Cold Ocean",
"color": 8103167
},
{
"id": 10,
"name": "deep_dark",
"category": "underground",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Dark",
"color": 7907327
},
{
"id": 11,
"name": "deep_frozen_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Frozen Ocean",
"color": 8103167
},
{
"id": 12,
"name": "deep_lukewarm_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Lukewarm Ocean",
"color": 8103167
},
{
"id": 13,
"name": "deep_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Ocean",
"color": 8103167
},
{
"id": 14,
"name": "desert",
"category": "desert",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Desert",
"color": 7254527
},
{
"id": 15,
"name": "dripstone_caves",
"category": "underground",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Dripstone Caves",
"color": 7907327
},
{
"id": 16,
"name": "end_barrens",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "End Barrens",
"color": 0
},
{
"id": 17,
"name": "end_highlands",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "End Highlands",
"color": 0
},
{
"id": 18,
"name": "end_midlands",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "End Midlands",
"color": 0
},
{
"id": 19,
"name": "eroded_badlands",
"category": "mesa",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Eroded Badlands",
"color": 7254527
},
{
"id": 20,
"name": "flower_forest",
"category": "forest",
"temperature": 0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Flower Forest",
"color": 7972607
},
{
"id": 21,
"name": "forest",
"category": "forest",
"temperature": 0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Forest",
"color": 7972607
},
{
"id": 22,
"name": "frozen_ocean",
"category": "ocean",
"temperature": 0.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Frozen Ocean",
"color": 8364543
},
{
"id": 23,
"name": "frozen_peaks",
"category": "ice",
"temperature": -0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Frozen Peaks",
"color": 8756735
},
{
"id": 24,
"name": "frozen_river",
"category": "ice",
"temperature": 0.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Frozen River",
"color": 8364543
},
{
"id": 25,
"name": "grove",
"category": "forest",
"temperature": -0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Grove",
"color": 8495359
},
{
"id": 26,
"name": "ice_spikes",
"category": "ice",
"temperature": 0.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Ice Spikes",
"color": 8364543
},
{
"id": 27,
"name": "jagged_peaks",
"category": "mountain",
"temperature": -0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Jagged Peaks",
"color": 8756735
},
{
"id": 28,
"name": "jungle",
"category": "jungle",
"temperature": 0.95,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Jungle",
"color": 7842047
},
{
"id": 29,
"name": "lukewarm_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Lukewarm Ocean",
"color": 8103167
},
{
"id": 30,
"name": "lush_caves",
"category": "underground",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Lush Caves",
"color": 8103167
},
{
"id": 31,
"name": "mangrove_swamp",
"category": "forest",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Mangrove Swamp",
"color": 7907327
},
{
"id": 32,
"name": "meadow",
"category": "mountain",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Meadow",
"color": 8103167
},
{
"id": 33,
"name": "mushroom_fields",
"category": "mushroom",
"temperature": 0.9,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Mushroom Fields",
"color": 7842047
},
{
"id": 34,
"name": "nether_wastes",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Nether Wastes",
"color": 0
},
{
"id": 35,
"name": "ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Ocean",
"color": 8103167
},
{
"id": 36,
"name": "old_growth_birch_forest",
"category": "forest",
"temperature": 0.6,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Old Growth Birch Forest",
"color": 8037887
},
{
"id": 37,
"name": "old_growth_pine_taiga",
"category": "taiga",
"temperature": 0.3,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Old Growth Pine Taiga",
"color": 8168447
},
{
"id": 38,
"name": "old_growth_spruce_taiga",
"category": "taiga",
"temperature": 0.25,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Old Growth Spruce Taiga",
"color": 8233983
},
{
"id": 39,
"name": "pale_garden",
"category": "none",
"temperature": 0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Pale Garden",
"color": 12171705
},
{
"id": 40,
"name": "plains",
"category": "plains",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Plains",
"color": 7907327
},
{
"id": 41,
"name": "river",
"category": "river",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "River",
"color": 8103167
},
{
"id": 42,
"name": "savanna",
"category": "savanna",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Savanna",
"color": 7254527
},
{
"id": 43,
"name": "savanna_plateau",
"category": "savanna",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Savanna Plateau",
"color": 7254527
},
{
"id": 44,
"name": "small_end_islands",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "Small End Islands",
"color": 0
},
{
"id": 45,
"name": "snowy_beach",
"category": "beach",
"temperature": 0.05,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Snowy Beach",
"color": 8364543
},
{
"id": 46,
"name": "snowy_plains",
"category": "plains",
"temperature": 0.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Snowy Plains",
"color": 8364543
},
{
"id": 47,
"name": "snowy_slopes",
"category": "mountain",
"temperature": -0.3,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Snowy Slopes",
"color": 8560639
},
{
"id": 48,
"name": "snowy_taiga",
"category": "taiga",
"temperature": -0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Snowy Taiga",
"color": 8625919
},
{
"id": 49,
"name": "soul_sand_valley",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Soul Sand Valley",
"color": 0
},
{
"id": 50,
"name": "sparse_jungle",
"category": "jungle",
"temperature": 0.95,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Sparse Jungle",
"color": 7842047
},
{
"id": 51,
"name": "stony_peaks",
"category": "mountain",
"temperature": 1.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Stony Peaks",
"color": 7776511
},
{
"id": 52,
"name": "stony_shore",
"category": "beach",
"temperature": 0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Stony Shore",
"color": 8233727
},
{
"id": 53,
"name": "sunflower_plains",
"category": "plains",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Sunflower Plains",
"color": 7907327
},
{
"id": 54,
"name": "swamp",
"category": "swamp",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Swamp",
"color": 7907327
},
{
"id": 55,
"name": "taiga",
"category": "taiga",
"temperature": 0.25,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Taiga",
"color": 8233983
},
{
"id": 56,
"name": "the_end",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "The End",
"color": 0
},
{
"id": 57,
"name": "the_void",
"category": "none",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "The Void",
"color": 8103167
},
{
"id": 58,
"name": "warm_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Warm Ocean",
"color": 8103167
},
{
"id": 59,
"name": "warped_forest",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Warped Forest",
"color": 0
},
{
"id": 60,
"name": "windswept_forest",
"category": "forest",
"temperature": 0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Windswept Forest",
"color": 8233727
},
{
"id": 61,
"name": "windswept_gravelly_hills",
"category": "extreme_hills",
"temperature": 0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Windswept Gravelly Hills",
"color": 8233727
},
{
"id": 62,
"name": "windswept_hills",
"category": "extreme_hills",
"temperature": 0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Windswept Hills",
"color": 8233727
},
{
"id": 63,
"name": "windswept_savanna",
"category": "savanna",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Windswept Savanna",
"color": 7254527
},
{
"id": 64,
"name": "wooded_badlands",
"category": "mesa",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Wooded Badlands",
"color": 7254527
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,242 @@
[
{
"id": 0,
"name": "Speed",
"displayName": "Speed",
"type": "good"
},
{
"id": 1,
"name": "Slowness",
"displayName": "Slowness",
"type": "bad"
},
{
"id": 2,
"name": "Haste",
"displayName": "Haste",
"type": "good"
},
{
"id": 3,
"name": "MiningFatigue",
"displayName": "Mining Fatigue",
"type": "bad"
},
{
"id": 4,
"name": "Strength",
"displayName": "Strength",
"type": "good"
},
{
"id": 5,
"name": "InstantHealth",
"displayName": "Instant Health",
"type": "good"
},
{
"id": 6,
"name": "InstantDamage",
"displayName": "Instant Damage",
"type": "bad"
},
{
"id": 7,
"name": "JumpBoost",
"displayName": "Jump Boost",
"type": "good"
},
{
"id": 8,
"name": "Nausea",
"displayName": "Nausea",
"type": "bad"
},
{
"id": 9,
"name": "Regeneration",
"displayName": "Regeneration",
"type": "good"
},
{
"id": 10,
"name": "Resistance",
"displayName": "Resistance",
"type": "good"
},
{
"id": 11,
"name": "FireResistance",
"displayName": "Fire Resistance",
"type": "good"
},
{
"id": 12,
"name": "WaterBreathing",
"displayName": "Water Breathing",
"type": "good"
},
{
"id": 13,
"name": "Invisibility",
"displayName": "Invisibility",
"type": "good"
},
{
"id": 14,
"name": "Blindness",
"displayName": "Blindness",
"type": "bad"
},
{
"id": 15,
"name": "NightVision",
"displayName": "Night Vision",
"type": "good"
},
{
"id": 16,
"name": "Hunger",
"displayName": "Hunger",
"type": "bad"
},
{
"id": 17,
"name": "Weakness",
"displayName": "Weakness",
"type": "bad"
},
{
"id": 18,
"name": "Poison",
"displayName": "Poison",
"type": "bad"
},
{
"id": 19,
"name": "Wither",
"displayName": "Wither",
"type": "bad"
},
{
"id": 20,
"name": "HealthBoost",
"displayName": "Health Boost",
"type": "good"
},
{
"id": 21,
"name": "Absorption",
"displayName": "Absorption",
"type": "good"
},
{
"id": 22,
"name": "Saturation",
"displayName": "Saturation",
"type": "good"
},
{
"id": 23,
"name": "Glowing",
"displayName": "Glowing",
"type": "bad"
},
{
"id": 24,
"name": "Levitation",
"displayName": "Levitation",
"type": "bad"
},
{
"id": 25,
"name": "Luck",
"displayName": "Luck",
"type": "good"
},
{
"id": 26,
"name": "BadLuck",
"displayName": "Bad Luck",
"type": "bad"
},
{
"id": 27,
"name": "SlowFalling",
"displayName": "Slow Falling",
"type": "good"
},
{
"id": 28,
"name": "ConduitPower",
"displayName": "Conduit Power",
"type": "good"
},
{
"id": 29,
"name": "DolphinsGrace",
"displayName": "Dolphin's Grace",
"type": "good"
},
{
"id": 30,
"name": "BadOmen",
"displayName": "Bad Omen",
"type": "bad"
},
{
"id": 31,
"name": "HeroOfTheVillage",
"displayName": "Hero of the Village",
"type": "good"
},
{
"id": 32,
"name": "Darkness",
"displayName": "Darkness",
"type": "bad"
},
{
"id": 33,
"name": "TrialOmen",
"displayName": "Trial Omen",
"type": "bad"
},
{
"id": 34,
"name": "RaidOmen",
"displayName": "Raid Omen",
"type": "bad"
},
{
"id": 35,
"name": "WindCharged",
"displayName": "Wind Charged",
"type": "bad"
},
{
"id": 36,
"name": "Weaving",
"displayName": "Weaving",
"type": "bad"
},
{
"id": 37,
"name": "Oozing",
"displayName": "Oozing",
"type": "bad"
},
{
"id": 38,
"name": "Infested",
"displayName": "Infested",
"type": "bad"
},
{
"id": 39,
"name": "BreathOfTheNautilus",
"displayName": "Breath of the Nautilus",
"type": "good"
}
]
@@ -0,0 +1,980 @@
[
{
"id": 0,
"name": "aqua_affinity",
"displayName": "Aqua Affinity",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "head_armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 1,
"name": "bane_of_arthropods",
"displayName": "Bane of Arthropods",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"breach",
"density",
"impaling",
"sharpness",
"smite"
],
"category": "weapon",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 2,
"name": "binding_curse",
"displayName": "Curse of Binding",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": true,
"curse": true,
"exclude": [],
"category": "equippable",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 3,
"name": "blast_protection",
"displayName": "Blast Protection",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"fire_protection",
"projectile_protection",
"protection"
],
"category": "armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 4,
"name": "breach",
"displayName": "Breach",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"density",
"impaling",
"sharpness",
"smite"
],
"category": "mace",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 5,
"name": "channeling",
"displayName": "Channeling",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"riptide"
],
"category": "trident",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 6,
"name": "density",
"displayName": "Density",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"breach",
"impaling",
"sharpness",
"smite"
],
"category": "mace",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 7,
"name": "depth_strider",
"displayName": "Depth Strider",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"frost_walker"
],
"category": "foot_armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 8,
"name": "efficiency",
"displayName": "Efficiency",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "mining",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 9,
"name": "feather_falling",
"displayName": "Feather Falling",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "foot_armor",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 10,
"name": "fire_aspect",
"displayName": "Fire Aspect",
"maxLevel": 2,
"minCost": {
"a": 1,
"b": 1
},
"maxCost": {
"a": 1,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "fire_aspect",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 11,
"name": "fire_protection",
"displayName": "Fire Protection",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"blast_protection",
"projectile_protection",
"protection"
],
"category": "armor",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 12,
"name": "flame",
"displayName": "Flame",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "bow",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 13,
"name": "fortune",
"displayName": "Fortune",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"silk_touch"
],
"category": "mining_loot",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 14,
"name": "frost_walker",
"displayName": "Frost Walker",
"maxLevel": 2,
"minCost": {
"a": 1,
"b": 1
},
"maxCost": {
"a": 1,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [
"depth_strider"
],
"category": "foot_armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 15,
"name": "impaling",
"displayName": "Impaling",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"breach",
"density",
"sharpness",
"smite"
],
"category": "trident",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 16,
"name": "infinity",
"displayName": "Infinity",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"mending"
],
"category": "bow",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 17,
"name": "knockback",
"displayName": "Knockback",
"maxLevel": 2,
"minCost": {
"a": 1,
"b": 1
},
"maxCost": {
"a": 1,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "melee_weapon",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 18,
"name": "looting",
"displayName": "Looting",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "melee_weapon",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 19,
"name": "loyalty",
"displayName": "Loyalty",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"riptide"
],
"category": "trident",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 20,
"name": "luck_of_the_sea",
"displayName": "Luck of the Sea",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "fishing",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 21,
"name": "lunge",
"displayName": "Lunge",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "lunge",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 22,
"name": "lure",
"displayName": "Lure",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "fishing",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 23,
"name": "mending",
"displayName": "Mending",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [
"infinity"
],
"category": "durability",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 24,
"name": "multishot",
"displayName": "Multishot",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"piercing"
],
"category": "crossbow",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 25,
"name": "piercing",
"displayName": "Piercing",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"multishot"
],
"category": "crossbow",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 26,
"name": "power",
"displayName": "Power",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "bow",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 27,
"name": "projectile_protection",
"displayName": "Projectile Protection",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"blast_protection",
"fire_protection",
"protection"
],
"category": "armor",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 28,
"name": "protection",
"displayName": "Protection",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"blast_protection",
"fire_protection",
"projectile_protection"
],
"category": "armor",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 29,
"name": "punch",
"displayName": "Punch",
"maxLevel": 2,
"minCost": {
"a": 1,
"b": 1
},
"maxCost": {
"a": 1,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "bow",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 30,
"name": "quick_charge",
"displayName": "Quick Charge",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "crossbow",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 31,
"name": "respiration",
"displayName": "Respiration",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "head_armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 32,
"name": "riptide",
"displayName": "Riptide",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"channeling",
"loyalty"
],
"category": "trident",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 33,
"name": "sharpness",
"displayName": "Sharpness",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"breach",
"density",
"impaling",
"smite"
],
"category": "sharp_weapon",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 34,
"name": "silk_touch",
"displayName": "Silk Touch",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"fortune"
],
"category": "mining_loot",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 35,
"name": "smite",
"displayName": "Smite",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"breach",
"density",
"impaling",
"sharpness"
],
"category": "weapon",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 36,
"name": "soul_speed",
"displayName": "Soul Speed",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [],
"category": "foot_armor",
"weight": 1,
"tradeable": false,
"discoverable": false
},
{
"id": 37,
"name": "sweeping_edge",
"displayName": "Sweeping Edge",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "sweeping",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 38,
"name": "swift_sneak",
"displayName": "Swift Sneak",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [],
"category": "leg_armor",
"weight": 1,
"tradeable": false,
"discoverable": false
},
{
"id": 39,
"name": "thorns",
"displayName": "Thorns",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "armor",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 40,
"name": "unbreaking",
"displayName": "Unbreaking",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "durability",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 41,
"name": "vanishing_curse",
"displayName": "Curse of Vanishing",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": true,
"curse": true,
"exclude": [],
"category": "vanishing",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 42,
"name": "wind_burst",
"displayName": "Wind Burst",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [],
"category": "mace",
"weight": 2,
"tradeable": false,
"discoverable": false
}
]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,442 @@
[
{
"id": 893,
"name": "apple",
"stackSize": 64,
"displayName": "Apple",
"foodPoints": 4.0,
"saturation": 19.2,
"effectiveQuality": 23.2,
"saturationRatio": 4.8
},
{
"id": 947,
"name": "mushroom_stew",
"stackSize": 1,
"displayName": "Mushroom Stew",
"foodPoints": 6.0,
"saturation": 86.4,
"effectiveQuality": 92.4,
"saturationRatio": 14.400001
},
{
"id": 953,
"name": "bread",
"stackSize": 64,
"displayName": "Bread",
"foodPoints": 5.0,
"saturation": 60.0,
"effectiveQuality": 65.0,
"saturationRatio": 12.0
},
{
"id": 983,
"name": "porkchop",
"stackSize": 64,
"displayName": "Raw Porkchop",
"foodPoints": 3.0,
"saturation": 10.8,
"effectiveQuality": 13.8,
"saturationRatio": 3.6000001
},
{
"id": 984,
"name": "cooked_porkchop",
"stackSize": 64,
"displayName": "Cooked Porkchop",
"foodPoints": 8.0,
"saturation": 204.8,
"effectiveQuality": 212.8,
"saturationRatio": 25.6
},
{
"id": 986,
"name": "golden_apple",
"stackSize": 64,
"displayName": "Golden Apple",
"foodPoints": 4.0,
"saturation": 76.8,
"effectiveQuality": 80.8,
"saturationRatio": 19.2
},
{
"id": 987,
"name": "enchanted_golden_apple",
"stackSize": 64,
"displayName": "Enchanted Golden Apple",
"foodPoints": 4.0,
"saturation": 76.8,
"effectiveQuality": 80.8,
"saturationRatio": 19.2
},
{
"id": 1019,
"name": "pufferfish_bucket",
"stackSize": 1,
"displayName": "Bucket of Pufferfish",
"foodPoints": 1.0,
"saturation": 0.4,
"effectiveQuality": 1.4,
"saturationRatio": 0.4
},
{
"id": 1020,
"name": "salmon_bucket",
"stackSize": 1,
"displayName": "Bucket of Salmon",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1021,
"name": "cod_bucket",
"stackSize": 1,
"displayName": "Bucket of Cod",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1022,
"name": "tropical_fish_bucket",
"stackSize": 1,
"displayName": "Bucket of Tropical Fish",
"foodPoints": 1.0,
"saturation": 0.4,
"effectiveQuality": 1.4,
"saturationRatio": 0.4
},
{
"id": 1057,
"name": "cod",
"stackSize": 64,
"displayName": "Raw Cod",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1058,
"name": "salmon",
"stackSize": 64,
"displayName": "Raw Salmon",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1059,
"name": "tropical_fish",
"stackSize": 64,
"displayName": "Tropical Fish",
"foodPoints": 1.0,
"saturation": 0.4,
"effectiveQuality": 1.4,
"saturationRatio": 0.4
},
{
"id": 1060,
"name": "pufferfish",
"stackSize": 64,
"displayName": "Pufferfish",
"foodPoints": 1.0,
"saturation": 0.4,
"effectiveQuality": 1.4,
"saturationRatio": 0.4
},
{
"id": 1061,
"name": "cooked_cod",
"stackSize": 64,
"displayName": "Cooked Cod",
"foodPoints": 5.0,
"saturation": 60.0,
"effectiveQuality": 65.0,
"saturationRatio": 12.0
},
{
"id": 1062,
"name": "cooked_salmon",
"stackSize": 64,
"displayName": "Cooked Salmon",
"foodPoints": 6.0,
"saturation": 115.200005,
"effectiveQuality": 121.200005,
"saturationRatio": 19.2
},
{
"id": 1102,
"name": "cookie",
"stackSize": 64,
"displayName": "Cookie",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1106,
"name": "melon_slice",
"stackSize": 64,
"displayName": "Melon Slice",
"foodPoints": 2.0,
"saturation": 4.8,
"effectiveQuality": 6.8,
"saturationRatio": 2.4
},
{
"id": 1107,
"name": "dried_kelp",
"stackSize": 64,
"displayName": "Dried Kelp",
"foodPoints": 1.0,
"saturation": 1.2,
"effectiveQuality": 2.2,
"saturationRatio": 1.2
},
{
"id": 1110,
"name": "beef",
"stackSize": 64,
"displayName": "Raw Beef",
"foodPoints": 3.0,
"saturation": 10.8,
"effectiveQuality": 13.8,
"saturationRatio": 3.6000001
},
{
"id": 1111,
"name": "cooked_beef",
"stackSize": 64,
"displayName": "Steak",
"foodPoints": 8.0,
"saturation": 204.8,
"effectiveQuality": 212.8,
"saturationRatio": 25.6
},
{
"id": 1112,
"name": "chicken",
"stackSize": 64,
"displayName": "Raw Chicken",
"foodPoints": 2.0,
"saturation": 4.8,
"effectiveQuality": 6.8,
"saturationRatio": 2.4
},
{
"id": 1113,
"name": "cooked_chicken",
"stackSize": 64,
"displayName": "Cooked Chicken",
"foodPoints": 6.0,
"saturation": 86.4,
"effectiveQuality": 92.4,
"saturationRatio": 14.400001
},
{
"id": 1114,
"name": "rotten_flesh",
"stackSize": 64,
"displayName": "Rotten Flesh",
"foodPoints": 4.0,
"saturation": 6.4,
"effectiveQuality": 10.4,
"saturationRatio": 1.6
},
{
"id": 1122,
"name": "spider_eye",
"stackSize": 64,
"displayName": "Spider Eye",
"foodPoints": 2.0,
"saturation": 12.8,
"effectiveQuality": 14.8,
"saturationRatio": 6.4
},
{
"id": 1227,
"name": "carrot",
"stackSize": 64,
"displayName": "Carrot",
"foodPoints": 3.0,
"saturation": 21.6,
"effectiveQuality": 24.6,
"saturationRatio": 7.2000003
},
{
"id": 1228,
"name": "potato",
"stackSize": 64,
"displayName": "Potato",
"foodPoints": 1.0,
"saturation": 1.2,
"effectiveQuality": 2.2,
"saturationRatio": 1.2
},
{
"id": 1229,
"name": "baked_potato",
"stackSize": 64,
"displayName": "Baked Potato",
"foodPoints": 5.0,
"saturation": 60.0,
"effectiveQuality": 65.0,
"saturationRatio": 12.0
},
{
"id": 1230,
"name": "poisonous_potato",
"stackSize": 64,
"displayName": "Poisonous Potato",
"foodPoints": 2.0,
"saturation": 4.8,
"effectiveQuality": 6.8,
"saturationRatio": 2.4
},
{
"id": 1232,
"name": "golden_carrot",
"stackSize": 64,
"displayName": "Golden Carrot",
"foodPoints": 6.0,
"saturation": 172.8,
"effectiveQuality": 178.8,
"saturationRatio": 28.800001
},
{
"id": 1241,
"name": "pumpkin_pie",
"stackSize": 64,
"displayName": "Pumpkin Pie",
"foodPoints": 8.0,
"saturation": 76.8,
"effectiveQuality": 84.8,
"saturationRatio": 9.6
},
{
"id": 1249,
"name": "rabbit",
"stackSize": 64,
"displayName": "Raw Rabbit",
"foodPoints": 3.0,
"saturation": 10.8,
"effectiveQuality": 13.8,
"saturationRatio": 3.6000001
},
{
"id": 1250,
"name": "cooked_rabbit",
"stackSize": 64,
"displayName": "Cooked Rabbit",
"foodPoints": 5.0,
"saturation": 60.0,
"effectiveQuality": 65.0,
"saturationRatio": 12.0
},
{
"id": 1251,
"name": "rabbit_stew",
"stackSize": 1,
"displayName": "Rabbit Stew",
"foodPoints": 10.0,
"saturation": 240.0,
"effectiveQuality": 250.0,
"saturationRatio": 24.0
},
{
"id": 1264,
"name": "mutton",
"stackSize": 64,
"displayName": "Raw Mutton",
"foodPoints": 2.0,
"saturation": 4.8,
"effectiveQuality": 6.8,
"saturationRatio": 2.4
},
{
"id": 1265,
"name": "cooked_mutton",
"stackSize": 64,
"displayName": "Cooked Mutton",
"foodPoints": 6.0,
"saturation": 115.200005,
"effectiveQuality": 121.200005,
"saturationRatio": 19.2
},
{
"id": 1283,
"name": "chorus_fruit",
"stackSize": 64,
"displayName": "Chorus Fruit",
"foodPoints": 4.0,
"saturation": 19.2,
"effectiveQuality": 23.2,
"saturationRatio": 4.8
},
{
"id": 1287,
"name": "beetroot",
"stackSize": 64,
"displayName": "Beetroot",
"foodPoints": 1.0,
"saturation": 2.4,
"effectiveQuality": 3.4,
"saturationRatio": 2.4
},
{
"id": 1289,
"name": "beetroot_soup",
"stackSize": 1,
"displayName": "Beetroot Soup",
"foodPoints": 6.0,
"saturation": 86.4,
"effectiveQuality": 92.4,
"saturationRatio": 14.400001
},
{
"id": 1340,
"name": "suspicious_stew",
"stackSize": 1,
"displayName": "Suspicious Stew",
"foodPoints": 6.0,
"saturation": 86.4,
"effectiveQuality": 92.4,
"saturationRatio": 14.400001
},
{
"id": 1373,
"name": "sweet_berries",
"stackSize": 64,
"displayName": "Sweet Berries",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1374,
"name": "glow_berries",
"stackSize": 64,
"displayName": "Glow Berries",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1381,
"name": "honey_bottle",
"stackSize": 16,
"displayName": "Honey Bottle",
"foodPoints": 6.0,
"saturation": 14.400001,
"effectiveQuality": 20.400002,
"saturationRatio": 2.4
}
]
@@ -0,0 +1,94 @@
[
{
"id": 0,
"name": "harp"
},
{
"id": 1,
"name": "basedrum"
},
{
"id": 2,
"name": "snare"
},
{
"id": 3,
"name": "hat"
},
{
"id": 4,
"name": "bass"
},
{
"id": 5,
"name": "flute"
},
{
"id": 6,
"name": "bell"
},
{
"id": 7,
"name": "guitar"
},
{
"id": 8,
"name": "chime"
},
{
"id": 9,
"name": "xylophone"
},
{
"id": 10,
"name": "iron_xylophone"
},
{
"id": 11,
"name": "cow_bell"
},
{
"id": 12,
"name": "didgeridoo"
},
{
"id": 13,
"name": "bit"
},
{
"id": 14,
"name": "banjo"
},
{
"id": 15,
"name": "pling"
},
{
"id": 16,
"name": "zombie"
},
{
"id": 17,
"name": "skeleton"
},
{
"id": 18,
"name": "creeper"
},
{
"id": 19,
"name": "dragon"
},
{
"id": 20,
"name": "wither_skeleton"
},
{
"id": 21,
"name": "piglin"
},
{
"id": 22,
"name": "custom_head"
}
]
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,233 @@
{
"default": {},
"leaves": {
"1105": 1.0,
"911": 1.5,
"916": 1.5,
"921": 1.5,
"926": 1.5,
"931": 1.5,
"936": 1.5,
"941": 1.5
},
"coweb": {
"1105": 15.0,
"911": 15.0,
"916": 15.0,
"921": 15.0,
"926": 15.0,
"931": 15.0,
"936": 15.0,
"941": 15.0
},
"plant": {
"911": 1.5,
"916": 1.5,
"921": 1.5,
"926": 1.5,
"931": 1.5,
"936": 1.5,
"941": 1.5
},
"gourd": {
"911": 1.5,
"916": 1.5,
"921": 1.5,
"926": 1.5,
"931": 1.5,
"936": 1.5,
"941": 1.5
},
"vine_or_glow_lichen": {
"1105": 2.0
},
"wool": {
"1105": 1.0
},
"sword_instantly_mines": {
"911": 2.0,
"916": 1.0,
"921": 4.0,
"926": 12.0,
"931": 6.0,
"936": 8.0,
"941": 9.0
},
"sword_efficient": {
"911": 2.0,
"916": 1.0,
"921": 4.0,
"926": 12.0,
"931": 6.0,
"936": 8.0,
"941": 9.0
},
"incorrect_for_wooden_tool": {
"912": 2.0,
"913": 2.0,
"914": 2.0,
"915": 2.0
},
"mineable/shovel": {
"912": 2.0,
"917": 1.0,
"922": 4.0,
"927": 12.0,
"932": 6.0,
"937": 8.0,
"942": 9.0
},
"mineable/pickaxe": {
"913": 2.0,
"918": 1.0,
"923": 4.0,
"928": 12.0,
"933": 6.0,
"938": 8.0,
"943": 9.0
},
"mineable/axe": {
"914": 2.0,
"919": 1.0,
"924": 4.0,
"929": 12.0,
"934": 6.0,
"939": 8.0,
"944": 9.0
},
"mineable/hoe": {
"915": 2.0,
"920": 1.0,
"925": 4.0,
"930": 12.0,
"935": 6.0,
"940": 8.0,
"945": 9.0
},
"incorrect_for_copper_tool": {
"917": 1.0,
"918": 1.0,
"919": 1.0,
"920": 1.0
},
"incorrect_for_stone_tool": {
"922": 4.0,
"923": 4.0,
"924": 4.0,
"925": 4.0
},
"incorrect_for_gold_tool": {
"927": 12.0,
"928": 12.0,
"929": 12.0,
"930": 12.0
},
"incorrect_for_iron_tool": {
"932": 6.0,
"933": 6.0,
"934": 6.0,
"935": 6.0
},
"incorrect_for_diamond_tool": {
"937": 8.0,
"938": 8.0,
"939": 8.0,
"940": 8.0
},
"incorrect_for_netherite_tool": {
"942": 9.0,
"943": 9.0,
"944": 9.0,
"945": 9.0
},
"plant;mineable/axe": {
"911": 1.5,
"916": 1.5,
"921": 1.5,
"926": 1.5,
"931": 1.5,
"936": 1.5,
"941": 1.5,
"914": 2.0,
"919": 1.0,
"924": 4.0,
"929": 12.0,
"934": 6.0,
"939": 8.0,
"944": 9.0
},
"gourd;mineable/axe": {
"911": 1.5,
"916": 1.5,
"921": 1.5,
"926": 1.5,
"931": 1.5,
"936": 1.5,
"941": 1.5,
"914": 2.0,
"919": 1.0,
"924": 4.0,
"929": 12.0,
"934": 6.0,
"939": 8.0,
"944": 9.0
},
"leaves;mineable/hoe": {
"1105": 1.0,
"911": 1.5,
"916": 1.5,
"921": 1.5,
"926": 1.5,
"931": 1.5,
"936": 1.5,
"941": 1.5,
"915": 2.0,
"920": 1.0,
"925": 4.0,
"930": 12.0,
"935": 6.0,
"940": 8.0,
"945": 9.0
},
"leaves;mineable/axe;mineable/hoe": {
"1105": 1.0,
"911": 1.5,
"916": 1.5,
"921": 1.5,
"926": 1.5,
"931": 1.5,
"936": 1.5,
"941": 1.5,
"914": 2.0,
"919": 1.0,
"924": 4.0,
"929": 12.0,
"934": 6.0,
"939": 8.0,
"944": 9.0,
"915": 2.0,
"920": 1.0,
"925": 4.0,
"930": 12.0,
"935": 6.0,
"940": 8.0,
"945": 9.0
},
"vine_or_glow_lichen;plant;mineable/axe": {
"1105": 2.0,
"911": 1.5,
"916": 1.5,
"921": 1.5,
"926": 1.5,
"931": 1.5,
"936": 1.5,
"941": 1.5,
"914": 2.0,
"919": 1.0,
"924": 4.0,
"929": 12.0,
"934": 6.0,
"939": 8.0,
"944": 9.0
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,462 @@
[
{
"id": 0,
"name": "angry_villager"
},
{
"id": 1,
"name": "block"
},
{
"id": 2,
"name": "block_marker"
},
{
"id": 3,
"name": "bubble"
},
{
"id": 4,
"name": "cloud"
},
{
"id": 5,
"name": "copper_fire_flame"
},
{
"id": 6,
"name": "crit"
},
{
"id": 7,
"name": "damage_indicator"
},
{
"id": 8,
"name": "dragon_breath"
},
{
"id": 9,
"name": "dripping_lava"
},
{
"id": 10,
"name": "falling_lava"
},
{
"id": 11,
"name": "landing_lava"
},
{
"id": 12,
"name": "dripping_water"
},
{
"id": 13,
"name": "falling_water"
},
{
"id": 14,
"name": "dust"
},
{
"id": 15,
"name": "dust_color_transition"
},
{
"id": 16,
"name": "effect"
},
{
"id": 17,
"name": "elder_guardian"
},
{
"id": 18,
"name": "enchanted_hit"
},
{
"id": 19,
"name": "enchant"
},
{
"id": 20,
"name": "end_rod"
},
{
"id": 21,
"name": "entity_effect"
},
{
"id": 22,
"name": "explosion_emitter"
},
{
"id": 23,
"name": "explosion"
},
{
"id": 24,
"name": "gust"
},
{
"id": 25,
"name": "small_gust"
},
{
"id": 26,
"name": "gust_emitter_large"
},
{
"id": 27,
"name": "gust_emitter_small"
},
{
"id": 28,
"name": "sonic_boom"
},
{
"id": 29,
"name": "falling_dust"
},
{
"id": 30,
"name": "firework"
},
{
"id": 31,
"name": "fishing"
},
{
"id": 32,
"name": "flame"
},
{
"id": 33,
"name": "infested"
},
{
"id": 34,
"name": "cherry_leaves"
},
{
"id": 35,
"name": "pale_oak_leaves"
},
{
"id": 36,
"name": "tinted_leaves"
},
{
"id": 37,
"name": "sculk_soul"
},
{
"id": 38,
"name": "sculk_charge"
},
{
"id": 39,
"name": "sculk_charge_pop"
},
{
"id": 40,
"name": "soul_fire_flame"
},
{
"id": 41,
"name": "soul"
},
{
"id": 42,
"name": "flash"
},
{
"id": 43,
"name": "happy_villager"
},
{
"id": 44,
"name": "composter"
},
{
"id": 45,
"name": "heart"
},
{
"id": 46,
"name": "instant_effect"
},
{
"id": 47,
"name": "item"
},
{
"id": 48,
"name": "vibration"
},
{
"id": 49,
"name": "trail"
},
{
"id": 50,
"name": "item_slime"
},
{
"id": 51,
"name": "item_cobweb"
},
{
"id": 52,
"name": "item_snowball"
},
{
"id": 53,
"name": "large_smoke"
},
{
"id": 54,
"name": "lava"
},
{
"id": 55,
"name": "mycelium"
},
{
"id": 56,
"name": "note"
},
{
"id": 57,
"name": "poof"
},
{
"id": 58,
"name": "portal"
},
{
"id": 59,
"name": "rain"
},
{
"id": 60,
"name": "smoke"
},
{
"id": 61,
"name": "white_smoke"
},
{
"id": 62,
"name": "sneeze"
},
{
"id": 63,
"name": "spit"
},
{
"id": 64,
"name": "squid_ink"
},
{
"id": 65,
"name": "sweep_attack"
},
{
"id": 66,
"name": "totem_of_undying"
},
{
"id": 67,
"name": "underwater"
},
{
"id": 68,
"name": "splash"
},
{
"id": 69,
"name": "witch"
},
{
"id": 70,
"name": "bubble_pop"
},
{
"id": 71,
"name": "current_down"
},
{
"id": 72,
"name": "bubble_column_up"
},
{
"id": 73,
"name": "nautilus"
},
{
"id": 74,
"name": "dolphin"
},
{
"id": 75,
"name": "campfire_cosy_smoke"
},
{
"id": 76,
"name": "campfire_signal_smoke"
},
{
"id": 77,
"name": "dripping_honey"
},
{
"id": 78,
"name": "falling_honey"
},
{
"id": 79,
"name": "landing_honey"
},
{
"id": 80,
"name": "falling_nectar"
},
{
"id": 81,
"name": "falling_spore_blossom"
},
{
"id": 82,
"name": "ash"
},
{
"id": 83,
"name": "crimson_spore"
},
{
"id": 84,
"name": "warped_spore"
},
{
"id": 85,
"name": "spore_blossom_air"
},
{
"id": 86,
"name": "dripping_obsidian_tear"
},
{
"id": 87,
"name": "falling_obsidian_tear"
},
{
"id": 88,
"name": "landing_obsidian_tear"
},
{
"id": 89,
"name": "reverse_portal"
},
{
"id": 90,
"name": "white_ash"
},
{
"id": 91,
"name": "small_flame"
},
{
"id": 92,
"name": "snowflake"
},
{
"id": 93,
"name": "dripping_dripstone_lava"
},
{
"id": 94,
"name": "falling_dripstone_lava"
},
{
"id": 95,
"name": "dripping_dripstone_water"
},
{
"id": 96,
"name": "falling_dripstone_water"
},
{
"id": 97,
"name": "glow_squid_ink"
},
{
"id": 98,
"name": "glow"
},
{
"id": 99,
"name": "wax_on"
},
{
"id": 100,
"name": "wax_off"
},
{
"id": 101,
"name": "electric_spark"
},
{
"id": 102,
"name": "scrape"
},
{
"id": 103,
"name": "shriek"
},
{
"id": 104,
"name": "egg_crack"
},
{
"id": 105,
"name": "dust_plume"
},
{
"id": 106,
"name": "trial_spawner_detection"
},
{
"id": 107,
"name": "trial_spawner_detection_ominous"
},
{
"id": 108,
"name": "vault_connection"
},
{
"id": 109,
"name": "dust_pillar"
},
{
"id": 110,
"name": "ominous_spawning"
},
{
"id": 111,
"name": "raid_omen"
},
{
"id": 112,
"name": "trial_omen"
},
{
"id": 113,
"name": "block_crumble"
},
{
"id": 114,
"name": "firefly"
}
]
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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,465 @@
{
"grass": {
"data": [
{
"keys": [
"badlands",
"eroded_badlands",
"wooded_badlands"
],
"color": -7306931
},
{
"keys": [
"bamboo_jungle",
"basalt_deltas",
"beach",
"birch_forest",
"cold_ocean",
"crimson_forest",
"deep_cold_ocean",
"deep_dark",
"deep_frozen_ocean",
"deep_lukewarm_ocean",
"deep_ocean",
"desert",
"dripstone_caves",
"end_barrens",
"end_highlands",
"end_midlands",
"flower_forest",
"forest",
"frozen_ocean",
"frozen_peaks",
"frozen_river",
"grove",
"ice_spikes",
"jagged_peaks",
"jungle",
"lukewarm_ocean",
"lush_caves",
"meadow",
"mushroom_fields",
"nether_wastes",
"ocean",
"old_growth_birch_forest",
"old_growth_pine_taiga",
"old_growth_spruce_taiga",
"plains",
"river",
"savanna",
"savanna_plateau",
"small_end_islands",
"snowy_beach",
"snowy_plains",
"snowy_slopes",
"snowy_taiga",
"soul_sand_valley",
"sparse_jungle",
"stony_peaks",
"stony_shore",
"sunflower_plains",
"taiga",
"the_end",
"the_void",
"warm_ocean",
"warped_forest",
"windswept_forest",
"windswept_gravelly_hills",
"windswept_hills",
"windswept_savanna"
],
"color": 0
},
{
"keys": [
"cherry_grove"
],
"color": -4793503
},
{
"keys": [
"dark_forest"
],
"color": 1317381
},
{
"keys": [
"mangrove_swamp",
"swamp"
],
"color": 6975545
},
{
"keys": [
"pale_garden"
],
"color": -8945038
}
]
},
"foliage": {
"data": [
{
"keys": [
"badlands",
"eroded_badlands",
"wooded_badlands"
],
"color": -6389427
},
{
"keys": [
"bamboo_jungle",
"basalt_deltas",
"beach",
"birch_forest",
"cold_ocean",
"crimson_forest",
"dark_forest",
"deep_cold_ocean",
"deep_dark",
"deep_frozen_ocean",
"deep_lukewarm_ocean",
"deep_ocean",
"desert",
"dripstone_caves",
"end_barrens",
"end_highlands",
"end_midlands",
"flower_forest",
"forest",
"frozen_ocean",
"frozen_peaks",
"frozen_river",
"grove",
"ice_spikes",
"jagged_peaks",
"jungle",
"lukewarm_ocean",
"lush_caves",
"meadow",
"mushroom_fields",
"nether_wastes",
"ocean",
"old_growth_birch_forest",
"old_growth_pine_taiga",
"old_growth_spruce_taiga",
"plains",
"river",
"savanna",
"savanna_plateau",
"small_end_islands",
"snowy_beach",
"snowy_plains",
"snowy_slopes",
"snowy_taiga",
"soul_sand_valley",
"sparse_jungle",
"stony_peaks",
"stony_shore",
"sunflower_plains",
"taiga",
"the_end",
"the_void",
"warm_ocean",
"warped_forest",
"windswept_forest",
"windswept_gravelly_hills",
"windswept_hills",
"windswept_savanna"
],
"color": 0
},
{
"keys": [
"cherry_grove"
],
"color": -4793503
},
{
"keys": [
"mangrove_swamp"
],
"color": -7491289
},
{
"keys": [
"pale_garden"
],
"color": -7893642
},
{
"keys": [
"swamp"
],
"color": -9801671
}
]
},
"water": {
"data": [
{
"keys": [
"badlands",
"bamboo_jungle",
"basalt_deltas",
"beach",
"birch_forest",
"crimson_forest",
"dark_forest",
"deep_dark",
"deep_ocean",
"desert",
"dripstone_caves",
"end_barrens",
"end_highlands",
"end_midlands",
"eroded_badlands",
"flower_forest",
"forest",
"frozen_peaks",
"grove",
"ice_spikes",
"jagged_peaks",
"jungle",
"lush_caves",
"mushroom_fields",
"nether_wastes",
"ocean",
"old_growth_birch_forest",
"old_growth_pine_taiga",
"old_growth_spruce_taiga",
"plains",
"river",
"savanna",
"savanna_plateau",
"small_end_islands",
"snowy_plains",
"snowy_slopes",
"soul_sand_valley",
"sparse_jungle",
"stony_peaks",
"stony_shore",
"sunflower_plains",
"taiga",
"the_end",
"the_void",
"warped_forest",
"windswept_forest",
"windswept_gravelly_hills",
"windswept_hills",
"windswept_savanna",
"wooded_badlands"
],
"color": -12618012
},
{
"keys": [
"cherry_grove"
],
"color": -10635281
},
{
"keys": [
"cold_ocean",
"deep_cold_ocean",
"snowy_beach",
"snowy_taiga"
],
"color": -12757034
},
{
"keys": [
"deep_frozen_ocean",
"frozen_ocean",
"frozen_river"
],
"color": -13027127
},
{
"keys": [
"deep_lukewarm_ocean",
"lukewarm_ocean"
],
"color": -12210702
},
{
"keys": [
"mangrove_swamp"
],
"color": -12944790
},
{
"keys": [
"meadow"
],
"color": -15839537
},
{
"keys": [
"pale_garden"
],
"color": -9008995
},
{
"keys": [
"swamp"
],
"color": -10388636
},
{
"keys": [
"warm_ocean"
],
"color": -12331538
}
]
},
"redstone": {
"data": [
{
"keys": [
0
],
"color": 4980736
},
{
"keys": [
1
],
"color": 7340032
},
{
"keys": [
2
],
"color": 7995392
},
{
"keys": [
3
],
"color": 8650752
},
{
"keys": [
4
],
"color": 9306112
},
{
"keys": [
5
],
"color": 10027008
},
{
"keys": [
6
],
"color": 10682368
},
{
"keys": [
7
],
"color": 11337728
},
{
"keys": [
8
],
"color": 11993088
},
{
"keys": [
9
],
"color": 12648448
},
{
"keys": [
10
],
"color": 13369344
},
{
"keys": [
11
],
"color": 14024704
},
{
"keys": [
12
],
"color": 14680064
},
{
"keys": [
13
],
"color": 15336960
},
{
"keys": [
14
],
"color": 15997696
},
{
"keys": [
15
],
"color": 16724480
}
]
},
"constant": {
"data": [
{
"keys": [
"birch_leaves"
],
"color": -8345771
},
{
"keys": [
"spruce_leaves"
],
"color": -10380959
},
{
"keys": [
"lily_pad"
],
"color": -14647248
},
{
"keys": [
"attached_melon_stem"
],
"color": -2046180
},
{
"keys": [
"attached_pumpkin_stem"
],
"color": -2046180
},
{
"keys": [
"melon_stem"
],
"color": -16711936
},
{
"keys": [
"pumpkin_stem"
],
"color": -16711936
}
]
}
}
@@ -0,0 +1,6 @@
{
"version": 774,
"minecraftVersion": "1.21.11",
"majorVersion": "1.21",
"releaseType": "release"
}
@@ -0,0 +1,247 @@
[
{
"name": "armor",
"resource": "minecraft:armor",
"min": 0.0,
"max": 30.0,
"default": 0.0
},
{
"name": "armorToughness",
"resource": "minecraft:armor_toughness",
"min": 0.0,
"max": 20.0,
"default": 0.0
},
{
"name": "attackDamage",
"resource": "minecraft:attack_damage",
"min": 0.0,
"max": 2048.0,
"default": 2.0
},
{
"name": "attackKnockback",
"resource": "minecraft:attack_knockback",
"min": 0.0,
"max": 5.0,
"default": 0.0
},
{
"name": "attackSpeed",
"resource": "minecraft:attack_speed",
"min": 0.0,
"max": 1024.0,
"default": 4.0
},
{
"name": "blockBreakSpeed",
"resource": "minecraft:block_break_speed",
"min": 0.0,
"max": 1024.0,
"default": 1.0
},
{
"name": "blockInteractionRange",
"resource": "minecraft:block_interaction_range",
"min": 0.0,
"max": 64.0,
"default": 4.5
},
{
"name": "burningTime",
"resource": "minecraft:burning_time",
"min": 0.0,
"max": 1024.0,
"default": 1.0
},
{
"name": "cameraDistance",
"resource": "minecraft:camera_distance",
"min": 0.0,
"max": 32.0,
"default": 4.0
},
{
"name": "explosionKnockbackResistance",
"resource": "minecraft:explosion_knockback_resistance",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "entityInteractionRange",
"resource": "minecraft:entity_interaction_range",
"min": 0.0,
"max": 64.0,
"default": 3.0
},
{
"name": "fallDamageMultiplier",
"resource": "minecraft:fall_damage_multiplier",
"min": 0.0,
"max": 100.0,
"default": 1.0
},
{
"name": "flyingSpeed",
"resource": "minecraft:flying_speed",
"min": 0.0,
"max": 1024.0,
"default": 0.4
},
{
"name": "followRange",
"resource": "minecraft:follow_range",
"min": 0.0,
"max": 2048.0,
"default": 32.0
},
{
"name": "gravity",
"resource": "minecraft:gravity",
"min": -1.0,
"max": 1.0,
"default": 0.08
},
{
"name": "jumpStrength",
"resource": "minecraft:jump_strength",
"min": 0.0,
"max": 32.0,
"default": 0.41999998688697815
},
{
"name": "knockbackResistance",
"resource": "minecraft:knockback_resistance",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "luck",
"resource": "minecraft:luck",
"min": -1024.0,
"max": 1024.0,
"default": 0.0
},
{
"name": "maxAbsorption",
"resource": "minecraft:max_absorption",
"min": 0.0,
"max": 2048.0,
"default": 0.0
},
{
"name": "maxHealth",
"resource": "minecraft:max_health",
"min": 1.0,
"max": 1024.0,
"default": 20.0
},
{
"name": "miningEfficiency",
"resource": "minecraft:mining_efficiency",
"min": 0.0,
"max": 1024.0,
"default": 0.0
},
{
"name": "movementEfficiency",
"resource": "minecraft:movement_efficiency",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "movementSpeed",
"resource": "minecraft:movement_speed",
"min": 0.0,
"max": 1024.0,
"default": 0.7
},
{
"name": "oxygenBonus",
"resource": "minecraft:oxygen_bonus",
"min": 0.0,
"max": 1024.0,
"default": 0.0
},
{
"name": "safeFallDistance",
"resource": "minecraft:safe_fall_distance",
"min": -1024.0,
"max": 1024.0,
"default": 3.0
},
{
"name": "scale",
"resource": "minecraft:scale",
"min": 0.0625,
"max": 16.0,
"default": 1.0
},
{
"name": "sneakingSpeed",
"resource": "minecraft:sneaking_speed",
"min": 0.0,
"max": 1.0,
"default": 0.3
},
{
"name": "spawnReinforcements",
"resource": "minecraft:spawn_reinforcements",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "stepHeight",
"resource": "minecraft:step_height",
"min": 0.0,
"max": 10.0,
"default": 0.6
},
{
"name": "submergedMiningSpeed",
"resource": "minecraft:submerged_mining_speed",
"min": 0.0,
"max": 20.0,
"default": 0.2
},
{
"name": "sweepingDamageRatio",
"resource": "minecraft:sweeping_damage_ratio",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "temptRange",
"resource": "minecraft:tempt_range",
"min": 0.0,
"max": 2048.0,
"default": 10.0
},
{
"name": "waterMovementEfficiency",
"resource": "minecraft:water_movement_efficiency",
"min": 0.0,
"max": 1.0,
"default": 0.0
},
{
"name": "waypointTransmitRange",
"resource": "minecraft:waypoint_transmit_range",
"min": 0.0,
"max": 6.0E7,
"default": 0.0
},
{
"name": "waypointReceiveRange",
"resource": "minecraft:waypoint_receive_range",
"min": 0.0,
"max": 6.0E7,
"default": 0.0
}
]
@@ -0,0 +1,652 @@
[
{
"id": 0,
"name": "badlands",
"category": "mesa",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Badlands",
"color": 7254527
},
{
"id": 1,
"name": "bamboo_jungle",
"category": "jungle",
"temperature": 0.95,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Bamboo Jungle",
"color": 7842047
},
{
"id": 2,
"name": "basalt_deltas",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Basalt Deltas",
"color": 7254527
},
{
"id": 3,
"name": "beach",
"category": "beach",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Beach",
"color": 7907327
},
{
"id": 4,
"name": "birch_forest",
"category": "forest",
"temperature": 0.6,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Birch Forest",
"color": 8037887
},
{
"id": 5,
"name": "cherry_grove",
"category": "forest",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Cherry Grove",
"color": 8103167
},
{
"id": 6,
"name": "cold_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Cold Ocean",
"color": 8103167
},
{
"id": 7,
"name": "crimson_forest",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Crimson Forest",
"color": 7254527
},
{
"id": 8,
"name": "dark_forest",
"category": "forest",
"temperature": 0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Dark Forest",
"color": 7972607
},
{
"id": 9,
"name": "deep_cold_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Cold Ocean",
"color": 8103167
},
{
"id": 10,
"name": "deep_dark",
"category": "underground",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Dark",
"color": 7907327
},
{
"id": 11,
"name": "deep_frozen_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Frozen Ocean",
"color": 8103167
},
{
"id": 12,
"name": "deep_lukewarm_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Lukewarm Ocean",
"color": 8103167
},
{
"id": 13,
"name": "deep_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Deep Ocean",
"color": 8103167
},
{
"id": 14,
"name": "desert",
"category": "desert",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Desert",
"color": 7254527
},
{
"id": 15,
"name": "dripstone_caves",
"category": "underground",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Dripstone Caves",
"color": 7907327
},
{
"id": 16,
"name": "end_barrens",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "End Barrens",
"color": 0
},
{
"id": 17,
"name": "end_highlands",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "End Highlands",
"color": 0
},
{
"id": 18,
"name": "end_midlands",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "End Midlands",
"color": 0
},
{
"id": 19,
"name": "eroded_badlands",
"category": "mesa",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Eroded Badlands",
"color": 7254527
},
{
"id": 20,
"name": "flower_forest",
"category": "forest",
"temperature": 0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Flower Forest",
"color": 7972607
},
{
"id": 21,
"name": "forest",
"category": "forest",
"temperature": 0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Forest",
"color": 7972607
},
{
"id": 22,
"name": "frozen_ocean",
"category": "ocean",
"temperature": 0.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Frozen Ocean",
"color": 8364543
},
{
"id": 23,
"name": "frozen_peaks",
"category": "ice",
"temperature": -0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Frozen Peaks",
"color": 8756735
},
{
"id": 24,
"name": "frozen_river",
"category": "ice",
"temperature": 0.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Frozen River",
"color": 8364543
},
{
"id": 25,
"name": "grove",
"category": "forest",
"temperature": -0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Grove",
"color": 8495359
},
{
"id": 26,
"name": "ice_spikes",
"category": "ice",
"temperature": 0.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Ice Spikes",
"color": 8364543
},
{
"id": 27,
"name": "jagged_peaks",
"category": "mountain",
"temperature": -0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Jagged Peaks",
"color": 8756735
},
{
"id": 28,
"name": "jungle",
"category": "jungle",
"temperature": 0.95,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Jungle",
"color": 7842047
},
{
"id": 29,
"name": "lukewarm_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Lukewarm Ocean",
"color": 8103167
},
{
"id": 30,
"name": "lush_caves",
"category": "underground",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Lush Caves",
"color": 8103167
},
{
"id": 31,
"name": "mangrove_swamp",
"category": "forest",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Mangrove Swamp",
"color": 7907327
},
{
"id": 32,
"name": "meadow",
"category": "mountain",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Meadow",
"color": 8103167
},
{
"id": 33,
"name": "mushroom_fields",
"category": "mushroom",
"temperature": 0.9,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Mushroom Fields",
"color": 7842047
},
{
"id": 34,
"name": "nether_wastes",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Nether Wastes",
"color": 7254527
},
{
"id": 35,
"name": "ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Ocean",
"color": 8103167
},
{
"id": 36,
"name": "old_growth_birch_forest",
"category": "forest",
"temperature": 0.6,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Old Growth Birch Forest",
"color": 8037887
},
{
"id": 37,
"name": "old_growth_pine_taiga",
"category": "taiga",
"temperature": 0.3,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Old Growth Pine Taiga",
"color": 8168447
},
{
"id": 38,
"name": "old_growth_spruce_taiga",
"category": "taiga",
"temperature": 0.25,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Old Growth Spruce Taiga",
"color": 8233983
},
{
"id": 39,
"name": "pale_garden",
"category": "none",
"temperature": 0.7,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Pale Garden",
"color": 12171705
},
{
"id": 40,
"name": "plains",
"category": "plains",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Plains",
"color": 7907327
},
{
"id": 41,
"name": "river",
"category": "river",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "River",
"color": 8103167
},
{
"id": 42,
"name": "savanna",
"category": "savanna",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Savanna",
"color": 7254527
},
{
"id": 43,
"name": "savanna_plateau",
"category": "savanna",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Savanna Plateau",
"color": 7254527
},
{
"id": 44,
"name": "small_end_islands",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "Small End Islands",
"color": 0
},
{
"id": 45,
"name": "snowy_beach",
"category": "beach",
"temperature": 0.05,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Snowy Beach",
"color": 8364543
},
{
"id": 46,
"name": "snowy_plains",
"category": "plains",
"temperature": 0.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Snowy Plains",
"color": 8364543
},
{
"id": 47,
"name": "snowy_slopes",
"category": "mountain",
"temperature": -0.3,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Snowy Slopes",
"color": 8560639
},
{
"id": 48,
"name": "snowy_taiga",
"category": "taiga",
"temperature": -0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Snowy Taiga",
"color": 8625919
},
{
"id": 49,
"name": "soul_sand_valley",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Soul Sand Valley",
"color": 7254527
},
{
"id": 50,
"name": "sparse_jungle",
"category": "jungle",
"temperature": 0.95,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Sparse Jungle",
"color": 7842047
},
{
"id": 51,
"name": "stony_peaks",
"category": "mountain",
"temperature": 1.0,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Stony Peaks",
"color": 7776511
},
{
"id": 52,
"name": "stony_shore",
"category": "beach",
"temperature": 0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Stony Shore",
"color": 8233727
},
{
"id": 53,
"name": "sunflower_plains",
"category": "plains",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Sunflower Plains",
"color": 7907327
},
{
"id": 54,
"name": "swamp",
"category": "swamp",
"temperature": 0.8,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Swamp",
"color": 7907327
},
{
"id": 55,
"name": "taiga",
"category": "taiga",
"temperature": 0.25,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Taiga",
"color": 8233983
},
{
"id": 56,
"name": "the_end",
"category": "the_end",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "end",
"displayName": "The End",
"color": 0
},
{
"id": 57,
"name": "the_void",
"category": "none",
"temperature": 0.5,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "The Void",
"color": 8103167
},
{
"id": 58,
"name": "warm_ocean",
"category": "ocean",
"temperature": 0.5,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Warm Ocean",
"color": 8103167
},
{
"id": 59,
"name": "warped_forest",
"category": "nether",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "nether",
"displayName": "Warped Forest",
"color": 7254527
},
{
"id": 60,
"name": "windswept_forest",
"category": "forest",
"temperature": 0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Windswept Forest",
"color": 8233727
},
{
"id": 61,
"name": "windswept_gravelly_hills",
"category": "extreme_hills",
"temperature": 0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Windswept Gravelly Hills",
"color": 8233727
},
{
"id": 62,
"name": "windswept_hills",
"category": "extreme_hills",
"temperature": 0.2,
"has_precipitation": true,
"dimension": "overworld",
"displayName": "Windswept Hills",
"color": 8233727
},
{
"id": 63,
"name": "windswept_savanna",
"category": "savanna",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Windswept Savanna",
"color": 7254527
},
{
"id": 64,
"name": "wooded_badlands",
"category": "mesa",
"temperature": 2.0,
"has_precipitation": false,
"dimension": "overworld",
"displayName": "Wooded Badlands",
"color": 7254527
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,236 @@
[
{
"id": 0,
"name": "Speed",
"displayName": "Speed",
"type": "good"
},
{
"id": 1,
"name": "Slowness",
"displayName": "Slowness",
"type": "bad"
},
{
"id": 2,
"name": "Haste",
"displayName": "Haste",
"type": "good"
},
{
"id": 3,
"name": "MiningFatigue",
"displayName": "Mining Fatigue",
"type": "bad"
},
{
"id": 4,
"name": "Strength",
"displayName": "Strength",
"type": "good"
},
{
"id": 5,
"name": "InstantHealth",
"displayName": "Instant Health",
"type": "good"
},
{
"id": 6,
"name": "InstantDamage",
"displayName": "Instant Damage",
"type": "bad"
},
{
"id": 7,
"name": "JumpBoost",
"displayName": "Jump Boost",
"type": "good"
},
{
"id": 8,
"name": "Nausea",
"displayName": "Nausea",
"type": "bad"
},
{
"id": 9,
"name": "Regeneration",
"displayName": "Regeneration",
"type": "good"
},
{
"id": 10,
"name": "Resistance",
"displayName": "Resistance",
"type": "good"
},
{
"id": 11,
"name": "FireResistance",
"displayName": "Fire Resistance",
"type": "good"
},
{
"id": 12,
"name": "WaterBreathing",
"displayName": "Water Breathing",
"type": "good"
},
{
"id": 13,
"name": "Invisibility",
"displayName": "Invisibility",
"type": "good"
},
{
"id": 14,
"name": "Blindness",
"displayName": "Blindness",
"type": "bad"
},
{
"id": 15,
"name": "NightVision",
"displayName": "Night Vision",
"type": "good"
},
{
"id": 16,
"name": "Hunger",
"displayName": "Hunger",
"type": "bad"
},
{
"id": 17,
"name": "Weakness",
"displayName": "Weakness",
"type": "bad"
},
{
"id": 18,
"name": "Poison",
"displayName": "Poison",
"type": "bad"
},
{
"id": 19,
"name": "Wither",
"displayName": "Wither",
"type": "bad"
},
{
"id": 20,
"name": "HealthBoost",
"displayName": "Health Boost",
"type": "good"
},
{
"id": 21,
"name": "Absorption",
"displayName": "Absorption",
"type": "good"
},
{
"id": 22,
"name": "Saturation",
"displayName": "Saturation",
"type": "good"
},
{
"id": 23,
"name": "Glowing",
"displayName": "Glowing",
"type": "bad"
},
{
"id": 24,
"name": "Levitation",
"displayName": "Levitation",
"type": "bad"
},
{
"id": 25,
"name": "Luck",
"displayName": "Luck",
"type": "good"
},
{
"id": 26,
"name": "BadLuck",
"displayName": "Bad Luck",
"type": "bad"
},
{
"id": 27,
"name": "SlowFalling",
"displayName": "Slow Falling",
"type": "good"
},
{
"id": 28,
"name": "ConduitPower",
"displayName": "Conduit Power",
"type": "good"
},
{
"id": 29,
"name": "DolphinsGrace",
"displayName": "Dolphin's Grace",
"type": "good"
},
{
"id": 30,
"name": "BadOmen",
"displayName": "Bad Omen",
"type": "bad"
},
{
"id": 31,
"name": "HeroOfTheVillage",
"displayName": "Hero of the Village",
"type": "good"
},
{
"id": 32,
"name": "Darkness",
"displayName": "Darkness",
"type": "bad"
},
{
"id": 33,
"name": "TrialOmen",
"displayName": "Trial Omen",
"type": "bad"
},
{
"id": 34,
"name": "RaidOmen",
"displayName": "Raid Omen",
"type": "bad"
},
{
"id": 35,
"name": "WindCharged",
"displayName": "Wind Charged",
"type": "bad"
},
{
"id": 36,
"name": "Weaving",
"displayName": "Weaving",
"type": "bad"
},
{
"id": 37,
"name": "Oozing",
"displayName": "Oozing",
"type": "bad"
},
{
"id": 38,
"name": "Infested",
"displayName": "Infested",
"type": "bad"
}
]
@@ -0,0 +1,959 @@
[
{
"id": 0,
"name": "aqua_affinity",
"displayName": "Aqua Affinity",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "head_armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 1,
"name": "bane_of_arthropods",
"displayName": "Bane of Arthropods",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"breach",
"density",
"impaling",
"sharpness",
"smite"
],
"category": "weapon",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 2,
"name": "binding_curse",
"displayName": "Curse of Binding",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": true,
"curse": true,
"exclude": [],
"category": "equippable",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 3,
"name": "blast_protection",
"displayName": "Blast Protection",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"fire_protection",
"projectile_protection",
"protection"
],
"category": "armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 4,
"name": "breach",
"displayName": "Breach",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"density",
"impaling",
"sharpness",
"smite"
],
"category": "mace",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 5,
"name": "channeling",
"displayName": "Channeling",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"riptide"
],
"category": "trident",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 6,
"name": "density",
"displayName": "Density",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"breach",
"impaling",
"sharpness",
"smite"
],
"category": "mace",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 7,
"name": "depth_strider",
"displayName": "Depth Strider",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"frost_walker"
],
"category": "foot_armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 8,
"name": "efficiency",
"displayName": "Efficiency",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "mining",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 9,
"name": "feather_falling",
"displayName": "Feather Falling",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "foot_armor",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 10,
"name": "fire_aspect",
"displayName": "Fire Aspect",
"maxLevel": 2,
"minCost": {
"a": 1,
"b": 1
},
"maxCost": {
"a": 1,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "fire_aspect",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 11,
"name": "fire_protection",
"displayName": "Fire Protection",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"blast_protection",
"projectile_protection",
"protection"
],
"category": "armor",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 12,
"name": "flame",
"displayName": "Flame",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "bow",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 13,
"name": "fortune",
"displayName": "Fortune",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"silk_touch"
],
"category": "mining_loot",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 14,
"name": "frost_walker",
"displayName": "Frost Walker",
"maxLevel": 2,
"minCost": {
"a": 1,
"b": 1
},
"maxCost": {
"a": 1,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [
"depth_strider"
],
"category": "foot_armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 15,
"name": "impaling",
"displayName": "Impaling",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"breach",
"density",
"sharpness",
"smite"
],
"category": "trident",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 16,
"name": "infinity",
"displayName": "Infinity",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"mending"
],
"category": "bow",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 17,
"name": "knockback",
"displayName": "Knockback",
"maxLevel": 2,
"minCost": {
"a": 1,
"b": 1
},
"maxCost": {
"a": 1,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "sword",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 18,
"name": "looting",
"displayName": "Looting",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "sword",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 19,
"name": "loyalty",
"displayName": "Loyalty",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"riptide"
],
"category": "trident",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 20,
"name": "luck_of_the_sea",
"displayName": "Luck of the Sea",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "fishing",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 21,
"name": "lure",
"displayName": "Lure",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "fishing",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 22,
"name": "mending",
"displayName": "Mending",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [
"infinity"
],
"category": "durability",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 23,
"name": "multishot",
"displayName": "Multishot",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"piercing"
],
"category": "crossbow",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 24,
"name": "piercing",
"displayName": "Piercing",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"multishot"
],
"category": "crossbow",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 25,
"name": "power",
"displayName": "Power",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "bow",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 26,
"name": "projectile_protection",
"displayName": "Projectile Protection",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"blast_protection",
"fire_protection",
"protection"
],
"category": "armor",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 27,
"name": "protection",
"displayName": "Protection",
"maxLevel": 4,
"minCost": {
"a": 3,
"b": 1
},
"maxCost": {
"a": 3,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"blast_protection",
"fire_protection",
"projectile_protection"
],
"category": "armor",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 28,
"name": "punch",
"displayName": "Punch",
"maxLevel": 2,
"minCost": {
"a": 1,
"b": 1
},
"maxCost": {
"a": 1,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "bow",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 29,
"name": "quick_charge",
"displayName": "Quick Charge",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "crossbow",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 30,
"name": "respiration",
"displayName": "Respiration",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "head_armor",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 31,
"name": "riptide",
"displayName": "Riptide",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"channeling",
"loyalty"
],
"category": "trident",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 32,
"name": "sharpness",
"displayName": "Sharpness",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"breach",
"density",
"impaling",
"smite"
],
"category": "sharp_weapon",
"weight": 10,
"tradeable": true,
"discoverable": true
},
{
"id": 33,
"name": "silk_touch",
"displayName": "Silk Touch",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"fortune"
],
"category": "mining_loot",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 34,
"name": "smite",
"displayName": "Smite",
"maxLevel": 5,
"minCost": {
"a": 4,
"b": 1
},
"maxCost": {
"a": 4,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [
"bane_of_arthropods",
"breach",
"density",
"impaling",
"sharpness"
],
"category": "weapon",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 35,
"name": "soul_speed",
"displayName": "Soul Speed",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [],
"category": "foot_armor",
"weight": 1,
"tradeable": false,
"discoverable": false
},
{
"id": 36,
"name": "sweeping_edge",
"displayName": "Sweeping Edge",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "sword",
"weight": 2,
"tradeable": true,
"discoverable": true
},
{
"id": 37,
"name": "swift_sneak",
"displayName": "Swift Sneak",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [],
"category": "leg_armor",
"weight": 1,
"tradeable": false,
"discoverable": false
},
{
"id": 38,
"name": "thorns",
"displayName": "Thorns",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "armor",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 39,
"name": "unbreaking",
"displayName": "Unbreaking",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": false,
"curse": false,
"exclude": [],
"category": "durability",
"weight": 5,
"tradeable": true,
"discoverable": true
},
{
"id": 40,
"name": "vanishing_curse",
"displayName": "Curse of Vanishing",
"maxLevel": 1,
"minCost": {
"a": 0,
"b": 1
},
"maxCost": {
"a": 0,
"b": 1
},
"treasureOnly": true,
"curse": true,
"exclude": [],
"category": "vanishing",
"weight": 1,
"tradeable": true,
"discoverable": true
},
{
"id": 41,
"name": "wind_burst",
"displayName": "Wind Burst",
"maxLevel": 3,
"minCost": {
"a": 2,
"b": 1
},
"maxCost": {
"a": 2,
"b": 1
},
"treasureOnly": true,
"curse": false,
"exclude": [],
"category": "mace",
"weight": 2,
"tradeable": false,
"discoverable": false
}
]
File diff suppressed because it is too large Load Diff
+402
View File
@@ -0,0 +1,402 @@
[
{
"id": 857,
"name": "apple",
"stackSize": 64,
"displayName": "Apple",
"foodPoints": 4.0,
"saturation": 19.2,
"effectiveQuality": 23.2,
"saturationRatio": 4.8
},
{
"id": 906,
"name": "mushroom_stew",
"stackSize": 1,
"displayName": "Mushroom Stew",
"foodPoints": 6.0,
"saturation": 86.4,
"effectiveQuality": 92.4,
"saturationRatio": 14.400001
},
{
"id": 912,
"name": "bread",
"stackSize": 64,
"displayName": "Bread",
"foodPoints": 5.0,
"saturation": 60.0,
"effectiveQuality": 65.0,
"saturationRatio": 12.0
},
{
"id": 938,
"name": "porkchop",
"stackSize": 64,
"displayName": "Raw Porkchop",
"foodPoints": 3.0,
"saturation": 10.8,
"effectiveQuality": 13.8,
"saturationRatio": 3.6000001
},
{
"id": 939,
"name": "cooked_porkchop",
"stackSize": 64,
"displayName": "Cooked Porkchop",
"foodPoints": 8.0,
"saturation": 204.8,
"effectiveQuality": 212.8,
"saturationRatio": 25.6
},
{
"id": 941,
"name": "golden_apple",
"stackSize": 64,
"displayName": "Golden Apple",
"foodPoints": 4.0,
"saturation": 76.8,
"effectiveQuality": 80.8,
"saturationRatio": 19.2
},
{
"id": 942,
"name": "enchanted_golden_apple",
"stackSize": 64,
"displayName": "Enchanted Golden Apple",
"foodPoints": 4.0,
"saturation": 76.8,
"effectiveQuality": 80.8,
"saturationRatio": 19.2
},
{
"id": 1012,
"name": "cod",
"stackSize": 64,
"displayName": "Raw Cod",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1013,
"name": "salmon",
"stackSize": 64,
"displayName": "Raw Salmon",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1014,
"name": "tropical_fish",
"stackSize": 64,
"displayName": "Tropical Fish",
"foodPoints": 1.0,
"saturation": 0.4,
"effectiveQuality": 1.4,
"saturationRatio": 0.4
},
{
"id": 1015,
"name": "pufferfish",
"stackSize": 64,
"displayName": "Pufferfish",
"foodPoints": 1.0,
"saturation": 0.4,
"effectiveQuality": 1.4,
"saturationRatio": 0.4
},
{
"id": 1016,
"name": "cooked_cod",
"stackSize": 64,
"displayName": "Cooked Cod",
"foodPoints": 5.0,
"saturation": 60.0,
"effectiveQuality": 65.0,
"saturationRatio": 12.0
},
{
"id": 1017,
"name": "cooked_salmon",
"stackSize": 64,
"displayName": "Cooked Salmon",
"foodPoints": 6.0,
"saturation": 115.200005,
"effectiveQuality": 121.200005,
"saturationRatio": 19.2
},
{
"id": 1057,
"name": "cookie",
"stackSize": 64,
"displayName": "Cookie",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1061,
"name": "melon_slice",
"stackSize": 64,
"displayName": "Melon Slice",
"foodPoints": 2.0,
"saturation": 4.8,
"effectiveQuality": 6.8,
"saturationRatio": 2.4
},
{
"id": 1062,
"name": "dried_kelp",
"stackSize": 64,
"displayName": "Dried Kelp",
"foodPoints": 1.0,
"saturation": 1.2,
"effectiveQuality": 2.2,
"saturationRatio": 1.2
},
{
"id": 1065,
"name": "beef",
"stackSize": 64,
"displayName": "Raw Beef",
"foodPoints": 3.0,
"saturation": 10.8,
"effectiveQuality": 13.8,
"saturationRatio": 3.6000001
},
{
"id": 1066,
"name": "cooked_beef",
"stackSize": 64,
"displayName": "Steak",
"foodPoints": 8.0,
"saturation": 204.8,
"effectiveQuality": 212.8,
"saturationRatio": 25.6
},
{
"id": 1067,
"name": "chicken",
"stackSize": 64,
"displayName": "Raw Chicken",
"foodPoints": 2.0,
"saturation": 4.8,
"effectiveQuality": 6.8,
"saturationRatio": 2.4
},
{
"id": 1068,
"name": "cooked_chicken",
"stackSize": 64,
"displayName": "Cooked Chicken",
"foodPoints": 6.0,
"saturation": 86.4,
"effectiveQuality": 92.4,
"saturationRatio": 14.400001
},
{
"id": 1069,
"name": "rotten_flesh",
"stackSize": 64,
"displayName": "Rotten Flesh",
"foodPoints": 4.0,
"saturation": 6.4,
"effectiveQuality": 10.4,
"saturationRatio": 1.6
},
{
"id": 1077,
"name": "spider_eye",
"stackSize": 64,
"displayName": "Spider Eye",
"foodPoints": 2.0,
"saturation": 12.8,
"effectiveQuality": 14.8,
"saturationRatio": 6.4
},
{
"id": 1177,
"name": "carrot",
"stackSize": 64,
"displayName": "Carrot",
"foodPoints": 3.0,
"saturation": 21.6,
"effectiveQuality": 24.6,
"saturationRatio": 7.2000003
},
{
"id": 1178,
"name": "potato",
"stackSize": 64,
"displayName": "Potato",
"foodPoints": 1.0,
"saturation": 1.2,
"effectiveQuality": 2.2,
"saturationRatio": 1.2
},
{
"id": 1179,
"name": "baked_potato",
"stackSize": 64,
"displayName": "Baked Potato",
"foodPoints": 5.0,
"saturation": 60.0,
"effectiveQuality": 65.0,
"saturationRatio": 12.0
},
{
"id": 1180,
"name": "poisonous_potato",
"stackSize": 64,
"displayName": "Poisonous Potato",
"foodPoints": 2.0,
"saturation": 4.8,
"effectiveQuality": 6.8,
"saturationRatio": 2.4
},
{
"id": 1182,
"name": "golden_carrot",
"stackSize": 64,
"displayName": "Golden Carrot",
"foodPoints": 6.0,
"saturation": 172.8,
"effectiveQuality": 178.8,
"saturationRatio": 28.800001
},
{
"id": 1191,
"name": "pumpkin_pie",
"stackSize": 64,
"displayName": "Pumpkin Pie",
"foodPoints": 8.0,
"saturation": 76.8,
"effectiveQuality": 84.8,
"saturationRatio": 9.6
},
{
"id": 1199,
"name": "rabbit",
"stackSize": 64,
"displayName": "Raw Rabbit",
"foodPoints": 3.0,
"saturation": 10.8,
"effectiveQuality": 13.8,
"saturationRatio": 3.6000001
},
{
"id": 1200,
"name": "cooked_rabbit",
"stackSize": 64,
"displayName": "Cooked Rabbit",
"foodPoints": 5.0,
"saturation": 60.0,
"effectiveQuality": 65.0,
"saturationRatio": 12.0
},
{
"id": 1201,
"name": "rabbit_stew",
"stackSize": 1,
"displayName": "Rabbit Stew",
"foodPoints": 10.0,
"saturation": 240.0,
"effectiveQuality": 250.0,
"saturationRatio": 24.0
},
{
"id": 1212,
"name": "mutton",
"stackSize": 64,
"displayName": "Raw Mutton",
"foodPoints": 2.0,
"saturation": 4.8,
"effectiveQuality": 6.8,
"saturationRatio": 2.4
},
{
"id": 1213,
"name": "cooked_mutton",
"stackSize": 64,
"displayName": "Cooked Mutton",
"foodPoints": 6.0,
"saturation": 115.200005,
"effectiveQuality": 121.200005,
"saturationRatio": 19.2
},
{
"id": 1231,
"name": "chorus_fruit",
"stackSize": 64,
"displayName": "Chorus Fruit",
"foodPoints": 4.0,
"saturation": 19.2,
"effectiveQuality": 23.2,
"saturationRatio": 4.8
},
{
"id": 1235,
"name": "beetroot",
"stackSize": 64,
"displayName": "Beetroot",
"foodPoints": 1.0,
"saturation": 2.4,
"effectiveQuality": 3.4,
"saturationRatio": 2.4
},
{
"id": 1237,
"name": "beetroot_soup",
"stackSize": 1,
"displayName": "Beetroot Soup",
"foodPoints": 6.0,
"saturation": 86.4,
"effectiveQuality": 92.4,
"saturationRatio": 14.400001
},
{
"id": 1275,
"name": "suspicious_stew",
"stackSize": 1,
"displayName": "Suspicious Stew",
"foodPoints": 6.0,
"saturation": 86.4,
"effectiveQuality": 92.4,
"saturationRatio": 14.400001
},
{
"id": 1300,
"name": "sweet_berries",
"stackSize": 64,
"displayName": "Sweet Berries",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1301,
"name": "glow_berries",
"stackSize": 64,
"displayName": "Glow Berries",
"foodPoints": 2.0,
"saturation": 1.6,
"effectiveQuality": 3.6,
"saturationRatio": 0.8
},
{
"id": 1308,
"name": "honey_bottle",
"stackSize": 16,
"displayName": "Honey Bottle",
"foodPoints": 6.0,
"saturation": 14.400001,
"effectiveQuality": 20.400002,
"saturationRatio": 2.4
}
]
@@ -0,0 +1,94 @@
[
{
"id": 0,
"name": "harp"
},
{
"id": 1,
"name": "basedrum"
},
{
"id": 2,
"name": "snare"
},
{
"id": 3,
"name": "hat"
},
{
"id": 4,
"name": "bass"
},
{
"id": 5,
"name": "flute"
},
{
"id": 6,
"name": "bell"
},
{
"id": 7,
"name": "guitar"
},
{
"id": 8,
"name": "chime"
},
{
"id": 9,
"name": "xylophone"
},
{
"id": 10,
"name": "iron_xylophone"
},
{
"id": 11,
"name": "cow_bell"
},
{
"id": 12,
"name": "didgeridoo"
},
{
"id": 13,
"name": "bit"
},
{
"id": 14,
"name": "banjo"
},
{
"id": 15,
"name": "pling"
},
{
"id": 16,
"name": "zombie"
},
{
"id": 17,
"name": "skeleton"
},
{
"id": 18,
"name": "creeper"
},
{
"id": 19,
"name": "dragon"
},
{
"id": 20,
"name": "wither_skeleton"
},
{
"id": 21,
"name": "piglin"
},
{
"id": 22,
"name": "custom_head"
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,206 @@
{
"default": {},
"leaves": {
"1060": 1.0,
"875": 1.5,
"880": 1.5,
"885": 1.5,
"890": 1.5,
"895": 1.5,
"900": 1.5
},
"coweb": {
"1060": 15.0,
"875": 15.0,
"880": 15.0,
"885": 15.0,
"890": 15.0,
"895": 15.0,
"900": 15.0
},
"plant": {
"875": 1.5,
"880": 1.5,
"885": 1.5,
"890": 1.5,
"895": 1.5,
"900": 1.5
},
"gourd": {
"875": 1.5,
"880": 1.5,
"885": 1.5,
"890": 1.5,
"895": 1.5,
"900": 1.5
},
"vine_or_glow_lichen": {
"1060": 2.0
},
"wool": {
"1060": 1.0
},
"sword_instantly_mines": {
"875": 2.0,
"880": 4.0,
"885": 12.0,
"890": 6.0,
"895": 8.0,
"900": 9.0
},
"sword_efficient": {
"875": 2.0,
"880": 4.0,
"885": 12.0,
"890": 6.0,
"895": 8.0,
"900": 9.0
},
"incorrect_for_wooden_tool": {
"876": 2.0,
"877": 2.0,
"878": 2.0,
"879": 2.0
},
"mineable/shovel": {
"876": 2.0,
"881": 4.0,
"886": 12.0,
"891": 6.0,
"896": 8.0,
"901": 9.0
},
"mineable/pickaxe": {
"877": 2.0,
"882": 4.0,
"887": 12.0,
"892": 6.0,
"897": 8.0,
"902": 9.0
},
"mineable/axe": {
"878": 2.0,
"883": 4.0,
"888": 12.0,
"893": 6.0,
"898": 8.0,
"903": 9.0
},
"mineable/hoe": {
"879": 2.0,
"884": 4.0,
"889": 12.0,
"894": 6.0,
"899": 8.0,
"904": 9.0
},
"incorrect_for_stone_tool": {
"881": 4.0,
"882": 4.0,
"883": 4.0,
"884": 4.0
},
"incorrect_for_gold_tool": {
"886": 12.0,
"887": 12.0,
"888": 12.0,
"889": 12.0
},
"incorrect_for_iron_tool": {
"891": 6.0,
"892": 6.0,
"893": 6.0,
"894": 6.0
},
"incorrect_for_diamond_tool": {
"896": 8.0,
"897": 8.0,
"898": 8.0,
"899": 8.0
},
"incorrect_for_netherite_tool": {
"901": 9.0,
"902": 9.0,
"903": 9.0,
"904": 9.0
},
"plant;mineable/axe": {
"875": 1.5,
"880": 1.5,
"885": 1.5,
"890": 1.5,
"895": 1.5,
"900": 1.5,
"878": 2.0,
"883": 4.0,
"888": 12.0,
"893": 6.0,
"898": 8.0,
"903": 9.0
},
"gourd;mineable/axe": {
"875": 1.5,
"880": 1.5,
"885": 1.5,
"890": 1.5,
"895": 1.5,
"900": 1.5,
"878": 2.0,
"883": 4.0,
"888": 12.0,
"893": 6.0,
"898": 8.0,
"903": 9.0
},
"leaves;mineable/hoe": {
"1060": 1.0,
"875": 1.5,
"880": 1.5,
"885": 1.5,
"890": 1.5,
"895": 1.5,
"900": 1.5,
"879": 2.0,
"884": 4.0,
"889": 12.0,
"894": 6.0,
"899": 8.0,
"904": 9.0
},
"leaves;mineable/axe;mineable/hoe": {
"1060": 1.0,
"875": 1.5,
"880": 1.5,
"885": 1.5,
"890": 1.5,
"895": 1.5,
"900": 1.5,
"878": 2.0,
"883": 4.0,
"888": 12.0,
"893": 6.0,
"898": 8.0,
"903": 9.0,
"879": 2.0,
"884": 4.0,
"889": 12.0,
"894": 6.0,
"899": 8.0,
"904": 9.0
},
"vine_or_glow_lichen;plant;mineable/axe": {
"1060": 2.0,
"875": 1.5,
"880": 1.5,
"885": 1.5,
"890": 1.5,
"895": 1.5,
"900": 1.5,
"878": 2.0,
"883": 4.0,
"888": 12.0,
"893": 6.0,
"898": 8.0,
"903": 9.0
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,458 @@
[
{
"id": 0,
"name": "angry_villager"
},
{
"id": 1,
"name": "block"
},
{
"id": 2,
"name": "block_marker"
},
{
"id": 3,
"name": "bubble"
},
{
"id": 4,
"name": "cloud"
},
{
"id": 5,
"name": "crit"
},
{
"id": 6,
"name": "damage_indicator"
},
{
"id": 7,
"name": "dragon_breath"
},
{
"id": 8,
"name": "dripping_lava"
},
{
"id": 9,
"name": "falling_lava"
},
{
"id": 10,
"name": "landing_lava"
},
{
"id": 11,
"name": "dripping_water"
},
{
"id": 12,
"name": "falling_water"
},
{
"id": 13,
"name": "dust"
},
{
"id": 14,
"name": "dust_color_transition"
},
{
"id": 15,
"name": "effect"
},
{
"id": 16,
"name": "elder_guardian"
},
{
"id": 17,
"name": "enchanted_hit"
},
{
"id": 18,
"name": "enchant"
},
{
"id": 19,
"name": "end_rod"
},
{
"id": 20,
"name": "entity_effect"
},
{
"id": 21,
"name": "explosion_emitter"
},
{
"id": 22,
"name": "explosion"
},
{
"id": 23,
"name": "gust"
},
{
"id": 24,
"name": "small_gust"
},
{
"id": 25,
"name": "gust_emitter_large"
},
{
"id": 26,
"name": "gust_emitter_small"
},
{
"id": 27,
"name": "sonic_boom"
},
{
"id": 28,
"name": "falling_dust"
},
{
"id": 29,
"name": "firework"
},
{
"id": 30,
"name": "fishing"
},
{
"id": 31,
"name": "flame"
},
{
"id": 32,
"name": "infested"
},
{
"id": 33,
"name": "cherry_leaves"
},
{
"id": 34,
"name": "pale_oak_leaves"
},
{
"id": 35,
"name": "tinted_leaves"
},
{
"id": 36,
"name": "sculk_soul"
},
{
"id": 37,
"name": "sculk_charge"
},
{
"id": 38,
"name": "sculk_charge_pop"
},
{
"id": 39,
"name": "soul_fire_flame"
},
{
"id": 40,
"name": "soul"
},
{
"id": 41,
"name": "flash"
},
{
"id": 42,
"name": "happy_villager"
},
{
"id": 43,
"name": "composter"
},
{
"id": 44,
"name": "heart"
},
{
"id": 45,
"name": "instant_effect"
},
{
"id": 46,
"name": "item"
},
{
"id": 47,
"name": "vibration"
},
{
"id": 48,
"name": "trail"
},
{
"id": 49,
"name": "item_slime"
},
{
"id": 50,
"name": "item_cobweb"
},
{
"id": 51,
"name": "item_snowball"
},
{
"id": 52,
"name": "large_smoke"
},
{
"id": 53,
"name": "lava"
},
{
"id": 54,
"name": "mycelium"
},
{
"id": 55,
"name": "note"
},
{
"id": 56,
"name": "poof"
},
{
"id": 57,
"name": "portal"
},
{
"id": 58,
"name": "rain"
},
{
"id": 59,
"name": "smoke"
},
{
"id": 60,
"name": "white_smoke"
},
{
"id": 61,
"name": "sneeze"
},
{
"id": 62,
"name": "spit"
},
{
"id": 63,
"name": "squid_ink"
},
{
"id": 64,
"name": "sweep_attack"
},
{
"id": 65,
"name": "totem_of_undying"
},
{
"id": 66,
"name": "underwater"
},
{
"id": 67,
"name": "splash"
},
{
"id": 68,
"name": "witch"
},
{
"id": 69,
"name": "bubble_pop"
},
{
"id": 70,
"name": "current_down"
},
{
"id": 71,
"name": "bubble_column_up"
},
{
"id": 72,
"name": "nautilus"
},
{
"id": 73,
"name": "dolphin"
},
{
"id": 74,
"name": "campfire_cosy_smoke"
},
{
"id": 75,
"name": "campfire_signal_smoke"
},
{
"id": 76,
"name": "dripping_honey"
},
{
"id": 77,
"name": "falling_honey"
},
{
"id": 78,
"name": "landing_honey"
},
{
"id": 79,
"name": "falling_nectar"
},
{
"id": 80,
"name": "falling_spore_blossom"
},
{
"id": 81,
"name": "ash"
},
{
"id": 82,
"name": "crimson_spore"
},
{
"id": 83,
"name": "warped_spore"
},
{
"id": 84,
"name": "spore_blossom_air"
},
{
"id": 85,
"name": "dripping_obsidian_tear"
},
{
"id": 86,
"name": "falling_obsidian_tear"
},
{
"id": 87,
"name": "landing_obsidian_tear"
},
{
"id": 88,
"name": "reverse_portal"
},
{
"id": 89,
"name": "white_ash"
},
{
"id": 90,
"name": "small_flame"
},
{
"id": 91,
"name": "snowflake"
},
{
"id": 92,
"name": "dripping_dripstone_lava"
},
{
"id": 93,
"name": "falling_dripstone_lava"
},
{
"id": 94,
"name": "dripping_dripstone_water"
},
{
"id": 95,
"name": "falling_dripstone_water"
},
{
"id": 96,
"name": "glow_squid_ink"
},
{
"id": 97,
"name": "glow"
},
{
"id": 98,
"name": "wax_on"
},
{
"id": 99,
"name": "wax_off"
},
{
"id": 100,
"name": "electric_spark"
},
{
"id": 101,
"name": "scrape"
},
{
"id": 102,
"name": "shriek"
},
{
"id": 103,
"name": "egg_crack"
},
{
"id": 104,
"name": "dust_plume"
},
{
"id": 105,
"name": "trial_spawner_detection"
},
{
"id": 106,
"name": "trial_spawner_detection_ominous"
},
{
"id": 107,
"name": "vault_connection"
},
{
"id": 108,
"name": "dust_pillar"
},
{
"id": 109,
"name": "ominous_spawning"
},
{
"id": 110,
"name": "raid_omen"
},
{
"id": 111,
"name": "trial_omen"
},
{
"id": 112,
"name": "block_crumble"
},
{
"id": 113,
"name": "firefly"
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

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