Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a347aedfc2 | ||
|
|
e324a95727 | ||
|
|
7f878f40a1 | ||
|
|
7baa9990cb | ||
|
|
5c5c391aa6 | ||
|
|
b966925212 | ||
|
|
efd7049859 | ||
|
|
3ef9446f50 | ||
|
|
68612969c9 | ||
|
|
b4712ff5c6 | ||
|
|
eab050e291 | ||
|
|
a8b090227b | ||
|
|
201e0dfa44 | ||
|
|
666f250b3e | ||
|
|
b2784574bd | ||
|
|
c0b1501f23 | ||
|
|
4de76d8023 | ||
|
|
f08684fb67 | ||
|
|
9e4f9409c5 | ||
|
|
4d3fbed957 | ||
|
|
b338888b58 | ||
|
|
4956c1d874 | ||
|
|
d4fe362d9f | ||
|
|
5d8ff870df | ||
|
|
58c9a60236 | ||
|
|
3e5b141ec7 | ||
|
|
efeb5ecda6 | ||
|
|
624e8ad4de | ||
|
|
764cfd8c4d | ||
|
|
d43ff7f83d | ||
|
|
a39999b344 | ||
|
|
8ee48e1a45 | ||
|
|
bf6affeb97 | ||
|
|
ede9a47ca2 | ||
|
|
2fb290572a | ||
|
|
021b8ddc1a | ||
|
|
05e8da2ff4 | ||
|
|
a8936d92a5 | ||
|
|
c56898e787 | ||
|
|
c483bf9553 | ||
|
|
b5ee17d1ae | ||
|
|
57cef25e27 | ||
|
|
148302c94d | ||
|
|
02c402251d | ||
|
|
c800c5d471 | ||
|
|
477f51cce7 | ||
|
|
60103ebf47 | ||
|
|
21eeebf0f7 | ||
|
|
bb6f6548d5 | ||
|
|
d6c07cf428 | ||
|
|
e98921755d | ||
|
|
60383fede1 | ||
|
|
3bfa70d384 | ||
|
|
60147fd4b6 | ||
|
|
fe51287cc6 | ||
|
|
a44c5f8606 | ||
|
|
d5979b2c3d | ||
|
|
4b95ea9ae2 | ||
|
|
a80fd29297 | ||
|
|
589f35d147 | ||
|
|
c7b5f6ba0f | ||
|
|
0600315e69 | ||
|
|
12005df68b | ||
|
|
600842a679 | ||
|
|
c93d907d46 | ||
|
|
be6d2fb461 | ||
|
|
b2df88203d | ||
|
|
2ed4498181 | ||
|
|
0316b0aa09 | ||
|
|
62d12386e1 | ||
|
|
00ea02a529 | ||
|
|
e5231dfdb6 | ||
|
|
3b1b436ecf | ||
|
|
47180bf650 | ||
|
|
325db98b99 | ||
|
|
ceae543352 | ||
|
|
aef9615476 | ||
|
|
5d3860b91e | ||
|
|
94d15258d8 | ||
|
|
ab03b36b4f | ||
|
|
da0c3c6f14 | ||
|
|
b01e482bc5 | ||
|
|
c560966177 | ||
|
|
66f86c5599 | ||
|
|
5a2bfcb1c1 | ||
|
|
b34f8d68ff | ||
|
|
c98472165b | ||
|
|
4888a07ebb | ||
|
|
a9bf30a609 | ||
|
|
35030601e1 | ||
|
|
66f32b4cd2 | ||
|
|
e6d08dccbb | ||
|
|
8a4f203621 | ||
|
|
69e230a79c | ||
|
|
1a5f4f4f8e | ||
|
|
d0844dd6b5 | ||
|
|
d8e36aa6fa | ||
|
|
6781e4c16d | ||
|
|
fccba0248a | ||
|
|
52b3c1ce17 | ||
|
|
3511cd2699 | ||
|
|
6dddccab7f | ||
|
|
9109d77f98 | ||
|
|
9eec1e8370 | ||
|
|
96481da0a4 | ||
|
|
159437c1ca | ||
|
|
3d34ad59e9 | ||
|
|
4878c6cc7b | ||
|
|
719a3bacd8 | ||
|
|
ce0c58e7c5 | ||
|
|
8a4e44760e | ||
|
|
e810a60848 | ||
|
|
0981339028 | ||
|
|
7746dd8ee9 | ||
|
|
378b78312e | ||
|
|
bd3139754d | ||
|
|
841d91b91d | ||
|
|
895ec53698 | ||
|
|
f6001c8b22 | ||
|
|
d87c362270 | ||
|
|
236e414242 | ||
|
|
5222390b65 | ||
|
|
dc04fdc7d0 | ||
|
|
8de34dfda1 | ||
|
|
997594d7c3 | ||
|
|
c83a777eac | ||
|
|
65fea61667 | ||
|
|
c2777f2ca9 | ||
|
|
dcb42f1970 | ||
|
|
7a2081f0a0 | ||
|
|
acc583f694 | ||
|
|
76409d07a3 | ||
|
|
bb89730781 | ||
|
|
9f6b1f6bcf | ||
|
|
1b8a9677c6 | ||
|
|
79ab13ca4b | ||
|
|
3cc414185a | ||
|
|
fe4a17dce2 | ||
|
|
891551f9dd | ||
|
|
8fb48c6ea9 | ||
|
|
ec25c28061 | ||
|
|
45287f47d2 | ||
|
|
4cc4b6af5b | ||
|
|
09e6760d61 | ||
|
|
ca74a3c3d4 | ||
|
|
fbc3693219 | ||
|
|
fb69fd93ec | ||
|
|
a3a3c3714e | ||
|
|
9294631c6c | ||
|
|
293f0b1eed | ||
|
|
272dcb4d36 | ||
|
|
abb0fa4d37 | ||
|
|
e5212624af | ||
|
|
35772aca03 | ||
|
|
819d89b26e | ||
|
|
958102e43c | ||
|
|
f749d3bfc5 | ||
|
|
d207cf52cf | ||
|
|
420ec5611c | ||
|
|
4bfaf9b7c5 | ||
|
|
48beb861e8 | ||
|
|
9fd2f517d9 | ||
|
|
1b5af63b75 | ||
|
|
dfb847eb3e | ||
|
|
322bd77f7f | ||
|
|
aefe850281 | ||
|
|
57f88f2176 | ||
|
|
8dd566c0da | ||
|
|
e386a45d96 | ||
|
|
8f771166a6 | ||
|
|
5d6a1a25ea | ||
|
|
ca33185f20 | ||
|
|
e921e9e9d2 | ||
|
|
7337068d86 | ||
|
|
9983062522 | ||
|
|
4fd5324064 | ||
|
|
df0e5e354b | ||
|
|
23b5a5d9b7 | ||
|
|
46770ed980 | ||
|
|
6179c5f6b6 | ||
|
|
f758a10575 | ||
|
|
41d1ab4c45 | ||
|
|
032fa26927 | ||
|
|
f7401a287e | ||
|
|
2d1e8ef4f6 | ||
|
|
2802cc36ce | ||
|
|
ea024fe808 | ||
|
|
a6102b7ec9 | ||
|
|
254b75e76b | ||
|
|
33cec6ecc9 | ||
|
|
fbf9bacd05 | ||
|
|
29baf8da9f | ||
|
|
43fea40d08 | ||
|
|
b107eba37c | ||
|
|
abc0f6aebb | ||
|
|
8c6172dcda | ||
|
|
57e78478cd | ||
|
|
f554e91354 | ||
|
|
e0cf1834f1 | ||
|
|
047c4d02ff | ||
|
|
c516cc5283 | ||
|
|
165075ea02 | ||
|
|
2eee7bde24 | ||
|
|
73d4e07b06 | ||
|
|
5dfd8fc499 | ||
|
|
1b36949316 | ||
|
|
5c455bd2c8 | ||
|
|
8d1a4b581b | ||
|
|
085b83cb71 | ||
|
|
65bf63d67e | ||
|
|
d8cc9cab48 | ||
|
|
f7d7a72b7c | ||
|
|
29b1b92113 | ||
|
|
bd83b9689e | ||
|
|
85529b965b |
@@ -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
|
||||||
@@ -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,3 +1,4 @@
|
|||||||
.devres/
|
.devres/
|
||||||
.idea/
|
.idea/
|
||||||
.failed_packets/
|
.failed_packets/
|
||||||
|
.vscode/
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[submodule "data/minecraft-data"]
|
|
||||||
path = data/minecraft-data
|
|
||||||
url = git@github.com:PrismarineJS/minecraft-data.git
|
|
||||||
@@ -1,202 +1,61 @@
|
|||||||
|
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
|
||||||
Apache License
|
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
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,
|
“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
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.
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
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”.
|
||||||
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.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
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.
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
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.
|
||||||
including but not limited to software source code, documentation
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
|
||||||
transformation or translation of a Source form, including but
|
2. Conveying Modified Versions.
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
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:
|
||||||
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).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
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
|
||||||
form, that is based on (or derived from) the Work and for which the
|
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
|
||||||
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.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
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."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
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:
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
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.
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
b) Accompany the object code with a copy of the GNU GPL and this license document.
|
||||||
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.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
4. Combined Works.
|
||||||
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. Redistribution. You may reproduce and distribute copies of the
|
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:
|
||||||
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:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
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.
|
||||||
Derivative Works a copy of this License; and
|
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
|
5. Combined Libraries.
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
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:
|
||||||
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
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
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.
|
||||||
distribution, then any Derivative Works that You distribute must
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
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.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
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.
|
||||||
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.
|
|
||||||
@@ -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)
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
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/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,
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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/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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package print_replay
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
_ "embed"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"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")
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: maybe use the proto replay api
|
||||||
|
|
||||||
|
//go:embed failed_packet.go.tmpl
|
||||||
|
var TestSrcF string
|
||||||
|
|
||||||
|
func SaveUnCodeAbleAsTest(packet *proto.UnCodablePacket) {
|
||||||
|
hUntrimmed := sha256.Sum256(packet.Data)
|
||||||
|
h := hUntrimmed[:8]
|
||||||
|
f, err := os.Create(fmt.Sprintf(*CreateTestsPath+"/%v_%10x_test.go", len(packet.Data), h))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := data.LookUpVersionByProtocolVersion(packet.Info.ProtocolVersion)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
goPackage := "v" + strings.ReplaceAll(ret.MinecraftVersion, ".", "_")
|
||||||
|
|
||||||
|
//goland:noinspection GoUnhandledErrorResult
|
||||||
|
defer f.Close()
|
||||||
|
_, err = fmt.Fprintf(f, TestSrcF, goPackage, packet.Err, h, reflect.TypeOf(packet.Info.Type).Elem().Name(), packet.Data)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
slog.Info("Packet failed to parse", "error", packet.Err, "saved", f.Name(), "data", fmt.Sprintf("%q", packet.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = proto_base.Login
|
||||||
|
|
||||||
|
func UnpackPacket(r io.Reader) (packet proto_base.EncodeDecodeAble, err error) {
|
||||||
|
var timestamp, length uint32
|
||||||
|
err = binary.Read(r, binary.BigEndian, ×tamp)
|
||||||
|
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()
|
||||||
|
|
||||||
|
i, ok := proto.LookUpTypeByPacketInfo(proto_base.ToClient, state, packetId, int32(*ProtocolVersion))
|
||||||
|
if !ok {
|
||||||
|
packet = &proto.UnCodablePacket{Err: proto.BadPacketIdErr, Data: packetData, Info: i}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
switch i.Name {
|
||||||
|
case "success":
|
||||||
|
state = proto_base.Configuration
|
||||||
|
case "finish_configuration":
|
||||||
|
state = proto_base.Play
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
f, err := os.Open(*FileFlag)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var totalPackets, failedPackets int
|
||||||
|
for {
|
||||||
|
var packet proto_base.EncodeDecodeAble
|
||||||
|
packet, err = UnpackPacket(f)
|
||||||
|
unCodablePacket, ok := packet.(*proto.UnCodablePacket)
|
||||||
|
if ok {
|
||||||
|
if *CreateTestsPath != "" {
|
||||||
|
SaveUnCodeAbleAsTest(unCodablePacket)
|
||||||
|
}
|
||||||
|
failedPackets++
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Packet: %#v\n", packet)
|
||||||
|
}
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
totalPackets++
|
||||||
|
}
|
||||||
|
fmt.Printf("Total packets: %d, failed packets: %d, %%failed: %.2f%%\n", totalPackets, failedPackets, float64(failedPackets)/float64(totalPackets)*100)
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
Submodule data/minecraft-data deleted from 9443ed0dec
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
module github.com/admin-else/strom
|
module github.com/admin-else/strom
|
||||||
|
|
||||||
go 1.24
|
go 1.26.2
|
||||||
|
|
||||||
require (
|
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/go-cmp v0.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
)
|
)
|
||||||
|
|||||||
-149
@@ -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
|
|
||||||
}
|
|
||||||
@@ -9,32 +9,40 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/admin-else/strom/crypto"
|
|
||||||
"github.com/google/uuid"
|
"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 {
|
type Account struct {
|
||||||
|
*Servers
|
||||||
Name string
|
Name string
|
||||||
Uuid uuid.UUID
|
Uuid uuid.UUID
|
||||||
Ygg string
|
Ygg string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
BaseUrlApi = "https://api.minecraftservices.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
|
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
|
||||||
var b []byte
|
var b []byte
|
||||||
if method != "GET" {
|
if method != "GET" {
|
||||||
b, err = json.Marshal(from)
|
b, err = json.Marshal(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := http.NewRequest(method, url, bytes.NewReader(b))
|
r, err := http.NewRequest(method, url, bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Set("Content-Type", "application/json")
|
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)
|
resp, err := http.DefaultClient.Do(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
|
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
|
||||||
return fmt.Errorf("bad status code %v", resp.StatusCode)
|
return fmt.Errorf("bad status code %v", resp.StatusCode)
|
||||||
}
|
}
|
||||||
b, err = io.ReadAll(resp.Body)
|
b, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(b, to)
|
err = json.Unmarshal(b, to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlayerKeys struct {
|
type PlayerKeys struct {
|
||||||
@@ -73,7 +82,7 @@ type PlayerKeys struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) FetchKeys() (keys PlayerKeys, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,10 +99,12 @@ func (a *Account) JoinServer(serverId string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != 204 {
|
if resp.StatusCode != 204 {
|
||||||
body, err = io.ReadAll(resp.Body)
|
body, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -112,17 +123,10 @@ type NameChangResp struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) NameChangeInfo() (r NameChangResp, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOfflineAccount(name string) *Account {
|
|
||||||
return &Account{
|
|
||||||
Name: name,
|
|
||||||
Uuid: crypto.FromOfflinePlayer(name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlayerProfile represents the player's profile information
|
// PlayerProfile represents the player's profile information
|
||||||
type PlayerProfile struct {
|
type PlayerProfile struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
@@ -143,7 +147,7 @@ type PlayerProfile struct {
|
|||||||
|
|
||||||
// QueryProfile queries the player's profile
|
// QueryProfile queries the player's profile
|
||||||
func (a *Account) QueryProfile() (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +185,7 @@ type PlayerAttributes struct {
|
|||||||
|
|
||||||
// QueryAttributes queries the player's attributes
|
// QueryAttributes queries the player's attributes
|
||||||
func (a *Account) QueryAttributes() (attrs PlayerAttributes, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +198,7 @@ type ModifyAttributesRequest struct {
|
|||||||
|
|
||||||
// ModifyAttributes modifies the player's attributes
|
// ModifyAttributes modifies the player's attributes
|
||||||
func (a *Account) ModifyAttributes(req ModifyAttributesRequest) (attrs PlayerAttributes, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,24 +209,23 @@ type BlockedUsers struct {
|
|||||||
|
|
||||||
// GetBlocklist gets the list of blocked users
|
// GetBlocklist gets the list of blocked users
|
||||||
func (a *Account) GetBlocklist() (blocklist BlockedUsers, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckNameAvailability checks if a name is available
|
|
||||||
type NameAvailability struct {
|
type NameAvailability struct {
|
||||||
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
|
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckNameAvailability checks if a name is available
|
// CheckNameAvailability checks if a name is available
|
||||||
func (a *Account) CheckNameAvailability(name string) (avail NameAvailability, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeName changes the player's name
|
// ChangeName changes the player's name
|
||||||
func (a *Account) ChangeName(newName string) (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,19 +237,19 @@ type ChangeSkinRequest struct {
|
|||||||
|
|
||||||
// ChangeSkin changes the player's skin
|
// ChangeSkin changes the player's skin
|
||||||
func (a *Account) ChangeSkin(req ChangeSkinRequest) (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetSkin resets the player's skin to default
|
// ResetSkin resets the player's skin to default
|
||||||
func (a *Account) ResetSkin() (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// HideCape hides the player's active cape
|
// HideCape hides the player's active cape
|
||||||
func (a *Account) HideCape() (profile PlayerProfile, err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,29 +261,53 @@ type ShowCapeRequest struct {
|
|||||||
// ShowCape shows a specific cape
|
// ShowCape shows a specific cape
|
||||||
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
|
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
|
||||||
req := ShowCapeRequest{CapeID: capeID}
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckGiftCodeValidity checks if a gift code is valid
|
// CheckGiftCodeValidity checks if a gift code is valid
|
||||||
func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error) {
|
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)
|
r, err := http.NewRequest("GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Header.Set("Authorization", "Bearer "+a.Ygg)
|
r.Header.Set("Authorization", "Bearer "+a.Ygg)
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(r)
|
resp, err := http.DefaultClient.Do(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Returns HTTP 200 or 204 if valid, 404 if invalid
|
// Returns HTTP 200 or 204 if valid, 404 if invalid
|
||||||
if resp.StatusCode == 200 || resp.StatusCode == 204 {
|
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
|
|
||||||
}
|
}
|
||||||
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/admin-else/strom/event"
|
"github.com/admin-else/strom/mc/event"
|
||||||
"github.com/admin-else/strom/proto"
|
"github.com/admin-else/strom/mc/proto"
|
||||||
"github.com/admin-else/strom/proto_base"
|
"github.com/admin-else/strom/mc/proto_base"
|
||||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
"github.com/admin-else/strom/mc/proto_generated/v1_21_8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigIgnorer struct {
|
type ConfigIgnorer struct {
|
||||||
*proto.Conn
|
*proto.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConfigIgnorer) Default(event any) (err error) {
|
func (c *ConfigIgnorer) OnStart() (err error) {
|
||||||
fmt.Printf("%#v\n", event)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
|
||||||
|
c.Log.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +27,7 @@ func (c *ConfigIgnorer) OnKnownPacks(packet *v1_21_8.ConfigurationToClientPacket
|
|||||||
return c.Send(&v1_21_8.ConfigurationToServerPacketCommonSelectKnownPacks{Packs: packet.Packs})
|
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{})
|
err = c.Send(&v1_21_8.ConfigurationToServerPacketFinishConfiguration{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -44,7 +48,14 @@ func (c *ConfigIgnorer) OnKeepAlive(packet *v1_21_8.ConfigurationToClientPacketK
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IgnoreConfig(c *proto.Conn) (err error) {
|
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 {
|
if err != nil {
|
||||||
err = errors.Join(err, errors.New("failed to ignore config"))
|
err = errors.Join(err, errors.New("failed to ignore config"))
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
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/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) 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.RegisterCriticalUntilLatest(lc.OnSuccess)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ package crypto
|
|||||||
import (
|
import (
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ type CFB8 struct {
|
|||||||
ivPos int
|
ivPos int
|
||||||
iv []byte
|
iv []byte
|
||||||
de bool
|
de bool
|
||||||
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCFB8Decrypt(c cipher.Block, iv []byte) *CFB8 {
|
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) {
|
func (cf *CFB8) XORKeyStream(dst, src []byte) {
|
||||||
|
cf.mutex.Lock()
|
||||||
|
defer cf.mutex.Unlock()
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"github.com/google/uuid"
|
"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
|
// 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 := md5.New()
|
||||||
h.Reset()
|
h.Reset()
|
||||||
h.Write(space) //nolint:errcheck
|
h.Write(space) //nolint:errcheck
|
||||||
@@ -22,5 +22,5 @@ func UUIDv3(space []byte, data []byte) uuid.UUID {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FromOfflinePlayer(displayName string) uuid.UUID {
|
func FromOfflinePlayer(displayName string) uuid.UUID {
|
||||||
return UUIDv3([]byte("OfflinePlayer:"), []byte(displayName))
|
return uuidV3([]byte("OfflinePlayer:"), []byte(displayName))
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
// [
|
// [
|
||||||
//
|
//
|
||||||
// {
|
// {
|
||||||
@@ -14,7 +16,7 @@ package data
|
|||||||
// },
|
// },
|
||||||
|
|
||||||
type Biome struct {
|
type Biome struct {
|
||||||
Id int
|
Id int32
|
||||||
Name string
|
Name string
|
||||||
Category string
|
Category string
|
||||||
Temperature float64
|
Temperature float64
|
||||||
@@ -24,22 +26,35 @@ type Biome struct {
|
|||||||
Color int
|
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
|
var ok bool
|
||||||
if ret, ok = BiomesCache[v]; ok {
|
if ret, ok = BiomesCache[v]; ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var b []Biome
|
var b []*Biome
|
||||||
must(LoadVersionedJson(v, "biomes", &b))
|
must(LoadVersionedJson(v, "biomes", &b))
|
||||||
BiomesCache[v] = b
|
BiomesCache[v] = b
|
||||||
return 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) {
|
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
|
biome = b
|
||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
@@ -3,7 +3,7 @@ package data_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/admin-else/strom/data"
|
"github.com/admin-else/strom/mc/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLookupBiomeById(t *testing.T) {
|
func TestLookupBiomeById(t *testing.T) {
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -4,9 +4,12 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"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
|
var MinecraftData embed.FS
|
||||||
|
|
||||||
func LoadJson(path string, data any) (err error) {
|
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) {
|
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 {
|
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)
|
err = json.Unmarshal(b, data)
|
||||||
return
|
return
|
||||||
@@ -9,5 +9,5 @@ var Paths struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
must(LoadJson("minecraft-data/data/dataPaths.json", &Paths))
|
must(LoadJson("minecraft-data/dataPaths.json", &Paths))
|
||||||
}
|
}
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/sh
|
||||||
|
set -xeuo pipefail
|
||||||
|
|
||||||
|
# Go get / install does not clone git submodules so we have to do it like this
|
||||||
|
|
||||||
|
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
|
||||||
@@ -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
@@ -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
@@ -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
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": 9470285
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": 11983713
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"dark_forest"
|
||||||
|
],
|
||||||
|
"color": 1317381
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"mangrove_swamp",
|
||||||
|
"swamp"
|
||||||
|
],
|
||||||
|
"color": 6975545
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"pale_garden"
|
||||||
|
],
|
||||||
|
"color": 7832178
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"foliage": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"badlands",
|
||||||
|
"eroded_badlands",
|
||||||
|
"wooded_badlands"
|
||||||
|
],
|
||||||
|
"color": 10387789
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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": 11983713
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"mangrove_swamp"
|
||||||
|
],
|
||||||
|
"color": 9285927
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"pale_garden"
|
||||||
|
],
|
||||||
|
"color": 8883574
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"swamp"
|
||||||
|
],
|
||||||
|
"color": 6975545
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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": 4159204
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"cherry_grove"
|
||||||
|
],
|
||||||
|
"color": 6141935
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"cold_ocean",
|
||||||
|
"deep_cold_ocean",
|
||||||
|
"snowy_beach",
|
||||||
|
"snowy_taiga"
|
||||||
|
],
|
||||||
|
"color": 4020182
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"deep_frozen_ocean",
|
||||||
|
"frozen_ocean",
|
||||||
|
"frozen_river"
|
||||||
|
],
|
||||||
|
"color": 3750089
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"deep_lukewarm_ocean",
|
||||||
|
"lukewarm_ocean"
|
||||||
|
],
|
||||||
|
"color": 4566514
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"mangrove_swamp"
|
||||||
|
],
|
||||||
|
"color": 3832426
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"meadow"
|
||||||
|
],
|
||||||
|
"color": 937679
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"pale_garden"
|
||||||
|
],
|
||||||
|
"color": 7768221
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"swamp"
|
||||||
|
],
|
||||||
|
"color": 6388580
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keys": [
|
||||||
|
"warm_ocean"
|
||||||
|
],
|
||||||
|
"color": 4445678
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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": 772,
|
||||||
|
"minecraftVersion": "1.21.8",
|
||||||
|
"majorVersion": "1.21",
|
||||||
|
"releaseType": "release"
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user