Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -1,3 +1,5 @@
|
||||
.devres/
|
||||
.idea/
|
||||
.failed_packets/
|
||||
tools/.*/
|
||||
.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
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
|
||||
0. Additional Definitions.
|
||||
|
||||
1. Definitions.
|
||||
As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
“The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
|
||||
b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license document.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
4. Combined Works.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license document.
|
||||
c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
|
||||
d) Do one of the following:
|
||||
0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
|
||||
1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
|
||||
e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
5. Combined Libraries.
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
|
||||
b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
|
||||
@@ -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)
|
||||
+64
-37
@@ -9,32 +9,40 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/admin-else/strom/crypto"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Servers struct {
|
||||
ServicesHost string
|
||||
SessionHost string
|
||||
ProfilesHost string
|
||||
}
|
||||
|
||||
var DefaultServers = &Servers{
|
||||
ServicesHost: "https://api.minecraftservices.com",
|
||||
SessionHost: "https://sessionserver.mojang.com",
|
||||
ProfilesHost: "https://api.mojang.com",
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
*Servers
|
||||
Name string
|
||||
Uuid uuid.UUID
|
||||
Ygg string
|
||||
}
|
||||
|
||||
const (
|
||||
BaseUrlApi = "https://api.minecraftservices.com"
|
||||
)
|
||||
|
||||
func (a *Account) doMcApiRequest(method string, url string, from any, to any) (err error) {
|
||||
var b []byte
|
||||
if method != "GET" {
|
||||
b, err = json.Marshal(from)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r, err := http.NewRequest(method, url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
@@ -44,21 +52,22 @@ func (a *Account) doMcApiRequest(method string, url string, from any, to any) (e
|
||||
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !(resp.StatusCode >= 200 && resp.StatusCode <= 299) {
|
||||
return fmt.Errorf("bad status code %v", resp.StatusCode)
|
||||
}
|
||||
b, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(b, to)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
type PlayerKeys struct {
|
||||
@@ -73,7 +82,7 @@ type PlayerKeys struct {
|
||||
}
|
||||
|
||||
func (a *Account) FetchKeys() (keys PlayerKeys, err error) {
|
||||
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/certificates", nil, &keys)
|
||||
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/certificates", nil, &keys)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -90,10 +99,12 @@ func (a *Account) JoinServer(serverId string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err := http.Post("https://sessionserver.mojang.com/session/minecraft/join", "application/json", bytes.NewReader(body))
|
||||
resp, err := http.Post(a.SessionHost+"/session/minecraft/join", "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 204 {
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
@@ -112,17 +123,10 @@ type NameChangResp struct {
|
||||
}
|
||||
|
||||
func (a *Account) NameChangeInfo() (r NameChangResp, err error) {
|
||||
err = a.doMcApiRequest("GET", "https://api.minecraftservices.com/minecraft/profile/namechange", nil, &r)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/namechange", nil, &r)
|
||||
return
|
||||
}
|
||||
|
||||
func NewOfflineAccount(name string) *Account {
|
||||
return &Account{
|
||||
Name: name,
|
||||
Uuid: crypto.FromOfflinePlayer(name),
|
||||
}
|
||||
}
|
||||
|
||||
// PlayerProfile represents the player's profile information
|
||||
type PlayerProfile struct {
|
||||
ID string `json:"id"`
|
||||
@@ -143,7 +147,7 @@ type PlayerProfile struct {
|
||||
|
||||
// QueryProfile queries the player's profile
|
||||
func (a *Account) QueryProfile() (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile", nil, &profile)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile", nil, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -181,7 +185,7 @@ type PlayerAttributes struct {
|
||||
|
||||
// QueryAttributes queries the player's attributes
|
||||
func (a *Account) QueryAttributes() (attrs PlayerAttributes, err error) {
|
||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/player/attributes", nil, &attrs)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/player/attributes", nil, &attrs)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -194,7 +198,7 @@ type ModifyAttributesRequest struct {
|
||||
|
||||
// ModifyAttributes modifies the player's attributes
|
||||
func (a *Account) ModifyAttributes(req ModifyAttributesRequest) (attrs PlayerAttributes, err error) {
|
||||
err = a.doMcApiRequest("POST", BaseUrlApi+"/player/attributes", req, &attrs)
|
||||
err = a.doMcApiRequest("POST", a.ServicesHost+"/player/attributes", req, &attrs)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -205,24 +209,23 @@ type BlockedUsers struct {
|
||||
|
||||
// GetBlocklist gets the list of blocked users
|
||||
func (a *Account) GetBlocklist() (blocklist BlockedUsers, err error) {
|
||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/privacy/blocklist", nil, &blocklist)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/privacy/blocklist", nil, &blocklist)
|
||||
return
|
||||
}
|
||||
|
||||
// CheckNameAvailability checks if a name is available
|
||||
type NameAvailability struct {
|
||||
Status string `json:"status"` // DUPLICATE, AVAILABLE, NOT_ALLOWED
|
||||
}
|
||||
|
||||
// CheckNameAvailability checks if a name is available
|
||||
func (a *Account) CheckNameAvailability(name string) (avail NameAvailability, err error) {
|
||||
err = a.doMcApiRequest("GET", BaseUrlApi+"/minecraft/profile/name/"+name+"/available", nil, &avail)
|
||||
err = a.doMcApiRequest("GET", a.ServicesHost+"/minecraft/profile/name/"+name+"/available", nil, &avail)
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeName changes the player's name
|
||||
func (a *Account) ChangeName(newName string) (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/name/"+newName, nil, &profile)
|
||||
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/name/"+newName, nil, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -234,19 +237,19 @@ type ChangeSkinRequest struct {
|
||||
|
||||
// ChangeSkin changes the player's skin
|
||||
func (a *Account) ChangeSkin(req ChangeSkinRequest) (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("POST", BaseUrlApi+"/minecraft/profile/skins", req, &profile)
|
||||
err = a.doMcApiRequest("POST", a.ServicesHost+"/minecraft/profile/skins", req, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
// ResetSkin resets the player's skin to default
|
||||
func (a *Account) ResetSkin() (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/skins/active", nil, &profile)
|
||||
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/skins/active", nil, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
// HideCape hides the player's active cape
|
||||
func (a *Account) HideCape() (profile PlayerProfile, err error) {
|
||||
err = a.doMcApiRequest("DELETE", BaseUrlApi+"/minecraft/profile/capes/active", nil, &profile)
|
||||
err = a.doMcApiRequest("DELETE", a.ServicesHost+"/minecraft/profile/capes/active", nil, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -258,29 +261,53 @@ type ShowCapeRequest struct {
|
||||
// ShowCape shows a specific cape
|
||||
func (a *Account) ShowCape(capeID string) (profile PlayerProfile, err error) {
|
||||
req := ShowCapeRequest{CapeID: capeID}
|
||||
err = a.doMcApiRequest("PUT", BaseUrlApi+"/minecraft/profile/capes/active", req, &profile)
|
||||
err = a.doMcApiRequest("PUT", a.ServicesHost+"/minecraft/profile/capes/active", req, &profile)
|
||||
return
|
||||
}
|
||||
|
||||
// CheckGiftCodeValidity checks if a gift code is valid
|
||||
func (a *Account) CheckGiftCodeValidity(giftCode string) (valid bool, err error) {
|
||||
url := BaseUrlApi + "/productvoucher/giftcode"
|
||||
url := a.ServicesHost + "/productvoucher/" + giftCode
|
||||
r, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return
|
||||
}
|
||||
|
||||
r.Header.Set("Authorization", "Bearer "+a.Ygg)
|
||||
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Returns HTTP 200 or 204 if valid, 404 if invalid
|
||||
if resp.StatusCode == 200 || resp.StatusCode == 204 {
|
||||
return true, nil
|
||||
valid = true
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewAccountFromYGG(ygg string) (a *Account, err error) {
|
||||
a = &Account{
|
||||
Ygg: ygg,
|
||||
Servers: DefaultServers,
|
||||
}
|
||||
p, err := a.QueryProfile()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a.Name = p.Name
|
||||
a.Uuid, err = uuid.Parse(strings.ReplaceAll(p.ID, "-", ""))
|
||||
return
|
||||
}
|
||||
|
||||
func NewAccount(name string, uuid uuid.UUID, ygg string) (a *Account) {
|
||||
return &Account{
|
||||
Name: name,
|
||||
Uuid: uuid,
|
||||
Ygg: ygg,
|
||||
Servers: DefaultServers,
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package api
|
||||
|
||||
import "github.com/admin-else/strom/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
|
||||
}
|
||||
+22
-5
@@ -1,22 +1,39 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
)
|
||||
|
||||
// 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
|
||||
func Connect(addr string) (ret *proto.Conn, err error) {
|
||||
ret = &proto.Conn{}
|
||||
err = ret.SetVersion("1.21.8")
|
||||
s, err := Status(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ret.SetState(proto_base.Handshaking)
|
||||
ret.CompressionThreshold = -1
|
||||
ret, err = ConnectVersionLess(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = ret.SetProtocolVersion(s.Version.Protocol)
|
||||
return
|
||||
}
|
||||
|
||||
func ConnectVersionLess(addr string) (ret *proto.Conn, err error) {
|
||||
return ConnectVersionLessCtx(context.Background(), addr)
|
||||
}
|
||||
|
||||
// ConnectVersionLess connects to the given address without setting the version you should set it
|
||||
// when using this function.
|
||||
// You must close the connection when you're done with it
|
||||
func ConnectVersionLessCtx(ctx context.Context, addr string) (ret *proto.Conn, err error) {
|
||||
ret = proto.NewConnCtx(ctx)
|
||||
ret.Actor = proto_base.Client
|
||||
ret.Conn, err = net.Dial("tcp", addr)
|
||||
ret.Conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package modules
|
||||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -14,8 +14,12 @@ type ConfigIgnorer struct {
|
||||
*proto.Conn
|
||||
}
|
||||
|
||||
func (c *ConfigIgnorer) Default(event any) (err error) {
|
||||
fmt.Printf("%#v\n", event)
|
||||
func (c *ConfigIgnorer) OnStart() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ConfigIgnorer) Default(e event.Anything) (err error) {
|
||||
c.Log.Debug("Config ignorer", "packet", fmt.Sprintf("%#v", e))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -23,7 +27,7 @@ func (c *ConfigIgnorer) OnKnownPacks(packet *v1_21_8.ConfigurationToClientPacket
|
||||
return c.Send(&v1_21_8.ConfigurationToServerPacketCommonSelectKnownPacks{Packs: packet.Packs})
|
||||
}
|
||||
|
||||
func (c *ConfigIgnorer) OnFinish(_ v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
|
||||
func (c *ConfigIgnorer) OnFinish(_ *v1_21_8.ConfigurationToClientPacketFinishConfiguration) (err error) {
|
||||
err = c.Send(&v1_21_8.ConfigurationToServerPacketFinishConfiguration{})
|
||||
if err != nil {
|
||||
return
|
||||
@@ -44,7 +48,14 @@ func (c *ConfigIgnorer) OnKeepAlive(packet *v1_21_8.ConfigurationToClientPacketK
|
||||
}
|
||||
|
||||
func IgnoreConfig(c *proto.Conn) (err error) {
|
||||
err = c.Start(&ConfigIgnorer{c})
|
||||
ci := &ConfigIgnorer{c}
|
||||
ci.RegisterCritical(ci.Default)
|
||||
ci.RegisterUntilLatest(ci.OnKnownPacks)
|
||||
ci.RegisterUntilLatest(ci.OnFinish)
|
||||
ci.RegisterUntilLatest(ci.OnPing)
|
||||
ci.RegisterUntilLatest(ci.OnKeepAlive)
|
||||
|
||||
err = ci.StartConn()
|
||||
if err != nil {
|
||||
err = errors.Join(err, errors.New("failed to ignore config"))
|
||||
}
|
||||
+113
-9
@@ -1,22 +1,126 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func LookupMinecraftSRV(host string, port uint16) (hostRet string, portRet uint16) {
|
||||
hostRet = host // in case of error
|
||||
portRet = port
|
||||
if port != 25565 {
|
||||
type DNSCheckFunc func(addr string) error
|
||||
|
||||
// DoDns resolves a DNS name to an IP address
|
||||
// It should resolve a minecraft compatible address into something net.Dial
|
||||
func DoDns(in string) (out string, host string, portN uint16, err error) {
|
||||
return DoDNSChecked(in, nil)
|
||||
}
|
||||
|
||||
// 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(in string, f DNSCheckFunc) (connectTo string, host string, portN uint16, err error) {
|
||||
// https://mcsrc.dev/1/26.1-pre-1/net/minecraft/client/multiplayer/resolver/ServerAddress#L34
|
||||
host, port, err := net.SplitHostPort(in)
|
||||
if err != nil {
|
||||
var addrErr *net.AddrError
|
||||
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
|
||||
err = nil
|
||||
host = in
|
||||
port = "25565"
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
porti, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, srvs, err := net.LookupSRV("minecraft", "tcp", host)
|
||||
if err != nil || len(srvs) == 0 {
|
||||
if f == nil {
|
||||
f = func(addr string) error { return nil }
|
||||
}
|
||||
|
||||
err = f(host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
target := strings.TrimSuffix(srvs[0].Target, ".")
|
||||
hostRet = target
|
||||
portRet = srvs[0].Port
|
||||
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(context.Background(), 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
|
||||
}
|
||||
|
||||
// DoDnsSimple resolves a DNS name to an IP address
|
||||
// It should resolve a minecraft compatible address into something net.Dial
|
||||
// It does not check the address
|
||||
func DoDnsSimple(in string) (out string, err error) {
|
||||
host, port, err := net.SplitHostPort(in)
|
||||
if err != nil {
|
||||
var addrErr *net.AddrError
|
||||
if errors.As(err, &addrErr) && addrErr.Err == "missing port in address" {
|
||||
err = nil
|
||||
host = in
|
||||
port = "25565"
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
porti, err := strconv.Atoi(port)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
portN := uint16(porti)
|
||||
_, err = net.ResolveIPAddr("ip", host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
out = host + ":" + strconv.Itoa(int(portN))
|
||||
if portN != 25565 {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 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
|
||||
out = host + ":" + strconv.Itoa(int(portN))
|
||||
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 := DoDns(c.Input)
|
||||
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/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package modules
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@@ -6,12 +6,8 @@ import (
|
||||
"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"
|
||||
@@ -30,6 +26,12 @@ type LoginClient struct {
|
||||
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
|
||||
}
|
||||
@@ -38,46 +40,20 @@ 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 {
|
||||
func (s *LoginClient) OnDefault(event event.Unhandled) (err error) {
|
||||
if _, ok := event.Val.(proto_base.EncodeDecodeAble); !ok {
|
||||
return
|
||||
}
|
||||
s.SetState(proto_base.Login)
|
||||
err = s.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: s.Account.Name, PlayerUUID: s.Account.Uuid})
|
||||
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.CompressionThreshold = compress.Threshold
|
||||
s.SetCompressionThreshold(compress.Threshold)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,7 +63,7 @@ func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBeg
|
||||
|
||||
if packet.ShouldAuthenticate {
|
||||
if s.Account.Ygg == "" {
|
||||
err = fmt.Errorf("the account has no token so we cant join online servers")
|
||||
err = NoTokenErr
|
||||
return
|
||||
}
|
||||
serverId := crypto.AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey)
|
||||
@@ -103,7 +79,7 @@ func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBeg
|
||||
}
|
||||
pub, ok := pubAny.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
err = fmt.Errorf("public key is not rsa")
|
||||
err = BadPublicKeyTypeErr
|
||||
return
|
||||
}
|
||||
verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, packet.VerifyToken)
|
||||
@@ -145,28 +121,59 @@ func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (er
|
||||
return
|
||||
}
|
||||
|
||||
func Login(c *proto.Conn, account *api.Account) (err error) {
|
||||
err = c.Start(&LoginClient{
|
||||
func (s *LoginClient) OnStart() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func LoginRaw(c *proto.Conn, account *api.Account, addr string) (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 {
|
||||
err = errors.Join(err, errors.New("failed to login"))
|
||||
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 ConnectAndLogin(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
||||
c, err = client.Connect(connectTo)
|
||||
func LoginNoSrv(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
||||
c, err = Connect(connectTo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.Start(&LoginClient{
|
||||
Conn: c,
|
||||
Account: account,
|
||||
})
|
||||
err = LoginRaw(c, account, connectTo)
|
||||
return
|
||||
}
|
||||
|
||||
func Login(connectTo string, account *api.Account) (c *proto.Conn, err error) {
|
||||
connectTo, err = DoDnsSimple(connectTo)
|
||||
if err != nil {
|
||||
err = errors.Join(errors.New("failed to login"), err)
|
||||
return
|
||||
}
|
||||
c, err = Connect(connectTo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = LoginRaw(c, account, connectTo)
|
||||
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,91 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/server"
|
||||
|
||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
||||
)
|
||||
|
||||
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(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(addr string) (status server.StatusResponse, err error) {
|
||||
addr, err = DoDnsSimple(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return StatusNoDns(addr)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/admin-else/strom/api"
|
||||
)
|
||||
|
||||
var cmd = flag.NewFlagSet("api", flag.ContinueOnError)
|
||||
var tokenFlag = cmd.String("token", os.Getenv("MC_TOKEN"), "token to use")
|
||||
|
||||
var renameFlag = cmd.String("rename", "", "rename the player")
|
||||
|
||||
var uuidFlag = cmd.String("uuid", "", "get data about player with uuid")
|
||||
var nameFlag = cmd.String("name", "", "get data about player with name")
|
||||
var unsignedFlag = cmd.Bool("unsigned", true, "use unsigned mode for properties signatures")
|
||||
|
||||
var PleaseProvideArgumentErr = errors.New("please provide an argument")
|
||||
|
||||
func Run(args []string) (err error) {
|
||||
err = cmd.Parse(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
a := &api.Account{}
|
||||
if *tokenFlag != "" {
|
||||
a, err = api.NewAccountFromYGG(*tokenFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
var data any
|
||||
if *renameFlag != "" {
|
||||
data, err = a.ChangeName(*renameFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if *uuidFlag != "" || *nameFlag != "" {
|
||||
if *nameFlag != "" {
|
||||
var idName api.IdName
|
||||
idName, err = api.NameToId(*nameFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
*uuidFlag = idName.Id
|
||||
}
|
||||
data, err = api.Profile(*uuidFlag, *unsignedFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = PleaseProvideArgumentErr
|
||||
return
|
||||
}
|
||||
err = json.NewEncoder(os.Stdout).Encode(data)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"os"
|
||||
"slices"
|
||||
|
||||
"github.com/admin-else/strom/cmd/strom/api"
|
||||
"github.com/admin-else/strom/cmd/strom/offline_uuid"
|
||||
"github.com/admin-else/strom/cmd/strom/packet_inspector"
|
||||
"github.com/admin-else/strom/cmd/strom/print_nbt"
|
||||
"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")
|
||||
|
||||
var subcommands = map[string]func(args []string) error{
|
||||
"offline-uuid": offline_uuid.Run,
|
||||
"print-nbt": print_nbt.Run,
|
||||
"status": status.Run,
|
||||
"packet-spy": packet_inspector.Run,
|
||||
"api": api.Run,
|
||||
}
|
||||
|
||||
func Help(args []string) (err error) {
|
||||
fmt.Println("Available subcommands", slices.Collect(maps.Keys(subcommands)))
|
||||
return
|
||||
}
|
||||
|
||||
func mainE() (err error) {
|
||||
subcommands["help"] = Help // this is here because if it isn't tools/cli/main.go:17:5: initialization cycle for subcommands
|
||||
// tools/cli/main.go:17:5: subcommands refers to Help
|
||||
// tools/cli/main.go:22:6: Help refers to subcommands
|
||||
args := os.Args
|
||||
|
||||
// Debug stuff
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
//args = []string{"TEST", "api", "-name", "Admin_Else"}
|
||||
|
||||
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,20 @@
|
||||
package offline_uuid
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/admin-else/strom/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,129 @@
|
||||
package packet_inspector
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log/slog"
|
||||
|
||||
"github.com/admin-else/strom/api"
|
||||
"github.com/admin-else/strom/client"
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/nbt"
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/proto_generated/v1_21_11"
|
||||
"github.com/admin-else/strom/server"
|
||||
"github.com/admin-else/strom/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")
|
||||
|
||||
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)
|
||||
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
errChan <- p.Client.StartConn()
|
||||
}()
|
||||
go func() {
|
||||
errChan <- p.Servee.StartConn()
|
||||
}()
|
||||
err = <-errChan
|
||||
return
|
||||
}
|
||||
|
||||
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(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
|
||||
}
|
||||
|
||||
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.SetLogLoggerLevel(slog.LevelDebug)
|
||||
slog.Info("Starting packet inspector", "listen", *ListenAddr)
|
||||
err = server.StartServerWithOnConn(*ListenAddr, func(serveeConn *proto.Conn) (err error) {
|
||||
serveeConn.DebugPrintPackets = []string{""}
|
||||
p := &Proxy{Servee: serveeConn, Client: nil}
|
||||
var acc = api.NewOfflineAccount(*OfflineNameFlag)
|
||||
if *Token != "" {
|
||||
acc, err = api.NewAccountFromYGG(*Token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
_, err = server.ServeLogin(serveeConn, server.WithOtherAccount(acc), server.WithStatus(StatusResponse))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
*TargetAddr, err = client.DoDnsSimple(*TargetAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c, err := client.Connect(*TargetAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.DebugPrintPackets = []string{""}
|
||||
err = client.LoginRaw(c, acc, *TargetAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
p.Client = c
|
||||
err = p.Start()
|
||||
return
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package print_nbt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/admin-else/strom/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
|
||||
}
|
||||
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,98 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/admin-else/strom/client"
|
||||
"github.com/admin-else/strom/event"
|
||||
"github.com/admin-else/strom/proto"
|
||||
"github.com/admin-else/strom/proto_base"
|
||||
"github.com/admin-else/strom/proto_generated/v1_21_8"
|
||||
)
|
||||
|
||||
var cmd = flag.NewFlagSet("status", flag.ContinueOnError)
|
||||
var addrFlag = cmd.String("addr", "localhost:25565", "address to connect to")
|
||||
var srvFlag = cmd.Bool("srv", false, "use SRV records to resolve the address")
|
||||
var versionFlag = cmd.String("version", "1.21.8", "version to use")
|
||||
|
||||
type StatusClient struct {
|
||||
*proto.Conn
|
||||
Status string
|
||||
PingSendTime, PingReceiveTime time.Time
|
||||
DoPingRoundTripTime bool
|
||||
}
|
||||
|
||||
func (s *StatusClient) OnStatus(p *v1_21_8.StatusToClientPacketServerInfo) (err error) {
|
||||
s.Status = p.Response
|
||||
s.PingSendTime = time.Now()
|
||||
if s.DoPingRoundTripTime {
|
||||
err = s.Send(&v1_21_8.StatusToServerPacketPing{Time: s.PingSendTime.UnixMilli()})
|
||||
} else {
|
||||
err = event.HandlerDoneErr{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *StatusClient) OnPong(p *v1_21_8.StatusToClientPacketPing) (err error) {
|
||||
s.PingReceiveTime = time.Now()
|
||||
err = event.HandlerDoneErr{}
|
||||
return
|
||||
}
|
||||
|
||||
// StatusRaw returns a StatusClient that is not connected to a server.
|
||||
// So ignore the resource leak warning. And maybe attach a warn ignore comment.
|
||||
// It does not resolve SRV records.
|
||||
func StatusRaw(addr string) (s *StatusClient, err error) {
|
||||
c, err := client.ConnectVersionLess(addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
err = c.SetVersion(*versionFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s = &StatusClient{
|
||||
Conn: c,
|
||||
}
|
||||
s.RegisterCriticalUntilLatest(s.OnStatus)
|
||||
s.RegisterCriticalUntilLatest(s.OnPong)
|
||||
|
||||
p, err := client.MakeHandshakePacketAddr(s.Conn, proto_base.Status, addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = s.Send(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.SetState(proto_base.Status)
|
||||
err = s.Send(&v1_21_8.StatusToServerPacketPingStart{})
|
||||
|
||||
err = s.StartConn()
|
||||
return
|
||||
}
|
||||
|
||||
func Run(args []string) (err error) {
|
||||
err = cmd.Parse(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if *srvFlag {
|
||||
*addrFlag, err = client.DoDnsSimple(*addrFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
slog.Debug("resolved address to ", "addr", *addrFlag)
|
||||
|
||||
status, err := StatusRaw(*addrFlag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(status.Status)
|
||||
return
|
||||
}
|
||||
+21
-6
@@ -1,5 +1,7 @@
|
||||
package data
|
||||
|
||||
import "errors"
|
||||
|
||||
// [
|
||||
//
|
||||
// {
|
||||
@@ -14,7 +16,7 @@ package data
|
||||
// },
|
||||
|
||||
type Biome struct {
|
||||
Id int
|
||||
Id int32
|
||||
Name string
|
||||
Category string
|
||||
Temperature float64
|
||||
@@ -24,22 +26,35 @@ type Biome struct {
|
||||
Color int
|
||||
}
|
||||
|
||||
var BiomesCache = make(map[string][]Biome)
|
||||
var BiomesCache = make(map[string][]*Biome)
|
||||
|
||||
func BiomesForVersion(v string) (ret []Biome) {
|
||||
func BiomesForVersion(v string) (ret []*Biome) {
|
||||
var ok bool
|
||||
if ret, ok = BiomesCache[v]; ok {
|
||||
return
|
||||
}
|
||||
var b []Biome
|
||||
var b []*Biome
|
||||
must(LoadVersionedJson(v, "biomes", &b))
|
||||
BiomesCache[v] = b
|
||||
return b
|
||||
}
|
||||
|
||||
func LookupBiomeById(version string, id int32) (biome Biome, ok bool) {
|
||||
func LookupBiomeById(version string, id int32) (biome *Biome, ok bool) {
|
||||
for _, b := range BiomesForVersion(version) {
|
||||
if b.Id == int(id) {
|
||||
if b.Id == id {
|
||||
biome = b
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var UnknownBiomeNameErr = errors.New("unknown biome Name")
|
||||
|
||||
func LookupBiomeByName(version string, name string) (biome *Biome, ok bool) {
|
||||
for _, b := range BiomesForVersion(version) {
|
||||
if b.Name == name {
|
||||
biome = b
|
||||
ok = true
|
||||
return
|
||||
|
||||
+146
-13
@@ -1,6 +1,11 @@
|
||||
package data
|
||||
|
||||
// {
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// {
|
||||
// "id": 0,
|
||||
// "name": "air",
|
||||
// "displayName": "Air",
|
||||
@@ -35,34 +40,162 @@ type Block struct {
|
||||
DefaultState int32
|
||||
MinStateId int32
|
||||
MaxStateId int32
|
||||
States []struct {
|
||||
Name string
|
||||
Type string
|
||||
NumValues int
|
||||
}
|
||||
Drops []int
|
||||
BoundingBox string
|
||||
States []BlockState
|
||||
Drops []int
|
||||
BoundingBox string
|
||||
}
|
||||
|
||||
var BlocksCache = make(map[string][]Block)
|
||||
type BlockState struct {
|
||||
Name string
|
||||
Type string
|
||||
NumValues int32 `json:"num_values"`
|
||||
Values []string
|
||||
}
|
||||
|
||||
func BlocksForVersion(v string) (ret []Block) {
|
||||
var BlocksCache = make(map[string][]*Block)
|
||||
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
|
||||
var b []*Block
|
||||
must(LoadVersionedJson(v, "blocks", &b))
|
||||
BlocksCache[v] = b
|
||||
return b
|
||||
}
|
||||
|
||||
func LookupBlockByStateId(version string, stateId int32) (block Block, ok bool) {
|
||||
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.MinStateId <= stateId && stateId <= b.MaxStateId {
|
||||
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
|
||||
}
|
||||
stateId, err = b.IdFromStateMap(stateMap)
|
||||
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), 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
|
||||
}
|
||||
|
||||
@@ -14,3 +14,21 @@ func TestLookupBlockByStateId(t *testing.T) {
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
//go:embed minecraft-data/data
|
||||
//go:embed minecraft-data/data/pc minecraft-data/data/dataPaths.json
|
||||
var MinecraftData embed.FS
|
||||
|
||||
func LoadJson(path string, data any) (err error) {
|
||||
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# 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
|
||||
rm -rf minecraft-data/.git/
|
||||
@@ -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
|
||||
}
|
||||
Submodule data/minecraft-data deleted from 9443ed0dec
+2
@@ -0,0 +1,2 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
name: Blank Issue
|
||||
about: Open a new issue
|
||||
---
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: New Version
|
||||
about: Add a new minecraft version to MCD
|
||||
title: 'Add version: '
|
||||
---
|
||||
|
||||
- [ ] Version folder containing "version.json" created
|
||||
- [ ] "version.json" filled out appropriately
|
||||
- [ ] Added version to dataPath.json
|
||||
- [ ] "version" field of dataPath.json points to new version folder
|
||||
- [ ] Version added to common/versions.json
|
||||
- [ ] Version added to ReadMe
|
||||
|
||||
Additional Requirements For This Update:
|
||||
@@ -0,0 +1,93 @@
|
||||
PrismarineJS/minecraft-data holds Minecraft data including game versions, protocol schemas, block/item/entity states, and more. It provides a comprehensive cross-language set of data files combined with schemas that can be consumed in any language.
|
||||
|
||||
## Directory Structure
|
||||
- `.github/`
|
||||
- `workflows/`
|
||||
- `ci.yml` -- Runs CI here (cd tools/js && npm install && npm test)
|
||||
- `bedrock-ci.yml` -- Runs CI for Bedrock Edition via workflow dispatch to PrismarineJS/bedrock-protocol. This allows for testing and validation of Bedrock-specific changes.
|
||||
- `commands.yml` -- Slash commands
|
||||
- `handleMcpcGeneratedArtifacts.js`
|
||||
- `helper-bot/` -- Files for the GitHub Actions cron helper.
|
||||
- `data/`
|
||||
- `dataPaths.json` -- map of { ['pc' | 'bedrock']: { '$version': { 'dataType': 'folderPath' } } }
|
||||
- `[pc | bedrock]/` -- Minecraft Java Edition data
|
||||
- `1.20/` -- Versioned data for Minecraft Java Edition 1.20
|
||||
- `$version/` -- Versioned data for Minecraft Java Edition $version
|
||||
- `common/` -- Common data for all Minecraft Java Edition versions
|
||||
- `protocolVersions.json` -- List of protocol versions for Minecraft Java Edition
|
||||
- `latest/` -- This holds the latest protocol yaml files. They are used to generate the protocol.json files in each versioned folder (more on this below).
|
||||
- `schemas/` -- JSON schemas for validating data files
|
||||
- `tools/js` -- JavaScript utilities for working with repo (holds tests, some data generators and helper scripts)
|
||||
|
||||
## Setup
|
||||
No setup is needed as we just hold data. To set up the development environment that runs tests, run the following commands:
|
||||
|
||||
```bash
|
||||
cd tools/js
|
||||
npm install
|
||||
```
|
||||
|
||||
This will ensure all data matches schema among other checks.
|
||||
|
||||
There are no 'releases' in this repo beyond updating the data itself. Instead, we have a workflow that will automatically create tagged releases if the user runs the /makerelease slash command, so you can inform the user about that if a release is needed.
|
||||
|
||||
## Testing
|
||||
Always run tests after data changes to ensure local tests are passing:
|
||||
|
||||
```sh
|
||||
cd tools/js
|
||||
npm install
|
||||
npm test -- --bail 2>&1 | tail -100
|
||||
```
|
||||
|
||||
## Data
|
||||
|
||||
Most data is generated with data generators. For mcpc, data is generated with [minecraft-data-generator](https://github.com/PrismarineJS/minecraft-data-generator).
|
||||
|
||||
Avoid updating data manually unless necessary, like when specific data is not automated yet (for tips on what is and not, see doc/add-data-new-version.md). Instead, update the data generators or add new ones as needed. Since you don't have capability to make changes outside this repo, please inform the user what changes need to be made and be as detailed as possible.
|
||||
|
||||
Note we do not currently have any automation setup to generate bedrock edition data.
|
||||
|
||||
## Github Workflows
|
||||
|
||||
### Automation
|
||||
|
||||
Our GH Actions workflow (.github/helper-bot) auto updates the pc protocolVersions.json with new detected versions and also auto opens a scaffolding PR.
|
||||
|
||||
cron workflow .github/workflows/update-helper.yml -> .github/helper-bot/.
|
||||
|
||||
helper-bot dispatches to minecraft-data-generator so that it can run data generators for new versions. minecraft-data-generator then sends a workflow dispatch back to us so we can then take its generated artifacts and commit them here.
|
||||
|
||||
.github/workflows/handle-mcpc-generator.yml ->
|
||||
.github/helper-bot/handleMcpcGeneratedArtifacts.js
|
||||
|
||||
## Notes
|
||||
Not all data is generated. Some data (like protocol schemas) is manually curated on both pc and bedrock.
|
||||
|
||||
### Protocol data
|
||||
|
||||
We use a special YAML-like DSL to generate protocol.json files. Refer to doc/protocol.md for info.
|
||||
These files are stored inside proto.yml (and an imported types.yml support file on bedrock) files in the latest/ folder (like bedrock/latest/proto.yml) for the latest version, otherwise in the versioned folder (like pc/1.20/proto.yml).
|
||||
|
||||
👉 Run `npm run build` in tools/js to regenerate protocol.json files after making changes to the protocol yaml files.
|
||||
|
||||
❌ Don't make changes to protocol.json files directly. Instead, update the relevant proto.yml file in latest/ and regenerate protocol.json by running `npm run build` in tools/js.
|
||||
|
||||
If you need to edit many files at once, consider writing a simple Node.js script to replace. E.g., from `cd tools/js && npm i && node __replace_something.js`):
|
||||
```js
|
||||
const cp = require('child_process')
|
||||
const fs = require('fs')
|
||||
const glob = require('glob')
|
||||
const pcVersionsOrdered = require('../../data/pc/common/versions.json')
|
||||
const after1_20_5 = pcVersionsOrdered.slice(pcVersionsOrdered.indexOf('1.20.5')) // everything after 1.20.5...
|
||||
for (const version of pcVersionsOrdered) {
|
||||
// globSync, fs.readFileSync...fs.writeFileSync ; avoid async
|
||||
}
|
||||
```
|
||||
|
||||
When updating, apply minimum required changes when possible.
|
||||
|
||||
1. Do not rename fields or packets unless their value has changed, even if they may have 'officially' changed. If their values *have* significantly changed (varint -> i32 maybe insignificant from primitive standpoint, string to number is), it maybe a good idea to rename the field or packet.
|
||||
2. Do not rename existing enums or other data, even if they were renamed in the game. You may add or remove data as is needed as this does not unnecessarily affect existing code relying on old names.
|
||||
3. Always read the information in doc/protocol.md to understand our YAML format.
|
||||
4. Try to inline types as much as possible--this includes mappers and switch statements. Don't create extra types unless they are actually re-used in multiple places, or very large to warrant seperation.
|
||||
@@ -0,0 +1,113 @@
|
||||
const fs = require('fs')
|
||||
const github = require('gh-helpers')()
|
||||
const { join } = require('path')
|
||||
const { extractPcEntityMetadata } = require('../../tools/js/extractPcEntityMetadata')
|
||||
const { exec, createInitialPR } = require('./utils')
|
||||
|
||||
const artifactsDir = join(__dirname, './artifacts')
|
||||
const root = join(__dirname, '..', '..')
|
||||
|
||||
async function handle (ourPR, genPullNo, version, artifactURL, shouldPull) {
|
||||
const branchNameVersion = version.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()
|
||||
const branch = ourPR.headBranch || `pc-${branchNameVersion}`
|
||||
if (shouldPull) exec('git', ['pull', 'origin'])
|
||||
|
||||
// if external PR:
|
||||
// const branch = ourPR.headBranch
|
||||
// exec('git', ['remote', 'add', 'fo', ourPR.headCloneURL])
|
||||
// exec('git', ['fetch', 'fo', branch])
|
||||
// exec('git', ['checkout', '-b', branch, `fo/` + branch])
|
||||
|
||||
try {
|
||||
exec('git', ['checkout', branch])
|
||||
} catch (err) {
|
||||
console.error('Error checking out branch:', err)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const dataPaths = require('../../data/dataPaths.json')
|
||||
const dataPath = dataPaths.pc[version]
|
||||
|
||||
const destDir = join(root, `./data/pc/${version}`)
|
||||
if (!fs.existsSync(destDir) || !dataPath) {
|
||||
console.warn(`⚠️ Version ${version} not found (checked ${destDir}) ; cannot continue.`, fs.existsSync(destDir), dataPath)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (ourPR.body) {
|
||||
await github.updateIssue(ourPR.number, {
|
||||
body: ourPR.body.replace('<!--minecraft-data-generator-placeholder-->', `- https://github.com/PrismarineJS/minecraft-data-generator/pull/${genPullNo}`)
|
||||
})
|
||||
}
|
||||
|
||||
console.log('Handling PR:', ourPR)
|
||||
|
||||
fs.mkdirSync(artifactsDir, { recursive: true })
|
||||
|
||||
// https://github.com/PrismarineJS/minecraft-data-generator/actions/runs/17261281146/artifacts/3861320839
|
||||
const s = artifactURL.split('github.com/')[1]
|
||||
const [ownerName, repoName, _actions, _runs, _runId, _artifacts, artifactId] = s.split('/') // eslint-disable-line
|
||||
console.log('Downloading artifacts', { ownerName, repoName, artifactId, artifactsDir })
|
||||
await github.artifacts.downloadIdFrom(ownerName, repoName, artifactId, artifactsDir)
|
||||
|
||||
console.log(fs.readdirSync(artifactsDir))
|
||||
|
||||
// Now copy artifacts/${version}/*.json to data/pc/$version/*.json
|
||||
const versionArtifactsDir = join(artifactsDir, version)
|
||||
for (const file of fs.readdirSync(versionArtifactsDir)) {
|
||||
if (file.endsWith('.json')) {
|
||||
const src = join(versionArtifactsDir, file)
|
||||
const dest = join(destDir, file)
|
||||
fs.mkdirSync(destDir, { recursive: true })
|
||||
console.log(`copy ${src} => ${dest}`)
|
||||
fs.copyFileSync(src, dest)
|
||||
dataPath[file.replace('.json', '')] = 'pc/' + version
|
||||
}
|
||||
}
|
||||
|
||||
// Commit the new dataPath
|
||||
fs.writeFileSync(join(root, 'data', 'dataPaths.json'), JSON.stringify(dataPaths, null, 2))
|
||||
|
||||
try {
|
||||
process.chdir(join(__dirname, '../../tools/js'))
|
||||
extractPcEntityMetadata(version, version, { write: true, cloneIfMissing: true })
|
||||
} catch (e) {
|
||||
console.log('Failed to extract PC entity metadata', e)
|
||||
}
|
||||
|
||||
// Now, we need to commit the changes
|
||||
exec('git', ['config', 'user.name', 'github-actions[bot]'])
|
||||
exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com'])
|
||||
exec('git', ['add', '--all'])
|
||||
exec('git', ['commit', '-m', `[Auto] Apply generated data from PrismarineJS/minecraft-data-generator#${genPullNo}`])
|
||||
exec('git', ['push', 'origin', branch])
|
||||
}
|
||||
|
||||
async function main (versions, genPullNo, artifactUrl, createPR) {
|
||||
const version = versions.at(-1)
|
||||
const pr = await github.findPullRequest({ titleIncludes: '🎈', author: null })
|
||||
console.log('Found PR', pr)
|
||||
if (pr && pr.isOpen) {
|
||||
const details = await github.getPullRequest(pr.id)
|
||||
console.log('PR', details)
|
||||
await handle(details, genPullNo, version, artifactUrl, true)
|
||||
} else if (createPR) {
|
||||
const pr = await createInitialPR('pc', '(This issue was created for a minecraft-data-generator PR)', {
|
||||
version,
|
||||
protocolVersion: null
|
||||
})
|
||||
console.log('Created PR', pr)
|
||||
const details = await github.getPullRequest(pr.number)
|
||||
console.log('PR', details)
|
||||
await handle(details, genPullNo, version, artifactUrl, false)
|
||||
} else {
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main(
|
||||
JSON.parse(process.env.TRIGGER_MC_VERSIONS),
|
||||
process.env.TRIGGER_PR_NO,
|
||||
process.env.TRIGGER_ARTIFACT_URL,
|
||||
process.env.CREATE_PR_IF_NONE
|
||||
)
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
/* eslint-disable no-return-assign, no-sequences */
|
||||
const fs = require('fs')
|
||||
const github = require('gh-helpers')()
|
||||
const pcManifestURL = 'https://launchermeta.mojang.com/mc/game/version_manifest.json'
|
||||
const changelogURL = 'https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs'
|
||||
const { exec, createInitialPR } = require('./utils')
|
||||
|
||||
const download = (url, dest) => exec('curl', ['-L', url, '-o', dest])
|
||||
|
||||
function buildFirstIssue (title, result, jarData) {
|
||||
const protocolVersion = jarData?.protocol_version || 'Failed to obtain from JAR'
|
||||
const name = jarData?.name || result.id
|
||||
const date = result.releaseTime
|
||||
|
||||
return {
|
||||
title,
|
||||
body: `
|
||||
A new Minecraft Java Edition version is available (as of ${date}), version **${result.id}**
|
||||
## Official Changelog
|
||||
* ${changelogURL}
|
||||
## Protocol Details
|
||||
(I will close this issue automatically if "${result.id}" is added to data/pc/common/versions.json on "master")
|
||||
<table>
|
||||
<tr><td><b>Name</b></td><td>${name}</td>
|
||||
<tr><td><b>Protocol ID</b></td><td>${protocolVersion}</td>
|
||||
<tr><td><b>Release Date</b></td><td>${date}</td>
|
||||
<tr><td><b>Release Type</b></td><td>${result.type}</td>
|
||||
<tr><td><b>Data Version</b></td><td>${jarData?.world_version}</td>
|
||||
<tr><td><b>Java Version</b></td><td>${jarData?.java_version}</td>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
🤖 I am a bot, I check for updates every 2 hours without a trigger. You can close this issue to prevent any further updates.
|
||||
<!-- RP: ${JSON.stringify({ title, versionJson: jarData, latestVersionData: result, latestVersion: result.id })} -->
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
const protocolVersions = {
|
||||
pc: require('../../data/pc/common/protocolVersions.json'),
|
||||
bedrock: require('../../data/bedrock/common/protocolVersions.json')
|
||||
}
|
||||
const supportedVersions = {
|
||||
pc: require('../../data/pc/common/versions.json'),
|
||||
bedrock: require('../../data/bedrock/common/versions.json')
|
||||
}
|
||||
|
||||
async function updateManifestPC () {
|
||||
const manifest = await fetch(pcManifestURL).then(res => res.json())
|
||||
// fs.writeFileSync('./manifest.json', JSON.stringify(manifest, null, 2))
|
||||
const knownVersions = protocolVersions.pc.reduce((acc, cur) => (acc[cur.minecraftVersion] = cur, acc), {})
|
||||
const latestVersion = manifest.latest.snapshot
|
||||
const latestReleaseVersion = manifest.latest.release
|
||||
const latestVersionData = manifest.versions.find(v => v.id === latestVersion)
|
||||
const latestVersionIsSnapshot = latestVersionData.type !== 'release'
|
||||
|
||||
const title = `Support Minecraft PC ${latestReleaseVersion}`
|
||||
const issueStatus = await github.findIssue({ titleIncludes: title, author: null }) || {}
|
||||
console.log('issueStatus', issueStatus)
|
||||
|
||||
if (issueStatus?.isOpen) {
|
||||
if (supportedVersions.pc.includes(latestReleaseVersion)) {
|
||||
// If the issue is open and the latest release version is supported, we can close the issue
|
||||
github.close(issueStatus.id, `Closing as PC ${latestReleaseVersion} is now supported`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (latestVersionIsSnapshot) {
|
||||
// don't make issues for snapshots
|
||||
if (supportedVersions.pc.includes(latestVersion) || knownVersions[latestVersion]) {
|
||||
console.log('Latest version is a known snapshot, no work to do')
|
||||
return
|
||||
}
|
||||
} else if (knownVersions[latestVersion]) {
|
||||
console.log(`Latest PC version ${latestVersion} is known in protocolVersions.json, but not in versions.json (protocol version ${knownVersions[latestVersion].version})`)
|
||||
return // We expect to have already handled this version.
|
||||
} else {
|
||||
console.log(`Latest PC version ${latestVersion} is not known in protocolVersions.json, adding and making issue`)
|
||||
}
|
||||
|
||||
let versionJson
|
||||
try {
|
||||
versionJson = await addEntryFor(latestVersion, latestVersionData)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
if (latestVersionIsSnapshot) {
|
||||
console.warn('Failed to update protocolVersions.json for the snapshot', latestVersion)
|
||||
} else {
|
||||
console.log('Latest PC version is not supported and we failed to load data. Opening issue...')
|
||||
const issuePayload = buildFirstIssue(title, latestVersionData)
|
||||
github.createIssue(issuePayload)
|
||||
|
||||
fs.writeFileSync('./issue.md', issuePayload.body)
|
||||
console.log('OK, wrote to ./issue.md', issuePayload)
|
||||
}
|
||||
}
|
||||
|
||||
if (!latestVersionIsSnapshot && !issueStatus.isOpen && !issueStatus.isClosed) {
|
||||
await openIssueAndDispatch(title, versionJson, latestVersionData, latestVersion)
|
||||
}
|
||||
|
||||
async function addEntryFor (releaseVersion, releaseData) {
|
||||
const latestReleaseManifest = await fetch(releaseData.url).then(res => res.json())
|
||||
// Download client jar
|
||||
if (!fs.existsSync(`./${releaseVersion}.jar`)) {
|
||||
const clientJarUrl = latestReleaseManifest.downloads.client.url
|
||||
console.log('Downloading client jar', clientJarUrl)
|
||||
download(clientJarUrl, `./${releaseVersion}.jar`)
|
||||
}
|
||||
|
||||
// Log the byte size of the client jar
|
||||
const clientJarSize = fs.statSync(`./${releaseVersion}.jar`).size
|
||||
console.log(`Downloaded client jar ${releaseVersion}.jar (${clientJarSize} bytes), extracting its version.json...`)
|
||||
|
||||
// unzip with tar / unzip, Actions image uses 7z
|
||||
if (process.platform === 'win32') exec('tar', ['-xf', `./${releaseVersion}.jar`, 'version.json'])
|
||||
else exec('7z', ['-y', 'e', `./${releaseVersion}.jar`, 'version.json'])
|
||||
const versionJson = require('./version.json')
|
||||
|
||||
let majorVersion
|
||||
if (versionJson.id && versionJson.id.includes('.')) {
|
||||
majorVersion = versionJson.id.split('.', 2).join('.')
|
||||
} else {
|
||||
console.error(`Version '${versionJson.id}' appears to be a snapshot (no dot in name). Using the last known majorVersion in protocolVersions.json for this version (may be incorrect)`)
|
||||
majorVersion = protocolVersions.pc[0].majorVersion
|
||||
}
|
||||
|
||||
const newEntry = {
|
||||
minecraftVersion: versionJson.id,
|
||||
version: versionJson.protocol_version,
|
||||
dataVersion: versionJson.world_version,
|
||||
usesNetty: true,
|
||||
majorVersion,
|
||||
releaseType: latestVersionData.type
|
||||
}
|
||||
console.log('Adding new entry to pc protocolVersions.json', newEntry)
|
||||
const updatedProtocolVersions = [newEntry, ...protocolVersions.pc]
|
||||
fs.writeFileSync('../../data/pc/common/protocolVersions.json', JSON.stringify(updatedProtocolVersions, null, 2))
|
||||
|
||||
if (process.env.CI) {
|
||||
console.log('Committing changes to protocolVersions.json')
|
||||
// https://github.com/actions/checkout/pull/1184
|
||||
exec('git', ['config', 'user.name', 'github-actions[bot]'])
|
||||
exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com'])
|
||||
exec('git', ['add', '../../data/pc/common/protocolVersions.json'])
|
||||
exec('git', ['commit', '-m', `Add ${versionJson.id} to pc protocolVersions.json`])
|
||||
exec('git', ['push'])
|
||||
}
|
||||
|
||||
return versionJson
|
||||
}
|
||||
}
|
||||
|
||||
async function openIssueAndDispatch (title, versionJson, latestVersionData, latestVersion) {
|
||||
console.log('Opening issue', versionJson)
|
||||
const issuePayload = buildFirstIssue(title, latestVersionData, versionJson)
|
||||
|
||||
const issue = await github.createIssue(issuePayload)
|
||||
console.log('Created issue', issue)
|
||||
|
||||
// Now create an initial PR with the new version data
|
||||
const pr = await createInitialPR('pc', issue.url, {
|
||||
minecraftVersion: versionJson.id,
|
||||
version: latestVersion,
|
||||
protocolVersion: versionJson.protocol_version
|
||||
})
|
||||
console.log('Created PR', pr)
|
||||
// Ask minecraft-data-generator to handle new update
|
||||
const dispatchPayload = {
|
||||
owner: 'PrismarineJS',
|
||||
repo: 'minecraft-data-generator',
|
||||
workflow: 'handle-mcdata-update.yml',
|
||||
branch: 'main',
|
||||
inputs: {
|
||||
version: latestVersion,
|
||||
issue_number: String(issue?.number),
|
||||
pr_number: String(pr?.number)
|
||||
}
|
||||
}
|
||||
console.log('Sending workflow dispatch', dispatchPayload)
|
||||
await github.sendWorkflowDispatch(dispatchPayload)
|
||||
// Ask node-minecraft-protocol to handle new update
|
||||
const nodeDispatchPayload = {
|
||||
owner: 'PrismarineJS',
|
||||
repo: 'node-minecraft-protocol',
|
||||
workflow: 'update-from-minecraft-data.yml',
|
||||
branch: 'master',
|
||||
inputs: {
|
||||
new_mc_version: latestVersion,
|
||||
mcdata_branch: pr.branchName,
|
||||
mcdata_pr_url: pr.url
|
||||
}
|
||||
}
|
||||
console.log('Sending workflow dispatch', nodeDispatchPayload)
|
||||
await github.sendWorkflowDispatch(nodeDispatchPayload)
|
||||
// node-minecraft-protocol would then dispatch to mineflayer
|
||||
|
||||
return { issue, pr }
|
||||
}
|
||||
|
||||
if (process.env.FORCE_CREATE_PR) {
|
||||
const payload = JSON.parse(process.env.FORCE_CREATE_PR)
|
||||
openIssueAndDispatch(payload.title, payload.versionJson, payload.latestVersionData, payload.latestVersion).then(() => {
|
||||
console.log('Done with manual PR')
|
||||
}).catch(err => {
|
||||
console.error('Failed to do manual PR', err)
|
||||
process.exit(1)
|
||||
})
|
||||
} else {
|
||||
updateManifestPC()
|
||||
}
|
||||
|
||||
module.exports = { openIssueAndDispatch, updateManifestPC }
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"scripts": {
|
||||
"fix": "standard --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"gh-helpers": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"standard": "^17.1.2"
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
const cp = require('child_process')
|
||||
const github = require('gh-helpers')()
|
||||
const path = require('path')
|
||||
|
||||
const pm = process.versions.bun ? process.execPath : 'npm'
|
||||
|
||||
function exec (file, args = [], options = {}) {
|
||||
const opts = { stdio: ['inherit', 'inherit', 'inherit'], ...options }
|
||||
console.log('> ', file, args.join(' '), options.cwd ? `(cwd: ${options.cwd})` : '')
|
||||
return github.mock ? undefined : cp.execFileSync(file, args, opts)
|
||||
}
|
||||
|
||||
const toolsJs = path.join(__dirname, '..', '..', 'tools', 'js')
|
||||
const sanitizeBranch = (branchName) => branchName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()
|
||||
|
||||
async function createInitialPR (edition, issueUrl, { version, protocolVersion }) {
|
||||
exec(pm, ['install'], { cwd: toolsJs })
|
||||
if (protocolVersion) exec(pm, ['run', 'version', edition, version, protocolVersion], { cwd: toolsJs })
|
||||
exec(pm, ['run', 'build'], { cwd: toolsJs })
|
||||
const branchNameVersion = sanitizeBranch(version)
|
||||
const branchName = `${edition}_${branchNameVersion}`
|
||||
const title = `🎈 Add Minecraft ${edition} ${version} data`
|
||||
// First, delete any existing branch
|
||||
try {
|
||||
exec('git', ['branch', '-D', branchName])
|
||||
} catch (e) {
|
||||
// Branch doesn't exist, ignore error
|
||||
}
|
||||
exec('git', ['checkout', '-b', branchName])
|
||||
exec('git', ['config', 'user.name', 'github-actions[bot]'])
|
||||
exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com'])
|
||||
exec('git', ['add', '--all'])
|
||||
exec('git', ['commit', '--allow-empty', '-m', title])
|
||||
exec('git', ['push', 'origin', branchName, '--force'])
|
||||
const body = `
|
||||
This automated PR sets up the relevant boilerplate for Minecraft ${edition} version ${version}. Fixes ${issueUrl}.
|
||||
|
||||
Related:
|
||||
- Issue: ${issueUrl}
|
||||
- Protocol Version: ${protocolVersion}
|
||||
<!--minecraft-data-generator-placeholder-->
|
||||
|
||||
* You can help contribute to this PR by opening a PR against this <code branch>${branchName}</code> branch instead of <code>master</code>.
|
||||
`
|
||||
const pr = await github.createPullRequest(title, body, branchName, 'master')
|
||||
pr.branchName = branchName
|
||||
return pr
|
||||
}
|
||||
|
||||
module.exports = { createInitialPR, exec }
|
||||
@@ -0,0 +1,59 @@
|
||||
name: bedrock-ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 14
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 22.x
|
||||
# Old versions of bedrock use old libssl that Ubuntu no longer ships with; need manual install
|
||||
- name: Install libssl 1.1
|
||||
run: |
|
||||
wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb
|
||||
sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb
|
||||
- name: Checkout node-minecraft-data
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: PrismarineJS/node-minecraft-data
|
||||
submodules: recursive
|
||||
path: node-mcdata
|
||||
# Erase the submodule for minecraft-data with a checkout of the current minecraft-data repo
|
||||
- run: rm -rf node-mcdata/minecraft-data
|
||||
- name: Checkout minecraft-data
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: node-mcdata/minecraft-data
|
||||
# Now install local node-minecraft-data. Running "prepare" should gen data and types
|
||||
- name: Install minecraft-data
|
||||
run: |
|
||||
cd node-mcdata
|
||||
npm install
|
||||
npm run prepare
|
||||
npm link
|
||||
- name: Checkout bedrock-protocol
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: PrismarineJS/bedrock-protocol
|
||||
path: bedrock-protocol
|
||||
# I forget the correct install order, do both
|
||||
- name: Install bedrock-protocol
|
||||
run: |
|
||||
cd bedrock-protocol
|
||||
npm install ../node-mcdata
|
||||
npm i
|
||||
npm install ../node-mcdata
|
||||
- name: Running bedrock-protocol tests
|
||||
run: |
|
||||
curl -o node_modules/protodef/src/serializer.js https://raw.githubusercontent.com/extremeheat/node-protodef/refs/heads/dlog/src/serializer.js && curl -o node_modules/protodef/src/compiler.js https://raw.githubusercontent.com/extremeheat/node-protodef/refs/heads/dlog/src/compiler.js
|
||||
npm test
|
||||
working-directory: bedrock-protocol
|
||||
@@ -0,0 +1,92 @@
|
||||
name: Bedrock Version Bump
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
minecraftVersion:
|
||||
description: 'Bedrock Minecraft version (e.g., 1.21.124)'
|
||||
required: true
|
||||
type: string
|
||||
protocolVersion:
|
||||
description: 'Protocol version number (e.g., 860)'
|
||||
required: true
|
||||
type: string
|
||||
createPr:
|
||||
description: 'Create a pull request with the changes'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
bump-bedrock-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.PAT_PASSWORD || secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd tools/js
|
||||
npm install
|
||||
|
||||
- name: Run version bump script
|
||||
run: |
|
||||
cd tools/js
|
||||
npm run version bedrock ${{ github.event.inputs.minecraftVersion }} ${{ github.event.inputs.protocolVersion }}
|
||||
|
||||
- name: Build protocol files
|
||||
run: |
|
||||
cd tools/js
|
||||
npm run build
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
cd tools/js
|
||||
npm test
|
||||
|
||||
- name: Create Pull Request
|
||||
if: ${{ github.event.inputs.createPr == 'true' }}
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.PAT_PASSWORD || secrets.GITHUB_TOKEN }}
|
||||
commit-message: |
|
||||
Add bedrock ${{ github.event.inputs.minecraftVersion }} protocol data
|
||||
|
||||
Updates:
|
||||
- Protocol version → ${{ github.event.inputs.protocolVersion }}
|
||||
- Minecraft version → ${{ github.event.inputs.minecraftVersion }}
|
||||
- Moved previous proto.yml to versioned folder
|
||||
- Updated protocolVersions.json, versions.json, and dataPaths.json
|
||||
- Regenerated protocol.json files
|
||||
branch: bedrock-bump-${{ github.event.inputs.minecraftVersion }}
|
||||
delete-branch: true
|
||||
title: Add bedrock ${{ github.event.inputs.minecraftVersion }} data
|
||||
body: |
|
||||
## Bedrock Version Bump
|
||||
|
||||
This PR adds support for Minecraft Bedrock Edition version **${{ github.event.inputs.minecraftVersion }}** with protocol version **${{ github.event.inputs.protocolVersion }}**.
|
||||
|
||||
### Changes
|
||||
- ✅ Protocol version bumped to ${{ github.event.inputs.protocolVersion }}
|
||||
- ✅ Minecraft version updated to ${{ github.event.inputs.minecraftVersion }}
|
||||
- ✅ Previous proto.yml files moved to versioned folder
|
||||
- ✅ Updated protocolVersions.json and versions.json
|
||||
- ✅ Regenerated all protocol.json files
|
||||
- ✅ All tests passing
|
||||
|
||||
### How this was generated
|
||||
This PR was automatically generated using the bedrock version bump workflow.
|
||||
|
||||
---
|
||||
🤖 Generated by GitHub Actions workflow
|
||||
labels: |
|
||||
bedrock
|
||||
version-bump
|
||||
automated
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Repository Dispatch
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: [node-mcData-release]
|
||||
jobs:
|
||||
myEvent:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: web
|
||||
- name: Use Node.js 22.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 22.x
|
||||
- name: Run tool
|
||||
run: |
|
||||
npm install
|
||||
git config user.name 'rom1504bot'
|
||||
git config user.email 'rom1504bot@users.noreply.github.com'
|
||||
npm run gh-publish
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [22.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
working-directory: tools/js
|
||||
- run: npm test
|
||||
working-directory: tools/js
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Repo Commands
|
||||
|
||||
on:
|
||||
issue_comment: # Handle comment commands
|
||||
types: [created]
|
||||
pull_request: # Handle renamed PRs
|
||||
types: [edited]
|
||||
|
||||
jobs:
|
||||
comment-trigger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Run command handlers
|
||||
uses: PrismarineJS/prismarine-repo-actions@master
|
||||
with:
|
||||
# NOTE: You must specify a Personal Access Token (PAT) with repo access here. While you can use the default GITHUB_TOKEN, actions taken with it will not trigger other actions, so if you have a CI workflow, commits created by this action will not trigger it.
|
||||
token: ${{ secrets.PAT_PASSWORD }}
|
||||
# See `Options` section below for more info on these options
|
||||
install-command: cd tools/js && npm install
|
||||
/fixlint.fix-command: cd tools/js && npm run fix
|
||||
@@ -0,0 +1,39 @@
|
||||
name: handle-mcpc-generator
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
versions:
|
||||
description: 'JSON stringified list of known versions in generator (last is expected to be latest)'
|
||||
required: true
|
||||
default: '["1.21.6"]'
|
||||
mergedArtifactURL:
|
||||
description: 'Merged artifact download URL'
|
||||
required: true
|
||||
prNumber:
|
||||
description: 'Relevant minecraft-data-generator PR #'
|
||||
required: false
|
||||
createPr:
|
||||
description: 'Whether to create a new PR if none exists'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
jobs:
|
||||
update-mcdata:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
- run: cd .github/helper-bot && bun install
|
||||
- run: cd tools/js && bun install
|
||||
- run: bun handleMcpcGeneratedArtifacts.js
|
||||
working-directory: .github/helper-bot
|
||||
env:
|
||||
TRIGGER_MC_VERSIONS: ${{ github.event.inputs.versions }}
|
||||
TRIGGER_PR_NO: ${{ github.event.inputs.prNumber }}
|
||||
TRIGGER_ARTIFACT_URL: ${{ github.event.inputs.mergedArtifactURL }}
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_PASSWORD }}
|
||||
CREATE_PR_IF_NONE: ${{ github.event.inputs.createPr }}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
name: 'tag'
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
tag:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-ecosystem/action-regex-match@v2
|
||||
id: regex-match
|
||||
with:
|
||||
text: ${{ github.event.head_commit.message }}
|
||||
regex: '^Release ([0-9]+\.[0-9]+\.[0-9]+)'
|
||||
- name: Create Release
|
||||
if: steps.regex-match.outputs.match != ''
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.regex-match.outputs.group1 }}
|
||||
release_name: Release ${{ steps.regex-match.outputs.group1 }}
|
||||
body: ${{ steps.regex-match.outputs.group1 }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
- uses: peter-evans/repository-dispatch@v1
|
||||
if: ${{ steps.regex-match.outputs.match != '' }}
|
||||
with:
|
||||
token: ${{ secrets.PAT_PASSWORD }}
|
||||
repository: PrismarineJS/node-minecraft-data
|
||||
event-type: mcData-release
|
||||
client-payload: '{"tag": "${{ steps.regex-match.outputs.group1 }}"}'
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Update Helper
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
createPr:
|
||||
description: 'If provided, force create an update PR. JSON encoded payload {title, versionJson, latestVersionData, latestVersion}'
|
||||
required: false
|
||||
default: ''
|
||||
schedule:
|
||||
- cron: "0 */2 * * *"
|
||||
|
||||
jobs:
|
||||
helper:
|
||||
name: update-checker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@master
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@master
|
||||
with:
|
||||
node-version: 22.0.0
|
||||
- name: Install Github Actions toolkit
|
||||
run: npm i
|
||||
working-directory: .github/helper-bot
|
||||
# The env vars contain the relevant trigger information, so we don't need to pass it
|
||||
- name: Runs helper
|
||||
run: node index.js
|
||||
working-directory: .github/helper-bot
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_PASSWORD }}
|
||||
FORCE_CREATE_PR: ${{ github.event.inputs.createPr }}
|
||||
@@ -0,0 +1,154 @@
|
||||
# minecraft-data
|
||||
|
||||
[](https://discord.gg/GsEFRM8)
|
||||
[](https://gitter.im/PrismarineJS/general)
|
||||
[](https://irc.gitter.im/)
|
||||
[](https://github.com/PrismarineJS/minecraft-data/actions?query=workflow%3A%22CI%22)
|
||||
|
||||
Language independent module providing minecraft data for minecraft clients, servers and libraries.
|
||||
|
||||
Supports
|
||||
* Minecraft PC version 0.30c (classic), 1.7.10, 1.8.8, 1.9 (15w40b, 1.9, 1.9.1-pre2, 1.9.2, 1.9.4),
|
||||
1.10 (16w20a, 1.10-pre1, 1.10, 1.10.1, 1.10.2), 1.11 (16w35a, 1.11, 1.11.2), 1.12 (17w15a, 17w18b, 1.12-pre4, 1.12, 1.12.1, 1.12.2), 1.13 (17w50a, 1.13, 1.13.1, 1.13.2-pre1, 1.13.2-pre2, 1.13.2), 1.14 (1.14, 1.14.1, 1.14.3, 1.14.4), 1.15 (1.15, 1.15.1, 1.15.2), 1.16 (20w13b, 20w14a, 1.16-rc1, 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5), 1.17, 1.17.1, 1.18 (1.18, 1.18.1, 1.18.2), 1.19 (1.19, 1.19.2, 1.19.3, 1.19.4), 1.20 (1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6), 1.21 (1.21, 1.21.1, 1.21.3), 1.21.8, 1.21.9, 1.21.10
|
||||
<!--NEXT PC-->
|
||||
* Minecraft bedrock version 0.14, 0.15, 1.0, 1.16.201, 1.16.210, 1.16.220, 1.17.0, 1.17.10, 1.17.30, 1.17.40, 1.18.0, 1.18.11, 1.18.30, 1.19.1, 1.19.10, 1.19.20, 1.19.21, 1.19.30, 1.19.40, 1.19.50, 1.19.60, 1.19.62, 1.19.63, 1.19.70, 1.19.80, 1.20.0, 1.20.10, 1.20.30, 1.20.40, 1.20.50, 1.20.61, 1.20.71, 1.20.80, 1.21.0, 1.21.2, 1.21.20, 1.21.30, 1.21.42, 1.21.50, 1.21.60, 1.21.70, 1.21.80, 1.21.90, 1.21.93, 1.21.100, 1.21.111, 1.21.120, 1.21.124, 1.21.130, 1.26.0
|
||||
<!--NEXT BEDROCK-->
|
||||
|
||||
## Wrappers
|
||||
|
||||
Minecraft-data is language independent, you can use it with these language specific modules :
|
||||
|
||||
| Wrapper name | Language | Data provided |
|
||||
| --- | --- | --- |
|
||||
| [node-minecraft-data](https://github.com/PrismarineJS/node-minecraft-data) | Node.js | everything |
|
||||
| [python-minecraft-data](https://github.com/SpockBotMC/python-minecraft-data) | python | everything |
|
||||
| [minebase](https://github.com/py-mine/minebase) | python | everything |
|
||||
| [McData](https://github.com/McEx/McData) | Elixir | protocol |
|
||||
| [ProtocolGen](https://github.com/Johni0702/ProtocolGen) | java | generated java files from protocol.json to read and write minecraft packets |
|
||||
| [mcdata](https://github.com/wlwanpan/mcdata) | Go | everything |
|
||||
| [minecraft-data-rs](https://github.com/Trivernis/minecraft-data-rs) | Rust | everything |
|
||||
|
||||
If you want to use minecraft-data in a new language, we advise you to [create a new wrapper](doc/make-a-new-wrapper.md)
|
||||
|
||||
## Data
|
||||
|
||||
Data provided:
|
||||
|
||||
| Data | Description |
|
||||
| --- | --- |
|
||||
| Biomes | |
|
||||
| Blocks | |
|
||||
| Enchantment | |
|
||||
| Effect | Status/Potion effects |
|
||||
| Entities | |
|
||||
| Language | language code and their english strings |
|
||||
| Instruments | sounds emitted by blocks |
|
||||
| Items | |
|
||||
| Map icons | Provides all available map icons as used by (crafted) maps |
|
||||
| Materials | to be used with blocks data. Provides tool speed multipliers against material types. |
|
||||
| Particles | Id and names of minecraft particles |
|
||||
| Protocol | a complete description of the protocol, can be used to automatically implement the protocol, see [protodef doc](https://github.com/ProtoDef-io/ProtoDef) |
|
||||
| Protocol Versions | the whole list of minecraft protocol versions |
|
||||
| Recipes | to be used with blocks and items data |
|
||||
| Windows | the different kind of windows and their characteristics |
|
||||
| Version | |
|
||||
| Versions | all the versions supported in minecraft-data |
|
||||
| Block collision shapes | |
|
||||
| Foods | list of foods each with there id, saturation, foodpoints and more |
|
||||
| Commands | a tree structure for vanilla minecraft server commands, and some info needed to implement sub-parsers.
|
||||
| Legacy | mappings between legacy (1.12) and post-flattening (1.13+) blocks and items ids
|
||||
| Skin data | (bedrock edition) Skin geometry and texture data for steve skin
|
||||
| Features | This can be used to check is a specific feature is available in the current Minecraft version. This is usually only required for handling version-specific functionality.
|
||||
| Sounds | For versions that use a sound id over the network (1.9+) this provides mappings between network sound id and sound name.
|
||||
|
||||
See more information about this data in the [documentation](http://prismarinejs.github.io/minecraft-data/)
|
||||
|
||||
## Documentation
|
||||
|
||||
* See [doc/history.md](doc/history.md)
|
||||
* [Documentation generated using the json schemas and docson](http://prismarinejs.github.io/minecraft-data)
|
||||
* [Textual documentation of the recipe format](doc/recipes.md)
|
||||
|
||||
## Projects using minecraft-data
|
||||
|
||||
These are the projects that use minecraft-data:
|
||||
|
||||
| Project name | Language | Project type | Data used |
|
||||
| --- | --- | --- | --- |
|
||||
| [mineflayer](https://github.com/PrismarineJS/mineflayer) | Node.js | bot library | biomes, blocks, entities, instruments, items, materials, recipes |
|
||||
| [node-minecraft-protocol](https://github.com/PrismarineJS/node-minecraft-protocol) | Node.js | protocol serialization | protocol |
|
||||
| [flying-squid](https://github.com/PrismarineJS/flying-squid) | Node.js | server library | biomes, blocks, materials |
|
||||
| [SpockBot](https://github.com/SpockBotMC/SpockBot) | Python | bot library | biomes, blocks, items, materials, windows, recipes |
|
||||
| [PhaseBot](https://github.com/phase/PhaseBot) | Java | bot | blocks, items, materials |
|
||||
| [McEx](https://github.com/hansihe/McEx) | Elixir | server | blocks |
|
||||
| [VoxelGamesLib](https://github.com/MiniDigger/VoxelGamesLib) | Java | minigames server library | blocks, items |
|
||||
| [Phpcraft](https://github.com/Phpcraft/core) | PHP | all-around library | blocks, items, entities, protocol |
|
||||
| [MineSharp](https://github.com/psu-de/MineSharp) | C# | Bots | Protocol, Blocks, Items, Entities, Biomes, Recipes, Enchantments |
|
||||
|
||||
## Extraction
|
||||
|
||||
### Java Edition
|
||||
|
||||
The recommended way to extract the data is documented at [add-data-new-version.md](https://github.com/PrismarineJS/minecraft-data/blob/master/doc/add-data-new-version.md).
|
||||
|
||||
The [minecraft-data-auto-updater](https://github.com/PrismarineJS/minecraft-data-auto-updater) calls [minecraft-data-generator-server](https://github.com/PrismarineJS/minecraft-data-generator-server).
|
||||
|
||||
### Bedrock Edition
|
||||
|
||||
See [bedrock.md](./doc/bedrock.md) for information on updating data.
|
||||
|
||||
Specifically, [minecraft-data-auto-updater](https://github.com/PrismarineJS/minecraft-data-auto-updater) calls [minecraft-data-generator-server](https://github.com/PrismarineJS/minecraft-data-generator-server)
|
||||
|
||||
Projects that provide data:
|
||||
|
||||
| Project name | Language | Source | Data provided |
|
||||
| --- | --- | --- | --- |
|
||||
| [minecraft-wiki-extractor](https://github.com/PrismarineJS/minecraft-wiki-extractor) | Node.js | [minecraft wiki](https://minecraft.wiki) | blocks, items, entities, recipes |
|
||||
| [mcdevs-wiki-extractor](https://github.com/PrismarineJS/mcdevs-wiki-extractor) | Node.js | [wiki.vg](http://wiki.vg/Protocol) | a partial protocol, entities |
|
||||
| [node-minecraft-extractor](https://github.com/PrismarineJS/node-minecraft-extractor) | Node.js | merge between wiki.vg and mcwiki | a complete entities file |
|
||||
| [minecraft-jar-extractor](https://github.com/PrismarineJS/minecraft-jar-extractor) | Node.js | minecraft server jar | a very partial but completely up to date protocol |
|
||||
| [minecraft-jar-extractor in python](https://github.com/pangeacake/minecraft-jar-extractor) | Python | minecraft server jar | about the same protocol information as minecraft-jar-extractor in node.js |
|
||||
| [burger](https://github.com/mcdevs/Burger) with [burger-extractor](https://github.com/PrismarineJS/burger-extractor) | Node.js | minecraft server jar | used to provide items, blocks, biomes and recipes |
|
||||
| [burger 1.16](https://github.com/Pokechu22/Burger/tree/1.16-dev) | --//-- | --// -- | --//-- |
|
||||
| [command-generator](https://github.com/Miro-Andrin/mc-data-command-generator) | Python | [Minecraft data generator](https://wiki.vg/Data_Generators) | Data used for parsing all vanilla server commands. |
|
||||
| [minecraft-data-generator](https://github.com/Archengius/minecraft-data-generator) | Java | minecraft client | biomes, block collision shapes, blocks, effects, enchantments, entities, foods, items, particles, tints. Required Fabric updated to the provided minecraft version. |
|
||||
|
||||
|
||||
Pages interesting to manually update the data if necessary:
|
||||
|
||||
| Page | Data |
|
||||
| ---- | ---- |
|
||||
| [wiki.vg Inventory page](http://wiki.vg/Inventory) | windows |
|
||||
| [wiki.vg](http://wiki.vg/Block_Actions) | instruments |
|
||||
| [a mineflayer PR](https://github.com/PrismarineJS/mineflayer/pull/197) | biomes |
|
||||
| [mcwiki effects page](https://minecraft.wiki/w/Effect) | effects |
|
||||
| [wiki.vg protocol version numbers](http://wiki.vg/Protocol_version_numbers) | protocolVersions |
|
||||
|
||||
## Data quality
|
||||
|
||||
Minecraft data provides scripts to audit the data, they can be useful to check the data is correct :
|
||||
|
||||
* [audit_blocks](tools/js/test/audit_blocks.js)
|
||||
* [audit_items](tools/js/test/audit_items.js)
|
||||
* [audit_recipes](tools/js/test/audit_recipes.js)
|
||||
* [audit_shapes](tools/js/test/audit_shapes.js)
|
||||
|
||||
Minecraft data also provides json schemas in enums_schemas/ that are used in
|
||||
test/test.js to check the json file are valid relative to these schemas.
|
||||
These schemas can also be used to understand better how the json files are
|
||||
formatted in order to use it.
|
||||
|
||||
## Contribute
|
||||
|
||||
Please read https://github.com/PrismarineJS/prismarine-contribute
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
Some of the data was extracted manually or automatically from wiki.vg and minecraft.gamepedia.com.
|
||||
If required by one of the sources the license might change to something more appropriate.
|
||||
|
||||
## Related project
|
||||
|
||||
[minecraft-assets](https://github.com/rom1504/minecraft-assets) which provide minecraft assets along with json files that help to use them.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version":70,
|
||||
"minecraftVersion":"0.14.3",
|
||||
"majorVersion":"0.14"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version":82,
|
||||
"minecraftVersion":"0.15.6",
|
||||
"majorVersion":"0.15"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version":100,
|
||||
"minecraftVersion":"1.0.0",
|
||||
"majorVersion":"1.0"
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
[
|
||||
{
|
||||
"name": "followRange",
|
||||
"resource": "generic.follow_range",
|
||||
"default": 32,
|
||||
"min": 0,
|
||||
"max": 2048
|
||||
},
|
||||
{
|
||||
"name": "knockbackResistance",
|
||||
"resource": "generic.knockback_resistance",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
{
|
||||
"name": "movementSpeed",
|
||||
"resource": "generic.movement_speed",
|
||||
"default": 0.7,
|
||||
"min": 0,
|
||||
"max": 1024
|
||||
},
|
||||
{
|
||||
"name": "attackDamage",
|
||||
"resource": "generic.attack_damage",
|
||||
"default": 2,
|
||||
"min": 0,
|
||||
"max": 2048
|
||||
},
|
||||
{
|
||||
"name": "horseJumpStrength",
|
||||
"resource": "horse.jump_strength",
|
||||
"default": 0.7,
|
||||
"min": 0,
|
||||
"max": 2
|
||||
},
|
||||
{
|
||||
"name": "zombieSpawnReinforcements",
|
||||
"resource": "zombie.spawn_reinforcements",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
{
|
||||
"name": "flyingSpeed",
|
||||
"resource": "generic.flying_speed",
|
||||
"default": 0.4,
|
||||
"min": 0,
|
||||
"max": 1024
|
||||
},
|
||||
{
|
||||
"name": "luck",
|
||||
"resource": "generic.luck",
|
||||
"default": 0,
|
||||
"min": -1024,
|
||||
"max": 1024
|
||||
},
|
||||
{
|
||||
"name": "health",
|
||||
"resource": "health",
|
||||
"default": 20,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
},
|
||||
{
|
||||
"name": "absorption",
|
||||
"resource": "absorption",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 1024
|
||||
},
|
||||
{
|
||||
"name": "exhaustion",
|
||||
"resource": "player.exhaustion",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 5
|
||||
},
|
||||
{
|
||||
"name": "experienceProgress",
|
||||
"resource": "player.experience",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
{
|
||||
"name": "experienceLevel",
|
||||
"resource": "player.level",
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 24791
|
||||
},
|
||||
{
|
||||
"name": "hunger",
|
||||
"resource": "player.hunger",
|
||||
"default": 20,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
},
|
||||
{
|
||||
"name": "saturation",
|
||||
"resource": "player.saturation",
|
||||
"default": 20,
|
||||
"min": 0,
|
||||
"max": 20
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,833 @@
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"name": "protection",
|
||||
"displayName": "Protection",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 11,
|
||||
"b": -10
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 11,
|
||||
"b": 1
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"fire_protection",
|
||||
"blast_protection",
|
||||
"projectile_protection"
|
||||
],
|
||||
"category": "armor",
|
||||
"weight": 10,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "fire_protection",
|
||||
"displayName": "Fire Protection",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": 2
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 8,
|
||||
"b": 10
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"protection",
|
||||
"blast_protection",
|
||||
"projectile_protection"
|
||||
],
|
||||
"category": "armor",
|
||||
"weight": 5,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "feather_falling",
|
||||
"displayName": "Feather Falling",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 6,
|
||||
"b": -1
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 6,
|
||||
"b": 5
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "armor_feet",
|
||||
"weight": 5,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "blast_protection",
|
||||
"displayName": "Blast Protection",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 8,
|
||||
"b": 5
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"protection",
|
||||
"fire_protection",
|
||||
"projectile_protection"
|
||||
],
|
||||
"category": "armor",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "projectile_protection",
|
||||
"displayName": "Projectile Protection",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 6,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 6,
|
||||
"b": 3
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"protection",
|
||||
"fire_protection",
|
||||
"blast_protection"
|
||||
],
|
||||
"category": "armor",
|
||||
"weight": 5,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "thorns",
|
||||
"displayName": "Thorns",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 20,
|
||||
"b": -10
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "armor_chest",
|
||||
"weight": 1,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "respiration",
|
||||
"displayName": "Respiration",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": 0
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 30
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "armor_head",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "depth_strider",
|
||||
"displayName": "Depth Strider",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": 0
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 15
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"frost_walker"
|
||||
],
|
||||
"category": "armor_feet",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "aqua_affinity",
|
||||
"displayName": "Aqua Affinity",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 1
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 41
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "armor_head",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "sharpness",
|
||||
"displayName": "Sharpness",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 11,
|
||||
"b": -10
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 11,
|
||||
"b": 10
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"smite",
|
||||
"bane_of_arthropods"
|
||||
],
|
||||
"category": "weapon",
|
||||
"weight": 10,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "smite",
|
||||
"displayName": "Smite",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 8,
|
||||
"b": 17
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"sharpness",
|
||||
"bane_of_arthropods"
|
||||
],
|
||||
"category": "weapon",
|
||||
"weight": 5,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "bane_of_arthropods",
|
||||
"displayName": "Bane of Arthropods",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 8,
|
||||
"b": 17
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"sharpness",
|
||||
"smite"
|
||||
],
|
||||
"category": "weapon",
|
||||
"weight": 5,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "knockback",
|
||||
"displayName": "Knockback",
|
||||
"maxLevel": 2,
|
||||
"minCost": {
|
||||
"a": 20,
|
||||
"b": -15
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "weapon",
|
||||
"weight": 5,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "fire_aspect",
|
||||
"displayName": "Fire Aspect",
|
||||
"maxLevel": 2,
|
||||
"minCost": {
|
||||
"a": 20,
|
||||
"b": -10
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "weapon",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "looting",
|
||||
"displayName": "Looting",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 9,
|
||||
"b": 6
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"silk_touch"
|
||||
],
|
||||
"category": "weapon",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "efficiency",
|
||||
"displayName": "Efficiency",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": -9
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "digger",
|
||||
"weight": 10,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "silk_touch",
|
||||
"displayName": "Silk Touch",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 15
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"looting",
|
||||
"fortune",
|
||||
"luck_of_the_sea"
|
||||
],
|
||||
"category": "digger",
|
||||
"weight": 1,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "unbreaking",
|
||||
"displayName": "Unbreaking",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": -3
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "breakable",
|
||||
"weight": 5,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "fortune",
|
||||
"displayName": "Fortune",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 9,
|
||||
"b": 6
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"silk_touch"
|
||||
],
|
||||
"category": "digger",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "power",
|
||||
"displayName": "Power",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": -9
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 6
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "bow",
|
||||
"weight": 10,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "punch",
|
||||
"displayName": "Punch",
|
||||
"maxLevel": 2,
|
||||
"minCost": {
|
||||
"a": 20,
|
||||
"b": -8
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 20,
|
||||
"b": 17
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "bow",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"name": "flame",
|
||||
"displayName": "Flame",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 20
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "bow",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "infinity",
|
||||
"displayName": "Infinity",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 20
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"mending"
|
||||
],
|
||||
"category": "bow",
|
||||
"weight": 1,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"name": "luck_of_the_sea",
|
||||
"displayName": "Luck of the Sea",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 9,
|
||||
"b": 6
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"silk_touch"
|
||||
],
|
||||
"category": "fishing_rod",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"name": "lure",
|
||||
"displayName": "Lure",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 9,
|
||||
"b": 6
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 51
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "fishing_rod",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"name": "frost_walker",
|
||||
"displayName": "Frost Walker",
|
||||
"maxLevel": 2,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": 0
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 15
|
||||
},
|
||||
"treasureOnly": true,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"depth_strider"
|
||||
],
|
||||
"category": "armor_feet",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"name": "mending",
|
||||
"displayName": "Mending",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 25,
|
||||
"b": 0
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 25,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": true,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"infinity"
|
||||
],
|
||||
"category": "breakable",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"name": "binding_curse",
|
||||
"displayName": "Curse of Binding",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 25
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": true,
|
||||
"curse": true,
|
||||
"exclude": [],
|
||||
"category": "wearable",
|
||||
"weight": 1,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "vanishing_curse",
|
||||
"displayName": "Curse of Vanishing",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 25
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": true,
|
||||
"curse": true,
|
||||
"exclude": [],
|
||||
"category": "vanishable",
|
||||
"weight": 1,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"name": "impaling",
|
||||
"displayName": "Impaling",
|
||||
"maxLevel": 5,
|
||||
"minCost": {
|
||||
"a": 8,
|
||||
"b": -7
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 8,
|
||||
"b": 13
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "trident",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"name": "riptide",
|
||||
"displayName": "Riptide",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 7,
|
||||
"b": 10
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"loyalty",
|
||||
"channeling"
|
||||
],
|
||||
"category": "trident",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"name": "loyalty",
|
||||
"displayName": "Loyalty",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 7,
|
||||
"b": 5
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"riptide"
|
||||
],
|
||||
"category": "trident",
|
||||
"weight": 5,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"name": "channeling",
|
||||
"displayName": "Channeling",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 25
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"riptide"
|
||||
],
|
||||
"category": "trident",
|
||||
"weight": 1,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"name": "multishot",
|
||||
"displayName": "Multishot",
|
||||
"maxLevel": 1,
|
||||
"minCost": {
|
||||
"a": 0,
|
||||
"b": 20
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"piercing"
|
||||
],
|
||||
"category": "crossbow",
|
||||
"weight": 2,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"name": "piercing",
|
||||
"displayName": "Piercing",
|
||||
"maxLevel": 4,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": -9
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [
|
||||
"multishot"
|
||||
],
|
||||
"category": "crossbow",
|
||||
"weight": 10,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"name": "quick_charge",
|
||||
"displayName": "Quick Charge",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 20,
|
||||
"b": -8
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 0,
|
||||
"b": 50
|
||||
},
|
||||
"treasureOnly": false,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "crossbow",
|
||||
"weight": 5,
|
||||
"tradeable": true,
|
||||
"discoverable": true
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"name": "soul_speed",
|
||||
"displayName": "Soul Speed",
|
||||
"maxLevel": 3,
|
||||
"minCost": {
|
||||
"a": 10,
|
||||
"b": 0
|
||||
},
|
||||
"maxCost": {
|
||||
"a": 10,
|
||||
"b": 15
|
||||
},
|
||||
"treasureOnly": true,
|
||||
"curse": false,
|
||||
"exclude": [],
|
||||
"category": "armor_feet",
|
||||
"weight": 1,
|
||||
"tradeable": false,
|
||||
"discoverable": false
|
||||
}
|
||||
]
|
||||
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 one or more lines are too long
@@ -0,0 +1,978 @@
|
||||
# !StartDocs: Types
|
||||
|
||||
BehaviourPackInfos: []li16
|
||||
uuid: string
|
||||
version: string
|
||||
size: lu64
|
||||
content_key: string
|
||||
sub_pack_name: string
|
||||
content_identity: string
|
||||
has_scripts: bool
|
||||
|
||||
TexturePackInfos: []li16
|
||||
uuid: string
|
||||
version: string
|
||||
size: lu64
|
||||
content_key: string
|
||||
sub_pack_name: string
|
||||
content_identity: string
|
||||
has_scripts: bool
|
||||
rtx_enabled: bool
|
||||
|
||||
ResourcePackIdVersions: []varint
|
||||
# The ID of the resource pack.
|
||||
uuid: string
|
||||
# The version of the resource pack.
|
||||
version: string
|
||||
# The subpack name of the resource pack.
|
||||
name: string
|
||||
|
||||
ResourcePackIds: string[]li16
|
||||
|
||||
Experiment:
|
||||
name: string
|
||||
enabled: bool
|
||||
|
||||
Experiments: Experiment[]li32
|
||||
|
||||
GameMode: zigzag32 =>
|
||||
0: survival
|
||||
1: creative
|
||||
2: adventure
|
||||
3: survival_spectator
|
||||
4: creative_spectator
|
||||
5: fallback
|
||||
|
||||
GameRule:
|
||||
name: string
|
||||
type: varint =>
|
||||
1: bool
|
||||
2: int
|
||||
3: float
|
||||
value: type?
|
||||
if bool: bool
|
||||
if int: zigzag32
|
||||
if float: lf32
|
||||
|
||||
GameRules: GameRule[]varint
|
||||
|
||||
# CacheBlob represents a blob as used in the client side blob cache protocol. It holds a hash of its data and
|
||||
# the full data of it.
|
||||
Blob:
|
||||
# Hash is the hash of the blob. The hash is computed using xxHash, and must be deterministic for the same
|
||||
# chunk data.
|
||||
hash: lu64
|
||||
# Payload is the data of the blob. When sent, the client will associate the Hash of the blob with the
|
||||
# Payload in it.
|
||||
payload: ByteArray
|
||||
|
||||
BlockPalette: []varint
|
||||
name: string
|
||||
state: nbt
|
||||
|
||||
Itemstates: []varint
|
||||
name: string
|
||||
runtime_id: li16
|
||||
component_based: bool
|
||||
|
||||
Item:
|
||||
network_id: zigzag32
|
||||
_: network_id?
|
||||
if 0: void
|
||||
default:
|
||||
auxiliary_value: zigzag32
|
||||
has_nbt: lu16 =>
|
||||
0xffff: 'true'
|
||||
0x0000: 'false'
|
||||
nbt: has_nbt?
|
||||
if true:
|
||||
version: u8
|
||||
nbt: nbt
|
||||
default: void
|
||||
can_place_on: string[]zigzag32
|
||||
can_destroy: string[]zigzag32
|
||||
_: network_id?
|
||||
if 355:
|
||||
blocking_tick: zigzag64
|
||||
|
||||
vec3i:
|
||||
x: zigzag32
|
||||
y: zigzag32
|
||||
z: zigzag32
|
||||
|
||||
vec3u:
|
||||
x: varint
|
||||
y: varint
|
||||
z: varint
|
||||
|
||||
vec3f:
|
||||
x: lf32
|
||||
y: lf32
|
||||
z: lf32
|
||||
|
||||
vec2f:
|
||||
x: lf32
|
||||
z: lf32
|
||||
|
||||
MetadataDictionary: []varint
|
||||
# https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/entity/Entity.php#L101
|
||||
key: varint =>
|
||||
0: index
|
||||
1: health #int (minecart/boat)
|
||||
2: variant #int
|
||||
3: color #byte
|
||||
4: nametag #string
|
||||
5: owner_eid #long
|
||||
6: target_eid #long
|
||||
7: air #short
|
||||
8: potion_color #int (ARGB!)
|
||||
9: potion_ambient #byte
|
||||
10: jump_duration #long
|
||||
11: hurt_time #int (minecart/boat)
|
||||
12: hurt_direction #int (minecart/boat)
|
||||
13: paddle_time_left #float
|
||||
14: paddle_time_right #float
|
||||
15: experience_value #int (xp orb)
|
||||
16: minecart_display_block #int (id | (data << 16))
|
||||
17: minecart_display_offset #int
|
||||
18: minecart_has_display #byte (must be 1 for minecart to show block inside)
|
||||
20: old_swell
|
||||
21: swell_dir
|
||||
22: charge_amount
|
||||
23: enderman_held_runtime_id #short
|
||||
24: entity_age #short
|
||||
26: player_flags
|
||||
27: player_index
|
||||
28: player_bed_position #block coords
|
||||
29: fireball_power_x #float
|
||||
30: fireball_power_y
|
||||
31: fireball_power_z
|
||||
32: aux_power
|
||||
33: fish_x
|
||||
34: fish_z
|
||||
35: fish_angle
|
||||
36: potion_aux_value #short
|
||||
37: lead_holder_eid #long
|
||||
38: scale
|
||||
39: interactive_tag #string
|
||||
40: npc_skin_id #string
|
||||
41: url_tag #string
|
||||
42: max_airdata_max_air
|
||||
43: mark_variant #int
|
||||
44: container_type #byte
|
||||
45: container_base_size #int
|
||||
46: container_extra_slots_per_strength #int
|
||||
47: block_target
|
||||
48: wither_invulnerable_ticks #int
|
||||
49: wither_target_1 #long
|
||||
50: wither_target_2 #long
|
||||
51: wither_target_3 #long
|
||||
52: aerial_attack
|
||||
53: boundingbox_width
|
||||
54: boundingbox_height
|
||||
55: fuse_length
|
||||
56: rider_seat_position #vector3f
|
||||
57: rider_rotation_locked #byte
|
||||
58: rider_max_rotation #float
|
||||
59: rider_min_rotation #float
|
||||
60: area_effect_cloud_radius #float
|
||||
61: area_effect_cloud_waiting #int
|
||||
62: area_effect_cloud_particle_id #int
|
||||
63: shulker_peek_id #int
|
||||
64: shulker_attach_face #byte
|
||||
65: shulker_attached #short
|
||||
66: shulker_attach_pos
|
||||
67: trading_player_eid #long
|
||||
68: trading_career
|
||||
69: has_command_block
|
||||
70: command_block_command #string
|
||||
71: command_block_last_output #string
|
||||
72: command_block_track_output #byte
|
||||
73: controlling_rider_seat_number #byte
|
||||
74: strength #int
|
||||
75: max_strength #int
|
||||
76: spell_casting_color #int
|
||||
77: limited_life
|
||||
78: armor_stand_pose_index # int
|
||||
79: ender_crystal_time_offset # int
|
||||
80: always_show_nametag # byte
|
||||
81: color_2 # byte
|
||||
82: name_author
|
||||
83: score_tag #String
|
||||
84: balloon_attached_entity # long
|
||||
85: pufferfish_size
|
||||
86: bubble_time
|
||||
87: agent
|
||||
88: sitting_amount
|
||||
89: sitting_amount_previous
|
||||
90: eating_counter
|
||||
91: flags_extended
|
||||
92: laying_amount
|
||||
93: laying_amount_previous
|
||||
94: duration
|
||||
95: spawn_time
|
||||
96: change_rate
|
||||
97: change_on_pickup
|
||||
98: pickup_count
|
||||
99: interact_text
|
||||
100: trade_tier
|
||||
101: max_trade_tier
|
||||
102: trade_experience
|
||||
103: skin_id
|
||||
104: spawning_frames
|
||||
105: command_block_tick_delay
|
||||
106: command_block_execute_on_first_tick
|
||||
107: ambient_sound_interval
|
||||
108: ambient_sound_interval_range
|
||||
109: ambient_sound_event_name
|
||||
110: fall_damage_multiplier
|
||||
111: name_raw_text
|
||||
112: can_ride_target
|
||||
113: low_tier_cured_discount
|
||||
114: high_tier_cured_discount
|
||||
115: nearby_cured_discount
|
||||
116: nearby_cured_discount_timestamp
|
||||
117: hitbox
|
||||
118: is_buoyant
|
||||
119: buoyancy_data
|
||||
type: varint =>
|
||||
0: byte
|
||||
1: short
|
||||
2: int
|
||||
3: float
|
||||
4: string
|
||||
5: compound
|
||||
6: vec3i
|
||||
7: long
|
||||
8: vec3f
|
||||
value: type?
|
||||
if byte: i8
|
||||
if short: li16
|
||||
if int: zigzag32
|
||||
if float: lf32
|
||||
if string: string
|
||||
if compound: nbt
|
||||
if vec3i: vec3i
|
||||
if long: zigzag64
|
||||
if vec3f: vec3f
|
||||
|
||||
Link:
|
||||
ridden_entity_id: zigzag64
|
||||
rider_entity_id: zigzag64
|
||||
type: u8
|
||||
immediate: bool
|
||||
rider_initiated: bool
|
||||
|
||||
Links: Link[]varint
|
||||
|
||||
EntityAttributes: []varint
|
||||
name: string
|
||||
min: lf32
|
||||
value: lf32
|
||||
max: lf32
|
||||
|
||||
Rotation:
|
||||
yaw: byterot
|
||||
pitch: byterot
|
||||
head_yaw: byterot
|
||||
|
||||
BlockCoordinates: # mojang...
|
||||
x: zigzag32
|
||||
y: varint
|
||||
z: zigzag32
|
||||
|
||||
PlayerAttributes: []varint
|
||||
min: lf32
|
||||
max: lf32
|
||||
current: lf32
|
||||
default: lf32
|
||||
name: string
|
||||
|
||||
Transaction:
|
||||
# LegacyRequestID is an ID that is only non-zero at times when sent by the client. The server should
|
||||
# always send 0 for this. When this field is not 0, the LegacySetItemSlots slice below will have values
|
||||
# in it.
|
||||
# LegacyRequestID ties in with the ItemStackResponse packet. If this field is non-0, the server should
|
||||
# respond with an ItemStackResponse packet. Some inventory actions such as dropping an item out of the
|
||||
# hotbar are still one using this packet, and the ItemStackResponse packet needs to tie in with it.
|
||||
legacy_request_id: zigzag32
|
||||
# `legacy_transactions` are only present if the LegacyRequestID is non-zero. These item slots inform the
|
||||
# server of the slots that were changed during the inventory transaction, and the server should send
|
||||
# back an ItemStackResponse packet with these slots present in it. (Or false with no slots, if rejected.)
|
||||
legacy_transactions: legacy_request_id?
|
||||
if 0: void
|
||||
default: []varint
|
||||
container_id: u8
|
||||
changed_slots: []varint
|
||||
slot_id: u8
|
||||
transaction_type: varint =>
|
||||
# Sent for container UI operations depending on if ItemStackNetManager is enabled
|
||||
0: normal
|
||||
# Sent from server to client to reject a transaction
|
||||
1: inventory_mismatch
|
||||
# Sent for a player performing right click style item use.
|
||||
# See the contained ItemUseInventoryTransaction::ActionType for the expected use case.
|
||||
2: item_use
|
||||
# Sent for a player right clicking on an entity or attacking them.
|
||||
# See ItemUseInventoryTransaction::ActionType for which it is.
|
||||
3: item_use_on_entity
|
||||
# Sent when releasing right click on a chargeable item like a bow or finishing charging like a crossbow.
|
||||
# This is different than canceling item use early which would be in Player Auth Input.
|
||||
# See ItemReleaseInventoryTransaction::ActionType for which it is.
|
||||
4: item_release
|
||||
network_ids: bool
|
||||
inventory_actions: []varint
|
||||
source_type: varint =>
|
||||
0: container
|
||||
1: global
|
||||
2: world_interaction
|
||||
3: creative
|
||||
100: craft_slot
|
||||
99999: craft
|
||||
_: source_type?
|
||||
if container or creative:
|
||||
inventory_id: varint
|
||||
if world_interaction:
|
||||
flags: varint
|
||||
if craft or craft_slot:
|
||||
action: varint
|
||||
default: void
|
||||
slot: varint
|
||||
old_item: Item
|
||||
new_item: Item
|
||||
new_item_stack_id: ../network_ids?
|
||||
if true: zigzag32
|
||||
default: void
|
||||
transaction_data: transaction_type?
|
||||
if normal or inventory_mismatch: void
|
||||
# UseItemTransactionData represents an inventory transaction data object sent when the client uses an item on
|
||||
# a block.
|
||||
if item_use:
|
||||
# ActionType is the type of the UseItem inventory transaction. It is one of the action types found above,
|
||||
# and specifies the way the player interacted with the block.
|
||||
action_type: varint =>
|
||||
0: click_block
|
||||
1: click_air
|
||||
2: break_block
|
||||
# BlockPosition is the position of the block that was interacted with. This is only really a correct
|
||||
# block position if ActionType is not UseItemActionClickAir.
|
||||
block_position: BlockCoordinates
|
||||
# BlockFace is the face of the block that was interacted with. When clicking the block, it is the face
|
||||
# clicked. When breaking the block, it is the face that was last being hit until the block broke.
|
||||
face: varint
|
||||
# HotBarSlot is the hot bar slot that the player was holding while clicking the block. It should be used
|
||||
# to ensure that the hot bar slot and held item are correctly synchronised with the server.
|
||||
hotbar_slot: varint
|
||||
# HeldItem is the item that was held to interact with the block. The server should check if this item
|
||||
# is actually present in the HotBarSlot.
|
||||
held_item: Item
|
||||
# Position is the position of the player at the time of interaction. For clicking a block, this is the
|
||||
# position at that time, whereas for breaking the block it is the position at the time of breaking.
|
||||
player_pos: vec3f
|
||||
# ClickedPosition is the position that was clicked relative to the block's base coordinate. It can be
|
||||
# used to find out exactly where a player clicked the block.
|
||||
click_pos: vec3f
|
||||
# BlockRuntimeID is the runtime ID of the block that was clicked. It may be used by the server to verify
|
||||
# that the player's world client-side is synchronised with the server's.
|
||||
block_runtime_id: varint
|
||||
# UseItemOnEntityTransactionData represents an inventory transaction data object sent when the client uses
|
||||
# an item on an entity.
|
||||
if item_use_on_entity:
|
||||
# TargetEntityRuntimeID is the entity runtime ID of the target that was clicked. It is the runtime ID
|
||||
# that was assigned to it in the AddEntity packet.
|
||||
entity_runtime_id: varint64
|
||||
# ActionType is the type of the UseItemOnEntity inventory transaction. It is one of the action types
|
||||
# found in the constants above, and specifies the way the player interacted with the entity.
|
||||
action_type: varint =>
|
||||
# Right click interact with actor.
|
||||
0: interact
|
||||
# Left click style attack of actor or elytra spin attack. Server is expected to deal damage to the entity with visuals.
|
||||
1: attack
|
||||
# HotBarSlot is the hot bar slot that the player was holding while clicking the entity. It should be used
|
||||
# to ensure that the hot bar slot and held item are correctly synchronised with the server.
|
||||
hotbar_slot: zigzag32
|
||||
# HeldItem is the item that was held to interact with the entity. The server should check if this item
|
||||
# is actually present in the HotBarSlot.
|
||||
held_item: Item
|
||||
# Position is the position of the player at the time of clicking the entity.
|
||||
player_pos: vec3f
|
||||
# ClickedPosition is the position that was clicked relative to the entity's base coordinate. It can be
|
||||
# used to find out exactly where a player clicked the entity.
|
||||
click_pos: vec3f
|
||||
# ReleaseItemTransactionData represents an inventory transaction data object sent when the client releases
|
||||
# the item it was using, for example when stopping while eating or stopping the charging of a bow.
|
||||
if item_release:
|
||||
# ActionType is the type of the ReleaseItem inventory transaction. It is one of the action types found
|
||||
# in the constants above, and specifies the way the item was released.
|
||||
# As of 1.13, the ActionType is always 0. This field can be ignored, because releasing food (by consuming
|
||||
# it) or releasing a bow (to shoot an arrow) is essentially the same.
|
||||
action_type: varint =>
|
||||
# Release right click and hold style item use, like firing a bow
|
||||
0: release
|
||||
# Finish right click and hold style item use, like charging a crossbow
|
||||
1: consume
|
||||
# HotBarSlot is the hot bar slot that the player was holding while releasing the item. It should be used
|
||||
# to ensure that the hot bar slot and held item are correctly synchronised with the server.
|
||||
hotbar_slot: zigzag32
|
||||
# HeldItem is the item that was released. The server should check if this item is actually present in the
|
||||
# HotBarSlot.
|
||||
held_item: Item
|
||||
# HeadPosition is the position of the player's head at the time of releasing the item. This is used
|
||||
# mainly for purposes such as spawning eating particles at that position.
|
||||
head_pos: vec3f
|
||||
|
||||
# An "ItemStack" here represents an Item instance. You can think about it like a pointer
|
||||
# to an item class. The data for the class gets updated with the data in the `item` field
|
||||
ItemStack:
|
||||
# StackNetworkID is the network ID of the item stack. If the stack is empty, 0 is always written for this
|
||||
# field. If not, the field should be set to 1 if the server authoritative inventories are disabled in the
|
||||
# StartGame packet, or to a unique stack ID if it is enabled.
|
||||
runtime_id: zigzag32
|
||||
# Stack is the actual item stack of the item instance.
|
||||
item: Item
|
||||
|
||||
ItemStacks: ItemStack[]varint
|
||||
|
||||
RecipeIngredient:
|
||||
network_id: zigzag32
|
||||
_: network_id?
|
||||
if 0: void
|
||||
default:
|
||||
network_data: zigzag32
|
||||
count: zigzag32
|
||||
|
||||
PotionTypeRecipes: []varint
|
||||
input_item_id: zigzag32
|
||||
input_item_meta: zigzag32
|
||||
ingredient_id: zigzag32
|
||||
ingredient_meta: zigzag32
|
||||
output_item_id: zigzag32
|
||||
output_item_meta: zigzag32
|
||||
|
||||
PotionContainerChangeRecipes: []varint
|
||||
input_item_id: zigzag32
|
||||
ingredient_id: zigzag32
|
||||
output_item_id: zigzag32
|
||||
|
||||
Recipes: []varint
|
||||
type: zigzag32 =>
|
||||
'0': 'shapeless' #'ENTRY_SHAPELESS',
|
||||
'1': 'shaped' #'ENTRY_SHAPED',
|
||||
'2': 'furnace' # 'ENTRY_FURNACE',
|
||||
# `furnace_with_metadata` is a recipe specifically used for furnace-type crafting stations. It is equal to
|
||||
# `furnace`, except it has an input item with a specific metadata value, instead of any metadata value.
|
||||
'3': 'furnace_with_metadata' # 'ENTRY_FURNACE_DATA', // has metadata
|
||||
'4': 'multi' #'ENTRY_MULTI', //TODO
|
||||
'5': 'shulker_box' #'ENTRY_SHULKER_BOX', //TODO
|
||||
'6': 'shapeless_chemistry' #'ENTRY_SHAPELESS_CHEMISTRY', //TODO
|
||||
'7': 'shaped_chemistry' #'ENTRY_SHAPED_CHEMISTRY', //TODO
|
||||
recipe: type?
|
||||
if shapeless or shulker_box or shapeless_chemistry:
|
||||
recipe_id: LatinString
|
||||
input: RecipeIngredient[]varint
|
||||
output: Item[]varint
|
||||
uuid: uuid
|
||||
block: string
|
||||
priority: zigzag32
|
||||
network_id: zigzag32
|
||||
if shaped or shaped_chemistry:
|
||||
recipe_id: LatinString
|
||||
width: zigzag32
|
||||
height: zigzag32
|
||||
# todo: can this become
|
||||
# RecipeIngredient[$height][$width] or RecipeIngredient[]$height[]$width ?
|
||||
input: []$width
|
||||
_: RecipeIngredient[]$height
|
||||
output: Item[]varint
|
||||
uuid: uuid
|
||||
block: string
|
||||
priority: zigzag32
|
||||
network_id: zigzag32
|
||||
if furnace:
|
||||
input_id: zigzag32
|
||||
output: Item
|
||||
block: string
|
||||
if furnace_with_metadata:
|
||||
input_id: zigzag32
|
||||
input_meta: zigzag32
|
||||
output: Item
|
||||
block: string
|
||||
if multi:
|
||||
uuid: uuid
|
||||
network_id: zigzag32
|
||||
|
||||
SkinImage:
|
||||
width: li32
|
||||
height: li32
|
||||
data: string
|
||||
|
||||
Skin:
|
||||
skin_id: string
|
||||
skin_resource_pack: string
|
||||
skin_data: SkinImage
|
||||
animations: []li32
|
||||
skin_image: SkinImage
|
||||
animation_type: li32
|
||||
animation_frames: lf32
|
||||
expression_type: lf32
|
||||
cape_data: SkinImage
|
||||
geometry_data: string
|
||||
animation_data: string
|
||||
premium: string
|
||||
persona: bool
|
||||
cape_on_classic: bool
|
||||
cape_id: string
|
||||
full_skin_id: string
|
||||
arm_size: string
|
||||
skin_color: string
|
||||
personal_pieces: []li32
|
||||
piece_id: string
|
||||
piece_type: string
|
||||
pack_id: string
|
||||
is_default_piece: bool
|
||||
product_id: string
|
||||
piece_tint_colors: []li32
|
||||
piece_type: string
|
||||
colors: string[]li32
|
||||
|
||||
PlayerRecords:
|
||||
type: u8 =>
|
||||
0: add
|
||||
1: remove
|
||||
records_count: varint
|
||||
records: []$records_count
|
||||
_: type?
|
||||
if add:
|
||||
uuid: uuid
|
||||
entity_unique_id: zigzag64
|
||||
username: string
|
||||
xbox_user_id: string
|
||||
platform_chat_id: string
|
||||
build_platform: li32
|
||||
skin_data: Skin
|
||||
is_teacher: bool
|
||||
is_host: bool
|
||||
if remove:
|
||||
uuid: uuid
|
||||
verified: bool[]$records_count
|
||||
|
||||
ScoreEntries:
|
||||
type: u8 =>
|
||||
0: change
|
||||
1: remove
|
||||
entries: []varint
|
||||
scoreboard_id: zigzag64
|
||||
objective_name: string
|
||||
score: li32
|
||||
_: type?
|
||||
if remove:
|
||||
entry_type: i8 =>
|
||||
1: player
|
||||
2: entity
|
||||
3: fake_player
|
||||
entity_unique_id: entry_type?
|
||||
if player or entity: zigzag64
|
||||
custom_name: entry_type?
|
||||
if fake_player: string
|
||||
|
||||
ScoreboardIdentityEntries:
|
||||
type: i8 =>
|
||||
0: TYPE_REGISTER_IDENTITY
|
||||
1: TYPE_CLEAR_IDENTITY
|
||||
entries: []varint
|
||||
scoreboard_id: zigzag64
|
||||
entity_unique_id: type ?
|
||||
if TYPE_REGISTER_IDENTITY: zigzag64
|
||||
default: void
|
||||
|
||||
Enchant:
|
||||
id: u8
|
||||
level: u8
|
||||
|
||||
EnchantOptions: []varint
|
||||
cost: varint
|
||||
slot_flags: li32
|
||||
equip_enchants: Enchant[]varint
|
||||
held_enchants: Enchant[]varint
|
||||
self_enchants: Enchant[]varint
|
||||
name: string
|
||||
option_id: zigzag32
|
||||
|
||||
|
||||
StackRequestSlotInfo:
|
||||
container_id: u8
|
||||
slot_id: u8
|
||||
stack_id: zigzag32
|
||||
|
||||
#
|
||||
|
||||
ItemStackRequests: []varint
|
||||
# RequestID is a unique ID for the request. This ID is used by the server to send a response for this
|
||||
# specific request in the ItemStackResponse packet.
|
||||
request_id: zigzag32
|
||||
actions: []varint
|
||||
type_id: u8 =>
|
||||
# TakeStackRequestAction is sent by the client to the server to take x amount of items from one slot in a
|
||||
# container to the cursor.
|
||||
0: take
|
||||
# PlaceStackRequestAction is sent by the client to the server to place x amount of items from one slot into
|
||||
# another slot, such as when shift clicking an item in the inventory to move it around or when moving an item
|
||||
# in the cursor into a slot.
|
||||
1: place
|
||||
# SwapStackRequestAction is sent by the client to swap the item in its cursor with an item present in another
|
||||
# container. The two item stacks swap places.
|
||||
2: swap
|
||||
# DropStackRequestAction is sent by the client when it drops an item out of the inventory when it has its
|
||||
# inventory opened. This action is not sent when a player drops an item out of the hotbar using the Q button
|
||||
# (or the equivalent on mobile). The InventoryTransaction packet is still used for that action, regardless of
|
||||
# whether the item stack network IDs are used or not.
|
||||
3: drop
|
||||
# DestroyStackRequestAction is sent by the client when it destroys an item in creative mode by moving it
|
||||
# back into the creative inventory.
|
||||
4: destroy
|
||||
# ConsumeStackRequestAction is sent by the client when it uses an item to craft another item. The original
|
||||
# item is 'consumed'.
|
||||
5: consume
|
||||
# CreateStackRequestAction is sent by the client when an item is created through being used as part of a
|
||||
# recipe. For example, when milk is used to craft a cake, the buckets are leftover. The buckets are moved to
|
||||
# the slot sent by the client here.
|
||||
# Note that before this is sent, an action for consuming all items in the crafting table/grid is sent. Items
|
||||
# that are not fully consumed when used for a recipe should not be destroyed there, but instead, should be
|
||||
# turned into their respective resulting items.
|
||||
6: create
|
||||
# LabTableCombineStackRequestAction is sent by the client when it uses a lab table to combine item stacks.
|
||||
7: lab_table_combine
|
||||
# BeaconPaymentStackRequestAction is sent by the client when it submits an item to enable effects from a
|
||||
# beacon. These items will have been moved into the beacon item slot in advance.
|
||||
8: beacon_payment
|
||||
# CraftRecipeStackRequestAction is sent by the client the moment it begins crafting an item. This is the
|
||||
# first action sent, before the Consume and Create item stack request actions.
|
||||
# This action is also sent when an item is enchanted. Enchanting should be treated mostly the same way as
|
||||
# crafting, where the old item is consumed.
|
||||
9: craft_recipe
|
||||
# AutoCraftRecipeStackRequestAction is sent by the client similarly to the CraftRecipeStackRequestAction. The
|
||||
# only difference is that the recipe is automatically created and crafted by shift clicking the recipe book.
|
||||
10: craft_recipe_auto #recipe book?
|
||||
# CraftCreativeStackRequestAction is sent by the client when it takes an item out fo the creative inventory.
|
||||
# The item is thus not really crafted, but instantly created.
|
||||
11: craft_creative
|
||||
# CraftRecipeOptionalStackRequestAction is sent when using an anvil. When this action is sent, the
|
||||
# CustomNames field in the respective stack request is non-empty and contains the name of the item created
|
||||
# using the anvil.
|
||||
12: optional
|
||||
# CraftNonImplementedStackRequestAction is an action sent for inventory actions that aren't yet implemented
|
||||
# in the new system. These include, for example, anvils.
|
||||
13: non_implemented #anvils aren't fully implemented yet
|
||||
# CraftResultsDeprecatedStackRequestAction is an additional, deprecated packet sent by the client after
|
||||
# crafting. It holds the final results and the amount of times the recipe was crafted. It shouldn't be used.
|
||||
# This action is also sent when an item is enchanted. Enchanting should be treated mostly the same way as
|
||||
# crafting, where the old item is consumed.
|
||||
14: results_deprecated
|
||||
_: type_id ?
|
||||
if take or place:
|
||||
count: u8
|
||||
source: StackRequestSlotInfo
|
||||
destination: StackRequestSlotInfo
|
||||
if swap:
|
||||
# Source and Destination point to the source slot from which Count of the item stack were taken and the
|
||||
# destination slot to which this item was moved.
|
||||
source: StackRequestSlotInfo
|
||||
destination: StackRequestSlotInfo
|
||||
if drop:
|
||||
# Count is the count of the item in the source slot that was taken towards the destination slot.
|
||||
count: u8
|
||||
# Source is the source slot from which items were dropped to the ground.
|
||||
source: StackRequestSlotInfo
|
||||
# Randomly seems to be set to false in most cases. I'm not entirely sure what this does, but this is what
|
||||
# vanilla calls this field.
|
||||
randomly: bool
|
||||
if destroy or consume:
|
||||
# Count is the count of the item in the source slot that was destroyed.
|
||||
count: u8
|
||||
# Source is the source slot from which items came that were destroyed by moving them into the creative
|
||||
# inventory.
|
||||
source: StackRequestSlotInfo
|
||||
if create:
|
||||
# ResultsSlot is the slot in the inventory in which the results of the crafting ingredients are to be
|
||||
# placed.
|
||||
result_slot_id: u8
|
||||
if beacon_payment:
|
||||
# PrimaryEffect and SecondaryEffect are the effects that were selected from the beacon.
|
||||
primary_effect: zigzag32
|
||||
secondary_effect: zigzag32
|
||||
if craft_recipe:
|
||||
# RecipeNetworkID is the network ID of the recipe that is about to be crafted. This network ID matches
|
||||
# one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as
|
||||
# of 1.16.
|
||||
recipe_network_id: varint
|
||||
if craft_recipe_auto:
|
||||
# RecipeNetworkID is the network ID of the recipe that is about to be crafted. This network ID matches
|
||||
# one of the recipes sent in the CraftingData packet, where each of the recipes have a RecipeNetworkID as
|
||||
recipe_network_id: varint
|
||||
times_crafted: u8
|
||||
if craft_creative:
|
||||
# CreativeItemNetworkID is the network ID of the creative item that is being created. This is one of the
|
||||
# creative item network IDs sent in the CreativeContent packet.
|
||||
creative_item_network_id: varint
|
||||
if optional:
|
||||
# For the cartography table, if a certain MULTI recipe is being called, this points to the network ID that was assigned.
|
||||
recipe_network_id: varint
|
||||
# Most likely the index in the request's filter strings that this action is using
|
||||
filtered_string_index: li32
|
||||
if non_implemented: void
|
||||
if results_deprecated:
|
||||
result_items: Item[]varint
|
||||
times_crafted: u8
|
||||
# CustomNames is a list of custom names involved in the request. This is typically filled with one string
|
||||
# when an anvil is used.
|
||||
# * Used for the server to determine which strings should be filtered. Used in anvils to verify a renamed item.
|
||||
custom_names: string[]varint
|
||||
|
||||
ItemStackResponses: []varint
|
||||
result: u8
|
||||
request_id: varint
|
||||
containers: []varint
|
||||
container_id: u8
|
||||
slots: []varint
|
||||
slot: u8
|
||||
hotbar_slot: u8
|
||||
count: u8
|
||||
item_stack_id: varint
|
||||
custom_name: string
|
||||
|
||||
ItemComponentList: []varint
|
||||
# Name is the name of the item, which is a name like 'minecraft:stick'.
|
||||
name: string
|
||||
# Data is a map containing the components and properties of the item.
|
||||
nbt: nbt
|
||||
|
||||
CommandOrigin:
|
||||
# Origin is one of the values above that specifies the origin of the command. The origin may change,
|
||||
# depending on what part of the client actually called the command. The command may be issued by a
|
||||
# websocket server, for example.
|
||||
type: varint =>
|
||||
0: player
|
||||
1: block
|
||||
2: minecart_block
|
||||
3: dev_console
|
||||
4: test
|
||||
5: automation_player
|
||||
6: client_automation
|
||||
7: dedicated_server
|
||||
8: entity
|
||||
9: virtual
|
||||
10: game_argument
|
||||
11: entity_server
|
||||
# UUID is the UUID of the command called. This UUID is a bit odd as it is not specified by the server. It
|
||||
# is not clear what exactly this UUID is meant to identify, but it is unique for each command called.
|
||||
uuid: uuid
|
||||
# RequestID is an ID that identifies the request of the client. The server should send a CommandOrigin
|
||||
# with the same request ID to ensure it can be matched with the request by the caller of the command.
|
||||
# This is especially important for websocket servers and it seems that this field is only non-empty for
|
||||
# these websocket servers.
|
||||
request_id: string
|
||||
# PlayerUniqueID is an ID that identifies the player, the same as the one found in the AdventureSettings
|
||||
# packet. Filling it out with 0 seems to work.
|
||||
# PlayerUniqueID is only written if Origin is CommandOriginDevConsole or CommandOriginTest.
|
||||
player_entity_id: type?
|
||||
if dev_console or test:
|
||||
player_entity_id: zigzag64
|
||||
|
||||
# Some arbitrary definitions from CBMC, Window IDs are normally
|
||||
# unique + sequential
|
||||
WindowID: i8 =>
|
||||
-100: drop_contents
|
||||
-24: beacon
|
||||
-23: trading_output
|
||||
-22: trading_use_inputs
|
||||
-21: trading_input_2
|
||||
-20: trading_input_1
|
||||
-17: enchant_output
|
||||
-16: enchant_material
|
||||
-15: enchant_input
|
||||
-13: anvil_output
|
||||
-12: anvil_result
|
||||
-11: anvil_material
|
||||
-10: container_input
|
||||
-5: crafting_use_ingredient
|
||||
-4: crafting_result
|
||||
-3: crafting_remove_ingredient
|
||||
-2: crafting_add_ingredient
|
||||
-1: none
|
||||
0: inventory
|
||||
1: first
|
||||
100: last
|
||||
119: offhand
|
||||
120: armor
|
||||
121: creative
|
||||
122: hotbar
|
||||
123: fixed_inventory
|
||||
124: ui
|
||||
|
||||
WindowIDVarint: varint =>
|
||||
-100: drop_contents
|
||||
-24: beacon
|
||||
-23: trading_output
|
||||
-22: trading_use_inputs
|
||||
-21: trading_input_2
|
||||
-20: trading_input_1
|
||||
-17: enchant_output
|
||||
-16: enchant_material
|
||||
-15: enchant_input
|
||||
-13: anvil_output
|
||||
-12: anvil_result
|
||||
-11: anvil_material
|
||||
-10: container_input
|
||||
-5: crafting_use_ingredient
|
||||
-4: crafting_result
|
||||
-3: crafting_remove_ingredient
|
||||
-2: crafting_add_ingredient
|
||||
-1: none
|
||||
0: inventory
|
||||
1: first
|
||||
100: last
|
||||
119: offhand
|
||||
120: armor
|
||||
121: creative
|
||||
122: hotbar
|
||||
123: fixed_inventory
|
||||
124: ui
|
||||
|
||||
WindowType: u8 =>
|
||||
0: container
|
||||
1: workbench
|
||||
2: furnace
|
||||
3: enchantment
|
||||
4: brewing_stand
|
||||
5: anvil
|
||||
6: dispenser
|
||||
7: dropper
|
||||
8: hopper
|
||||
9: cauldron
|
||||
10: minecart_chest
|
||||
11: minecart_hopper
|
||||
12: horse
|
||||
13: beacon
|
||||
14: structure_editor
|
||||
15: trading
|
||||
16: command_block
|
||||
17: jukebox
|
||||
18: armor
|
||||
19: hand
|
||||
20: compound_creator
|
||||
21: element_constructor
|
||||
22: material_reducer
|
||||
23: lab_table
|
||||
24: loom
|
||||
25: lectern
|
||||
26: grindstone
|
||||
27: blast_furnace
|
||||
28: smoker
|
||||
29: stonecutter
|
||||
30: cartography
|
||||
31: hud
|
||||
32: jigsaw_editor
|
||||
33: smithing_table
|
||||
|
||||
# TODO: remove?
|
||||
LegacyEntityType: li32 =>
|
||||
10: chicken
|
||||
11: cow
|
||||
12: pig
|
||||
13: sheep
|
||||
14: wolf
|
||||
15: villager
|
||||
16: mooshroom
|
||||
17: squid
|
||||
18: rabbit
|
||||
19: bat
|
||||
20: iron_golem
|
||||
21: snow_golem
|
||||
22: ocelot
|
||||
23: horse
|
||||
24: donkey
|
||||
25: mule
|
||||
26: skeleton_horse
|
||||
27: zombie_horse
|
||||
28: polar_bear
|
||||
29: llama
|
||||
30: parrot
|
||||
31: dolphin
|
||||
32: zombie
|
||||
33: creeper
|
||||
34: skeleton
|
||||
35: spider
|
||||
36: zombie_pigman
|
||||
37: slime
|
||||
38: enderman
|
||||
39: silverfish
|
||||
40: cave_spider
|
||||
41: ghast
|
||||
42: magma_cube
|
||||
43: blaze
|
||||
44: zombie_villager
|
||||
45: witch
|
||||
46: stray
|
||||
47: husk
|
||||
48: wither_skeleton
|
||||
49: guardian
|
||||
50: elder_guardian
|
||||
51: npc
|
||||
52: wither
|
||||
53: ender_dragon
|
||||
54: shulker
|
||||
55: endermite
|
||||
56: agent # LEARN_TO_CODE_MASCOT
|
||||
57: vindicator
|
||||
58: phantom
|
||||
61: armor_stand
|
||||
62: tripod_camera
|
||||
63: player
|
||||
64: item
|
||||
65: tnt
|
||||
66: falling_block
|
||||
67: moving_block
|
||||
68: xp_bottle
|
||||
69: xp_orb
|
||||
70: eye_of_ender_signal
|
||||
71: ender_crystal
|
||||
72: fireworks_rocket
|
||||
73: thrown_trident
|
||||
74: turtle
|
||||
75: cat
|
||||
76: shulker_bullet
|
||||
77: fishing_hook
|
||||
78: chalkboard
|
||||
79: dragon_fireball
|
||||
80: arrow
|
||||
81: snowball
|
||||
82: egg
|
||||
83: painting
|
||||
84: minecart
|
||||
85: fireball
|
||||
86: splash_potion
|
||||
87: ender_pearl
|
||||
88: leash_knot
|
||||
89: wither_skull
|
||||
90: boat
|
||||
91: wither_skull_dangerous
|
||||
93: lightning_bolt
|
||||
94: small_fireball
|
||||
95: area_effect_cloud
|
||||
96: hopper_minecart
|
||||
97: tnt_minecart
|
||||
98: chest_minecart
|
||||
100: command_block_minecart
|
||||
101: lingering_potion
|
||||
102: llama_spit
|
||||
103: evocation_fang
|
||||
104: evocation_illager
|
||||
105: vex
|
||||
106: ice_bomb
|
||||
107: balloon
|
||||
108: pufferfish
|
||||
109: salmon
|
||||
110: drowned
|
||||
111: tropicalfish
|
||||
112: cod
|
||||
113: panda
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 422,
|
||||
"minecraftVersion": "1.16.201",
|
||||
"majorVersion": "1.16",
|
||||
"releaseType": "release"
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
[
|
||||
{
|
||||
"id": "cursor",
|
||||
"name": "cursor",
|
||||
"slots": [
|
||||
{
|
||||
"name": "cursor",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "armor",
|
||||
"name": "armor",
|
||||
"slots": [
|
||||
{
|
||||
"name": "armor",
|
||||
"index": 0,
|
||||
"size": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hand",
|
||||
"name": "hand",
|
||||
"slots": [
|
||||
{
|
||||
"name": "offhand",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "container",
|
||||
"name": "container",
|
||||
"slots": [
|
||||
{
|
||||
"name": "container",
|
||||
"index": 0,
|
||||
"size": 28
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "inventory",
|
||||
"name": "Inventory",
|
||||
"slots": [
|
||||
{
|
||||
"name": "hotbar_and_inventory",
|
||||
"index": 0,
|
||||
"size": 36
|
||||
},
|
||||
{
|
||||
"name": "inventory",
|
||||
"index": 9,
|
||||
"size": 36
|
||||
},
|
||||
{
|
||||
"name": "hotbar",
|
||||
"index": 0,
|
||||
"size": 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "workbench",
|
||||
"name": "Crafting Table",
|
||||
"slots": [
|
||||
{
|
||||
"name": "crafting_input",
|
||||
"index": 0,
|
||||
"size": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "furnace",
|
||||
"name": "furnace",
|
||||
"slots": [
|
||||
{
|
||||
"name": "furnace_fuel",
|
||||
"index": 1
|
||||
},
|
||||
{
|
||||
"name": "furnace_ingredient",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "enchantment",
|
||||
"name": "enchantment",
|
||||
"slots": [
|
||||
{
|
||||
"name": "enchanting_lapis",
|
||||
"index": 15
|
||||
},
|
||||
{
|
||||
"name": "enchanting_input",
|
||||
"index": 14
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "brewing_stand",
|
||||
"name": "brewing_stand",
|
||||
"slots": [
|
||||
{
|
||||
"name": "brewing_input",
|
||||
"index": 1,
|
||||
"size": 3
|
||||
},
|
||||
{
|
||||
"name": "brewing_fuel",
|
||||
"index": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "anvil",
|
||||
"name": "anvil",
|
||||
"slots": [
|
||||
{
|
||||
"name": "anvil_input",
|
||||
"index": 1
|
||||
},
|
||||
{
|
||||
"name": "anvil_material",
|
||||
"index": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dispenser",
|
||||
"name": "dispenser",
|
||||
"slots": [
|
||||
{
|
||||
"name": "container",
|
||||
"index": 0,
|
||||
"size": 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dropper",
|
||||
"name": "dropper",
|
||||
"slots": [
|
||||
{
|
||||
"name": "container",
|
||||
"index": 0,
|
||||
"size": 9
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hopper",
|
||||
"name": "hopper",
|
||||
"slots": [
|
||||
{
|
||||
"name": "container",
|
||||
"index": 0,
|
||||
"size": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "minecart_hopper",
|
||||
"name": "minecart_hopper",
|
||||
"slots": [
|
||||
{
|
||||
"name": "container",
|
||||
"index": 0,
|
||||
"size": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
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,6 @@
|
||||
{
|
||||
"version": 428,
|
||||
"minecraftVersion": "1.16.210",
|
||||
"majorVersion": "1.16",
|
||||
"releaseType": "release"
|
||||
}
|
||||
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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 431,
|
||||
"minecraftVersion": "1.16.220",
|
||||
"majorVersion": "1.16",
|
||||
"releaseType": "release"
|
||||
}
|
||||
@@ -0,0 +1,968 @@
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"name": "ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "rain",
|
||||
"depth": -1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Ocean",
|
||||
"color": 112,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "plains",
|
||||
"category": "plains",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.125,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Plains",
|
||||
"color": 9286496,
|
||||
"rainfall": 0.4000000059604645,
|
||||
"temperature": 0.800000011920929,
|
||||
"child": 129
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "desert",
|
||||
"category": "desert",
|
||||
"precipitation": "none",
|
||||
"depth": 0.125,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Desert",
|
||||
"color": 16421912,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"child": 130
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "extreme_hills",
|
||||
"category": "extreme_hills",
|
||||
"precipitation": "rain",
|
||||
"depth": 1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Mountains",
|
||||
"color": 6316128,
|
||||
"rainfall": 0.30000001192092896,
|
||||
"temperature": 0.20000000298023224,
|
||||
"child": 131
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "forest",
|
||||
"category": "forest",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Forest",
|
||||
"color": 353825,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.699999988079071,
|
||||
"child": 132
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "taiga",
|
||||
"category": "taiga",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Taiga",
|
||||
"color": 747097,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.25,
|
||||
"child": 133
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "swampland",
|
||||
"category": "swamp",
|
||||
"precipitation": "rain",
|
||||
"depth": -0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Swamp",
|
||||
"color": 522674,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.800000011920929,
|
||||
"child": 134
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "river",
|
||||
"category": "river",
|
||||
"precipitation": "rain",
|
||||
"depth": -0.5,
|
||||
"dimension": "overworld",
|
||||
"displayName": "River",
|
||||
"color": 255,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "hell",
|
||||
"category": "",
|
||||
"precipitation": "rain",
|
||||
"depth": 0,
|
||||
"dimension": "overworld",
|
||||
"displayName": "hell",
|
||||
"color": 0,
|
||||
"rainfall": 0,
|
||||
"temperature": 2
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "the_end",
|
||||
"category": "the_end",
|
||||
"precipitation": "none",
|
||||
"depth": 0.1,
|
||||
"dimension": "end",
|
||||
"displayName": "The End",
|
||||
"color": 8421631,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "frozen_river",
|
||||
"category": "river",
|
||||
"precipitation": "snow",
|
||||
"depth": -0.5,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Frozen River",
|
||||
"color": 10526975,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "ice_plains",
|
||||
"category": "icy",
|
||||
"precipitation": "snow",
|
||||
"depth": 0.125,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Snowy Tundra",
|
||||
"color": 16777215,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0,
|
||||
"child": 140
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "ice_mountains",
|
||||
"category": "icy",
|
||||
"precipitation": "snow",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Snowy Mountains",
|
||||
"color": 10526880,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "mushroom_island",
|
||||
"category": "mushroom",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Mushroom Fields",
|
||||
"color": 16711935,
|
||||
"rainfall": 1,
|
||||
"temperature": 0.8999999761581421
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "mushroom_island_shore",
|
||||
"category": "mushroom",
|
||||
"precipitation": "rain",
|
||||
"depth": 0,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Mushroom Fields Shore",
|
||||
"color": 10486015,
|
||||
"rainfall": 1,
|
||||
"temperature": 0.8999999761581421
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "beach",
|
||||
"category": "beach",
|
||||
"precipitation": "rain",
|
||||
"depth": 0,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Beach",
|
||||
"color": 16440917,
|
||||
"rainfall": 0.4000000059604645,
|
||||
"temperature": 0.800000011920929
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "desert_hills",
|
||||
"category": "desert",
|
||||
"precipitation": "none",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Desert Hills",
|
||||
"color": 13786898,
|
||||
"rainfall": 0,
|
||||
"temperature": 2
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "forest_hills",
|
||||
"category": "forest",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Wooded Hills",
|
||||
"color": 2250012,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.699999988079071
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "taiga_hills",
|
||||
"category": "taiga",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Taiga Hills",
|
||||
"color": 1456435,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.25
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "extreme_hills_edge",
|
||||
"category": "extreme_hills",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.8,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Mountain Edge",
|
||||
"color": 7501978,
|
||||
"rainfall": 0.30000001192092896,
|
||||
"temperature": 0.20000000298023224
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"name": "jungle",
|
||||
"category": "jungle",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Jungle",
|
||||
"color": 5470985,
|
||||
"rainfall": 0.8999999761581421,
|
||||
"temperature": 0.949999988079071,
|
||||
"child": 149
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "jungle_hills",
|
||||
"category": "jungle",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Jungle Hills",
|
||||
"color": 2900485,
|
||||
"rainfall": 0.8999999761581421,
|
||||
"temperature": 0.949999988079071
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"name": "jungle_edge",
|
||||
"category": "jungle",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Jungle Edge",
|
||||
"color": 6458135,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.949999988079071,
|
||||
"child": 151
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"name": "deep_ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "rain",
|
||||
"depth": -1.8,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Deep Ocean",
|
||||
"color": 48,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"name": "stone_beach",
|
||||
"category": "none",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Stone Shore",
|
||||
"color": 10658436,
|
||||
"rainfall": 0.30000001192092896,
|
||||
"temperature": 0.20000000298023224
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"name": "cold_beach",
|
||||
"category": "beach",
|
||||
"precipitation": "snow",
|
||||
"depth": 0,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Snowy Beach",
|
||||
"color": 16445632,
|
||||
"rainfall": 0.30000001192092896,
|
||||
"temperature": 0.05000000074505806
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"name": "birch_forest",
|
||||
"category": "forest",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Birch Forest",
|
||||
"color": 3175492,
|
||||
"rainfall": 0.6000000238418579,
|
||||
"temperature": 0.6000000238418579,
|
||||
"child": 155
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "birch_forest_hills",
|
||||
"category": "forest",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Birch Forest Hills",
|
||||
"color": 2055986,
|
||||
"rainfall": 0.6000000238418579,
|
||||
"temperature": 0.6000000238418579,
|
||||
"child": 156
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"name": "roofed_forest",
|
||||
"category": "forest",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Dark Forest",
|
||||
"color": 4215066,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.699999988079071,
|
||||
"child": 157
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"name": "cold_taiga",
|
||||
"category": "taiga",
|
||||
"precipitation": "snow",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Snowy Taiga",
|
||||
"color": 3233098,
|
||||
"rainfall": 0.4000000059604645,
|
||||
"temperature": -0.5,
|
||||
"child": 158
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"name": "cold_taiga_hills",
|
||||
"category": "taiga",
|
||||
"precipitation": "snow",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Snowy Taiga Hills",
|
||||
"color": 2375478,
|
||||
"rainfall": 0.4000000059604645,
|
||||
"temperature": -0.5
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"name": "mega_taiga",
|
||||
"category": "taiga",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Giant Tree Taiga",
|
||||
"color": 5858897,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.30000001192092896,
|
||||
"child": 160
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"name": "mega_taiga_hills",
|
||||
"category": "taiga",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Giant Tree Taiga Hills",
|
||||
"color": 4542270,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.30000001192092896,
|
||||
"child": 161
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"name": "extreme_hills_plus_trees",
|
||||
"category": "extreme_hills",
|
||||
"precipitation": "rain",
|
||||
"depth": 1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Wooded Mountains",
|
||||
"color": 5271632,
|
||||
"rainfall": 0.30000001192092896,
|
||||
"temperature": 0.20000000298023224,
|
||||
"child": 162
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"name": "savanna",
|
||||
"category": "savanna",
|
||||
"precipitation": "none",
|
||||
"depth": 0.125,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Savanna",
|
||||
"color": 12431967,
|
||||
"rainfall": 0,
|
||||
"temperature": 1.2000000476837158,
|
||||
"child": 163
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"name": "savanna_plateau",
|
||||
"category": "savanna",
|
||||
"precipitation": "none",
|
||||
"depth": 1.5,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Savanna Plateau",
|
||||
"color": 10984804,
|
||||
"rainfall": 0,
|
||||
"temperature": 1,
|
||||
"child": 164
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"name": "mesa",
|
||||
"category": "mesa",
|
||||
"precipitation": "none",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Badlands",
|
||||
"color": 14238997,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"child": 165
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"name": "mesa_plateau_stone",
|
||||
"category": "mesa",
|
||||
"precipitation": "none",
|
||||
"depth": 1.5,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Wooded Badlands Plateau",
|
||||
"color": 11573093,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"child": 166
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"name": "mesa_plateau",
|
||||
"category": "mesa",
|
||||
"precipitation": "none",
|
||||
"depth": 1.5,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Badlands Plateau",
|
||||
"color": 13274213,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"child": 167
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"name": "warm_ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "rain",
|
||||
"depth": -1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Warm Ocean",
|
||||
"color": 172,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"name": "deep_warm_ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "rain",
|
||||
"depth": -1.8,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Deep Warm Ocean",
|
||||
"color": 80,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"name": "lukewarm_ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "rain",
|
||||
"depth": -1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Lukewarm Ocean",
|
||||
"color": 144,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"name": "deep_lukewarm_ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "rain",
|
||||
"depth": -1.8,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Deep Lukewarm Ocean",
|
||||
"color": 64,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"name": "cold_ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "rain",
|
||||
"depth": -1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Cold Ocean",
|
||||
"color": 2105456,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"name": "deep_cold_ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "rain",
|
||||
"depth": -1.8,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Deep Cold Ocean",
|
||||
"color": 2105400,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.5
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"name": "frozen_ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "snow",
|
||||
"depth": -1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Frozen Ocean",
|
||||
"color": 7368918,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"name": "deep_frozen_ocean",
|
||||
"category": "ocean",
|
||||
"precipitation": "rain",
|
||||
"depth": -1.8,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Deep Frozen Ocean",
|
||||
"color": 4210832,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"name": "bamboo_jungle",
|
||||
"category": "jungle",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Bamboo Jungle",
|
||||
"color": 7769620,
|
||||
"rainfall": 0.8999999761581421,
|
||||
"temperature": 0.949999988079071
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"name": "bamboo_jungle_hills",
|
||||
"category": "jungle",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Bamboo Jungle Hills",
|
||||
"color": 3884810,
|
||||
"rainfall": 0.8999999761581421,
|
||||
"temperature": 0.949999988079071
|
||||
},
|
||||
{
|
||||
"id": 129,
|
||||
"name": "sunflower_plains",
|
||||
"category": "plains",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.125,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Sunflower Plains",
|
||||
"color": 11918216,
|
||||
"rainfall": 0.4000000059604645,
|
||||
"temperature": 0.800000011920929,
|
||||
"parent": "plains"
|
||||
},
|
||||
{
|
||||
"id": 130,
|
||||
"name": "desert_mutated",
|
||||
"category": "desert",
|
||||
"precipitation": "none",
|
||||
"depth": 0.125,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Desert Lakes",
|
||||
"color": 16759872,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"parent": "desert"
|
||||
},
|
||||
{
|
||||
"id": 131,
|
||||
"name": "extreme_hills_mutated",
|
||||
"category": "extreme_hills",
|
||||
"precipitation": "rain",
|
||||
"depth": 1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Gravelly Mountains",
|
||||
"color": 8947848,
|
||||
"rainfall": 0.30000001192092896,
|
||||
"temperature": 0.20000000298023224,
|
||||
"parent": "mountains"
|
||||
},
|
||||
{
|
||||
"id": 132,
|
||||
"name": "flower_forest",
|
||||
"category": "forest",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Flower Forest",
|
||||
"color": 2985545,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.699999988079071,
|
||||
"parent": "forest"
|
||||
},
|
||||
{
|
||||
"id": 133,
|
||||
"name": "taiga_mutated",
|
||||
"category": "taiga",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.3,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Taiga Mountains",
|
||||
"color": 3378817,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.25,
|
||||
"parent": "taiga"
|
||||
},
|
||||
{
|
||||
"id": 134,
|
||||
"name": "swampland_mutated",
|
||||
"category": "swamp",
|
||||
"precipitation": "rain",
|
||||
"depth": -0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Swamp Hills",
|
||||
"color": 3145690,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 0.800000011920929,
|
||||
"parent": "swamp"
|
||||
},
|
||||
{
|
||||
"id": 140,
|
||||
"name": "ice_plains_spikes",
|
||||
"category": "icy",
|
||||
"precipitation": "snow",
|
||||
"depth": 0.425,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Ice Spikes",
|
||||
"color": 11853020,
|
||||
"rainfall": 1,
|
||||
"temperature": 0,
|
||||
"parent": "snowy_tundra"
|
||||
},
|
||||
{
|
||||
"id": 149,
|
||||
"name": "jungle_mutated",
|
||||
"category": "jungle",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Modified Jungle",
|
||||
"color": 8102705,
|
||||
"rainfall": 0.8999999761581421,
|
||||
"temperature": 0.949999988079071,
|
||||
"parent": "jungle"
|
||||
},
|
||||
{
|
||||
"id": 151,
|
||||
"name": "jungle_edge_mutated",
|
||||
"category": "jungle",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Modified Jungle Edge",
|
||||
"color": 9089855,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.949999988079071,
|
||||
"parent": "jungle_edge"
|
||||
},
|
||||
{
|
||||
"id": 155,
|
||||
"name": "birch_forest_mutated",
|
||||
"category": "forest",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Tall Birch Forest",
|
||||
"color": 5807212,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.699999988079071,
|
||||
"parent": "birch_forest"
|
||||
},
|
||||
{
|
||||
"id": 156,
|
||||
"name": "birch_forest_hills_mutated",
|
||||
"category": "forest",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.55,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Tall Birch Hills",
|
||||
"color": 4687706,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.699999988079071,
|
||||
"parent": "birch_forest_hills"
|
||||
},
|
||||
{
|
||||
"id": 157,
|
||||
"name": "roofed_forest_mutated",
|
||||
"category": "forest",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Dark Forest Hills",
|
||||
"color": 6846786,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.699999988079071,
|
||||
"parent": "dark_forest"
|
||||
},
|
||||
{
|
||||
"id": 158,
|
||||
"name": "cold_taiga_mutated",
|
||||
"category": "taiga",
|
||||
"precipitation": "snow",
|
||||
"depth": 0.3,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Snowy Taiga Mountains",
|
||||
"color": 5864818,
|
||||
"rainfall": 0.4000000059604645,
|
||||
"temperature": -0.5,
|
||||
"parent": "snowy_taiga"
|
||||
},
|
||||
{
|
||||
"id": 160,
|
||||
"name": "redwood_taiga_mutated",
|
||||
"category": "taiga",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Giant Spruce Taiga",
|
||||
"color": 8490617,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.25,
|
||||
"parent": "giant_tree_taiga"
|
||||
},
|
||||
{
|
||||
"id": 161,
|
||||
"name": "redwood_taiga_hills_mutated",
|
||||
"category": "taiga",
|
||||
"precipitation": "rain",
|
||||
"depth": 0.2,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Giant Spruce Taiga Hills",
|
||||
"color": 7173990,
|
||||
"rainfall": 0.800000011920929,
|
||||
"temperature": 0.30000001192092896,
|
||||
"parent": "giant_tree_taiga_hills"
|
||||
},
|
||||
{
|
||||
"id": 162,
|
||||
"name": "extreme_hills_plus_trees_mutated",
|
||||
"category": "extreme_hills",
|
||||
"precipitation": "rain",
|
||||
"depth": 1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Gravelly Mountains+",
|
||||
"color": 7903352,
|
||||
"rainfall": 0.30000001192092896,
|
||||
"temperature": 0.20000000298023224,
|
||||
"parent": "wooded_mountains"
|
||||
},
|
||||
{
|
||||
"id": 163,
|
||||
"name": "savanna_mutated",
|
||||
"category": "savanna",
|
||||
"precipitation": "none",
|
||||
"depth": 0.3625,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Shattered Savanna",
|
||||
"color": 15063687,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 1.100000023841858,
|
||||
"parent": "savanna"
|
||||
},
|
||||
{
|
||||
"id": 164,
|
||||
"name": "savanna_plateau_mutated",
|
||||
"category": "savanna",
|
||||
"precipitation": "none",
|
||||
"depth": 1.05,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Shattered Savanna Plateau",
|
||||
"color": 13616524,
|
||||
"rainfall": 0.5,
|
||||
"temperature": 1,
|
||||
"parent": "savanna_plateau"
|
||||
},
|
||||
{
|
||||
"id": 165,
|
||||
"name": "mesa_bryce",
|
||||
"category": "mesa",
|
||||
"precipitation": "none",
|
||||
"depth": 0.1,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Eroded Badlands",
|
||||
"color": 16739645,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"parent": "badlands"
|
||||
},
|
||||
{
|
||||
"id": 166,
|
||||
"name": "mesa_plateau_stone_mutated",
|
||||
"category": "mesa",
|
||||
"precipitation": "none",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Modified Wooded Badlands Plateau",
|
||||
"color": 14204813,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"parent": "wooded_badlands_plateau"
|
||||
},
|
||||
{
|
||||
"id": 167,
|
||||
"name": "mesa_plateau_mutated",
|
||||
"category": "mesa",
|
||||
"precipitation": "none",
|
||||
"depth": 0.45,
|
||||
"dimension": "overworld",
|
||||
"displayName": "Modified Badlands Plateau",
|
||||
"color": 15905933,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"parent": "badlands_plateau"
|
||||
},
|
||||
{
|
||||
"id": 178,
|
||||
"name": "soulsand_valley",
|
||||
"category": "nether",
|
||||
"precipitation": "none",
|
||||
"depth": 0.1,
|
||||
"dimension": "nether",
|
||||
"displayName": "Soul Sand Valley",
|
||||
"color": 6174768,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"climates": [
|
||||
{
|
||||
"temperature": 0,
|
||||
"humidity": -0.5,
|
||||
"altitude": 0,
|
||||
"weirdness": 0,
|
||||
"offset": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 179,
|
||||
"name": "crimson_forest",
|
||||
"category": "nether",
|
||||
"precipitation": "none",
|
||||
"depth": 0.1,
|
||||
"dimension": "nether",
|
||||
"displayName": "Crimson Forest",
|
||||
"color": 14485512,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"climates": [
|
||||
{
|
||||
"temperature": 0.4,
|
||||
"humidity": 0,
|
||||
"altitude": 0,
|
||||
"weirdness": 0,
|
||||
"offset": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 180,
|
||||
"name": "warped_forest",
|
||||
"category": "nether",
|
||||
"precipitation": "none",
|
||||
"depth": 0.1,
|
||||
"dimension": "nether",
|
||||
"displayName": "Warped Forest",
|
||||
"color": 4821115,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"climates": [
|
||||
{
|
||||
"temperature": 0,
|
||||
"humidity": 0.5,
|
||||
"altitude": 0,
|
||||
"weirdness": 0,
|
||||
"offset": 0.375
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 181,
|
||||
"name": "basalt_deltas",
|
||||
"category": "nether",
|
||||
"precipitation": "none",
|
||||
"depth": 0.1,
|
||||
"dimension": "nether",
|
||||
"displayName": "Basalt Deltas",
|
||||
"color": 4208182,
|
||||
"rainfall": 0,
|
||||
"temperature": 2,
|
||||
"climates": [
|
||||
{
|
||||
"temperature": -0.5,
|
||||
"humidity": 0,
|
||||
"altitude": 0,
|
||||
"weirdness": 0,
|
||||
"offset": 0.175
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,82 @@
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"sound": "note.harp",
|
||||
"name": "harp"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"sound": "note.bd",
|
||||
"name": "basedrum"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"sound": "note.snare",
|
||||
"name": "snare"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"sound": "note.hat",
|
||||
"name": "hat"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"sound": "note.bass",
|
||||
"name": "bass"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"sound": "note.flute",
|
||||
"name": "flute"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"sound": "note.bell",
|
||||
"name": "bell"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"sound": "note.guitar",
|
||||
"name": "guitar"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"sound": "note.chime",
|
||||
"name": "chime"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"sound": "note.xylophone",
|
||||
"name": "xylophone"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"sound": "note.iron_xylophone",
|
||||
"name": "iron_xylophone"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"sound": "note.cow_bell",
|
||||
"name": "cow_bell"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"sound": "note.didgeridoo",
|
||||
"name": "didgeridoo"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"sound": "note.bit",
|
||||
"name": "bit"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"sound": "note.banjo",
|
||||
"name": "banjo"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"sound": "note.pling",
|
||||
"name": "pling"
|
||||
}
|
||||
]
|
||||
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,6 @@
|
||||
{
|
||||
"version": 440,
|
||||
"minecraftVersion": "1.17.0",
|
||||
"majorVersion": "1.17",
|
||||
"releaseType": "release"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
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
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
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user