Compare commits
20 commits
Author | SHA1 | Date | |
---|---|---|---|
|
7d01f14808 | ||
|
67c4decf8e | ||
|
70b109758d | ||
|
e7841b1b27 | ||
|
447a199379 | ||
|
b54b14782e | ||
|
1d64b215f4 | ||
|
622f611624 | ||
|
3725ed9367 | ||
|
ad40784871 | ||
|
51aa7b5aff | ||
|
ec4b105f09 | ||
|
5340a50f0c | ||
|
3313663a3f | ||
|
9a378bb69c | ||
|
986dc2c7a0 | ||
|
307486a32c | ||
|
3c5661aed9 | ||
|
cf2e0e012d | ||
|
a39989b331 |
1033 changed files with 18554 additions and 68910 deletions
76
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
76
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
|
@ -1,76 +0,0 @@
|
|||
name: Report a Bug
|
||||
description: File a bug report
|
||||
title: "[Bug] "
|
||||
labels: [ "bug" ]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to report a Bug in BCLib!
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you see!
|
||||
value: "A bug happened!"
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Versions
|
||||
- type: input
|
||||
id: bn_version
|
||||
attributes:
|
||||
label: BCLib
|
||||
description: What version of BCLib are you running?
|
||||
placeholder: 3.x.x
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: fabric_api_version
|
||||
attributes:
|
||||
label: Fabric API
|
||||
description: What version of Fabric API is installed
|
||||
placeholder: 0.8x.x
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: fabric_loader_version
|
||||
attributes:
|
||||
label: Fabric Loader
|
||||
description: What version of Fabric Loader do you use
|
||||
placeholder: 0.14.x
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: mc_version
|
||||
attributes:
|
||||
label: Minecraft
|
||||
description: What version of Minecraft is installed?
|
||||
options:
|
||||
- 1.20.1
|
||||
- 1.20.0
|
||||
- 1.19.4
|
||||
- 1.19.3 [unsupported]
|
||||
- 1.19.2
|
||||
- Older
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Additional Information
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: other_mods
|
||||
attributes:
|
||||
label: Other Mods
|
||||
description: If you can, please supply a list of installed Mods (besides BetterNether and BCLib). This information may already be included in the log above.
|
||||
render: shell
|
18
.github/ISSUE_TEMPLATE/SUGGEST_FROM.yml
vendored
18
.github/ISSUE_TEMPLATE/SUGGEST_FROM.yml
vendored
|
@ -1,18 +0,0 @@
|
|||
name: Suggest a Feature or Change
|
||||
description: Have a new Idea, then suggest a Feature here.
|
||||
title: "[Suggestion] "
|
||||
labels: ["suggestion"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to suggest a new Feature for BCLib. We appreciate your time!
|
||||
- type: textarea
|
||||
id: describe
|
||||
attributes:
|
||||
label: Description
|
||||
description: Tell us your idea
|
||||
placeholder:
|
||||
value:
|
||||
validations:
|
||||
required: true
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
1
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1 +0,0 @@
|
|||
blank_issues_enabled: true
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -27,10 +27,5 @@ bin/
|
|||
# fabric
|
||||
|
||||
run/
|
||||
run-client/
|
||||
run-server/
|
||||
output/
|
||||
*.log
|
||||
/CHANGES.md
|
||||
/src/main/generated/.cache/
|
||||
/modrinth.json
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
<code_scheme name="BetterX" version="173">
|
||||
<option name="ENABLE_SECOND_REFORMAT" value="true" />
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="org.betterx" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com.mojang" withSubpackages="true" static="false" />
|
||||
<package name="net.minecraft" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="net.fabricmc" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="javax" withSubpackages="true" static="false" />
|
||||
<package name="java" withSubpackages="true" static="false" />
|
||||
<package name="org.jetbrains" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="" withSubpackages="true" static="true" />
|
||||
</value>
|
||||
</option>
|
||||
</JavaCodeStyleSettings>
|
||||
<codeStyleSettings language="JAVA">
|
||||
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="5" />
|
||||
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="5" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="5" />
|
||||
<option name="TERNARY_OPERATION_WRAP" value="5" />
|
||||
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||
<option name="ARRAY_INITIALIZER_WRAP" value="5" />
|
||||
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
|
||||
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
4
LICENSE
4
LICENSE
|
@ -19,7 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
___________________________
|
||||
Some of our Assets (see LICENSE.ASSETS for a Listing) is licensed under CC BY-NC-SA 4.0
|
||||
See https://creativecommons.org/licenses/by-nc-sa/4.0/ for Details.
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
Some of our Assets (see List below) is licensed under CC BY-NC-SA 4.0
|
||||
See https://creativecommons.org/licenses/by-nc-sa/4.0/ for Details.
|
||||
|
||||
Please use the Attribution "Team BetterX".
|
||||
|
||||
The following Files are distributed under this License:
|
||||
* src/main/resources/assets/bclib/lang/de_de.json
|
||||
* src/main/resources/assets/bclib/textures/*
|
||||
* src/main/resources/assets/bclib/betterx.png
|
||||
* src/main/resources/assets/bclib/header.jpg
|
||||
* src/main/resources/assets/bclib/icon_betterend.png
|
||||
* src/main/resources/assets/bclib/icon_betternether.png
|
||||
* src/main/resources/assets/bclib/icon_bright.png
|
||||
* src/main/resources/assets/bclib/icon_updater.png
|
157
README.md
157
README.md
|
@ -1,146 +1,41 @@
|
|||
[](https://jitpack.io/#quiqueck/BCLib)
|
||||
|
||||
[](https://jitpack.io/#paulevsGitch/BCLib)
|
||||
# BCLib
|
||||
|
||||
BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.19
|
||||
|
||||
## Importing:
|
||||
|
||||
You can easily include BCLib into your own mod by adding the following to your `build.gradle`:
|
||||
|
||||
```
|
||||
repositories {
|
||||
...
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
dependencies {
|
||||
...
|
||||
modImplementation "com.github.quiqueck:BCLib:${project.bclib_version}"
|
||||
}
|
||||
```
|
||||
|
||||
You should also add a dependency to `fabirc.mod.json`. BCLib uses Semantic versioning, so adding the dependcy as follows
|
||||
should respect that and ensure that your mod is not loaded with an incompatible version of BCLib:
|
||||
|
||||
```
|
||||
"depends": {
|
||||
...
|
||||
"bclib": "2.0.x"
|
||||
},
|
||||
"breaks": {
|
||||
"bclib": "<2.0.6"
|
||||
}
|
||||
```
|
||||
|
||||
In this example `2.0.6` is the BCLIb Version you are building against.
|
||||
BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.16.5
|
||||
|
||||
## Features:
|
||||
|
||||
### Rendering
|
||||
|
||||
* Emissive textures (with _e suffix)
|
||||
* Can be applied to Solid and Transparent blocks;
|
||||
* Can be changed/added with resourcepacks;
|
||||
* Incompatible with Sodium and Canvas (just will be not rendered);
|
||||
* Incompatible with Iris shaders (Iris without shaders works fine).
|
||||
* Procedural block and item models (from paterns or from code);
|
||||
* Block render interfaces.
|
||||
|
||||
### API:
|
||||
|
||||
* Simple Mod Integration API:
|
||||
* Get mod inner methods, classes and objects on runtime.
|
||||
* Structure Features API:
|
||||
* Sructure Features with automatical registration, Helpers and math stuff.
|
||||
* World Data API:
|
||||
* World fixers for comfortable migration between mod versions when content was removed;
|
||||
* Support for Block name changes and Tile Entities (WIP).
|
||||
* Bonemeal API:
|
||||
* Add custom spreadable blocks;
|
||||
* Add custom plants grow with weight, biomes and other checks;
|
||||
* Custom underwater plants.
|
||||
* Features API:
|
||||
* Features with automatical registration, Helpers and math.
|
||||
* Biome API:
|
||||
* Biome wrapper around MC biomes;
|
||||
* Custom biome data storage;
|
||||
* Custom fog density.
|
||||
* Tag API:
|
||||
* Pre-builded set of tags;
|
||||
* Dynamical tag registration with code;
|
||||
* Adding blocks and items into tags at runtime.
|
||||
* Simple Mod Integration API;
|
||||
* Structure Features API;
|
||||
* World Data API;
|
||||
* Bonemeal API;
|
||||
* Features API;
|
||||
* Biome API;
|
||||
* Tag API.
|
||||
|
||||
### Libs:
|
||||
|
||||
* Spline library (simple):
|
||||
* Helper to create simple splines as set of points;
|
||||
* Some basic operation with splines;
|
||||
* Converting splines to SDF.
|
||||
* Recipe manager:
|
||||
* Register recipes from code with configs and ingredients check.
|
||||
* Noise library:
|
||||
* Voronoi noise and Open Simplex Noise.
|
||||
* Math library:
|
||||
* Many basic math functions that are missing in MC.
|
||||
* SDF library:
|
||||
* Implementation of Signed Distance Functions;
|
||||
* Different SDF Operations and Primitives;
|
||||
* Different materials for SDF Primitives;
|
||||
* Block post-processing;
|
||||
* Feature generation using SDF.
|
||||
* Spline library (simple);
|
||||
* Recipe manager;
|
||||
* Noise library;
|
||||
* Math library;
|
||||
* SDF library.
|
||||
|
||||
### Helpers And Utils:
|
||||
* Custom surface builders;
|
||||
* Translation helper;
|
||||
* Weighted list;
|
||||
* Block helper.
|
||||
|
||||
* Custom surface builders.
|
||||
* Translation helper:
|
||||
* Generates translation template.
|
||||
* Weighted list:
|
||||
* A list of objects by weight;
|
||||
* Weighted Tree:
|
||||
* Fast approach for big weight structures;
|
||||
* Block helper:
|
||||
* Some useful functions to operate with blocks;
|
||||
### Rendering:
|
||||
* Procedural block models (from paterns or from code);
|
||||
* Block render layer interface.
|
||||
|
||||
### Complex Materials
|
||||
|
||||
* Utility classes used for mass content generation (wooden blocks, stone blocks, etc.);
|
||||
* Contains a set of defined blocks, items, recipes and tags;
|
||||
* Can be modified before mods startup (will add new block type for all instances in all mods);
|
||||
* All inner blocks and items are Patterned (will have auto-generated models with ability to override them with resource
|
||||
packs or mod resources).
|
||||
|
||||
### Pre-Defined Blocks and Items:
|
||||
|
||||
* Most basic blocks from MC;
|
||||
* Automatic item & block model generation;
|
||||
|
||||
### Configs:
|
||||
|
||||
* Custom config system based on Json;
|
||||
* Hierarchical configs;
|
||||
* Different entry types;
|
||||
* Only-changes saves.
|
||||
|
||||
### Interfaces:
|
||||
|
||||
* BlockModelProvider:
|
||||
* Allows block to return custom model and blockstate.
|
||||
* ItemModelProvider:
|
||||
* Allows block to return custom item model.
|
||||
* CustomColorProvider:
|
||||
* Make available to add block and item color provider.
|
||||
* RenderLayerProvider:
|
||||
* Determine block render layer (Transparent and Translucent).
|
||||
* PostInitable:
|
||||
* Allows block to init something after all mods are loaded.
|
||||
* CustomItemProvider:
|
||||
* Allows block to change its registered item (example - signs, water lilies).
|
||||
## Importing:
|
||||
* Clone repo
|
||||
* Edit gradle.properties if necessary
|
||||
* Run command line in folder: gradlew genSources eclipse (or Another-IDE-Name)
|
||||
* Import project to IDE
|
||||
|
||||
## Building:
|
||||
|
||||
* Clone repo
|
||||
* Run command line in folder: gradlew build
|
||||
* Mod .jar will be in ./build/libs
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
plugins {
|
||||
id 'idea'
|
||||
id 'eclipse'
|
||||
id 'fabric-loom'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
apply from: "bclib.gradle"
|
186
bclib.gradle
186
bclib.gradle
|
@ -1,186 +0,0 @@
|
|||
buildscript {
|
||||
dependencies {
|
||||
classpath 'org.kohsuke:github-api:1.114'
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
archivesBaseName = project.archives_base_name
|
||||
version = project.mod_version
|
||||
group = project.maven_group
|
||||
|
||||
repositories {
|
||||
maven { url "https://maven.dblsaiko.net/" }
|
||||
maven { url "https://maven.fabricmc.net/" }
|
||||
maven { url "https://maven.shedaniel.me/" }
|
||||
maven { url 'https://maven.blamejared.com' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url 'https://maven.terraformersmc.com/releases' }
|
||||
maven { url 'https://maven.terraformersmc.com' }
|
||||
}
|
||||
|
||||
def local_wunderlib = findProject(':WunderLib') != null
|
||||
|
||||
loom {
|
||||
accessWidenerPath = file("src/main/resources/bclib.accesswidener")
|
||||
runs {
|
||||
// This adds a new gradle task that runs the datagen API: "gradlew runDatagenClient"
|
||||
datagenClient {
|
||||
inherit client
|
||||
name "Data Generation"
|
||||
vmArg "-Dfabric-api.datagen"
|
||||
vmArg "-Dfabric-api.datagen.output-dir=${file("src/main/generated")}"
|
||||
vmArg "-Dfabric-api.datagen.strict-validation"
|
||||
|
||||
runDir "build/datagen"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
// Add the datagenned files into the jar.
|
||||
resources {
|
||||
srcDirs += [
|
||||
'src/main/generated'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||
mappings loom.officialMojangMappings()
|
||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
//make sure we are compatible to the old model API
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}"
|
||||
modCompileOnly "com.terraformersmc:modmenu:${project.modmenu_version}"
|
||||
|
||||
modCompileOnly "dev.emi:emi-fabric:${emi_version}:api"
|
||||
modLocalRuntime "dev.emi:emi-fabric:${emi_version}"
|
||||
|
||||
println "Using local WunderLib: ${local_wunderlib}"
|
||||
if (local_wunderlib) {
|
||||
implementation project(path: ":WunderLib", configuration: 'dev')
|
||||
include project(path: ":WunderLib", configuration: 'dev')
|
||||
} else {
|
||||
modApi "com.github.quiqueck:WunderLib:${project.wunderlib_version}"
|
||||
include "com.github.quiqueck:WunderLib:${project.wunderlib_version}"
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
println "Version: ${project.mod_version}"
|
||||
inputs.property "version", project.mod_version
|
||||
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand "version": project.mod_version
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
||||
// this fixes some edge cases with special characters not displaying correctly
|
||||
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
it.options.release = 17
|
||||
}
|
||||
|
||||
javadoc {
|
||||
options.tags = ["reason"]
|
||||
options.stylesheetFile = new File(projectDir, "javadoc.css");
|
||||
}
|
||||
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
|
||||
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
||||
// if it is present.
|
||||
// If you remove this task, sources will not be generated.
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
jar {
|
||||
from "LICENSE"
|
||||
from "LICENSE.ASSETS"
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar
|
||||
archives javadocJar
|
||||
}
|
||||
|
||||
def env = System.getenv()
|
||||
|
||||
import org.kohsuke.github.GHReleaseBuilder
|
||||
import org.kohsuke.github.GitHub
|
||||
|
||||
task release(dependsOn: [remapJar, sourcesJar, javadocJar]) {
|
||||
onlyIf {
|
||||
env.GITHUB_TOKEN
|
||||
}
|
||||
|
||||
doLast {
|
||||
def github = GitHub.connectUsingOAuth(env.GITHUB_TOKEN as String)
|
||||
def repository = github.getRepository("quiqueck/BCLib")
|
||||
|
||||
def releaseBuilder = new GHReleaseBuilder(repository, version as String)
|
||||
releaseBuilder.name("${archivesBaseName}-${version}")
|
||||
releaseBuilder.body("A changelog can be found at https://github.com/quiqueck/BCLib/commits")
|
||||
releaseBuilder.commitish("main")
|
||||
|
||||
def ghRelease = releaseBuilder.create()
|
||||
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar"), "application/java-archive");
|
||||
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-sources.jar"), "application/java-archive");
|
||||
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-javadoc.jar"), "application/java-archive");
|
||||
}
|
||||
}
|
||||
|
||||
// configure the maven publication
|
||||
publishing {
|
||||
publications {
|
||||
gpr(MavenPublication) {
|
||||
artifactId archivesBaseName
|
||||
artifact(remapJar) {
|
||||
builtBy remapJar
|
||||
}
|
||||
artifact(sourcesJar) {
|
||||
builtBy remapSourcesJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// select the repositories you want to publish to
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/quiqueck/bclib")
|
||||
credentials {
|
||||
username = env.GITHUB_USER
|
||||
password = env.GITHUB_TOKEN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
dev {
|
||||
canBeResolved = false
|
||||
canBeConsumed = true
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
dev jar
|
||||
}
|
315
build.gradle
315
build.gradle
|
@ -1,189 +1,164 @@
|
|||
buildscript {
|
||||
dependencies {
|
||||
classpath 'org.kohsuke:github-api:1.114'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'idea'
|
||||
id 'eclipse'
|
||||
id 'fabric-loom' version "${loom_version}"
|
||||
id 'maven-publish'
|
||||
id "com.modrinth.minotaur" version "2.+"
|
||||
id "com.matthewprenger.cursegradle" version "1.4.0"
|
||||
id 'idea'
|
||||
id 'eclipse'
|
||||
id 'fabric-loom' version '0.8-SNAPSHOT'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
apply from: "bclib.gradle"
|
||||
sourceCompatibility = JavaVersion.VERSION_16
|
||||
targetCompatibility = JavaVersion.VERSION_16
|
||||
|
||||
//from https://lowcarbrob.medium.com/android-pro-tip-generating-your-apps-changelog-from-git-inside-build-gradle-19a07533eec4
|
||||
String generateChangelog() {
|
||||
println "Assembeling Changelog ..."
|
||||
def lastTag = "git describe --tags --abbrev=0".execute().text.trim()
|
||||
def gitLogCmd = "git log $lastTag..HEAD --oneline --no-merges --pretty=format:\"%s\"".execute().text.trim()
|
||||
archivesBaseName = project.archives_base_name
|
||||
version = project.mod_version
|
||||
group = project.maven_group
|
||||
|
||||
def features = ""
|
||||
def fixes = ""
|
||||
def changes = ""
|
||||
gitLogCmd.eachLine { gitLine ->
|
||||
def line = gitLine.substring(1, gitLine.length() - 1)
|
||||
if (line.trim().startsWith("[")) {
|
||||
def sline = line.split("]", 2)
|
||||
if (sline.length == 2) {
|
||||
def type = sline[0].trim().toLowerCase().substring(1)
|
||||
def comment = sline[1].trim()
|
||||
|
||||
//filter issue links
|
||||
if (comment.contains("(")) {
|
||||
def cline = comment.split("\\(", 2)
|
||||
if (cline.length == 2 && cline[1].contains("#")) {
|
||||
comment = cline[0].trim()
|
||||
}
|
||||
}
|
||||
|
||||
if (type == "fix" || type == "fixes" || type == "fixed") {
|
||||
fixes += "- $comment \n"
|
||||
} else if (type == "feature" || type == "features") {
|
||||
features += "- $comment \n"
|
||||
} else if (type == "change" || type == "changes" || type == "changed") {
|
||||
changes += "- $comment \n"
|
||||
} else {
|
||||
println "Unknown Type: $type ($line)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
def changelog = ""
|
||||
if (!features.isEmpty()) {
|
||||
changelog += "#### Features\n"
|
||||
changelog += features.trim()
|
||||
changelog += "\n\n"
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
changelog += "#### Changes\n"
|
||||
changelog += changes.trim()
|
||||
changelog += "\n\n"
|
||||
}
|
||||
if (!fixes.isEmpty()) {
|
||||
changelog += "#### Fixes\n"
|
||||
changelog += fixes.trim()
|
||||
changelog += "\n\n"
|
||||
}
|
||||
|
||||
println "Changelog since $lastTag:\n$changelog"
|
||||
return changelog
|
||||
repositories {
|
||||
maven { url "https://maven.dblsaiko.net/" }
|
||||
maven { url "https://server.bbkr.space:8081/artifactory/libs-release/" }
|
||||
maven { url "https://maven.fabricmc.net/" }
|
||||
maven { url 'https://maven.blamejared.com' }
|
||||
maven { url "https://maven.shedaniel.me/" }
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
|
||||
task changelog() {
|
||||
doLast {
|
||||
new File(projectDir, "CHANGES.md").text = generateChangelog()
|
||||
}
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||
mappings minecraft.officialMojangMappings()
|
||||
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
|
||||
|
||||
//useApi "vazkii.patchouli:Patchouli:1.16.4-${project.patchouli_version}"
|
||||
}
|
||||
|
||||
modrinth {
|
||||
def changes = new File(projectDir, "CHANGES.md")
|
||||
if (changes.exists()) {
|
||||
changes = changes.getText('UTF-8')
|
||||
} else {
|
||||
changes = ""
|
||||
}
|
||||
def modrinth_token = new File(projectDir, "../MODRINTH_TOKEN")
|
||||
if (modrinth_token.exists()) {
|
||||
modrinth_token = modrinth_token.text
|
||||
} else {
|
||||
modrinth_token = ""
|
||||
}
|
||||
def slurper = new groovy.json.JsonSlurper()
|
||||
token = modrinth_token
|
||||
projectId = project.archives_base_name
|
||||
versionNumber = project.mod_version
|
||||
versionType = project.release_channel
|
||||
uploadFile = remapJar
|
||||
gameVersions = slurper.parseText(project.modrinth_versions)
|
||||
loaders = ["fabric"]
|
||||
changelog = changes
|
||||
dependencies {
|
||||
required.project "fabric-api"
|
||||
optional.project "modmenu"
|
||||
}
|
||||
debugMode = false
|
||||
def useOptional(String dep) {
|
||||
dependencies.modRuntime (dep) {
|
||||
exclude group: "net.fabricmc.fabric-api"
|
||||
exclude group: "net.fabricmc"
|
||||
if (!dep.contains("me.shedaniel")) {
|
||||
exclude group: "me.shedaniel"
|
||||
}
|
||||
}
|
||||
dependencies.modCompileOnly (dep) {
|
||||
exclude group: "net.fabricmc.fabric-api"
|
||||
exclude group: "net.fabricmc"
|
||||
if (!dep.contains("me.shedaniel")) {
|
||||
exclude group: "me.shedaniel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
curseforge {
|
||||
def slurper = new groovy.json.JsonSlurper()
|
||||
apiKey = new File(projectDir, "../CURSEFORGE_TOKEN")
|
||||
if (apiKey.exists()) {
|
||||
apiKey = apiKey.text
|
||||
} else {
|
||||
apiKey = ""
|
||||
}
|
||||
|
||||
def changes = new File(projectDir, "CHANGES.md")
|
||||
if (changes.exists()) {
|
||||
changes = changes.getText('UTF-8')
|
||||
} else {
|
||||
changes = ""
|
||||
}
|
||||
|
||||
project {
|
||||
id = '495191'
|
||||
changelogType = 'markdown'
|
||||
changelog = changes
|
||||
releaseType = project.release_channel
|
||||
def versions = slurper.parseText(project.modrinth_versions);
|
||||
def latestVersion = ''
|
||||
for (v in versions) {
|
||||
addGameVersion v
|
||||
latestVersion = "[$v]"
|
||||
}
|
||||
addGameVersion 'Fabric'
|
||||
addGameVersion 'Java 17'
|
||||
relations {
|
||||
requiredDependency 'fabric-api'
|
||||
optionalDependency 'modmenu'
|
||||
}
|
||||
mainArtifact(remapJar) {
|
||||
displayName = "$project.archives_base_name-$project.version $latestVersion"
|
||||
}
|
||||
afterEvaluate {
|
||||
mainArtifact(remapJar.outputs)
|
||||
}
|
||||
}
|
||||
|
||||
options {
|
||||
debug = false
|
||||
forgeGradleIntegration = false
|
||||
}
|
||||
def useApi(String dep) {
|
||||
dependencies.modApi (dep) {
|
||||
exclude group: "net.fabricmc.fabric-api"
|
||||
exclude group: "net.fabricmc"
|
||||
if (!dep.contains("me.shedaniel")) {
|
||||
exclude group: "me.shedaniel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
inputs.property "version", project.version
|
||||
duplicatesStrategy = 'EXCLUDE'
|
||||
|
||||
task nextVersion() {
|
||||
doLast {
|
||||
def inputFile = new File('modrinth.json')
|
||||
def gameVersions = java.net.URLEncoder.encode(project.modrinth_versions, "UTF-8")
|
||||
new URL("https://api.modrinth.com/v2/project/${project.archives_base_name}/version?&game_versions=${gameVersions}").withInputStream { i -> inputFile.withOutputStream { it << i } }
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
include "fabric.mod.json"
|
||||
expand "version": project.version
|
||||
}
|
||||
|
||||
def json = new groovy.json.JsonSlurper().parseText(inputFile.text)
|
||||
def version = json[0].version_number
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
exclude "fabric.mod.json"
|
||||
}
|
||||
}
|
||||
|
||||
//increment patch version
|
||||
def indexedVersionList = version.split(/\./).toList().withIndex()
|
||||
indexedVersionList = indexedVersionList.collect { num, idx -> num.toInteger() }
|
||||
indexedVersionList[2] = indexedVersionList[2].value + 1
|
||||
def updatedVersion = indexedVersionList.join(".")
|
||||
// ensure that the encoding is set to UTF-8, no matter what the system default is
|
||||
// this fixes some edge cases with special characters not displaying correctly
|
||||
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
println "\n\n"
|
||||
println "------------- CURRENT VERSION -------------"
|
||||
println "Last Published Version: " + version
|
||||
println " Game Versions: " + json[0].game_versions
|
||||
println " Status: " + json[0].status
|
||||
println " Featured: " + json[0].featured
|
||||
println " Downloaded: " + json[0].downloads
|
||||
println "\n"
|
||||
println "-------------- NEXT VERSION ---------------"
|
||||
println "Next Version: " + updatedVersion
|
||||
println "\n\n"
|
||||
javadoc {
|
||||
options.tags = [ "reason" ]
|
||||
}
|
||||
|
||||
def propertiesFile = new File("gradle.properties")
|
||||
def newContents = propertiesFile.text.replaceFirst("mod_version=\\d+.\\d+.\\d+", "mod_version=${updatedVersion}")
|
||||
propertiesFile.text = newContents
|
||||
task javadocJar(type: Jar, dependsOn: javadoc) {
|
||||
classifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
|
||||
def fabricFile = new File("src/main/resources/fabric.mod.json")
|
||||
newContents = fabricFile.text.replaceFirst('"version": ".+"', "\"version\": \"${updatedVersion}\"")
|
||||
fabricFile.text = newContents
|
||||
}
|
||||
}
|
||||
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
|
||||
// if it is present.
|
||||
// If you remove this task, sources will not be generated.
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
jar {
|
||||
from "LICENSE"
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives sourcesJar
|
||||
archives javadocJar
|
||||
}
|
||||
|
||||
def env = System.getenv()
|
||||
|
||||
import org.kohsuke.github.GHReleaseBuilder
|
||||
import org.kohsuke.github.GitHub
|
||||
|
||||
task release(dependsOn: [remapJar, sourcesJar, javadocJar]) {
|
||||
onlyIf {
|
||||
env.GITHUB_TOKEN
|
||||
}
|
||||
|
||||
doLast {
|
||||
def github = GitHub.connectUsingOAuth(env.GITHUB_TOKEN as String)
|
||||
def repository = github.getRepository("paulevsGitch/BCLib")
|
||||
|
||||
def releaseBuilder = new GHReleaseBuilder(repository, version as String)
|
||||
releaseBuilder.name("${archivesBaseName}-${version}")
|
||||
releaseBuilder.body("A changelog can be found at https://github.com/paulevsGitch/BCLib/commits")
|
||||
releaseBuilder.commitish("main")
|
||||
|
||||
def ghRelease = releaseBuilder.create()
|
||||
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar"), "application/java-archive");
|
||||
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-sources.jar"), "application/java-archive");
|
||||
ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-javadoc.jar"), "application/java-archive");
|
||||
}
|
||||
}
|
||||
|
||||
// configure the maven publication
|
||||
publishing {
|
||||
publications {
|
||||
gpr(MavenPublication) {
|
||||
artifactId archivesBaseName
|
||||
artifact(remapJar) {
|
||||
builtBy remapJar
|
||||
}
|
||||
artifact(sourcesJar) {
|
||||
builtBy remapSourcesJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// select the repositories you want to publish to
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/paulevsgitch/bclib")
|
||||
credentials {
|
||||
username = env.GITHUB_USER
|
||||
password = env.GITHUB_TOKEN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
# Done to increase the memory available to gradle.
|
||||
org.gradle.jvmargs=-Xmx8G
|
||||
#Modrinth
|
||||
modrinth_versions=["1.20", "1.20.1"]
|
||||
#`release`, `beta` or `alpha`
|
||||
release_channel=release
|
||||
#Loom
|
||||
loom_version=1.0-SNAPSHOT
|
||||
org.gradle.jvmargs=-Xmx2G
|
||||
|
||||
# Fabric Properties
|
||||
# check these on https://fabricmc.net/versions.html
|
||||
minecraft_version=1.20.1
|
||||
loader_version=0.14.21
|
||||
fabric_version=0.86.1+1.20.1
|
||||
# check these on https://fabricmc.net/use
|
||||
minecraft_version= 1.17
|
||||
yarn_mappings= 6
|
||||
loader_version= 0.11.6
|
||||
|
||||
# Mod Properties
|
||||
mod_version=3.0.13
|
||||
maven_group=org.betterx.bclib
|
||||
archives_base_name=bclib
|
||||
mod_version = 0.2.0
|
||||
maven_group = ru.bclib
|
||||
archives_base_name = bclib
|
||||
|
||||
# Dependencies
|
||||
modmenu_version=7.0.0
|
||||
emi_version=1.0.3+1.20
|
||||
wunderlib_version=1.1.5
|
||||
# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api
|
||||
patchouli_version = 50-FABRIC
|
||||
fabric_version = 0.36.0+1.17
|
||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
259
gradlew
vendored
Executable file → Normal file
259
gradlew
vendored
Executable file → Normal file
|
@ -1,7 +1,7 @@
|
|||
#!/bin/sh
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
|
@ -17,113 +17,78 @@
|
|||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
@ -132,7 +97,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
|
@ -140,95 +105,79 @@ location of your Java installation."
|
|||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
|
22
gradlew.bat
vendored
22
gradlew.bat
vendored
|
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
@ -54,7 +54,7 @@ goto fail
|
|||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
|
@ -64,14 +64,28 @@ echo location of your Java installation.
|
|||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
|
903
javadoc.css
903
javadoc.css
|
@ -1,903 +0,0 @@
|
|||
/*
|
||||
* Javadoc style sheet
|
||||
*/
|
||||
|
||||
@import url("resources/fonts/dejavu.css");
|
||||
|
||||
/*
|
||||
* Styles for individual HTML elements.
|
||||
*
|
||||
* These are styles that are specific to individual HTML elements. Changing them affects the style of a particular
|
||||
* HTML element throughout the page.
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #ffffff;
|
||||
color: #353833;
|
||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
iframe {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
border: none;
|
||||
}
|
||||
a:link,
|
||||
a:visited {
|
||||
text-decoration: none;
|
||||
color: #4a6782;
|
||||
}
|
||||
a[href]:hover,
|
||||
a[href]:focus {
|
||||
text-decoration: none;
|
||||
color: #bb7a2a;
|
||||
}
|
||||
a[name] {
|
||||
color: #353833;
|
||||
}
|
||||
pre {
|
||||
font-family: "DejaVu Sans Mono", monospace;
|
||||
font-size: 16px;
|
||||
background-color: #fffadb;
|
||||
border-radius: 5px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 13px;
|
||||
}
|
||||
h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
h6 {
|
||||
font-size: 11px;
|
||||
}
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
code,
|
||||
tt {
|
||||
font-family: "DejaVu Sans Mono", monospace;
|
||||
font-size: 14px;
|
||||
padding-top: 4px;
|
||||
margin-top: 8px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
dt code {
|
||||
font-family: "DejaVu Sans Mono", monospace;
|
||||
font-size: 14px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.summary-table dt code {
|
||||
font-family: "DejaVu Sans Mono", monospace;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
padding-top: 4px;
|
||||
}
|
||||
sup {
|
||||
font-size: 8px;
|
||||
}
|
||||
button {
|
||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
/*
|
||||
* Styles for HTML generated by javadoc.
|
||||
*
|
||||
* These are style classes that are used by the standard doclet to generate HTML documentation.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Styles for document title and copyright.
|
||||
*/
|
||||
.clear {
|
||||
clear: both;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.about-language {
|
||||
float: right;
|
||||
padding: 0 21px 8px 8px;
|
||||
font-size: 11px;
|
||||
margin-top: -9px;
|
||||
height: 2.9em;
|
||||
}
|
||||
.legal-copy {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
.tab {
|
||||
background-color: #0066ff;
|
||||
color: #ffffff;
|
||||
padding: 8px;
|
||||
width: 5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
/*
|
||||
* Styles for navigation bar.
|
||||
*/
|
||||
@media screen {
|
||||
.flex-box {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.flex-header {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.flex-content {
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
.top-nav {
|
||||
background-color: #4d7a97;
|
||||
color: #ffffff;
|
||||
float: left;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
clear: right;
|
||||
min-height: 2.8em;
|
||||
padding-top: 10px;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
}
|
||||
.sub-nav {
|
||||
background-color: #dee3e9;
|
||||
float: left;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
}
|
||||
.sub-nav div {
|
||||
clear: left;
|
||||
float: left;
|
||||
padding: 0 0 5px 6px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.sub-nav .nav-list {
|
||||
padding-top: 5px;
|
||||
}
|
||||
ul.nav-list {
|
||||
display: block;
|
||||
margin: 0 25px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.sub-nav-list {
|
||||
float: left;
|
||||
margin: 0 25px 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.nav-list li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
padding: 5px 6px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.sub-nav .nav-list-search {
|
||||
float: right;
|
||||
margin: 0 0 0 0;
|
||||
padding: 5px 6px;
|
||||
clear: none;
|
||||
}
|
||||
.nav-list-search label {
|
||||
position: relative;
|
||||
right: -16px;
|
||||
}
|
||||
ul.sub-nav-list li {
|
||||
list-style: none;
|
||||
float: left;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.top-nav a:link,
|
||||
.top-nav a:active,
|
||||
.top-nav a:visited {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.top-nav a:hover {
|
||||
text-decoration: none;
|
||||
color: #bb7a2a;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.nav-bar-cell1-rev {
|
||||
background-color: #f8981d;
|
||||
color: #253441;
|
||||
margin: auto 5px;
|
||||
}
|
||||
.skip-nav {
|
||||
position: absolute;
|
||||
top: auto;
|
||||
left: -9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
/*
|
||||
* Hide navigation links and search box in print layout
|
||||
*/
|
||||
@media print {
|
||||
ul.nav-list,
|
||||
div.sub-nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Styles for page header and footer.
|
||||
*/
|
||||
.title {
|
||||
color: #2c4557;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.sub-title {
|
||||
margin: 5px 0 0 0;
|
||||
}
|
||||
.header ul {
|
||||
margin: 0 0 15px 0;
|
||||
padding: 0;
|
||||
}
|
||||
.header ul li,
|
||||
.footer ul li {
|
||||
list-style: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
/*
|
||||
* Styles for headings.
|
||||
*/
|
||||
body.class-declaration-page .summary h2,
|
||||
body.class-declaration-page .details h2,
|
||||
body.class-use-page h2,
|
||||
body.module-declaration-page .block-list h2 {
|
||||
font-style: italic;
|
||||
padding: 0;
|
||||
margin: 15px 0;
|
||||
}
|
||||
body.class-declaration-page .summary h3,
|
||||
body.class-declaration-page .details h3,
|
||||
body.class-declaration-page .summary .inherited-list h2 {
|
||||
background-color: #dee3e9;
|
||||
border: 1px solid #d0d9e0;
|
||||
margin: 0 0 6px -8px;
|
||||
padding: 7px 5px;
|
||||
}
|
||||
/*
|
||||
* Styles for page layout containers.
|
||||
*/
|
||||
main {
|
||||
clear: both;
|
||||
padding: 10px 20px;
|
||||
position: relative;
|
||||
}
|
||||
dl.notes > dt {
|
||||
font-family: "DejaVu Sans", Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin: 10px 0 0 0;
|
||||
color: #4e4e4e;
|
||||
}
|
||||
dl.notes > dd {
|
||||
margin: 5px 0 10px 0;
|
||||
font-size: 15px;
|
||||
font-family: "Roboto", "DejaVu Sans", "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
dl.name-value > dt {
|
||||
margin-left: 1px;
|
||||
font-size: 1.1em;
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
}
|
||||
dl.name-value > dd {
|
||||
margin: 0 0 0 1px;
|
||||
font-size: 1.1em;
|
||||
display: inline;
|
||||
}
|
||||
/*
|
||||
* Styles for lists.
|
||||
*/
|
||||
li.circle {
|
||||
list-style: circle;
|
||||
}
|
||||
ul.horizontal li {
|
||||
display: inline;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
div.inheritance {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
div.inheritance div.inheritance {
|
||||
margin-left: 2em;
|
||||
}
|
||||
ul.block-list,
|
||||
ul.details-list,
|
||||
ul.member-list,
|
||||
ul.summary-list {
|
||||
margin: 10px 0 10px 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.block-list > li,
|
||||
ul.details-list > li,
|
||||
ul.member-list > li,
|
||||
ul.summary-list > li {
|
||||
list-style: none;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.summary-table dl,
|
||||
.summary-table dl dt,
|
||||
.summary-table dl dd {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
/*
|
||||
* Styles for tables.
|
||||
*/
|
||||
.summary-table {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-left: 1px solid #eee;
|
||||
border-right: 1px solid #eee;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.summary-table {
|
||||
padding: 0;
|
||||
}
|
||||
.caption {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
background-repeat: no-repeat;
|
||||
color: #253441;
|
||||
font-weight: bold;
|
||||
clear: none;
|
||||
overflow: hidden;
|
||||
padding: 0px;
|
||||
padding-top: 10px;
|
||||
padding-left: 1px;
|
||||
margin: 0px;
|
||||
white-space: pre;
|
||||
}
|
||||
.caption a:link,
|
||||
.caption a:visited {
|
||||
color: #1f389c;
|
||||
}
|
||||
.caption a:hover,
|
||||
.caption a:active {
|
||||
color: #ffffff;
|
||||
}
|
||||
.caption span {
|
||||
white-space: nowrap;
|
||||
padding-top: 5px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
padding-bottom: 7px;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
background-color: #f8981d;
|
||||
border: none;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
div.table-tabs > button {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 5px 12px 7px 12px;
|
||||
font-weight: bold;
|
||||
margin-right: 3px;
|
||||
}
|
||||
div.table-tabs > button.active-table-tab {
|
||||
background: #f8981d;
|
||||
color: #253441;
|
||||
}
|
||||
div.table-tabs > button.table-tab {
|
||||
background: #4d7a97;
|
||||
color: #ffffff;
|
||||
}
|
||||
.two-column-summary {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
|
||||
}
|
||||
.three-column-summary {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto);
|
||||
}
|
||||
.four-column-summary {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto);
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
.two-column-summary {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
.three-column-summary {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10%, max-content) minmax(25%, auto);
|
||||
}
|
||||
.three-column-summary .col-last {
|
||||
grid-column-end: span 2;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 1000px) {
|
||||
.four-column-summary {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
|
||||
}
|
||||
}
|
||||
.summary-table > div {
|
||||
text-align: left;
|
||||
padding: 8px 3px 3px 7px;
|
||||
}
|
||||
.col-first,
|
||||
.col-second,
|
||||
.col-last,
|
||||
.col-constructor-name,
|
||||
.col-deprecated-item-name {
|
||||
vertical-align: top;
|
||||
padding-right: 0;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
.table-header {
|
||||
background: #dee3e9;
|
||||
font-weight: bold;
|
||||
}
|
||||
.col-first,
|
||||
.col-first {
|
||||
font-size: 13px;
|
||||
}
|
||||
.col-second,
|
||||
.col-second,
|
||||
.col-last,
|
||||
.col-constructor-name,
|
||||
.col-deprecated-item-name,
|
||||
.col-last {
|
||||
font-size: 13px;
|
||||
}
|
||||
.col-first,
|
||||
.col-second,
|
||||
.col-constructor-name {
|
||||
vertical-align: top;
|
||||
overflow: auto;
|
||||
}
|
||||
.col-last {
|
||||
white-space: normal;
|
||||
}
|
||||
.col-first a:link,
|
||||
.col-first a:visited,
|
||||
.col-second a:link,
|
||||
.col-second a:visited,
|
||||
.col-first a:link,
|
||||
.col-first a:visited,
|
||||
.col-second a:link,
|
||||
.col-second a:visited,
|
||||
.col-constructor-name a:link,
|
||||
.col-constructor-name a:visited,
|
||||
.col-deprecated-item-name a:link,
|
||||
.col-deprecated-item-name a:visited,
|
||||
.constant-values-container a:link,
|
||||
.constant-values-container a:visited,
|
||||
.all-classes-container a:link,
|
||||
.all-classes-container a:visited,
|
||||
.all-packages-container a:link,
|
||||
.all-packages-container a:visited {
|
||||
font-weight: bold;
|
||||
}
|
||||
.table-sub-heading-color {
|
||||
background-color: #eeeeff;
|
||||
}
|
||||
.even-row-color,
|
||||
.even-row-color .table-header {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.odd-row-color,
|
||||
.odd-row-color .table-header {
|
||||
background-color: #eeeeef;
|
||||
}
|
||||
/*
|
||||
* Styles for contents.
|
||||
*/
|
||||
.deprecated-content {
|
||||
margin: 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
div.block {
|
||||
font-size: 15px;
|
||||
font-family: "Roboto", "DejaVu Sans", "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.col-last div {
|
||||
padding-top: 0;
|
||||
}
|
||||
.col-last a {
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
.module-signature,
|
||||
.package-signature,
|
||||
.type-signature,
|
||||
.member-signature {
|
||||
font-family: "DejaVu Sans Mono", monospace;
|
||||
font-size: 14px;
|
||||
margin: 14px 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.module-signature,
|
||||
.package-signature,
|
||||
.type-signature {
|
||||
margin-top: 0;
|
||||
}
|
||||
.member-signature .type-parameters-long,
|
||||
.member-signature .parameters,
|
||||
.member-signature .exceptions {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
white-space: pre;
|
||||
}
|
||||
.member-signature .type-parameters {
|
||||
white-space: normal;
|
||||
}
|
||||
/*
|
||||
* Styles for formatting effect.
|
||||
*/
|
||||
.source-line-no {
|
||||
color: green;
|
||||
padding: 0 30px 0 0;
|
||||
}
|
||||
h1.hidden {
|
||||
visibility: hidden;
|
||||
overflow: hidden;
|
||||
font-size: 10px;
|
||||
}
|
||||
.block {
|
||||
display: block;
|
||||
margin: 0 10px 5px 0;
|
||||
color: #474747;
|
||||
}
|
||||
.deprecated-label,
|
||||
.descfrm-type-label,
|
||||
.implementation-label,
|
||||
.member-name-label,
|
||||
.member-name-link,
|
||||
.module-label-in-package,
|
||||
.module-label-in-type,
|
||||
.override-specify-label,
|
||||
.package-label-in-type,
|
||||
.package-hierarchy-label,
|
||||
.type-name-label,
|
||||
.type-name-link,
|
||||
.search-tag-link {
|
||||
font-weight: bold;
|
||||
}
|
||||
.deprecation-comment,
|
||||
.help-footnote,
|
||||
.interface-name {
|
||||
font-style: italic;
|
||||
}
|
||||
.deprecation-block {
|
||||
font-size: 14px;
|
||||
font-family: "DejaVu Serif", Georgia, "Times New Roman", Times, serif;
|
||||
border-style: solid;
|
||||
border-width: thin;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
div.block div.deprecation-comment,
|
||||
div.block div.block span.emphasized-phrase,
|
||||
div.block div.block span.interface-name {
|
||||
font-style: normal;
|
||||
}
|
||||
/*
|
||||
* Styles specific to HTML5 elements.
|
||||
*/
|
||||
main,
|
||||
nav,
|
||||
header,
|
||||
footer,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
/*
|
||||
* Styles for javadoc search.
|
||||
*/
|
||||
.ui-autocomplete-category {
|
||||
font-weight: bold;
|
||||
font-size: 15px;
|
||||
padding: 7px 0 7px 3px;
|
||||
background-color: #4d7a97;
|
||||
color: #ffffff;
|
||||
}
|
||||
.result-item {
|
||||
font-size: 13px;
|
||||
}
|
||||
.ui-autocomplete {
|
||||
max-height: 85%;
|
||||
max-width: 65%;
|
||||
overflow-y: scroll;
|
||||
overflow-x: scroll;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
}
|
||||
ul.ui-autocomplete {
|
||||
position: fixed;
|
||||
z-index: 999999;
|
||||
}
|
||||
ul.ui-autocomplete li {
|
||||
float: left;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
}
|
||||
.result-highlight {
|
||||
font-weight: bold;
|
||||
}
|
||||
#search {
|
||||
background-image: url("resources/glass.png");
|
||||
background-size: 13px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 2px 3px;
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
right: -18px;
|
||||
width: 400px;
|
||||
}
|
||||
#reset {
|
||||
background-color: rgb(255, 255, 255);
|
||||
background-image: url("resources/x.png");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 12px;
|
||||
border: 0 none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
left: -4px;
|
||||
top: -4px;
|
||||
font-size: 0px;
|
||||
}
|
||||
.watermark {
|
||||
color: #545454;
|
||||
}
|
||||
.search-tag-desc-result {
|
||||
font-style: italic;
|
||||
font-size: 11px;
|
||||
}
|
||||
.search-tag-holder-result {
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
}
|
||||
.search-tag-result:target {
|
||||
background-color: yellow;
|
||||
}
|
||||
.module-graph span {
|
||||
display: none;
|
||||
position: absolute;
|
||||
}
|
||||
.module-graph:hover span {
|
||||
display: block;
|
||||
margin: -100px 0 0 100px;
|
||||
z-index: 1;
|
||||
}
|
||||
.inherited-list {
|
||||
margin: 10px 0 10px 0;
|
||||
}
|
||||
section.description {
|
||||
line-height: 1.4;
|
||||
}
|
||||
.summary section[class$="-summary"],
|
||||
.details section[class$="-details"],
|
||||
.class-uses .detail,
|
||||
.serialized-class-details {
|
||||
padding: 0px 20px 5px 10px;
|
||||
border: 1px solid #ededed;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
.inherited-list,
|
||||
section[class$="-details"] .detail {
|
||||
padding: 0 0 5px 8px;
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
}
|
||||
.vertical-separator {
|
||||
padding: 0 5px;
|
||||
}
|
||||
ul.help-section-list {
|
||||
margin: 0;
|
||||
}
|
||||
/*
|
||||
* Indicator icon for external links.
|
||||
*/
|
||||
main a[href*="://"]::after
|
||||
{
|
||||
content: "";
|
||||
display: inline-block;
|
||||
background-image: url('data:image/svg+xml; utf8, \
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
|
||||
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
|
||||
132-240 240 120 120 240-240 132 132V0z" fill="%234a6782"/>\
|
||||
</svg>');
|
||||
background-size: 100% 100%;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin-left: 2px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
main a[href*="://"]:hover::after,
|
||||
main a[href*="://"]:focus::after
|
||||
{
|
||||
background-image: url('data:image/svg+xml; utf8, \
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
|
||||
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
|
||||
132-240 240 120 120 240-240 132 132V0z" fill="%23bb7a2a"/>\
|
||||
</svg>');
|
||||
}
|
||||
|
||||
/*
|
||||
* Styles for user-provided tables.
|
||||
*
|
||||
* borderless:
|
||||
* No borders, vertical margins, styled caption.
|
||||
* This style is provided for use with existing doc comments.
|
||||
* In general, borderless tables should not be used for layout purposes.
|
||||
*
|
||||
* plain:
|
||||
* Plain borders around table and cells, vertical margins, styled caption.
|
||||
* Best for small tables or for complex tables for tables with cells that span
|
||||
* rows and columns, when the "striped" style does not work well.
|
||||
*
|
||||
* striped:
|
||||
* Borders around the table and vertical borders between cells, striped rows,
|
||||
* vertical margins, styled caption.
|
||||
* Best for tables that have a header row, and a body containing a series of simple rows.
|
||||
*/
|
||||
|
||||
table.borderless,
|
||||
table.plain,
|
||||
table.striped {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
table.borderless > caption,
|
||||
table.plain > caption,
|
||||
table.striped > caption {
|
||||
font-weight: bold;
|
||||
font-size: smaller;
|
||||
}
|
||||
table.borderless th,
|
||||
table.borderless td,
|
||||
table.plain th,
|
||||
table.plain td,
|
||||
table.striped th,
|
||||
table.striped td {
|
||||
padding: 2px 5px;
|
||||
}
|
||||
table.borderless,
|
||||
table.borderless > thead > tr > th,
|
||||
table.borderless > tbody > tr > th,
|
||||
table.borderless > tr > th,
|
||||
table.borderless > thead > tr > td,
|
||||
table.borderless > tbody > tr > td,
|
||||
table.borderless > tr > td {
|
||||
border: none;
|
||||
}
|
||||
table.borderless > thead > tr,
|
||||
table.borderless > tbody > tr,
|
||||
table.borderless > tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
table.plain {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid black;
|
||||
}
|
||||
table.plain > thead > tr,
|
||||
table.plain > tbody tr,
|
||||
table.plain > tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
table.plain > thead > tr > th,
|
||||
table.plain > tbody > tr > th,
|
||||
table.plain > tr > th,
|
||||
table.plain > thead > tr > td,
|
||||
table.plain > tbody > tr > td,
|
||||
table.plain > tr > td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
table.striped {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid black;
|
||||
}
|
||||
table.striped > thead {
|
||||
background-color: #e3e3e3;
|
||||
}
|
||||
table.striped > thead > tr > th,
|
||||
table.striped > thead > tr > td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
table.striped > tbody > tr:nth-child(even) {
|
||||
background-color: #eee;
|
||||
}
|
||||
table.striped > tbody > tr:nth-child(odd) {
|
||||
background-color: #fff;
|
||||
}
|
||||
table.striped > tbody > tr > th,
|
||||
table.striped > tbody > tr > td {
|
||||
border-left: 1px solid black;
|
||||
border-right: 1px solid black;
|
||||
}
|
||||
table.striped > tbody > tr > th {
|
||||
font-weight: normal;
|
||||
}
|
||||
/**
|
||||
* Tweak font sizes and paddings for small screens.
|
||||
*/
|
||||
@media screen and (max-width: 1050px) {
|
||||
#search {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
#search {
|
||||
width: 200px;
|
||||
}
|
||||
.top-nav,
|
||||
.bottom-nav {
|
||||
font-size: 11px;
|
||||
padding-top: 6px;
|
||||
}
|
||||
.sub-nav {
|
||||
font-size: 11px;
|
||||
}
|
||||
.about-language {
|
||||
padding-right: 16px;
|
||||
}
|
||||
ul.nav-list li,
|
||||
.sub-nav .nav-list-search {
|
||||
padding: 6px;
|
||||
}
|
||||
ul.sub-nav-list li {
|
||||
padding-top: 5px;
|
||||
}
|
||||
main {
|
||||
padding: 10px;
|
||||
}
|
||||
.summary section[class$="-summary"],
|
||||
.details section[class$="-details"],
|
||||
.class-uses .detail,
|
||||
.serialized-class-details {
|
||||
padding: 0 8px 5px 8px;
|
||||
}
|
||||
body {
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 500px) {
|
||||
#search {
|
||||
width: 150px;
|
||||
}
|
||||
.top-nav,
|
||||
.bottom-nav {
|
||||
font-size: 10px;
|
||||
}
|
||||
.sub-nav {
|
||||
font-size: 10px;
|
||||
}
|
||||
.about-language {
|
||||
font-size: 10px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# From https://github.com/jitpack/jitpack.io/issues/4506#issuecomment-864562270
|
||||
before_install:
|
||||
- source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||
- sdk update
|
||||
- sdk install java 17.0.1-tem
|
||||
- sdk use java 17.0.1-tem
|
|
@ -7,40 +7,3 @@ pluginManagement {
|
|||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
// #### Custom Settings ####
|
||||
|
||||
// Change the next line to disable local lib loading
|
||||
def allowLocalLibUse = true
|
||||
|
||||
//When true, the local lib is also used in commandline builds
|
||||
def allowLocalLibInConsoleMode = false
|
||||
|
||||
//The path were to look for the local BCLib
|
||||
def WunderLibPath = '../WunderLib'
|
||||
|
||||
|
||||
// #### Logic ####
|
||||
def isIDE = properties.containsKey('android.injected.invoked.from.ide')
|
||||
|| (System.getenv("XPC_SERVICE_NAME") ?: "").contains("intellij")
|
||||
|| (System.getenv("XPC_SERVICE_NAME") ?: "").contains(".idea")
|
||||
|| System.getenv("IDEA_INITIAL_DIRECTORY") != null
|
||||
|
||||
println "IntelliJ: ${isIDE}"
|
||||
|
||||
def WunderLibFolder = new File(WunderLibPath)
|
||||
if (allowLocalLibUse && (isIDE || allowLocalLibInConsoleMode) && WunderLibFolder.exists()) {
|
||||
println "Using local WunderLib from '${WunderLibFolder}' in IntelliJ"
|
||||
println "If you do not want to load the local version of WunderLib"
|
||||
println "either rename the Folder containing WunderLib to something"
|
||||
println "else, or set 'allowLocalLibUse' in settings.gradle"
|
||||
println "to false."
|
||||
println ""
|
||||
println "If you receive version-errors when launching minecraft"
|
||||
println "in IntelliJ, make sure you have set up gradle instead"
|
||||
println "of IntelliJ to compile and run."
|
||||
|
||||
include ':WunderLib'
|
||||
project(":WunderLib").projectDir = WunderLibFolder
|
||||
project(':WunderLib').buildFileName = './wunderlib-composit.gradle'
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_cauldron"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_cauldron"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_tag_minecraft_planks": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "minecraft:planks"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_shield"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_tag_minecraft_planks",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_shield"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_shulker_shell": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:shulker_shell"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_tag_c_chest": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:chest"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_shulker_box"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_tag_c_chest",
|
||||
"has_shulker_shell",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_shulker_box"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_tag_minecraft_planks": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "minecraft:planks"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_smith_table"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_tag_minecraft_planks",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_smith_table"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_stone": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:stone"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_stonecutter"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_stone",
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_stonecutter"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_bucket"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_bucket"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_tag_c_chest": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:chest"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_hopper"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_tag_c_chest",
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_hopper"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_cobblestone": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:cobblestone"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_redstone": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:redstone"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_tag_minecraft_planks": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "minecraft:planks"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_piston"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_cobblestone",
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_redstone",
|
||||
"has_tag_minecraft_planks",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_piston"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_redstone": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:redstone"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_compass"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_redstone",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_compass"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_minecart"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_minecart"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_stick": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
"minecraft:stick"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_tag_c_iron_ingots": {
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
]
|
||||
},
|
||||
"trigger": "minecraft:inventory_changed"
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"conditions": {
|
||||
"recipe": "bclib:tag_rail"
|
||||
},
|
||||
"trigger": "minecraft:recipe_unlocked"
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_tag_c_iron_ingots",
|
||||
"has_stick",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"bclib:tag_rail"
|
||||
]
|
||||
},
|
||||
"sends_telemetry_event": false
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "misc",
|
||||
"key": {
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"I I",
|
||||
" I "
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:bucket"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "misc",
|
||||
"key": {
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"I I",
|
||||
"I I",
|
||||
"III"
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:cauldron"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "equipment",
|
||||
"key": {
|
||||
"D": {
|
||||
"item": "minecraft:redstone"
|
||||
},
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
" I ",
|
||||
"IDI",
|
||||
" I "
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:compass"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "redstone",
|
||||
"key": {
|
||||
"C": {
|
||||
"tag": "c:chest"
|
||||
},
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"I I",
|
||||
"ICI",
|
||||
" I "
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:hopper"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "misc",
|
||||
"key": {
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"I I",
|
||||
"III"
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:minecart"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "redstone",
|
||||
"key": {
|
||||
"C": {
|
||||
"item": "minecraft:cobblestone"
|
||||
},
|
||||
"D": {
|
||||
"item": "minecraft:redstone"
|
||||
},
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
},
|
||||
"W": {
|
||||
"tag": "minecraft:planks"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"WWW",
|
||||
"CIC",
|
||||
"CDC"
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:piston"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "misc",
|
||||
"key": {
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
},
|
||||
"S": {
|
||||
"item": "minecraft:stick"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"I I",
|
||||
"ISI",
|
||||
"I I"
|
||||
],
|
||||
"result": {
|
||||
"count": 16,
|
||||
"item": "minecraft:rail"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "equipment",
|
||||
"key": {
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
},
|
||||
"W": {
|
||||
"tag": "minecraft:planks"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"WIW",
|
||||
"WWW",
|
||||
" W "
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:shield"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "misc",
|
||||
"key": {
|
||||
"C": {
|
||||
"tag": "c:chest"
|
||||
},
|
||||
"S": {
|
||||
"item": "minecraft:shulker_shell"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"S",
|
||||
"C",
|
||||
"S"
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:shulker_box"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "misc",
|
||||
"key": {
|
||||
"#": {
|
||||
"tag": "minecraft:planks"
|
||||
},
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
"II",
|
||||
"##",
|
||||
"##"
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:smithing_table"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "misc",
|
||||
"key": {
|
||||
"I": {
|
||||
"tag": "c:iron_ingots"
|
||||
},
|
||||
"S": {
|
||||
"item": "minecraft:stone"
|
||||
}
|
||||
},
|
||||
"pattern": [
|
||||
" I ",
|
||||
"SSS"
|
||||
],
|
||||
"result": {
|
||||
"item": "minecraft:stonecutter"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
|
@ -1,737 +0,0 @@
|
|||
{
|
||||
"aquifers_enabled": false,
|
||||
"default_block": {
|
||||
"Name": "minecraft:netherrack"
|
||||
},
|
||||
"default_fluid": {
|
||||
"Name": "minecraft:lava",
|
||||
"Properties": {
|
||||
"level": "0"
|
||||
}
|
||||
},
|
||||
"disable_mob_generation": false,
|
||||
"legacy_random_source": true,
|
||||
"noise": {
|
||||
"height": 256,
|
||||
"min_y": 0,
|
||||
"size_horizontal": 1,
|
||||
"size_vertical": 4
|
||||
},
|
||||
"noise_router": {
|
||||
"barrier": 0.0,
|
||||
"continents": 0.0,
|
||||
"depth": 0.0,
|
||||
"erosion": 0.0,
|
||||
"final_density": {
|
||||
"type": "minecraft:squeeze",
|
||||
"argument": {
|
||||
"type": "minecraft:mul",
|
||||
"argument1": 0.64,
|
||||
"argument2": {
|
||||
"type": "minecraft:interpolated",
|
||||
"argument": {
|
||||
"type": "minecraft:blend_density",
|
||||
"argument": {
|
||||
"type": "minecraft:add",
|
||||
"argument1": 2.5,
|
||||
"argument2": {
|
||||
"type": "minecraft:mul",
|
||||
"argument1": {
|
||||
"type": "minecraft:y_clamped_gradient",
|
||||
"from_value": 0.0,
|
||||
"from_y": -8,
|
||||
"to_value": 1.0,
|
||||
"to_y": 24
|
||||
},
|
||||
"argument2": {
|
||||
"type": "minecraft:add",
|
||||
"argument1": -2.5,
|
||||
"argument2": {
|
||||
"type": "minecraft:add",
|
||||
"argument1": 0.9375,
|
||||
"argument2": {
|
||||
"type": "minecraft:mul",
|
||||
"argument1": {
|
||||
"type": "minecraft:y_clamped_gradient",
|
||||
"from_value": 1.0,
|
||||
"from_y": 232,
|
||||
"to_value": 0.0,
|
||||
"to_y": 256
|
||||
},
|
||||
"argument2": {
|
||||
"type": "minecraft:add",
|
||||
"argument1": -0.9375,
|
||||
"argument2": "minecraft:nether/base_3d_noise"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fluid_level_floodedness": 0.0,
|
||||
"fluid_level_spread": 0.0,
|
||||
"initial_density_without_jaggedness": 0.0,
|
||||
"lava": 0.0,
|
||||
"ridges": 0.0,
|
||||
"temperature": {
|
||||
"type": "minecraft:shifted_noise",
|
||||
"noise": "minecraft:temperature",
|
||||
"shift_x": "minecraft:shift_x",
|
||||
"shift_y": 0.0,
|
||||
"shift_z": "minecraft:shift_z",
|
||||
"xz_scale": 0.25,
|
||||
"y_scale": 0.0
|
||||
},
|
||||
"vegetation": {
|
||||
"type": "minecraft:shifted_noise",
|
||||
"noise": "minecraft:vegetation",
|
||||
"shift_x": "minecraft:shift_x",
|
||||
"shift_y": 0.0,
|
||||
"shift_z": "minecraft:shift_z",
|
||||
"xz_scale": 0.25,
|
||||
"y_scale": 0.0
|
||||
},
|
||||
"vein_gap": 0.0,
|
||||
"vein_ridged": 0.0,
|
||||
"vein_toggle": 0.0
|
||||
},
|
||||
"ore_veins_enabled": false,
|
||||
"sea_level": 32,
|
||||
"spawn_target": [],
|
||||
"surface_rule": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:vertical_gradient",
|
||||
"false_at_and_above": {
|
||||
"above_bottom": 5
|
||||
},
|
||||
"random_name": "minecraft:bedrock_floor",
|
||||
"true_at_and_below": {
|
||||
"above_bottom": 0
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:bedrock"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:vertical_gradient",
|
||||
"false_at_and_above": {
|
||||
"below_top": 0
|
||||
},
|
||||
"random_name": "minecraft:bedrock_roof",
|
||||
"true_at_and_below": {
|
||||
"below_top": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:bedrock"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": false,
|
||||
"anchor": {
|
||||
"below_top": 5
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:netherrack"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:biome",
|
||||
"biome_is": [
|
||||
"minecraft:basalt_deltas"
|
||||
]
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:stone_depth",
|
||||
"add_surface_depth": true,
|
||||
"offset": 0,
|
||||
"secondary_depth_range": 0,
|
||||
"surface_type": "ceiling"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:basalt",
|
||||
"Properties": {
|
||||
"axis": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:stone_depth",
|
||||
"add_surface_depth": true,
|
||||
"offset": 0,
|
||||
"secondary_depth_range": 0,
|
||||
"surface_type": "floor"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": -0.012,
|
||||
"noise": "minecraft:patch"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": true,
|
||||
"anchor": {
|
||||
"absolute": 30
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": true,
|
||||
"anchor": {
|
||||
"absolute": 35
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:gravel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": 0.0,
|
||||
"noise": "minecraft:nether_state_selector"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:basalt",
|
||||
"Properties": {
|
||||
"axis": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:blackstone"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:biome",
|
||||
"biome_is": [
|
||||
"minecraft:soul_sand_valley"
|
||||
]
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:stone_depth",
|
||||
"add_surface_depth": true,
|
||||
"offset": 0,
|
||||
"secondary_depth_range": 0,
|
||||
"surface_type": "ceiling"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": 0.0,
|
||||
"noise": "minecraft:nether_state_selector"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:soul_sand"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:soul_soil"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:stone_depth",
|
||||
"add_surface_depth": true,
|
||||
"offset": 0,
|
||||
"secondary_depth_range": 0,
|
||||
"surface_type": "floor"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": -0.012,
|
||||
"noise": "minecraft:patch"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": true,
|
||||
"anchor": {
|
||||
"absolute": 30
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": true,
|
||||
"anchor": {
|
||||
"absolute": 35
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:gravel"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": 0.0,
|
||||
"noise": "minecraft:nether_state_selector"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:soul_sand"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:soul_soil"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:stone_depth",
|
||||
"add_surface_depth": false,
|
||||
"offset": 0,
|
||||
"secondary_depth_range": 0,
|
||||
"surface_type": "floor"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": false,
|
||||
"anchor": {
|
||||
"absolute": 32
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:hole"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:lava",
|
||||
"Properties": {
|
||||
"level": "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:biome",
|
||||
"biome_is": [
|
||||
"minecraft:warped_forest"
|
||||
]
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": 0.54,
|
||||
"noise": "minecraft:netherrack"
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": false,
|
||||
"anchor": {
|
||||
"absolute": 31
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": 1.17,
|
||||
"noise": "minecraft:nether_wart"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:warped_wart_block"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:warped_nylium"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:biome",
|
||||
"biome_is": [
|
||||
"minecraft:crimson_forest"
|
||||
]
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": 0.54,
|
||||
"noise": "minecraft:netherrack"
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": false,
|
||||
"anchor": {
|
||||
"absolute": 31
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": 1.17,
|
||||
"noise": "minecraft:nether_wart"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:nether_wart_block"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:crimson_nylium"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:biome",
|
||||
"biome_is": [
|
||||
"minecraft:nether_wastes"
|
||||
]
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:stone_depth",
|
||||
"add_surface_depth": true,
|
||||
"offset": 0,
|
||||
"secondary_depth_range": 0,
|
||||
"surface_type": "floor"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": -0.012,
|
||||
"noise": "minecraft:soul_sand_layer"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:hole"
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": true,
|
||||
"anchor": {
|
||||
"absolute": 30
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": true,
|
||||
"anchor": {
|
||||
"absolute": 35
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:soul_sand"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:netherrack"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:stone_depth",
|
||||
"add_surface_depth": false,
|
||||
"offset": 0,
|
||||
"secondary_depth_range": 0,
|
||||
"surface_type": "floor"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": false,
|
||||
"anchor": {
|
||||
"absolute": 31
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": true,
|
||||
"anchor": {
|
||||
"absolute": 35
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:noise_threshold",
|
||||
"max_threshold": 1.7976931348623157E308,
|
||||
"min_threshold": -0.012,
|
||||
"noise": "minecraft:gravel_layer"
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:sequence",
|
||||
"sequence": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:y_above",
|
||||
"add_stone_depth": false,
|
||||
"anchor": {
|
||||
"absolute": 32
|
||||
},
|
||||
"surface_depth_multiplier": 0
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:gravel"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"if_true": {
|
||||
"type": "minecraft:not",
|
||||
"invert": {
|
||||
"type": "minecraft:hole"
|
||||
}
|
||||
},
|
||||
"then_run": {
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:gravel"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"result_state": {
|
||||
"Name": "minecraft:netherrack"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"dimensions": {
|
||||
"minecraft:overworld": {
|
||||
"type": "minecraft:overworld",
|
||||
"generator": {
|
||||
"type": "minecraft:noise",
|
||||
"biome_source": {
|
||||
"type": "minecraft:multi_noise",
|
||||
"preset": "minecraft:overworld"
|
||||
},
|
||||
"settings": "minecraft:amplified"
|
||||
}
|
||||
},
|
||||
"minecraft:the_end": {
|
||||
"type": "minecraft:the_end",
|
||||
"generator": {
|
||||
"type": "bclib:betterx",
|
||||
"biome_source": {
|
||||
"type": "bclib:end_biome_source",
|
||||
"config": {
|
||||
"barrens_biomes_size": 256,
|
||||
"center_biomes_size": 256,
|
||||
"generator_version": "vanilla",
|
||||
"inner_void_radius_squared": 1048576,
|
||||
"land_biomes_size": 256,
|
||||
"map_type": "hex",
|
||||
"void_biomes_size": 256,
|
||||
"with_void_biomes": true
|
||||
},
|
||||
"seed": 0
|
||||
},
|
||||
"settings": "minecraft:end"
|
||||
}
|
||||
},
|
||||
"minecraft:the_nether": {
|
||||
"type": "minecraft:the_nether",
|
||||
"generator": {
|
||||
"type": "bclib:betterx",
|
||||
"biome_source": {
|
||||
"type": "bclib:nether_biome_source",
|
||||
"config": {
|
||||
"amplified": true,
|
||||
"biome_size": 256,
|
||||
"biome_size_vertical": 128,
|
||||
"map_type": "hex",
|
||||
"use_vertical_biomes": true
|
||||
},
|
||||
"seed": 0
|
||||
},
|
||||
"settings": "bclib:amplified_nether"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sort_order": 1000
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"dimensions": {
|
||||
"minecraft:overworld": {
|
||||
"type": "minecraft:overworld",
|
||||
"generator": {
|
||||
"type": "minecraft:noise",
|
||||
"biome_source": {
|
||||
"type": "minecraft:multi_noise",
|
||||
"preset": "minecraft:overworld"
|
||||
},
|
||||
"settings": "minecraft:large_biomes"
|
||||
}
|
||||
},
|
||||
"minecraft:the_end": {
|
||||
"type": "minecraft:the_end",
|
||||
"generator": {
|
||||
"type": "bclib:betterx",
|
||||
"biome_source": {
|
||||
"type": "bclib:end_biome_source",
|
||||
"config": {
|
||||
"barrens_biomes_size": 512,
|
||||
"center_biomes_size": 256,
|
||||
"generator_version": "vanilla",
|
||||
"inner_void_radius_squared": 1048576,
|
||||
"land_biomes_size": 1024,
|
||||
"map_type": "hex",
|
||||
"void_biomes_size": 512,
|
||||
"with_void_biomes": true
|
||||
},
|
||||
"seed": 0
|
||||
},
|
||||
"settings": "minecraft:end"
|
||||
}
|
||||
},
|
||||
"minecraft:the_nether": {
|
||||
"type": "minecraft:the_nether",
|
||||
"generator": {
|
||||
"type": "bclib:betterx",
|
||||
"biome_source": {
|
||||
"type": "bclib:nether_biome_source",
|
||||
"config": {
|
||||
"amplified": false,
|
||||
"biome_size": 1024,
|
||||
"biome_size_vertical": 172,
|
||||
"map_type": "hex",
|
||||
"use_vertical_biomes": true
|
||||
},
|
||||
"seed": 0
|
||||
},
|
||||
"settings": "minecraft:nether"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sort_order": 1000
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"dimensions": {
|
||||
"minecraft:overworld": {
|
||||
"type": "minecraft:overworld",
|
||||
"generator": {
|
||||
"type": "minecraft:noise",
|
||||
"biome_source": {
|
||||
"type": "minecraft:multi_noise",
|
||||
"preset": "minecraft:overworld"
|
||||
},
|
||||
"settings": "minecraft:overworld"
|
||||
}
|
||||
},
|
||||
"minecraft:the_end": {
|
||||
"type": "minecraft:the_end",
|
||||
"generator": {
|
||||
"type": "bclib:betterx",
|
||||
"biome_source": {
|
||||
"type": "bclib:end_biome_source",
|
||||
"config": {
|
||||
"barrens_biomes_size": 256,
|
||||
"center_biomes_size": 256,
|
||||
"generator_version": "paulevs",
|
||||
"inner_void_radius_squared": 1048576,
|
||||
"land_biomes_size": 256,
|
||||
"map_type": "square",
|
||||
"void_biomes_size": 256,
|
||||
"with_void_biomes": true
|
||||
},
|
||||
"seed": 0
|
||||
},
|
||||
"settings": "minecraft:end"
|
||||
}
|
||||
},
|
||||
"minecraft:the_nether": {
|
||||
"type": "minecraft:the_nether",
|
||||
"generator": {
|
||||
"type": "bclib:betterx",
|
||||
"biome_source": {
|
||||
"type": "bclib:nether_biome_source",
|
||||
"config": {
|
||||
"amplified": false,
|
||||
"biome_size": 256,
|
||||
"biome_size_vertical": 86,
|
||||
"map_type": "square",
|
||||
"use_vertical_biomes": true
|
||||
},
|
||||
"seed": 0
|
||||
},
|
||||
"settings": "minecraft:nether"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sort_order": 1000
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"dimensions": {
|
||||
"minecraft:overworld": {
|
||||
"type": "minecraft:overworld",
|
||||
"generator": {
|
||||
"type": "minecraft:noise",
|
||||
"biome_source": {
|
||||
"type": "minecraft:multi_noise",
|
||||
"preset": "minecraft:overworld"
|
||||
},
|
||||
"settings": "minecraft:overworld"
|
||||
}
|
||||
},
|
||||
"minecraft:the_end": {
|
||||
"type": "minecraft:the_end",
|
||||
"generator": {
|
||||
"type": "bclib:betterx",
|
||||
"biome_source": {
|
||||
"type": "bclib:end_biome_source",
|
||||
"config": {
|
||||
"barrens_biomes_size": 256,
|
||||
"center_biomes_size": 256,
|
||||
"generator_version": "vanilla",
|
||||
"inner_void_radius_squared": 1048576,
|
||||
"land_biomes_size": 256,
|
||||
"map_type": "hex",
|
||||
"void_biomes_size": 256,
|
||||
"with_void_biomes": true
|
||||
},
|
||||
"seed": 0
|
||||
},
|
||||
"settings": "minecraft:end"
|
||||
}
|
||||
},
|
||||
"minecraft:the_nether": {
|
||||
"type": "minecraft:the_nether",
|
||||
"generator": {
|
||||
"type": "bclib:betterx",
|
||||
"biome_source": {
|
||||
"type": "bclib:nether_biome_source",
|
||||
"config": {
|
||||
"amplified": false,
|
||||
"biome_size": 256,
|
||||
"biome_size_vertical": 86,
|
||||
"map_type": "hex",
|
||||
"use_vertical_biomes": true
|
||||
},
|
||||
"seed": 0
|
||||
},
|
||||
"settings": "minecraft:nether"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sort_order": 1000
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:end_barrens"
|
||||
]
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:the_end"
|
||||
]
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:end_highlands",
|
||||
"minecraft:end_midlands"
|
||||
]
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:end_midlands"
|
||||
]
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"minecraft:small_end_islands",
|
||||
{
|
||||
"id": "nullscape:void_barrens",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"replace": false,
|
||||
"values": [
|
||||
"bclib:normal",
|
||||
"bclib:amplified",
|
||||
"bclib:large"
|
||||
]
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:basalt_deltas",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 1.0,
|
||||
"intended_for": "OTHER_NETHER",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:crimson_forest",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 1.0,
|
||||
"intended_for": "OTHER_NETHER",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:end_barrens",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 1.0,
|
||||
"intended_for": "OTHER_END_BARRENS",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:end_highlands",
|
||||
"edge": "minecraft:end_midlands",
|
||||
"edgeSize": 8,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 0.5,
|
||||
"intended_for": "OTHER_END_LAND",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"parent": "minecraft:end_highlands",
|
||||
"biome": "minecraft:end_midlands",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 0.5,
|
||||
"intended_for": "OTHER_END_LAND",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:nether_wastes",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 1.0,
|
||||
"intended_for": "OTHER_NETHER",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:small_end_islands",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 1.0,
|
||||
"intended_for": "OTHER_END_VOID",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:soul_sand_valley",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 1.0,
|
||||
"intended_for": "OTHER_NETHER",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:the_end",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 1.0,
|
||||
"intended_for": "OTHER_END_CENTER",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:the_void",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 1.0,
|
||||
"intended_for": "NONE",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"type": "bclib:biome",
|
||||
"biome": "minecraft:warped_forest",
|
||||
"edgeSize": 0,
|
||||
"fogDensity": 1.0,
|
||||
"genChance": 1.0,
|
||||
"intended_for": "OTHER_NETHER",
|
||||
"terrainHeight": 0.1,
|
||||
"vertical": false
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package org.anti_ad.mc.ipn.api;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
// Included from "Inventory Profiles Next" (https://github.com/blackd/Inventory-Profiles)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface IPNIgnore {
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
package org.betterx.bclib;
|
||||
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.*;
|
||||
import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource;
|
||||
import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource;
|
||||
import org.betterx.bclib.api.v2.generator.GeneratorOptions;
|
||||
import org.betterx.bclib.api.v2.levelgen.LevelGenEvents;
|
||||
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry;
|
||||
import org.betterx.bclib.api.v2.levelgen.structures.BCLStructurePoolElementTypes;
|
||||
import org.betterx.bclib.api.v2.levelgen.structures.TemplatePiece;
|
||||
import org.betterx.bclib.api.v2.levelgen.surface.rules.Conditions;
|
||||
import org.betterx.bclib.api.v2.poi.PoiManager;
|
||||
import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates;
|
||||
import org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers;
|
||||
import org.betterx.bclib.api.v3.tag.BCLBlockTags;
|
||||
import org.betterx.bclib.blocks.signs.BaseHangingSignBlock;
|
||||
import org.betterx.bclib.blocks.signs.BaseSignBlock;
|
||||
import org.betterx.bclib.commands.CommandRegistry;
|
||||
import org.betterx.bclib.commands.arguments.BCLibArguments;
|
||||
import org.betterx.bclib.complexmaterials.BCLWoodTypeWrapper;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.config.PathConfig;
|
||||
import org.betterx.bclib.networking.VersionChecker;
|
||||
import org.betterx.bclib.recipes.AlloyingRecipe;
|
||||
import org.betterx.bclib.recipes.AnvilRecipe;
|
||||
import org.betterx.bclib.recipes.CraftingRecipes;
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
import org.betterx.bclib.registry.BaseRegistry;
|
||||
import org.betterx.bclib.registry.BlockRegistry;
|
||||
import org.betterx.bclib.registry.PresetsRegistry;
|
||||
import org.betterx.datagen.bclib.tests.TestStructure;
|
||||
import org.betterx.worlds.together.WorldsTogether;
|
||||
import org.betterx.worlds.together.util.Logger;
|
||||
import org.betterx.worlds.together.world.WorldConfig;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.CreativeModeTab;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.material.MapColor;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BCLib implements ModInitializer {
|
||||
public static final String MOD_ID = "bclib";
|
||||
public static final Logger LOGGER = new Logger(MOD_ID);
|
||||
|
||||
public static final boolean RUNS_NULLSCAPE = FabricLoader.getInstance()
|
||||
.getModContainer("nullscape")
|
||||
.isPresent();
|
||||
public static final boolean ADD_TEST_DATA = false;
|
||||
|
||||
private void onDatagen() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
WorldsTogether.onInitialize();
|
||||
BCLibArguments.register();
|
||||
PresetsRegistry.register();
|
||||
LevelGenEvents.register();
|
||||
BlockPredicates.ensureStaticInitialization();
|
||||
BCLBiomeRegistry.register();
|
||||
BaseRegistry.register();
|
||||
GeneratorOptions.init();
|
||||
BaseBlockEntities.register();
|
||||
BCLibEndBiomeSource.register();
|
||||
BCLibNetherBiomeSource.register();
|
||||
CraftingRecipes.init();
|
||||
BCLStructurePoolElementTypes.ensureStaticallyLoaded();
|
||||
WorldConfig.registerModCache(MOD_ID);
|
||||
DataExchangeAPI.registerMod(MOD_ID);
|
||||
AnvilRecipe.register();
|
||||
AlloyingRecipe.register();
|
||||
Conditions.registerAll();
|
||||
CommandRegistry.register();
|
||||
BCLBlockTags.ensureStaticallyLoaded();
|
||||
PoiManager.registerAll();
|
||||
if (isDevEnvironment()) {
|
||||
TestStructure.registerBase();
|
||||
}
|
||||
|
||||
if (ADD_TEST_DATA) {
|
||||
testObjects();
|
||||
}
|
||||
|
||||
DataExchangeAPI.registerDescriptors(List.of(
|
||||
HelloClient.DESCRIPTOR,
|
||||
HelloServer.DESCRIPTOR,
|
||||
RequestFiles.DESCRIPTOR,
|
||||
SendFiles.DESCRIPTOR,
|
||||
Chunker.DESCRIPTOR
|
||||
)
|
||||
);
|
||||
|
||||
BCLibPatch.register();
|
||||
TemplatePiece.ensureStaticInitialization();
|
||||
PlacementModifiers.ensureStaticInitialization();
|
||||
Configs.save();
|
||||
|
||||
WorldsTogether.FORCE_SERVER_TO_BETTERX_PRESET = Configs.SERVER_CONFIG.forceBetterXPreset();
|
||||
VersionChecker.registerMod(MOD_ID);
|
||||
|
||||
if (isDatagen()) {
|
||||
onDatagen();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDevEnvironment() {
|
||||
return FabricLoader.getInstance().isDevelopmentEnvironment();
|
||||
}
|
||||
|
||||
public static boolean isDatagen() {
|
||||
return System.getProperty("fabric-api.datagen") != null;
|
||||
}
|
||||
|
||||
public static boolean isClient() {
|
||||
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
|
||||
}
|
||||
|
||||
public static ResourceLocation makeID(String path) {
|
||||
return new ResourceLocation(MOD_ID, path);
|
||||
}
|
||||
|
||||
public static BCLWoodTypeWrapper TEST_WOOD;
|
||||
public static BaseSignBlock TEST_SIGN = null;
|
||||
public static BaseHangingSignBlock TEST_HANGING_SIGN = null;
|
||||
|
||||
private static void testObjects() {
|
||||
var bockReg = new BlockRegistry(new PathConfig(MOD_ID, "test"));
|
||||
bockReg.register(
|
||||
makeID("test_sign"),
|
||||
TEST_SIGN
|
||||
);
|
||||
bockReg.registerBlockOnly(
|
||||
makeID("test_wall_sign"),
|
||||
TEST_SIGN.getWallSignBlock()
|
||||
);
|
||||
bockReg.register(
|
||||
makeID("test_hanging_sign"),
|
||||
TEST_HANGING_SIGN
|
||||
);
|
||||
bockReg.registerBlockOnly(
|
||||
makeID("test_wall_hanging_sign"),
|
||||
TEST_HANGING_SIGN.getWallSignBlock()
|
||||
);
|
||||
}
|
||||
|
||||
static {
|
||||
if (ADD_TEST_DATA) {
|
||||
TEST_WOOD = BCLWoodTypeWrapper.create(makeID("test_wood")).setColor(MapColor.COLOR_MAGENTA).build();
|
||||
TEST_SIGN = new BaseSignBlock.Wood(TEST_WOOD);
|
||||
TEST_HANGING_SIGN = new BaseHangingSignBlock.Wood(TEST_WOOD);
|
||||
|
||||
|
||||
final ResourceKey<CreativeModeTab> TAB_TEST_KEY = ResourceKey.create(
|
||||
Registries.CREATIVE_MODE_TAB,
|
||||
makeID("test_tab")
|
||||
);
|
||||
|
||||
CreativeModeTab.Builder builder = FabricItemGroup
|
||||
.builder();
|
||||
builder.title(Component.translatable("itemGroup.bclib.test"));
|
||||
builder.icon(() -> new ItemStack(Items.BARRIER));
|
||||
builder.displayItems((itemDisplayParameters, output) -> {
|
||||
|
||||
var list = List.of(TEST_SIGN.asItem(), TEST_HANGING_SIGN.asItem())
|
||||
.stream().map(b -> new ItemStack(b, 1)).toList();
|
||||
|
||||
output.acceptAll(list);
|
||||
});
|
||||
final CreativeModeTab TAB_TEST = builder.build();
|
||||
;
|
||||
|
||||
Registry.register(
|
||||
BuiltInRegistries.CREATIVE_MODE_TAB,
|
||||
TAB_TEST_KEY,
|
||||
TAB_TEST
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package org.betterx.bclib;
|
||||
|
||||
import org.betterx.bclib.api.v2.datafixer.DataFixerAPI;
|
||||
import org.betterx.bclib.api.v2.datafixer.ForcedLevelPatch;
|
||||
import org.betterx.bclib.api.v2.datafixer.MigrationProfile;
|
||||
import org.betterx.bclib.api.v2.datafixer.Patch;
|
||||
import org.betterx.bclib.api.v2.generator.GeneratorOptions;
|
||||
import org.betterx.bclib.api.v2.levelgen.LevelGenUtil;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public final class BCLibPatch {
|
||||
public static void register() {
|
||||
if (Configs.MAIN_CONFIG.repairBiomes() && (GeneratorOptions.fixEndBiomeSource() || GeneratorOptions.fixNetherBiomeSource())) {
|
||||
DataFixerAPI.registerPatch(BiomeSourcePatch::new);
|
||||
}
|
||||
DataFixerAPI.registerPatch(SignPatch::new);
|
||||
}
|
||||
}
|
||||
|
||||
class SignPatch extends Patch {
|
||||
public SignPatch() {
|
||||
super(BCLib.MOD_ID, "3.0.11");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getIDReplacements() {
|
||||
return Map.ofEntries(
|
||||
Map.entry("bclib:sign", "minecraft:sign")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class BiomeSourcePatch extends ForcedLevelPatch {
|
||||
protected BiomeSourcePatch() {
|
||||
super(BCLib.MOD_ID, "1.2.1");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile) {
|
||||
//make sure we have a working generators file before attempting to patch
|
||||
LevelGenUtil.migrateGeneratorSettings();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package org.betterx.bclib.api.v2;
|
||||
|
||||
import org.betterx.bclib.mixin.common.ComposterBlockAccessor;
|
||||
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
public class ComposterAPI {
|
||||
public static Block allowCompost(float chance, Block block) {
|
||||
if (block != null) {
|
||||
allowCompost(chance, block.asItem());
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
public static Item allowCompost(float chance, Item item) {
|
||||
if (item != null && item != Items.AIR) {
|
||||
ComposterBlockAccessor.callAdd(chance, item);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package org.betterx.bclib.api.v2;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DiggerItemSpeed {
|
||||
public static final List<SpeedModifier> modifiers = new LinkedList<>();
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SpeedModifier {
|
||||
Optional<Float> calculateSpeed(ItemStack stack, BlockState state, float initialSpeed, float currentSpeed);
|
||||
}
|
||||
|
||||
public static void addModifier(SpeedModifier mod) {
|
||||
modifiers.add(mod);
|
||||
}
|
||||
|
||||
public static Optional<Float> getModifiedSpeed(ItemStack stack, BlockState state, float initialSpeed) {
|
||||
float currentSpeed = initialSpeed;
|
||||
Optional<Float> speed = Optional.empty();
|
||||
for (SpeedModifier mod : modifiers) {
|
||||
Optional<Float> res = mod.calculateSpeed(stack, state, initialSpeed, currentSpeed);
|
||||
if (res.isPresent()) {
|
||||
currentSpeed = res.get();
|
||||
speed = res;
|
||||
}
|
||||
}
|
||||
|
||||
return speed;
|
||||
}
|
||||
}
|
|
@ -1,146 +0,0 @@
|
|||
package org.betterx.bclib.api.v2;
|
||||
|
||||
import org.betterx.bclib.api.v2.datafixer.DataFixerAPI;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.world.level.CustomSpawner;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* provides some lifetime hooks for a Minecraft instance
|
||||
*/
|
||||
public class LifeCycleAPI {
|
||||
private final static List<LevelLoadBiomesCall> onLoadLevelBiomes = new ArrayList<>(2);
|
||||
private final static List<LevelLoadCall> onLoadLevel = new ArrayList<>(2);
|
||||
private final static List<BeforeLevelLoadCall> beforeLoadLevel = new ArrayList<>(2);
|
||||
|
||||
|
||||
/**
|
||||
* Register a callback that is called before a level is loaded or created,
|
||||
* but after the {@link org.betterx.worlds.together.world.WorldConfig} was initialized and patches from
|
||||
* the {@link DataFixerAPI} were applied.
|
||||
*
|
||||
* @param call The callback Method
|
||||
*/
|
||||
public static void beforeLevelLoad(BeforeLevelLoadCall call) {
|
||||
beforeLoadLevel.add(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback that is called when a new {@code ServerLevel is instantiated}.
|
||||
* This callback will receive the world seed as well as it's biome registry.
|
||||
*
|
||||
* @param call The calbback Method
|
||||
*/
|
||||
public static void onLevelLoad(LevelLoadBiomesCall call) {
|
||||
onLoadLevelBiomes.add(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback that is called when a new {@code ServerLevel is instantiated}.
|
||||
* This callbacl will receiv all parameters that were passed to the ServerLevel's constructor
|
||||
*
|
||||
* @param call The calbback Method
|
||||
*/
|
||||
public static void onLevelLoad(LevelLoadCall call) {
|
||||
onLoadLevel.add(call);
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use, You should not call this method!
|
||||
*/
|
||||
public static void _runBeforeLevelLoad() {
|
||||
beforeLoadLevel.forEach(c -> c.beforeLoad());
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use, You should not call this method!
|
||||
*
|
||||
* @param minecraftServer
|
||||
* @param executor
|
||||
* @param levelStorageAccess
|
||||
* @param serverLevelData
|
||||
* @param resourceKey
|
||||
* @param chunkProgressListener
|
||||
* @param bl
|
||||
* @param l
|
||||
* @param list
|
||||
* @param bl2
|
||||
*/
|
||||
public static void _runLevelLoad(
|
||||
ServerLevel world,
|
||||
MinecraftServer minecraftServer,
|
||||
Executor executor,
|
||||
LevelStorageSource.LevelStorageAccess levelStorageAccess,
|
||||
ServerLevelData serverLevelData,
|
||||
ResourceKey<Level> resourceKey,
|
||||
ChunkProgressListener chunkProgressListener,
|
||||
boolean bl,
|
||||
long l,
|
||||
List<CustomSpawner> list,
|
||||
boolean bl2
|
||||
) {
|
||||
onLoadLevel.forEach(c -> c.onLoad(
|
||||
world,
|
||||
minecraftServer,
|
||||
executor,
|
||||
levelStorageAccess,
|
||||
serverLevelData,
|
||||
resourceKey,
|
||||
chunkProgressListener,
|
||||
bl,
|
||||
l,
|
||||
list,
|
||||
bl2
|
||||
));
|
||||
|
||||
final long seed = world.getSeed();
|
||||
final Registry<Biome> biomeRegistry = world.registryAccess().registryOrThrow(Registries.BIOME);
|
||||
onLoadLevelBiomes.forEach(c -> c.onLoad(world, seed, biomeRegistry));
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function that is used for each new ServerLevel instance
|
||||
*/
|
||||
public interface BeforeLevelLoadCall {
|
||||
void beforeLoad();
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function that is used for each new ServerLevel instance
|
||||
*/
|
||||
public interface LevelLoadBiomesCall {
|
||||
void onLoad(ServerLevel world, long seed, Registry<Biome> registry);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function that is used for each new ServerLevel instance
|
||||
*/
|
||||
public interface LevelLoadCall {
|
||||
void onLoad(
|
||||
ServerLevel world,
|
||||
MinecraftServer minecraftServer,
|
||||
Executor executor,
|
||||
LevelStorageSource.LevelStorageAccess levelStorageAccess,
|
||||
ServerLevelData serverLevelData,
|
||||
ResourceKey<Level> resourceKey,
|
||||
ChunkProgressListener chunkProgressListener,
|
||||
boolean bl,
|
||||
long l,
|
||||
List<CustomSpawner> list,
|
||||
boolean bl2
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package org.betterx.bclib.api.v2;
|
||||
|
||||
import org.betterx.bclib.integration.ModIntegration;
|
||||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ModIntegrationAPI {
|
||||
private static final List<ModIntegration> INTEGRATIONS = Lists.newArrayList();
|
||||
private static final boolean HAS_CANVAS = FabricLoader.getInstance().isModLoaded("canvas");
|
||||
|
||||
/**
|
||||
* Registers mod integration
|
||||
*
|
||||
* @param integration
|
||||
* @return
|
||||
*/
|
||||
public static ModIntegration register(ModIntegration integration) {
|
||||
INTEGRATIONS.add(integration);
|
||||
return integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered mod integrations.
|
||||
*
|
||||
* @return {@link List} of {@link ModIntegration}.
|
||||
*/
|
||||
public static List<ModIntegration> getIntegrations() {
|
||||
return INTEGRATIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all integrations, only for internal usage.
|
||||
*/
|
||||
public static void registerAll() {
|
||||
INTEGRATIONS.forEach(integration -> {
|
||||
if (integration.modIsInstalled()) {
|
||||
integration.init();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean hasCanvas() {
|
||||
return HAS_CANVAS;
|
||||
}
|
||||
}
|
|
@ -1,267 +0,0 @@
|
|||
package org.betterx.bclib.api.v2;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI;
|
||||
import org.betterx.bclib.behaviours.interfaces.*;
|
||||
import org.betterx.bclib.blocks.BaseBarrelBlock;
|
||||
import org.betterx.bclib.blocks.BaseChestBlock;
|
||||
import org.betterx.bclib.blocks.BaseFurnaceBlock;
|
||||
import org.betterx.bclib.client.render.BCLRenderLayer;
|
||||
import org.betterx.bclib.client.render.BaseChestBlockEntityRenderer;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.interfaces.Fuel;
|
||||
import org.betterx.bclib.interfaces.PostInitable;
|
||||
import org.betterx.bclib.interfaces.RenderLayerProvider;
|
||||
import org.betterx.bclib.interfaces.TagProvider;
|
||||
import org.betterx.bclib.interfaces.tools.*;
|
||||
import org.betterx.bclib.items.tool.*;
|
||||
import org.betterx.bclib.networking.VersionChecker;
|
||||
import org.betterx.bclib.registry.BaseBlockEntities;
|
||||
import org.betterx.worlds.together.tag.v3.*;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.core.dispenser.ShearsDispenseItemBehavior;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.tags.ItemTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.DispenserBlock;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
|
||||
import net.fabricmc.fabric.api.registry.FuelRegistry;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class PostInitAPI {
|
||||
private static List<Consumer<Boolean>> postInitFunctions = Lists.newArrayList();
|
||||
private static List<TagKey<Block>> blockTags = Lists.newArrayList();
|
||||
private static List<TagKey<Item>> itemTags = Lists.newArrayList();
|
||||
|
||||
/**
|
||||
* Register a new function which will be called after all mods are initiated. Will be called on both client and server.
|
||||
*
|
||||
* @param function {@link Consumer} with {@code boolean} parameter ({@code true} for client, {@code false} for server).
|
||||
*/
|
||||
public static void register(Consumer<Boolean> function) {
|
||||
postInitFunctions.add(function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called in proper BCLib entry points, for internal usage only.
|
||||
*
|
||||
* @param isClient {@code boolean}, {@code true} for client, {@code false} for server.
|
||||
*/
|
||||
public static void postInit(boolean isClient) {
|
||||
BuiltInRegistries.BLOCK.forEach(block -> {
|
||||
processBlockCommon(block);
|
||||
if (isClient) {
|
||||
processBlockClient(block);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
BuiltInRegistries.ITEM.forEach(item -> {
|
||||
processItemCommon(item);
|
||||
});
|
||||
|
||||
if (postInitFunctions != null) {
|
||||
postInitFunctions.forEach(function -> function.accept(isClient));
|
||||
postInitFunctions = null;
|
||||
}
|
||||
blockTags = null;
|
||||
itemTags = null;
|
||||
InternalBiomeAPI.loadFabricAPIBiomes();
|
||||
Configs.BIOMES_CONFIG.saveChanges();
|
||||
|
||||
VersionChecker.startCheck(isClient);
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private static void processBlockClient(Block block) {
|
||||
if (block instanceof RenderLayerProvider) {
|
||||
BCLRenderLayer layer = ((RenderLayerProvider) block).getRenderLayer();
|
||||
if (layer == BCLRenderLayer.CUTOUT) BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.cutout());
|
||||
else if (layer == BCLRenderLayer.TRANSLUCENT)
|
||||
BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.translucent());
|
||||
}
|
||||
if (block instanceof BaseChestBlock) {
|
||||
BaseChestBlockEntityRenderer.registerRenderLayer(block);
|
||||
}
|
||||
}
|
||||
|
||||
private static void processItemCommon(Item item) {
|
||||
if (item instanceof TagProvider provider) {
|
||||
try {
|
||||
provider.addTags(null, itemTags);
|
||||
} catch (NullPointerException ex) {
|
||||
BCLib.LOGGER.error(item + " probably tried to access blockTags.", ex);
|
||||
}
|
||||
itemTags.forEach(tag -> TagManager.ITEMS.add(tag, item));
|
||||
itemTags.clear();
|
||||
}
|
||||
|
||||
if (item instanceof BaseShovelItem) {
|
||||
TagManager.ITEMS.add(item, ToolTags.FABRIC_SHOVELS, ItemTags.SHOVELS);
|
||||
} else if (item instanceof BaseSwordItem) {
|
||||
TagManager.ITEMS.add(item, ToolTags.FABRIC_SWORDS, ItemTags.SWORDS);
|
||||
} else if (item instanceof BasePickaxeItem) {
|
||||
TagManager.ITEMS.add(item, ToolTags.FABRIC_PICKAXES, ItemTags.PICKAXES);
|
||||
} else if (item instanceof BaseAxeItem) {
|
||||
TagManager.ITEMS.add(item, ToolTags.FABRIC_AXES, ItemTags.AXES);
|
||||
} else if (item instanceof BaseHoeItem) {
|
||||
TagManager.ITEMS.add(item, ToolTags.FABRIC_HOES, ItemTags.HOES);
|
||||
} else if (item instanceof BaseShearsItem) {
|
||||
TagManager.ITEMS.add(item, ToolTags.FABRIC_SHEARS, CommonItemTags.SHEARS);
|
||||
DispenserBlock.registerBehavior(item.asItem(), new ShearsDispenseItemBehavior());
|
||||
}
|
||||
}
|
||||
|
||||
private static void processBlockCommon(Block block) {
|
||||
//TODO: Some of this only needs to run on DataGen, add a special PostDataGenAPI for that
|
||||
final Item item = block.asItem();
|
||||
if (block instanceof PostInitable) {
|
||||
((PostInitable) block).postInit();
|
||||
}
|
||||
|
||||
if (block instanceof TagProvider) {
|
||||
((TagProvider) block).addTags(blockTags, itemTags);
|
||||
blockTags.forEach(tag -> TagManager.BLOCKS.add(tag, block));
|
||||
if (item != null && item != Items.AIR)
|
||||
itemTags.forEach(tag -> TagManager.ITEMS.add(tag, item));
|
||||
blockTags.clear();
|
||||
itemTags.clear();
|
||||
}
|
||||
|
||||
if (block instanceof BaseChestBlock) {
|
||||
BaseBlockEntities.CHEST.registerBlock(block);
|
||||
} else if (block instanceof BaseBarrelBlock) {
|
||||
BaseBlockEntities.BARREL.registerBlock(block);
|
||||
} else if (block instanceof BaseFurnaceBlock) {
|
||||
BaseBlockEntities.FURNACE.registerBlock(block);
|
||||
}
|
||||
if (!(block instanceof PreventMineableAdd)) {
|
||||
if (block instanceof AddMineableShears) {
|
||||
TagManager.BLOCKS.add(block, MineableTags.SHEARS);
|
||||
}
|
||||
if (block instanceof AddMineableAxe) {
|
||||
if (!TagManager.BLOCKS.contains(BlockTags.WOODEN_DOORS, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.WOODEN_BUTTONS, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.WOODEN_SLABS, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.WOODEN_FENCES, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.WOODEN_STAIRS, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.WOODEN_PRESSURE_PLATES, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.WOODEN_TRAPDOORS, block)
|
||||
&& !TagManager.BLOCKS.contains(CommonBlockTags.WOODEN_BARREL, block)
|
||||
&& !TagManager.BLOCKS.contains(CommonBlockTags.WOODEN_CHEST, block)
|
||||
&& !TagManager.BLOCKS.contains(CommonBlockTags.WOODEN_COMPOSTER, block)
|
||||
&& !TagManager.BLOCKS.contains(CommonBlockTags.WORKBENCHES, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.SIGNS, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.PLANKS, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.LOGS, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.FENCE_GATES, block)
|
||||
&& !TagManager.BLOCKS.contains(BlockTags.ALL_HANGING_SIGNS, block)
|
||||
&& !TagManager.BLOCKS.contains(CommonBlockTags.WORKBENCHES, block)
|
||||
&& !TagManager.BLOCKS.contains(CommonBlockTags.BOOKSHELVES, block)
|
||||
) {
|
||||
TagManager.BLOCKS.add(block, MineableTags.AXE);
|
||||
}
|
||||
}
|
||||
if (block instanceof AddMineablePickaxe) {
|
||||
TagManager.BLOCKS.add(block, MineableTags.PICKAXE);
|
||||
}
|
||||
if (block instanceof AddMineableShovel) {
|
||||
TagManager.BLOCKS.add(block, MineableTags.SHOVEL);
|
||||
}
|
||||
if (block instanceof AddMineableHoe) {
|
||||
TagManager.BLOCKS.add(block, MineableTags.HOE);
|
||||
}
|
||||
if (block instanceof AddMineableSword) {
|
||||
TagManager.BLOCKS.add(block, MineableTags.SWORD);
|
||||
}
|
||||
if (block instanceof AddMineableHammer) {
|
||||
TagManager.BLOCKS.add(block, MineableTags.HAMMER);
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourCompostable c) {
|
||||
if (item != null && item != Items.AIR) {
|
||||
TagManager.ITEMS.add(block, CommonItemTags.COMPOSTABLE);
|
||||
ComposterAPI.allowCompost(c.compostingChance(), item);
|
||||
} else if (BCLib.isDatagen() && Configs.MAIN_CONFIG.verboseLogging()) {
|
||||
BCLib.LOGGER.warning("Block " + block + " has compostable behaviour but no item!");
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourWaterPlantLike) {
|
||||
TagManager.BLOCKS.add(block, CommonBlockTags.WATER_PLANT);
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourPlant || block instanceof BehaviourShearablePlant) {
|
||||
TagManager.BLOCKS.add(block, CommonBlockTags.PLANT);
|
||||
}
|
||||
|
||||
|
||||
if (block instanceof BehaviourSeedLike) {
|
||||
TagManager.BLOCKS.add(block, CommonBlockTags.SEEDS);
|
||||
if (item != null && item != Items.AIR) {
|
||||
TagManager.ITEMS.add(block, CommonItemTags.SEEDS);
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourSaplingLike) {
|
||||
TagManager.BLOCKS.add(block, CommonBlockTags.SAPLINGS, BlockTags.SAPLINGS);
|
||||
if (item != null && item != Items.AIR) {
|
||||
TagManager.ITEMS.add(block, CommonItemTags.SAPLINGS, ItemTags.SAPLINGS);
|
||||
}
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourClimable c) {
|
||||
TagManager.BLOCKS.add(block, BlockTags.CLIMBABLE);
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourLeaves) {
|
||||
TagManager.BLOCKS.add(block, BlockTags.LEAVES, CommonBlockTags.LEAVES);
|
||||
if (item != null && item != Items.AIR)
|
||||
TagManager.ITEMS.add(item, ItemTags.LEAVES, CommonItemTags.LEAVES);
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourImmobile) {
|
||||
TagManager.BLOCKS.add(block, CommonBlockTags.IMMOBILE);
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourObsidian) {
|
||||
TagManager.BLOCKS.add(block, CommonBlockTags.IS_OBSIDIAN);
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourPortalFrame) {
|
||||
TagManager.BLOCKS.add(block, CommonBlockTags.NETHER_PORTAL_FRAME);
|
||||
}
|
||||
|
||||
if (block instanceof BehaviourOre) {
|
||||
TagManager.BLOCKS.add(block, CommonBlockTags.ORES);
|
||||
}
|
||||
|
||||
if (block instanceof Fuel fl) {
|
||||
FuelRegistry.INSTANCE.add(block, fl.getFuelTime());
|
||||
}
|
||||
|
||||
if (BCLib.isDatagen()) {
|
||||
final ResourceLocation location = BuiltInRegistries.BLOCK.getKey(block);
|
||||
if (!location.getNamespace().equals("minecraft")) {
|
||||
if (!(block instanceof HasMinableBehaviour) && block.defaultBlockState()
|
||||
.requiresCorrectToolForDrops()) {
|
||||
BCLib.LOGGER.error("Block " + block + "(" + block.getClass() + ")" + " has no mineable behaviour!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package org.betterx.bclib.api.v2;
|
||||
|
||||
import org.betterx.bclib.mixin.common.ShovelItemAccessor;
|
||||
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class ShovelAPI {
|
||||
/**
|
||||
* Will add left-click behaviour to shovel: when it is targeting cetrain {@link Block} it will be converting to new
|
||||
* {@link BlockState} on usage. Example: grass converting to path.
|
||||
*
|
||||
* @param target {@link Block} that will be converted.
|
||||
* @param convert {@link BlockState} to convert block into.
|
||||
*/
|
||||
public static void addShovelBehaviour(Block target, BlockState convert) {
|
||||
Map<Block, BlockState> map = ShovelItemAccessor.bclib_getFlattenables();
|
||||
map.put(target, convert);
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.advancement;
|
||||
|
||||
import net.minecraft.advancements.DisplayInfo;
|
||||
import net.minecraft.advancements.FrameType;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class Display {
|
||||
ItemStack icon;
|
||||
Component title;
|
||||
net.minecraft.network.chat.Component description;
|
||||
@Nullable ResourceLocation background;
|
||||
FrameType frame;
|
||||
boolean showToast;
|
||||
boolean announceChat;
|
||||
boolean hidden;
|
||||
|
||||
Display() {
|
||||
}
|
||||
|
||||
Display reset() {
|
||||
this.icon = null;
|
||||
this.title = null;
|
||||
this.description = null;
|
||||
frame = FrameType.TASK;
|
||||
background = null;
|
||||
showToast = true;
|
||||
announceChat = true;
|
||||
hidden = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
DisplayInfo build() {
|
||||
return new DisplayInfo(
|
||||
icon, title, description,
|
||||
background, frame, showToast, announceChat, hidden
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,509 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.advancement;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.levelgen.structures.BCLStructure;
|
||||
import org.betterx.bclib.complexmaterials.WoodenComplexMaterial;
|
||||
import org.betterx.bclib.complexmaterials.set.wood.WoodSlots;
|
||||
import org.betterx.bclib.items.complex.EquipmentSet;
|
||||
import org.betterx.bclib.items.complex.EquipmentSlot;
|
||||
|
||||
import net.minecraft.advancements.*;
|
||||
import net.minecraft.advancements.critereon.*;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.data.recipes.RecipeBuilder;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AdvancementManager {
|
||||
static class OrderedBuilder extends Advancement.Builder {
|
||||
OrderedBuilder() {
|
||||
super(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<ResourceLocation, Advancement.Builder> ADVANCEMENTS = new LinkedHashMap<>();
|
||||
|
||||
public static void register(ResourceLocation id, Advancement.Builder builder) {
|
||||
ADVANCEMENTS.put(id, builder);
|
||||
}
|
||||
|
||||
public static void registerAllDataGen(List<String> namespaces, Consumer<Advancement> consumer) {
|
||||
final Advancement ROOT_RECIPE = Advancement.Builder.advancement()
|
||||
.addCriterion(
|
||||
"impossible",
|
||||
new ImpossibleTrigger.TriggerInstance()
|
||||
)
|
||||
.build(RecipeBuilder.ROOT_RECIPE_ADVANCEMENT);
|
||||
final Map<ResourceLocation, Advancement> BUILT = new HashMap<>();
|
||||
|
||||
for (var entry : ADVANCEMENTS.entrySet()) {
|
||||
final ResourceLocation loc = entry.getKey();
|
||||
if (namespaces == null || namespaces.contains(loc.getNamespace())) {
|
||||
final Advancement.Builder builder = entry.getValue();
|
||||
if (builder.canBuild(locToAdd -> {
|
||||
if (locToAdd.equals(RecipeBuilder.ROOT_RECIPE_ADVANCEMENT)) return ROOT_RECIPE;
|
||||
return BUILT.get(locToAdd);
|
||||
})) {
|
||||
final Advancement adv = builder.build(loc);
|
||||
BUILT.put(loc, adv);
|
||||
consumer.accept(adv);
|
||||
} else {
|
||||
BCLib.LOGGER.error("Unable to build Advancement " + loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class RewardsBuilder {
|
||||
private final Builder calle;
|
||||
private final AdvancementRewards.Builder builder = new AdvancementRewards.Builder();
|
||||
|
||||
private RewardsBuilder(Builder calle) {
|
||||
this.calle = calle;
|
||||
}
|
||||
|
||||
public RewardsBuilder addExperience(int i) {
|
||||
builder.addExperience(i);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public RewardsBuilder addLootTable(ResourceLocation resourceLocation) {
|
||||
builder.addLootTable(resourceLocation);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public RewardsBuilder addRecipe(ResourceLocation resourceLocation) {
|
||||
builder.addRecipe(resourceLocation);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public RewardsBuilder runs(ResourceLocation resourceLocation) {
|
||||
builder.runs(resourceLocation);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder endReward() {
|
||||
calle.rewards(builder.build());
|
||||
return calle;
|
||||
}
|
||||
}
|
||||
|
||||
public enum AdvancementType {
|
||||
REGULAR,
|
||||
RECIPE_DECORATIONS,
|
||||
RECIPE_TOOL
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private static final ThreadLocal<DisplayBuilder> DISPLAY_BUILDER = ThreadLocal.withInitial(DisplayBuilder::new);
|
||||
private static final ResourceLocation RECIPES_ROOT = RecipeBuilder.ROOT_RECIPE_ADVANCEMENT;
|
||||
|
||||
private final Advancement.Builder builder = new OrderedBuilder();
|
||||
private final ResourceLocation id;
|
||||
private final AdvancementType type;
|
||||
private boolean canBuild = true;
|
||||
|
||||
private Builder(ResourceLocation id, AdvancementType type) {
|
||||
ResourceLocation ID;
|
||||
if (type == AdvancementType.RECIPE_DECORATIONS) {
|
||||
ID = new ResourceLocation(id.getNamespace(), "recipes/decorations/" + id.getPath());
|
||||
builder.parent(RECIPES_ROOT);
|
||||
} else if (type == AdvancementType.RECIPE_TOOL) {
|
||||
ID = new ResourceLocation(id.getNamespace(), "recipes/tools/" + id.getPath());
|
||||
builder.parent(RECIPES_ROOT);
|
||||
} else {
|
||||
ID = id;
|
||||
}
|
||||
this.id = ID;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static Builder createEmptyCopy(Builder builder) {
|
||||
return new Builder(builder.id, builder.type);
|
||||
}
|
||||
|
||||
public static Builder create(ResourceLocation id) {
|
||||
return new Builder(id, AdvancementType.REGULAR);
|
||||
}
|
||||
|
||||
public static Builder create(ResourceLocation id, AdvancementType type) {
|
||||
return new Builder(id, type);
|
||||
}
|
||||
|
||||
public static Builder create(Item icon) {
|
||||
return create(icon, AdvancementType.REGULAR);
|
||||
}
|
||||
|
||||
public static Builder create(ItemStack icon) {
|
||||
return create(icon, AdvancementType.REGULAR);
|
||||
}
|
||||
|
||||
public static Builder create(ItemLike icon, AdvancementType type) {
|
||||
return create(new ItemStack(icon), type);
|
||||
}
|
||||
|
||||
public static Builder create(ItemStack icon, AdvancementType type) {
|
||||
return create(icon, type, (displayBuilder) -> {
|
||||
});
|
||||
}
|
||||
|
||||
public static Builder create(Item icon, AdvancementType type, Consumer<DisplayBuilder> displayAdapter) {
|
||||
return create(new ItemStack(icon), type, displayAdapter);
|
||||
}
|
||||
|
||||
public static Builder create(
|
||||
ItemStack icon,
|
||||
AdvancementType type,
|
||||
Consumer<DisplayBuilder> displayAdapter
|
||||
) {
|
||||
var id = BuiltInRegistries.ITEM.getKey(icon.getItem());
|
||||
boolean canBuild = true;
|
||||
if (id == null || icon.is(Items.AIR)) {
|
||||
canBuild = false;
|
||||
id = BuiltInRegistries.ITEM.getDefaultKey();
|
||||
}
|
||||
|
||||
String baseName = "advancements." + id.getNamespace() + "." + id.getPath() + ".";
|
||||
Builder b = new Builder(id, type);
|
||||
var displayBuilder = b.startDisplay(
|
||||
icon,
|
||||
Component.translatable(baseName + "title"),
|
||||
Component.translatable(baseName + "description")
|
||||
);
|
||||
if (displayAdapter != null) displayAdapter.accept(displayBuilder);
|
||||
b = displayBuilder.endDisplay();
|
||||
b.canBuild = canBuild;
|
||||
return b;
|
||||
}
|
||||
|
||||
public static <C extends Container, T extends Recipe<C>> Builder createRecipe(
|
||||
T recipe,
|
||||
AdvancementType type
|
||||
) {
|
||||
Item item = recipe.getResultItem(Minecraft.getInstance().level.registryAccess()).getItem();
|
||||
return create(item, type, displayBuilder -> displayBuilder.hideToast().hideFromChat())
|
||||
//.awardRecipe(item)
|
||||
.addRecipeUnlockCriterion("has_the_recipe", recipe)
|
||||
.startReward()
|
||||
.addRecipe(recipe.getId())
|
||||
.endReward()
|
||||
.requirements(RequirementsStrategy.OR);
|
||||
}
|
||||
|
||||
public Builder parent(Advancement advancement) {
|
||||
builder.parent(advancement);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder parent(ResourceLocation resourceLocation) {
|
||||
builder.parent(resourceLocation);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder startDisplay(ItemLike icon) {
|
||||
String baseName = "advancements." + id.getNamespace() + "." + id.getPath() + ".";
|
||||
return startDisplay(
|
||||
icon,
|
||||
Component.translatable(baseName + "title"),
|
||||
Component.translatable(baseName + "description")
|
||||
);
|
||||
}
|
||||
|
||||
public DisplayBuilder startDisplay(
|
||||
ItemLike icon,
|
||||
Component title,
|
||||
Component description
|
||||
) {
|
||||
return startDisplay(new ItemStack(icon), title, description);
|
||||
}
|
||||
|
||||
public DisplayBuilder startDisplay(
|
||||
ItemStack icon,
|
||||
Component title,
|
||||
Component description
|
||||
) {
|
||||
if (icon == null) {
|
||||
canBuild = false;
|
||||
} else {
|
||||
var id = BuiltInRegistries.ITEM.getKey(icon.getItem());
|
||||
if (id == null) {
|
||||
canBuild = false;
|
||||
}
|
||||
}
|
||||
DisplayBuilder dp = DISPLAY_BUILDER.get().reset(this);
|
||||
return dp.icon(icon).title(title).description(description);
|
||||
}
|
||||
|
||||
Builder display(DisplayInfo displayInfo) {
|
||||
builder.display(displayInfo);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder awardRecipe(ItemLike... items) {
|
||||
RewardsBuilder rewardBuilder = startReward();
|
||||
for (ItemLike item : items) {
|
||||
ResourceLocation id = BuiltInRegistries.ITEM.getKey(item.asItem());
|
||||
if (id == null) continue;
|
||||
rewardBuilder.addRecipe(id);
|
||||
}
|
||||
return rewardBuilder.endReward();
|
||||
}
|
||||
|
||||
public RewardsBuilder startReward() {
|
||||
return new RewardsBuilder(this);
|
||||
}
|
||||
|
||||
public Builder rewards(AdvancementRewards advancementRewards) {
|
||||
builder.rewards(advancementRewards);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder rewardXP(int xp) {
|
||||
return rewards(AdvancementRewards.Builder.experience(500).build());
|
||||
}
|
||||
|
||||
public Builder addCriterion(String string, CriterionTriggerInstance criterionTriggerInstance) {
|
||||
builder.addCriterion(string, new Criterion(criterionTriggerInstance));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addCriterion(String string, Criterion criterion) {
|
||||
builder.addCriterion(string, criterion);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addAtStructureCriterion(String name, BCLStructure<?> structure) {
|
||||
return addAtStructureCriterion(name, structure.structureKey);
|
||||
}
|
||||
|
||||
public Builder addAtStructureCriterion(String name, ResourceKey<Structure> structure) {
|
||||
return addCriterion(
|
||||
name,
|
||||
PlayerTrigger
|
||||
.TriggerInstance
|
||||
.located(
|
||||
LocationPredicate.inStructure(structure)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public <C extends Container, T extends Recipe<C>> Builder addRecipeUnlockCriterion(String name, T recipe) {
|
||||
return addCriterion(
|
||||
name,
|
||||
RecipeUnlockedTrigger.unlocked(recipe.getId())
|
||||
);
|
||||
}
|
||||
|
||||
public Builder addInventoryChangedCriterion(String name, ItemLike... items) {
|
||||
return addCriterion(
|
||||
name,
|
||||
InventoryChangeTrigger.TriggerInstance.hasItems(items)
|
||||
);
|
||||
}
|
||||
|
||||
public Builder addInventoryChangedAnyCriterion(String name, ItemLike... items) {
|
||||
InventoryChangeTrigger.TriggerInstance trigger =
|
||||
InventoryChangeTrigger.TriggerInstance.hasItems(new ItemPredicate(
|
||||
null,
|
||||
Arrays.stream(items).map(i -> i.asItem()).collect(Collectors.toSet()),
|
||||
MinMaxBounds.Ints.ANY,
|
||||
MinMaxBounds.Ints.ANY,
|
||||
EnchantmentPredicate.NONE,
|
||||
EnchantmentPredicate.NONE,
|
||||
null,
|
||||
NbtPredicate.ANY
|
||||
));
|
||||
return addCriterion(name, trigger);
|
||||
}
|
||||
|
||||
public Builder addInventoryChangedCriterion(String name, TagKey<Item> tag) {
|
||||
return addCriterion(
|
||||
name,
|
||||
InventoryChangeTrigger.TriggerInstance.hasItems(new ItemPredicate(
|
||||
tag,
|
||||
null,
|
||||
MinMaxBounds.Ints.ANY,
|
||||
MinMaxBounds.Ints.ANY,
|
||||
EnchantmentPredicate.NONE,
|
||||
EnchantmentPredicate.NONE,
|
||||
null,
|
||||
NbtPredicate.ANY
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
public Builder addEquipmentSetSlotCriterion(EquipmentSet set, EquipmentSlot slot) {
|
||||
return addInventoryChangedCriterion(
|
||||
set.baseName + "_" + slot,
|
||||
set.getSlot(slot)
|
||||
);
|
||||
}
|
||||
|
||||
public Builder addArmorSetCriterion(EquipmentSet set) {
|
||||
return addEquipmentSetSlotCriterion(set, EquipmentSet.HELMET_SLOT)
|
||||
.addEquipmentSetSlotCriterion(set, EquipmentSet.CHESTPLATE_SLOT)
|
||||
.addEquipmentSetSlotCriterion(set, EquipmentSet.LEGGINGS_SLOT)
|
||||
.addEquipmentSetSlotCriterion(set, EquipmentSet.BOOTS_SLOT);
|
||||
}
|
||||
|
||||
public Builder addToolSetCriterion(EquipmentSet set) {
|
||||
return addEquipmentSetSlotCriterion(set, EquipmentSet.PICKAXE_SLOT)
|
||||
.addEquipmentSetSlotCriterion(set, EquipmentSet.AXE_SLOT)
|
||||
.addEquipmentSetSlotCriterion(set, EquipmentSet.SHOVEL_SLOT)
|
||||
.addEquipmentSetSlotCriterion(set, EquipmentSet.SWORD_SLOT)
|
||||
.addEquipmentSetSlotCriterion(set, EquipmentSet.HOE_SLOT);
|
||||
}
|
||||
|
||||
public Builder addWoodCriterion(WoodenComplexMaterial mat) {
|
||||
return addInventoryChangedAnyCriterion(
|
||||
"got_" + mat.getBaseName(),
|
||||
mat.getBlock(WoodSlots.LOG),
|
||||
mat.getBlock(WoodSlots.BARK),
|
||||
mat.getBlock(WoodSlots.PLANKS)
|
||||
);
|
||||
}
|
||||
|
||||
public Builder addVisitBiomesCriterion(List<ResourceKey<Biome>> list) {
|
||||
for (ResourceKey<Biome> resourceKey : list) {
|
||||
addCriterion(
|
||||
resourceKey.location().toString(),
|
||||
PlayerTrigger.TriggerInstance.located(LocationPredicate.inBiome(resourceKey))
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder requirements(RequirementsStrategy requirementsStrategy) {
|
||||
builder.requirements(requirementsStrategy);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder requirements(String[][] strings) {
|
||||
builder.requirements(strings);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder printDebugJson() {
|
||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
BCLib.LOGGER.info(gson.toJson(builder.serializeToJson()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResourceLocation build() {
|
||||
AdvancementManager.register(id, this.builder);
|
||||
return this.id;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DisplayBuilder {
|
||||
Builder base;
|
||||
final Display display = new Display();
|
||||
|
||||
DisplayBuilder reset(Builder base) {
|
||||
this.base = base;
|
||||
this.display.reset();
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder background(ResourceLocation value) {
|
||||
display.background = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder icon(ItemLike value) {
|
||||
display.icon = new ItemStack(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder icon(ItemStack value) {
|
||||
display.icon = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder title(Component value) {
|
||||
display.title = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder description(Component value) {
|
||||
display.description = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder showToast() {
|
||||
display.showToast = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder hideToast() {
|
||||
display.showToast = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder hidden() {
|
||||
display.hidden = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder visible() {
|
||||
display.hidden = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder announceToChat() {
|
||||
display.announceChat = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder hideFromChat() {
|
||||
display.announceChat = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder frame(FrameType type) {
|
||||
display.frame = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DisplayBuilder challenge() {
|
||||
return frame(FrameType.CHALLENGE);
|
||||
}
|
||||
|
||||
public DisplayBuilder task() {
|
||||
return frame(FrameType.TASK);
|
||||
}
|
||||
|
||||
public DisplayBuilder goal() {
|
||||
return frame(FrameType.GOAL);
|
||||
}
|
||||
|
||||
public Builder endDisplay() {
|
||||
base.display(display.build());
|
||||
return base;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class BaseDataHandler {
|
||||
private final boolean originatesOnServer;
|
||||
@NotNull
|
||||
private final ResourceLocation identifier;
|
||||
|
||||
protected BaseDataHandler(ResourceLocation identifier, boolean originatesOnServer) {
|
||||
this.originatesOnServer = originatesOnServer;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
final public boolean getOriginatesOnServer() {
|
||||
return originatesOnServer;
|
||||
}
|
||||
|
||||
final public ResourceLocation getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract void receiveFromServer(
|
||||
Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
);
|
||||
|
||||
private ServerPlayer lastMessageSender;
|
||||
|
||||
void receiveFromClient(
|
||||
MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
lastMessageSender = player;
|
||||
}
|
||||
|
||||
final protected boolean reply(BaseDataHandler message, MinecraftServer server) {
|
||||
if (lastMessageSender == null) return false;
|
||||
message.sendToClient(server, lastMessageSender);
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract void sendToClient(MinecraftServer server);
|
||||
|
||||
abstract void sendToClient(MinecraftServer server, ServerPlayer player);
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract void sendToServer(Minecraft client);
|
||||
|
||||
protected boolean isBlocking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BasDataHandler{" + "originatesOnServer=" + originatesOnServer + ", identifier=" + identifier + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a String to a buffer (Convenience Method)
|
||||
*
|
||||
* @param buf The buffer to write to
|
||||
* @param s The String you want to write
|
||||
*/
|
||||
public static void writeString(FriendlyByteBuf buf, String s) {
|
||||
buf.writeByteArray(s.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a string from a buffer (Convenience Method)
|
||||
*
|
||||
* @param buf Thea buffer to read from
|
||||
* @return The received String
|
||||
*/
|
||||
public static String readString(FriendlyByteBuf buf) {
|
||||
byte[] data = buf.readByteArray();
|
||||
return new String(data, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof BaseDataHandler)) return false;
|
||||
BaseDataHandler that = (BaseDataHandler) o;
|
||||
return originatesOnServer == that.originatesOnServer && identifier.equals(that.identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(originatesOnServer, identifier);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange;
|
||||
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
abstract class Connector {
|
||||
protected final DataExchange api;
|
||||
|
||||
Connector(DataExchange api) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
public abstract boolean onClient();
|
||||
|
||||
protected Set<DataHandlerDescriptor> getDescriptors() {
|
||||
return api.getDescriptors();
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
/**
|
||||
* This is an internal class that handles a Clienetside players Connection to a Server
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class ConnectorClientside extends Connector {
|
||||
private Minecraft client;
|
||||
|
||||
ConnectorClientside(DataExchange api) {
|
||||
super(api);
|
||||
this.client = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onClient() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void onPlayInit(ClientPacketListener handler, Minecraft client) {
|
||||
if (this.client != null && this.client != client) {
|
||||
BCLib.LOGGER.warning("Client changed!");
|
||||
}
|
||||
this.client = client;
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
ClientPlayNetworking.registerReceiver(desc.IDENTIFIER, (_client, _handler, _buf, _responseSender) -> {
|
||||
receiveFromServer(desc, _client, _handler, _buf, _responseSender);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayReady(ClientPacketListener handler, PacketSender sender, Minecraft client) {
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
if (desc.sendOnJoin) {
|
||||
BaseDataHandler h = desc.JOIN_INSTANCE.get();
|
||||
if (!h.getOriginatesOnServer()) {
|
||||
h.sendToServer(client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayDisconnect(ClientPacketListener handler, Minecraft client) {
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
ClientPlayNetworking.unregisterReceiver(desc.IDENTIFIER);
|
||||
}
|
||||
}
|
||||
|
||||
void receiveFromServer(
|
||||
DataHandlerDescriptor desc,
|
||||
Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
BaseDataHandler h = desc.INSTANCE.get();
|
||||
h.receiveFromServer(client, handler, buf, responseSender);
|
||||
}
|
||||
|
||||
public void sendToServer(BaseDataHandler h) {
|
||||
if (client == null) {
|
||||
throw new RuntimeException("[internal error] Client not initialized yet!");
|
||||
}
|
||||
h.sendToServer(this.client);
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
||||
/**
|
||||
* This is an internal class that handles a Serverside Connection to a Client-Player
|
||||
*/
|
||||
public class ConnectorServerside extends Connector {
|
||||
private MinecraftServer server;
|
||||
|
||||
ConnectorServerside(DataExchange api) {
|
||||
super(api);
|
||||
server = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onClient() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onPlayInit(ServerGamePacketListenerImpl handler, MinecraftServer server) {
|
||||
if (this.server != null && this.server != server) {
|
||||
BCLib.LOGGER.warning("Server changed!");
|
||||
}
|
||||
this.server = server;
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
ServerPlayNetworking.registerReceiver(
|
||||
handler,
|
||||
desc.IDENTIFIER,
|
||||
(_server, _player, _handler, _buf, _responseSender) -> {
|
||||
receiveFromClient(
|
||||
desc,
|
||||
_server,
|
||||
_player,
|
||||
_handler,
|
||||
_buf,
|
||||
_responseSender
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayReady(ServerGamePacketListenerImpl handler, PacketSender sender, MinecraftServer server) {
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
if (desc.sendOnJoin) {
|
||||
BaseDataHandler h = desc.JOIN_INSTANCE.get();
|
||||
if (h.getOriginatesOnServer()) {
|
||||
h.sendToClient(server, handler.player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onPlayDisconnect(ServerGamePacketListenerImpl handler, MinecraftServer server) {
|
||||
for (DataHandlerDescriptor desc : getDescriptors()) {
|
||||
ServerPlayNetworking.unregisterReceiver(handler, desc.IDENTIFIER);
|
||||
}
|
||||
}
|
||||
|
||||
void receiveFromClient(
|
||||
DataHandlerDescriptor desc,
|
||||
MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
BaseDataHandler h = desc.INSTANCE.get();
|
||||
h.receiveFromClient(server, player, handler, buf, responseSender);
|
||||
}
|
||||
|
||||
public void sendToClient(BaseDataHandler h) {
|
||||
if (server == null) {
|
||||
throw new RuntimeException("[internal error] Server not initialized yet!");
|
||||
}
|
||||
h.sendToClient(this.server);
|
||||
}
|
||||
}
|
|
@ -1,219 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSync;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSyncID;
|
||||
import org.betterx.bclib.config.Config;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.worlds.together.util.ModUtil;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class DataExchangeAPI extends DataExchange {
|
||||
private final static List<String> MODS = Lists.newArrayList();
|
||||
|
||||
/**
|
||||
* You should never need to create a custom instance of this Object.
|
||||
*/
|
||||
public DataExchangeAPI() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected ConnectorClientside clientSupplier(DataExchange api) {
|
||||
return new ConnectorClientside(api);
|
||||
}
|
||||
|
||||
protected ConnectorServerside serverSupplier(DataExchange api) {
|
||||
return new ConnectorServerside(api);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mod to participate in the DataExchange.
|
||||
*
|
||||
* @param modID - {@link String} modID.
|
||||
*/
|
||||
public static void registerMod(String modID) {
|
||||
if (!MODS.contains(modID)) MODS.add(modID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a mod dependency to participate in the DataExchange.
|
||||
*
|
||||
* @param modID - {@link String} modID.
|
||||
*/
|
||||
public static void registerModDependency(String modID) {
|
||||
if (ModUtil.getModInfo(modID, false) != null && !"0.0.0".equals(ModUtil.getModVersion(modID))) {
|
||||
registerMod(modID);
|
||||
} else {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info("Mod Dependency '" + modID + "' not found. This is probably OK.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of all registered Mods.
|
||||
*
|
||||
* @return List of modIDs
|
||||
*/
|
||||
public static List<String> registeredMods() {
|
||||
return MODS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Descriptor for a {@link DataHandler}.
|
||||
*
|
||||
* @param desc The Descriptor you want to add.
|
||||
*/
|
||||
public static void registerDescriptor(DataHandlerDescriptor desc) {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.getDescriptors()
|
||||
.add(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk-Add a Descriptors for your {@link DataHandler}-Objects.
|
||||
*
|
||||
* @param desc The Descriptors you want to add.
|
||||
*/
|
||||
public static void registerDescriptors(List<DataHandlerDescriptor> desc) {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.getDescriptors()
|
||||
.addAll(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the Handler.
|
||||
* <p>
|
||||
* Depending on what the result of {@link DataHandler#getOriginatesOnServer()}, the Data is sent from the server
|
||||
* to the client (if {@code true}) or the other way around.
|
||||
* <p>
|
||||
* The method {@link DataHandler#serializeData(FriendlyByteBuf, boolean)} is called just before the data is sent. You should
|
||||
* use this method to add the Data you need to the communication.
|
||||
*
|
||||
* @param h The Data that you want to send
|
||||
*/
|
||||
public static void send(BaseDataHandler h) {
|
||||
if (h.getOriginatesOnServer()) {
|
||||
DataExchangeAPI.getInstance().server.sendToClient(h);
|
||||
} else {
|
||||
DataExchangeAPI.getInstance().client.sendToServer(h);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param fileName The name of the File
|
||||
*/
|
||||
public static void addAutoSyncFile(String modID, File fileName) {
|
||||
AutoSync.addAutoSyncFileData(modID, fileName, false, SyncFileHash.NEED_TRANSFER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
* <p>
|
||||
* The file is synced of the {@link SyncFileHash} on client and server are not equal. This method will not copy the
|
||||
* configs content from the client to the server.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
|
||||
* Details
|
||||
* @param fileName The name of the File
|
||||
*/
|
||||
public static void addAutoSyncFile(String modID, String uniqueID, File fileName) {
|
||||
AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, false, SyncFileHash.NEED_TRANSFER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
* <p>
|
||||
* The content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* if the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param fileName The name of the File
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
*/
|
||||
public static void addAutoSyncFile(String modID, File fileName, AutoSync.NeedTransferPredicate needTransfer) {
|
||||
AutoSync.addAutoSyncFileData(modID, fileName, true, needTransfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
* <p>
|
||||
* The content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* if the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
|
||||
* Details
|
||||
* @param fileName The name of the File
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
*/
|
||||
public static void addAutoSyncFile(
|
||||
String modID,
|
||||
String uniqueID,
|
||||
File fileName,
|
||||
AutoSync.NeedTransferPredicate needTransfer
|
||||
) {
|
||||
AutoSync.addAutoSyncFileData(modID, uniqueID, fileName, true, needTransfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a function that is called whenever the client receives a file from the server and replaced toe local
|
||||
* file with the new content.
|
||||
* <p>
|
||||
* This callback is usefull if you need to reload the new content before the game is quit.
|
||||
*
|
||||
* @param callback A Function that receives the AutoSyncID as well as the Filename.
|
||||
*/
|
||||
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
|
||||
AutoSync.addOnWriteCallback(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sync-folder for a given Mod.
|
||||
* <p>
|
||||
* BCLib will ensure that the contents of sync-folder on the client is the same as the one on the server.
|
||||
*
|
||||
* @param modID ID of the Mod
|
||||
* @return The path to the sync-folder
|
||||
*/
|
||||
public static File getModSyncFolder(String modID) {
|
||||
File fl = AutoSync.SYNC_FOLDER.localFolder.resolve(modID.replace(".", "-")
|
||||
.replace(":", "-")
|
||||
.replace("\\", "-")
|
||||
.replace("/", "-"))
|
||||
.normalize()
|
||||
.toFile();
|
||||
|
||||
if (!fl.exists()) {
|
||||
fl.mkdirs();
|
||||
}
|
||||
return fl;
|
||||
}
|
||||
|
||||
static {
|
||||
addOnWriteCallback(Config::reloadSyncedConfig);
|
||||
}
|
||||
}
|
|
@ -1,332 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.Chunker;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientPacketListener;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class DataHandler extends BaseDataHandler {
|
||||
public abstract static class WithoutPayload extends DataHandler {
|
||||
protected WithoutPayload(ResourceLocation identifier, boolean originatesOnServer) {
|
||||
super(identifier, originatesOnServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareData(boolean isClient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeData(FriendlyByteBuf buf, boolean isClient) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient) {
|
||||
}
|
||||
}
|
||||
|
||||
protected DataHandler(ResourceLocation identifier, boolean originatesOnServer) {
|
||||
super(identifier, originatesOnServer);
|
||||
}
|
||||
|
||||
protected boolean prepareData(boolean isClient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract protected void serializeData(FriendlyByteBuf buf, boolean isClient);
|
||||
|
||||
abstract protected void deserializeIncomingData(FriendlyByteBuf buf, PacketSender responseSender, boolean isClient);
|
||||
|
||||
abstract protected void runOnGameThread(Minecraft client, MinecraftServer server, boolean isClient);
|
||||
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
void receiveFromServer(
|
||||
Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
deserializeIncomingData(buf, responseSender, true);
|
||||
final Runnable runner = () -> runOnGameThread(client, null, true);
|
||||
|
||||
if (isBlocking()) client.executeBlocking(runner);
|
||||
else client.execute(runner);
|
||||
}
|
||||
|
||||
@Override
|
||||
void receiveFromClient(
|
||||
MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
super.receiveFromClient(server, player, handler, buf, responseSender);
|
||||
|
||||
deserializeIncomingData(buf, responseSender, false);
|
||||
final Runnable runner = () -> runOnGameThread(null, server, false);
|
||||
|
||||
if (isBlocking()) server.executeBlocking(runner);
|
||||
else server.execute(runner);
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendToClient(MinecraftServer server) {
|
||||
if (prepareData(false)) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeData(buf, false);
|
||||
|
||||
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendToClient(MinecraftServer server, ServerPlayer player) {
|
||||
if (prepareData(false)) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeData(buf, false);
|
||||
|
||||
_sendToClient(getIdentifier(), server, List.of(player), buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void _sendToClient(
|
||||
ResourceLocation identifier,
|
||||
MinecraftServer server,
|
||||
Collection<ServerPlayer> players,
|
||||
FriendlyByteBuf buf
|
||||
) {
|
||||
if (buf.readableBytes() > Chunker.MAX_PACKET_SIZE) {
|
||||
final Chunker.PacketChunkSender sender = new Chunker.PacketChunkSender(buf, identifier);
|
||||
sender.sendChunks(players);
|
||||
} else {
|
||||
for (ServerPlayer player : players) {
|
||||
ServerPlayNetworking.send(player, identifier, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
void sendToServer(Minecraft client) {
|
||||
if (prepareData(true)) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeData(buf, true);
|
||||
ClientPlayNetworking.send(getIdentifier(), buf);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Message that always originates on the Client
|
||||
*/
|
||||
public abstract static class FromClient extends BaseDataHandler {
|
||||
public abstract static class WithoutPayload extends FromClient {
|
||||
protected WithoutPayload(ResourceLocation identifier) {
|
||||
super(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareDataOnClient() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnClient(FriendlyByteBuf buf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnServer(
|
||||
FriendlyByteBuf buf,
|
||||
Player player,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
protected FromClient(ResourceLocation identifier) {
|
||||
super(identifier, false);
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected boolean prepareDataOnClient() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract protected void serializeDataOnClient(FriendlyByteBuf buf);
|
||||
|
||||
protected abstract void deserializeIncomingDataOnServer(
|
||||
FriendlyByteBuf buf,
|
||||
Player player,
|
||||
PacketSender responseSender
|
||||
);
|
||||
protected abstract void runOnServerGameThread(MinecraftServer server, Player player);
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
void receiveFromServer(
|
||||
Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
|
||||
}
|
||||
|
||||
@Override
|
||||
void receiveFromClient(
|
||||
MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
super.receiveFromClient(server, player, handler, buf, responseSender);
|
||||
|
||||
deserializeIncomingDataOnServer(buf, player, responseSender);
|
||||
final Runnable runner = () -> runOnServerGameThread(server, player);
|
||||
|
||||
if (isBlocking()) server.executeBlocking(runner);
|
||||
else server.execute(runner);
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendToClient(MinecraftServer server) {
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendToClient(MinecraftServer server, ServerPlayer player) {
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the client!");
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
void sendToServer(Minecraft client) {
|
||||
if (prepareDataOnClient()) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeDataOnClient(buf);
|
||||
ClientPlayNetworking.send(getIdentifier(), buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Message that always originates on the Server
|
||||
*/
|
||||
public abstract static class FromServer extends BaseDataHandler {
|
||||
public abstract static class WithoutPayload extends FromServer {
|
||||
protected WithoutPayload(ResourceLocation identifier) {
|
||||
super(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareDataOnServer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnServer(FriendlyByteBuf buf) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
|
||||
}
|
||||
}
|
||||
|
||||
protected FromServer(ResourceLocation identifier) {
|
||||
super(identifier, true);
|
||||
}
|
||||
|
||||
protected boolean prepareDataOnServer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract protected void serializeDataOnServer(FriendlyByteBuf buf);
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender);
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
abstract protected void runOnClientGameThread(Minecraft client);
|
||||
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
final void receiveFromServer(
|
||||
Minecraft client,
|
||||
ClientPacketListener handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
deserializeIncomingDataOnClient(buf, responseSender);
|
||||
final Runnable runner = () -> runOnClientGameThread(client);
|
||||
|
||||
if (isBlocking()) client.executeBlocking(runner);
|
||||
else client.execute(runner);
|
||||
}
|
||||
|
||||
@Override
|
||||
final void receiveFromClient(
|
||||
MinecraftServer server,
|
||||
ServerPlayer player,
|
||||
ServerGamePacketListenerImpl handler,
|
||||
FriendlyByteBuf buf,
|
||||
PacketSender responseSender
|
||||
) {
|
||||
super.receiveFromClient(server, player, handler, buf, responseSender);
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
|
||||
}
|
||||
|
||||
public void receiveFromMemory(FriendlyByteBuf buf) {
|
||||
receiveFromServer(Minecraft.getInstance(), null, buf, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
final void sendToClient(MinecraftServer server) {
|
||||
if (prepareDataOnServer()) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeDataOnServer(buf);
|
||||
|
||||
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
final void sendToClient(MinecraftServer server, ServerPlayer player) {
|
||||
if (prepareDataOnServer()) {
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
serializeDataOnServer(buf);
|
||||
|
||||
_sendToClient(getIdentifier(), server, List.of(player), buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
final void sendToServer(Minecraft client) {
|
||||
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DataHandlerDescriptor {
|
||||
public DataHandlerDescriptor(@NotNull ResourceLocation identifier, @NotNull Supplier<BaseDataHandler> instancer) {
|
||||
this(identifier, instancer, instancer, false, false);
|
||||
}
|
||||
|
||||
public DataHandlerDescriptor(
|
||||
@NotNull ResourceLocation identifier,
|
||||
@NotNull Supplier<BaseDataHandler> instancer,
|
||||
boolean sendOnJoin,
|
||||
boolean sendBeforeEnter
|
||||
) {
|
||||
this(identifier, instancer, instancer, sendOnJoin, sendBeforeEnter);
|
||||
}
|
||||
|
||||
public DataHandlerDescriptor(
|
||||
@NotNull ResourceLocation identifier,
|
||||
@NotNull Supplier<BaseDataHandler> receiv_instancer,
|
||||
@NotNull Supplier<BaseDataHandler> join_instancer,
|
||||
boolean sendOnJoin,
|
||||
boolean sendBeforeEnter
|
||||
) {
|
||||
this.INSTANCE = receiv_instancer;
|
||||
this.JOIN_INSTANCE = join_instancer;
|
||||
this.IDENTIFIER = identifier;
|
||||
|
||||
this.sendOnJoin = sendOnJoin;
|
||||
this.sendBeforeEnter = sendBeforeEnter;
|
||||
}
|
||||
|
||||
public final boolean sendOnJoin;
|
||||
public final boolean sendBeforeEnter;
|
||||
@NotNull
|
||||
public final ResourceLocation IDENTIFIER;
|
||||
@NotNull
|
||||
public final Supplier<BaseDataHandler> INSTANCE;
|
||||
@NotNull
|
||||
public final Supplier<BaseDataHandler> JOIN_INSTANCE;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o instanceof ResourceLocation) {
|
||||
return o.equals(IDENTIFIER);
|
||||
}
|
||||
if (!(o instanceof DataHandlerDescriptor that)) return false;
|
||||
return IDENTIFIER.equals(that.IDENTIFIER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(IDENTIFIER);
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FileHash {
|
||||
private static final int ERR_DOES_NOT_EXIST = -10;
|
||||
private static final int ERR_IO_ERROR = -20;
|
||||
|
||||
/**
|
||||
* The md5-hash of the file
|
||||
*/
|
||||
@NotNull
|
||||
public final byte[] md5;
|
||||
|
||||
/**
|
||||
* The size (in bytes) of the input.
|
||||
*/
|
||||
public final int size;
|
||||
|
||||
/**
|
||||
* a value that is directly calculated from defined byte positions.
|
||||
*/
|
||||
public final int value;
|
||||
|
||||
FileHash(byte[] md5, int size, int value) {
|
||||
Objects.nonNull(md5);
|
||||
|
||||
this.md5 = md5;
|
||||
this.size = size;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static FileHash createForEmpty(int errCode) {
|
||||
return new FileHash(new byte[0], 0, errCode);
|
||||
}
|
||||
|
||||
public boolean noFile() {
|
||||
return md5.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the Object to a buffer
|
||||
*
|
||||
* @param buf The buffer to write to
|
||||
*/
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
buf.writeInt(size);
|
||||
buf.writeInt(value);
|
||||
buf.writeByteArray(md5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Buffer to a new {@link SyncFileHash}-Object
|
||||
*
|
||||
* @param buf Thea buffer to read from
|
||||
* @return The received String
|
||||
*/
|
||||
public static FileHash deserialize(FriendlyByteBuf buf) {
|
||||
final int size = buf.readInt();
|
||||
final int value = buf.readInt();
|
||||
final byte[] md5 = buf.readByteArray();
|
||||
|
||||
return new FileHash(md5, size, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the md5-hash to a human readable string
|
||||
*
|
||||
* @return The converted String
|
||||
*/
|
||||
public String getMd5String() {
|
||||
return toHexString(md5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a byte-array to a hex-string representation
|
||||
*
|
||||
* @param bytes The source array
|
||||
* @return The resulting string, or an empty String if the input was {@code null}
|
||||
*/
|
||||
public static String toHexString(byte[] bytes) {
|
||||
if (bytes == null) return "";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link FileHash}.
|
||||
*
|
||||
* @param file The input file
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static FileHash create(File file) {
|
||||
if (!file.exists()) return createForEmpty(ERR_DOES_NOT_EXIST);
|
||||
final Path path = file.toPath();
|
||||
|
||||
int size = 0;
|
||||
byte[] md5 = new byte[0];
|
||||
int value = 0;
|
||||
|
||||
try {
|
||||
byte[] data = Files.readAllBytes(path);
|
||||
|
||||
size = data.length;
|
||||
|
||||
value = size > 0 ? (data[size / 3] | (data[size / 2] << 8) | (data[size / 5] << 16)) : -1;
|
||||
if (size > 20) value |= data[20] << 24;
|
||||
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(data);
|
||||
md5 = md.digest();
|
||||
|
||||
return new FileHash(md5, size, value);
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.error("Failed to read file: " + file);
|
||||
return null;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
BCLib.LOGGER.error("Unable to build hash for file: " + file);
|
||||
}
|
||||
|
||||
return createForEmpty(ERR_IO_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof FileHash)) return false;
|
||||
FileHash fileHash = (FileHash) o;
|
||||
return size == fileHash.size && value == fileHash.value && Arrays.equals(md5, fileHash.md5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(size, value);
|
||||
result = 31 * result + Arrays.hashCode(md5);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%08x", size) + "-" + String.format("%08x", value) + "-" + getMd5String();
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange;
|
||||
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSync;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSyncID;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Calculates a hash based on the contents of a File.
|
||||
* <p>
|
||||
* A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions
|
||||
* <p>
|
||||
* You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical.
|
||||
*/
|
||||
public class SyncFileHash extends AutoSyncID {
|
||||
public final FileHash hash;
|
||||
|
||||
SyncFileHash(String modID, File file, byte[] md5, int size, int value) {
|
||||
this(modID, file.getName(), md5, size, value);
|
||||
}
|
||||
|
||||
SyncFileHash(String modID, String uniqueID, byte[] md5, int size, int value) {
|
||||
this(modID, uniqueID, new FileHash(md5, size, value));
|
||||
}
|
||||
|
||||
SyncFileHash(String modID, File file, FileHash hash) {
|
||||
this(modID, file.getName(), hash);
|
||||
}
|
||||
|
||||
SyncFileHash(String modID, String uniqueID, FileHash hash) {
|
||||
super(modID, uniqueID);
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
|
||||
final static AutoSync.NeedTransferPredicate NEED_TRANSFER = (clientHash, serverHash, content) -> !clientHash.equals(
|
||||
serverHash);
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ": " + hash.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof SyncFileHash)) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
SyncFileHash that = (SyncFileHash) o;
|
||||
return hash.equals(that.hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the Object to a buffer
|
||||
*
|
||||
* @param buf The buffer to write to
|
||||
*/
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
hash.serialize(buf);
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Buffer to a new {@link SyncFileHash}-Object
|
||||
*
|
||||
* @param buf Thea buffer to read from
|
||||
* @return The received String
|
||||
*/
|
||||
public static SyncFileHash deserialize(FriendlyByteBuf buf) {
|
||||
final FileHash hash = FileHash.deserialize(buf);
|
||||
final String modID = DataHandler.readString(buf);
|
||||
final String uniqueID = DataHandler.readString(buf);
|
||||
|
||||
return new SyncFileHash(modID, uniqueID, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SyncFileHash}.
|
||||
* <p>
|
||||
* Will call {@link #create(String, File, String)} using the name of the File as {@code uniqueID}.
|
||||
*
|
||||
* @param modID ID of the calling Mod
|
||||
* @param file The input file
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static SyncFileHash create(String modID, File file) {
|
||||
return create(modID, file, file.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link SyncFileHash}.
|
||||
*
|
||||
* @param modID ID of the calling Mod
|
||||
* @param file The input file
|
||||
* @param uniqueID The unique ID that is used for this File (see {@link SyncFileHash#uniqueID} for Details.
|
||||
* @return A new Instance. You can compare instances using {@link #equals(Object)} to determine if two files are
|
||||
* identical. Will return {@code null} when an error occurs or the File does not exist
|
||||
*/
|
||||
public static SyncFileHash create(String modID, File file, String uniqueID) {
|
||||
return new SyncFileHash(modID, uniqueID, FileHash.create(file));
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler;
|
||||
|
||||
import org.betterx.bclib.api.v2.dataexchange.*;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
abstract public class DataExchange {
|
||||
|
||||
|
||||
private static DataExchangeAPI instance;
|
||||
|
||||
protected static DataExchangeAPI getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new DataExchangeAPI();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
protected ConnectorServerside server;
|
||||
protected ConnectorClientside client;
|
||||
protected final Set<DataHandlerDescriptor> descriptors;
|
||||
|
||||
|
||||
private final boolean didLoadSyncFolder = false;
|
||||
|
||||
abstract protected ConnectorClientside clientSupplier(DataExchange api);
|
||||
|
||||
abstract protected ConnectorServerside serverSupplier(DataExchange api);
|
||||
|
||||
protected DataExchange() {
|
||||
descriptors = new HashSet<>();
|
||||
}
|
||||
|
||||
public Set<DataHandlerDescriptor> getDescriptors() {
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public static DataHandlerDescriptor getDescriptor(ResourceLocation identifier) {
|
||||
return getInstance().descriptors.stream().filter(d -> d.equals(identifier)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void initClientside() {
|
||||
if (client != null) return;
|
||||
client = clientSupplier(this);
|
||||
|
||||
ClientPlayConnectionEvents.INIT.register(client::onPlayInit);
|
||||
ClientPlayConnectionEvents.JOIN.register(client::onPlayReady);
|
||||
ClientPlayConnectionEvents.DISCONNECT.register(client::onPlayDisconnect);
|
||||
}
|
||||
|
||||
protected void initServerSide() {
|
||||
if (server != null) return;
|
||||
server = serverSupplier(this);
|
||||
|
||||
ServerPlayConnectionEvents.INIT.register(server::onPlayInit);
|
||||
ServerPlayConnectionEvents.JOIN.register(server::onPlayReady);
|
||||
ServerPlayConnectionEvents.DISCONNECT.register(server::onPlayDisconnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all datastructures that need to exist in the client component.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void prepareClientside() {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.initClientside();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all datastructures that need to exist in the server component.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. You can register {@link DataHandler}-Objects before this Method is called
|
||||
*/
|
||||
public static void prepareServerside() {
|
||||
DataExchange api = DataExchange.getInstance();
|
||||
api.initServerSide();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Automatically called before the player enters the world.
|
||||
* <p>
|
||||
* This is automatically called by BCLib. It will send all {@link DataHandler}-Objects that have {@link DataHandlerDescriptor#sendBeforeEnter} set to*
|
||||
* {@code true},
|
||||
*/
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void sendOnEnter() {
|
||||
getInstance().descriptors.forEach((desc) -> {
|
||||
if (desc.sendBeforeEnter) {
|
||||
BaseDataHandler h = desc.JOIN_INSTANCE.get();
|
||||
if (!h.getOriginatesOnServer()) {
|
||||
getInstance().client.sendToServer(h);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,262 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.v2.dataexchange.SyncFileHash;
|
||||
import org.betterx.bclib.util.Pair;
|
||||
import org.betterx.bclib.util.Triple;
|
||||
import org.betterx.worlds.together.util.ModUtil;
|
||||
import org.betterx.worlds.together.util.ModUtil.ModInfo;
|
||||
import org.betterx.worlds.together.util.PathUtil;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class AutoFileSyncEntry extends AutoSyncID {
|
||||
static class ForDirectFileRequest extends AutoFileSyncEntry {
|
||||
final File relFile;
|
||||
|
||||
ForDirectFileRequest(String syncID, File relFile, File absFile) {
|
||||
super(AutoSyncID.ForDirectFileRequest.MOD_ID, syncID, absFile, false, (a, b, c) -> false);
|
||||
this.relFile = relFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int serializeContent(FriendlyByteBuf buf) {
|
||||
int res = super.serializeContent(buf);
|
||||
DataHandler.writeString(buf, relFile.toString());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static AutoFileSyncEntry.ForDirectFileRequest finishDeserializeContent(String syncID, FriendlyByteBuf buf) {
|
||||
final String relFile = DataHandler.readString(buf);
|
||||
SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(syncID);
|
||||
if (desc != null) {
|
||||
//ensures that the file is not above the base-folder
|
||||
if (desc.acceptChildElements(desc.mapAbsolute(relFile))) {
|
||||
return new AutoFileSyncEntry.ForDirectFileRequest(
|
||||
syncID,
|
||||
new File(relFile),
|
||||
desc.localFolder.resolve(relFile)
|
||||
.normalize()
|
||||
.toFile()
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return uniqueID + " - " + relFile;
|
||||
}
|
||||
}
|
||||
|
||||
static class ForModFileRequest extends AutoFileSyncEntry {
|
||||
public static File getLocalPathForID(String modID, boolean matchLocalVersion) {
|
||||
ModInfo mi = ModUtil.getModInfo(modID, matchLocalVersion);
|
||||
if (mi != null) {
|
||||
return mi.jarPath.toFile();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public final String version;
|
||||
|
||||
ForModFileRequest(String modID, boolean matchLocalVersion, String version) {
|
||||
super(
|
||||
modID,
|
||||
AutoSyncID.ForModFileRequest.UNIQUE_ID,
|
||||
getLocalPathForID(modID, matchLocalVersion),
|
||||
false,
|
||||
(a, b, c) -> false
|
||||
);
|
||||
if (this.fileName == null && matchLocalVersion) {
|
||||
BCLib.LOGGER.error("Unknown mod '" + modID + "'.");
|
||||
}
|
||||
if (version == null)
|
||||
this.version = ModUtil.getModVersion(modID);
|
||||
else
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int serializeContent(FriendlyByteBuf buf) {
|
||||
final int res = super.serializeContent(buf);
|
||||
buf.writeInt(ModUtil.convertModVersion(version));
|
||||
return res;
|
||||
}
|
||||
|
||||
static AutoFileSyncEntry.ForModFileRequest finishDeserializeContent(String modID, FriendlyByteBuf buf) {
|
||||
final String version = ModUtil.convertModVersion(buf.readInt());
|
||||
return new AutoFileSyncEntry.ForModFileRequest(modID, false, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Mod " + modID + " (v" + version + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public final AutoSync.NeedTransferPredicate needTransfer;
|
||||
public final File fileName;
|
||||
public final boolean requestContent;
|
||||
private SyncFileHash hash;
|
||||
|
||||
AutoFileSyncEntry(
|
||||
String modID,
|
||||
File fileName,
|
||||
boolean requestContent,
|
||||
AutoSync.NeedTransferPredicate needTransfer
|
||||
) {
|
||||
this(modID, fileName.getName(), fileName, requestContent, needTransfer);
|
||||
}
|
||||
|
||||
AutoFileSyncEntry(
|
||||
String modID,
|
||||
String uniqueID,
|
||||
File fileName,
|
||||
boolean requestContent,
|
||||
AutoSync.NeedTransferPredicate needTransfer
|
||||
) {
|
||||
super(modID, uniqueID);
|
||||
this.needTransfer = needTransfer;
|
||||
this.fileName = fileName;
|
||||
this.requestContent = requestContent;
|
||||
}
|
||||
|
||||
|
||||
public SyncFileHash getFileHash() {
|
||||
if (hash == null) {
|
||||
hash = SyncFileHash.create(modID, fileName, uniqueID);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
if (!fileName.exists()) return new byte[0];
|
||||
final Path path = fileName.toPath();
|
||||
|
||||
try {
|
||||
return Files.readAllBytes(path);
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public int serializeContent(FriendlyByteBuf buf) {
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
return serializeFileContent(buf);
|
||||
}
|
||||
|
||||
public static Triple<AutoFileSyncEntry, byte[], AutoSyncID> deserializeContent(FriendlyByteBuf buf) {
|
||||
final String modID = DataHandler.readString(buf);
|
||||
final String uniqueID = DataHandler.readString(buf);
|
||||
byte[] data = deserializeFileContent(buf);
|
||||
|
||||
AutoFileSyncEntry entry;
|
||||
if (AutoSyncID.ForDirectFileRequest.MOD_ID.equals(modID)) {
|
||||
entry = AutoFileSyncEntry.ForDirectFileRequest.finishDeserializeContent(uniqueID, buf);
|
||||
} else if (AutoSyncID.ForModFileRequest.UNIQUE_ID.equals(uniqueID)) {
|
||||
entry = AutoFileSyncEntry.ForModFileRequest.finishDeserializeContent(modID, buf);
|
||||
} else {
|
||||
entry = AutoFileSyncEntry.findMatching(modID, uniqueID);
|
||||
}
|
||||
return new Triple<>(entry, data, new AutoSyncID(modID, uniqueID));
|
||||
}
|
||||
|
||||
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
getFileHash().serialize(buf);
|
||||
buf.writeBoolean(requestContent);
|
||||
|
||||
if (requestContent) {
|
||||
serializeFileContent(buf);
|
||||
}
|
||||
}
|
||||
|
||||
public static AutoSync.AutoSyncTriple deserializeAndMatch(FriendlyByteBuf buf) {
|
||||
Pair<SyncFileHash, byte[]> e = deserialize(buf);
|
||||
AutoFileSyncEntry match = findMatching(e.first);
|
||||
return new AutoSync.AutoSyncTriple(e.first, e.second, match);
|
||||
}
|
||||
|
||||
public static Pair<SyncFileHash, byte[]> deserialize(FriendlyByteBuf buf) {
|
||||
SyncFileHash hash = SyncFileHash.deserialize(buf);
|
||||
boolean withContent = buf.readBoolean();
|
||||
byte[] data = null;
|
||||
if (withContent) {
|
||||
data = deserializeFileContent(buf);
|
||||
}
|
||||
|
||||
return new Pair(hash, data);
|
||||
}
|
||||
|
||||
private int serializeFileContent(FriendlyByteBuf buf) {
|
||||
if (!org.betterx.worlds.together.util.PathUtil.isChildOf(
|
||||
org.betterx.worlds.together.util.PathUtil.GAME_FOLDER,
|
||||
fileName.toPath()
|
||||
)) {
|
||||
BCLib.LOGGER.error(fileName + " is not within game folder " + PathUtil.GAME_FOLDER + ". Pretending it does not exist.");
|
||||
buf.writeInt(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte[] content = getContent();
|
||||
buf.writeInt(content.length);
|
||||
buf.writeByteArray(content);
|
||||
return content.length;
|
||||
}
|
||||
|
||||
private static byte[] deserializeFileContent(FriendlyByteBuf buf) {
|
||||
byte[] data;
|
||||
int size = buf.readInt();
|
||||
data = buf.readByteArray(size);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
public static AutoFileSyncEntry findMatching(SyncFileHash hash) {
|
||||
return findMatching(hash.modID, hash.uniqueID);
|
||||
}
|
||||
|
||||
public static AutoFileSyncEntry findMatching(AutoSyncID aid) {
|
||||
if (aid instanceof AutoSyncID.ForDirectFileRequest) {
|
||||
AutoSyncID.ForDirectFileRequest freq = (AutoSyncID.ForDirectFileRequest) aid;
|
||||
SyncFolderDescriptor desc = AutoSync.getSyncFolderDescriptor(freq.uniqueID);
|
||||
if (desc != null) {
|
||||
SyncFolderDescriptor.SubFile subFile = desc.getLocalSubFile(freq.relFile.toString());
|
||||
if (subFile != null) {
|
||||
final File absPath = desc.localFolder.resolve(subFile.relPath)
|
||||
.normalize()
|
||||
.toFile();
|
||||
return new AutoFileSyncEntry.ForDirectFileRequest(
|
||||
freq.uniqueID,
|
||||
new File(subFile.relPath),
|
||||
absPath
|
||||
);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} else if (aid instanceof AutoSyncID.ForModFileRequest) {
|
||||
AutoSyncID.ForModFileRequest mreq = (AutoSyncID.ForModFileRequest) aid;
|
||||
return new AutoFileSyncEntry.ForModFileRequest(mreq.modID, true, null);
|
||||
}
|
||||
return findMatching(aid.modID, aid.uniqueID);
|
||||
}
|
||||
|
||||
public static AutoFileSyncEntry findMatching(String modID, String uniqueID) {
|
||||
return AutoSync.getAutoSyncFiles()
|
||||
.stream()
|
||||
.filter(asf -> asf.modID.equals(modID) && asf.uniqueID.equals(uniqueID))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
|
||||
import org.betterx.bclib.api.v2.dataexchange.SyncFileHash;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.config.ServerConfig;
|
||||
import org.betterx.worlds.together.util.PathUtil;
|
||||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class AutoSync {
|
||||
public static final String SYNC_CATEGORY = "auto_sync";
|
||||
public final static SyncFolderDescriptor SYNC_FOLDER = new SyncFolderDescriptor(
|
||||
"BCLIB-SYNC",
|
||||
FabricLoader.getInstance()
|
||||
.getGameDir()
|
||||
.resolve("bclib-sync")
|
||||
.normalize()
|
||||
.toAbsolutePath(),
|
||||
true
|
||||
);
|
||||
|
||||
@FunctionalInterface
|
||||
public interface NeedTransferPredicate {
|
||||
boolean test(SyncFileHash clientHash, SyncFileHash serverHash, FileContentWrapper content);
|
||||
}
|
||||
|
||||
final static class AutoSyncTriple {
|
||||
public final SyncFileHash serverHash;
|
||||
public final byte[] serverContent;
|
||||
public final AutoFileSyncEntry localMatch;
|
||||
|
||||
public AutoSyncTriple(SyncFileHash serverHash, byte[] serverContent, AutoFileSyncEntry localMatch) {
|
||||
this.serverHash = serverHash;
|
||||
this.serverContent = serverContent;
|
||||
this.localMatch = localMatch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return serverHash.modID + "." + serverHash.uniqueID;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ##### File Syncing
|
||||
protected final static List<BiConsumer<AutoSyncID, File>> onWriteCallbacks = new ArrayList<>(2);
|
||||
|
||||
/**
|
||||
* Register a function that is called whenever the client receives a file from the server and replaced toe local
|
||||
* file with the new content.
|
||||
* <p>
|
||||
* This callback is usefull if you need to reload the new content before the game is quit.
|
||||
*
|
||||
* @param callback A Function that receives the AutoSyncID as well as the Filename.
|
||||
*/
|
||||
public static void addOnWriteCallback(BiConsumer<AutoSyncID, File> callback) {
|
||||
onWriteCallbacks.add(callback);
|
||||
}
|
||||
|
||||
private static final List<AutoFileSyncEntry> autoSyncFiles = new ArrayList<>(4);
|
||||
|
||||
public static List<AutoFileSyncEntry> getAutoSyncFiles() {
|
||||
return autoSyncFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
* @param fileName The name of the File
|
||||
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* If the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*/
|
||||
public static void addAutoSyncFileData(
|
||||
String modID,
|
||||
File fileName,
|
||||
boolean requestContent,
|
||||
NeedTransferPredicate needTransfer
|
||||
) {
|
||||
if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
|
||||
BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
|
||||
} else {
|
||||
autoSyncFiles.add(new AutoFileSyncEntry(modID, fileName, requestContent, needTransfer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a File for automatic client syncing.
|
||||
*
|
||||
* @param modID The ID of the calling Mod
|
||||
* @param uniqueID A unique Identifier for the File. (see {@link SyncFileHash#uniqueID} for
|
||||
* Details
|
||||
* @param needTransfer If the predicate returns true, the file needs to get copied to the server.
|
||||
* @param fileName The name of the File
|
||||
* @param requestContent When {@code true} the content of the file is requested for comparison. This will copy the
|
||||
* entire file from the client to the server.
|
||||
* <p>
|
||||
* You should only use this option, if you need to compare parts of the file in order to decide
|
||||
* If the File needs to be copied. Normally using the {@link SyncFileHash}
|
||||
* for comparison is sufficient.
|
||||
*/
|
||||
public static void addAutoSyncFileData(
|
||||
String modID,
|
||||
String uniqueID,
|
||||
File fileName,
|
||||
boolean requestContent,
|
||||
NeedTransferPredicate needTransfer
|
||||
) {
|
||||
if (!PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
|
||||
BCLib.LOGGER.error(fileName + " is outside of Game Folder " + PathUtil.GAME_FOLDER);
|
||||
} else {
|
||||
autoSyncFiles.add(new AutoFileSyncEntry(modID, uniqueID, fileName, requestContent, needTransfer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when {@code SendFiles} received a File on the Client and wrote it to the FileSystem.
|
||||
* <p>
|
||||
* This is the place where reload Code should go.
|
||||
*
|
||||
* @param aid The ID of the received File
|
||||
* @param file The location of the FIle on the client
|
||||
*/
|
||||
static void didReceiveFile(AutoSyncID aid, File file) {
|
||||
onWriteCallbacks.forEach(fkt -> fkt.accept(aid, file));
|
||||
}
|
||||
|
||||
|
||||
// ##### Folder Syncing
|
||||
static final List<SyncFolderDescriptor> syncFolderDescriptions = Arrays.asList(SYNC_FOLDER);
|
||||
|
||||
private List<String> syncFolderContent;
|
||||
|
||||
protected List<String> getSyncFolderContent() {
|
||||
if (syncFolderContent == null) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
return syncFolderContent;
|
||||
}
|
||||
|
||||
private static boolean didRegisterAdditionalMods = false;
|
||||
|
||||
//we call this from HelloClient on the Server to prepare transfer
|
||||
protected static void loadSyncFolder() {
|
||||
if (Configs.SERVER_CONFIG.isOfferingFiles()) {
|
||||
syncFolderDescriptions.forEach(desc -> desc.loadCache());
|
||||
}
|
||||
|
||||
if (!didRegisterAdditionalMods && Configs.SERVER_CONFIG.isOfferingMods()) {
|
||||
didRegisterAdditionalMods = true;
|
||||
List<String> modIDs = Configs.SERVER_CONFIG.get(ServerConfig.ADDITIONAL_MODS);
|
||||
if (modIDs != null) {
|
||||
modIDs.stream().forEach(modID -> DataExchangeAPI.registerModDependency(modID));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static SyncFolderDescriptor getSyncFolderDescriptor(String folderID) {
|
||||
return syncFolderDescriptions.stream()
|
||||
.filter(d -> d.equals(folderID))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
protected static Path localBasePathForFolderID(String folderID) {
|
||||
final SyncFolderDescriptor desc = getSyncFolderDescriptor(folderID);
|
||||
if (desc != null) {
|
||||
return desc.localFolder;
|
||||
} else {
|
||||
BCLib.LOGGER.warning("Unknown Sync-Folder ID '" + folderID + "'");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void registerSyncFolder(String folderID, Path localBaseFolder, boolean removeAdditionalFiles) {
|
||||
localBaseFolder = localBaseFolder.normalize();
|
||||
if (PathUtil.isChildOf(PathUtil.GAME_FOLDER, localBaseFolder)) {
|
||||
final SyncFolderDescriptor desc = new SyncFolderDescriptor(
|
||||
folderID,
|
||||
localBaseFolder,
|
||||
removeAdditionalFiles
|
||||
);
|
||||
if (syncFolderDescriptions.contains(desc)) {
|
||||
BCLib.LOGGER.warning("Tried to override Folder Sync '" + folderID + "' again.");
|
||||
} else {
|
||||
syncFolderDescriptions.add(desc);
|
||||
}
|
||||
} else {
|
||||
BCLib.LOGGER.error(localBaseFolder + " (from " + folderID + ") is outside the game directory " + PathUtil.GAME_FOLDER + ". Sync is not allowed.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.config.Config;
|
||||
import org.betterx.worlds.together.util.ModUtil;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class AutoSyncID {
|
||||
static class WithContentOverride extends AutoSyncID {
|
||||
final FileContentWrapper contentWrapper;
|
||||
final File localFile;
|
||||
|
||||
WithContentOverride(String modID, String uniqueID, FileContentWrapper contentWrapper, File localFile) {
|
||||
super(modID, uniqueID);
|
||||
this.contentWrapper = contentWrapper;
|
||||
this.localFile = localFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " (Content override)";
|
||||
}
|
||||
}
|
||||
|
||||
static class ForDirectFileRequest extends AutoSyncID {
|
||||
public final static String MOD_ID = "bclib::FILE";
|
||||
final File relFile;
|
||||
|
||||
ForDirectFileRequest(String syncID, File relFile) {
|
||||
super(ForDirectFileRequest.MOD_ID, syncID);
|
||||
this.relFile = relFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
void serializeData(FriendlyByteBuf buf) {
|
||||
super.serializeData(buf);
|
||||
DataHandler.writeString(buf, relFile.toString());
|
||||
}
|
||||
|
||||
static ForDirectFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf) {
|
||||
final File fl = new File(DataHandler.readString(buf));
|
||||
return new ForDirectFileRequest(uniqueID, fl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.uniqueID + " (" + this.relFile + ")";
|
||||
}
|
||||
}
|
||||
|
||||
static class ForModFileRequest extends AutoSyncID {
|
||||
public final static String UNIQUE_ID = "bclib::MOD";
|
||||
private final String version;
|
||||
|
||||
ForModFileRequest(String modID, String version) {
|
||||
super(modID, ForModFileRequest.UNIQUE_ID);
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
void serializeData(FriendlyByteBuf buf) {
|
||||
super.serializeData(buf);
|
||||
buf.writeInt(ModUtil.convertModVersion(version));
|
||||
}
|
||||
|
||||
static ForModFileRequest finishDeserialize(String modID, String uniqueID, FriendlyByteBuf buf) {
|
||||
final String version = ModUtil.convertModVersion(buf.readInt());
|
||||
return new ForModFileRequest(modID, version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.modID + " (v" + this.version + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Unique ID for the referenced File.
|
||||
* <p>
|
||||
* Files with the same {@link #modID} need to have a unique IDs. Normally the filename from FileHash(String, File, byte[], int, int)
|
||||
* is used to generated that ID, but you can directly specify one using FileHash(String, String, byte[], int, int).
|
||||
*/
|
||||
@NotNull
|
||||
public final String uniqueID;
|
||||
|
||||
/**
|
||||
* The ID of the Mod that is registering the File
|
||||
*/
|
||||
@NotNull
|
||||
public final String modID;
|
||||
|
||||
public AutoSyncID(String modID, String uniqueID) {
|
||||
Objects.nonNull(modID);
|
||||
Objects.nonNull(uniqueID);
|
||||
|
||||
this.modID = modID;
|
||||
this.uniqueID = uniqueID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return modID + "." + uniqueID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof AutoSyncID)) return false;
|
||||
AutoSyncID that = (AutoSyncID) o;
|
||||
return uniqueID.equals(that.uniqueID) && modID.equals(that.modID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(uniqueID, modID);
|
||||
}
|
||||
|
||||
void serializeData(FriendlyByteBuf buf) {
|
||||
DataHandler.writeString(buf, modID);
|
||||
DataHandler.writeString(buf, uniqueID);
|
||||
}
|
||||
|
||||
static AutoSyncID deserializeData(FriendlyByteBuf buf) {
|
||||
String modID = DataHandler.readString(buf);
|
||||
String uID = DataHandler.readString(buf);
|
||||
|
||||
if (ForDirectFileRequest.MOD_ID.equals(modID)) {
|
||||
return ForDirectFileRequest.finishDeserialize(modID, uID, buf);
|
||||
} else if (ForModFileRequest.UNIQUE_ID.equals(uID)) {
|
||||
return ForModFileRequest.finishDeserialize(modID, uID, buf);
|
||||
} else {
|
||||
return new AutoSyncID(modID, uID);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConfigFile() {
|
||||
return this.uniqueID.startsWith(Config.CONFIG_SYNC_PREFIX);
|
||||
}
|
||||
}
|
|
@ -1,280 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.BaseDataHandler;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.util.ProgressListener;
|
||||
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||
|
||||
import java.util.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Used to seperate large data transfers into multiple smaller messages.
|
||||
* <p>
|
||||
* {@link DataHandler} will automatically convert larger messages into Chunks on the Server
|
||||
* and assemble the original message from those chunks on the client.
|
||||
*/
|
||||
public class Chunker extends DataHandler.FromServer {
|
||||
|
||||
/**
|
||||
* Responsible for assembling the original ByteBuffer created by {@link PacketChunkSender} on the
|
||||
* receiving end. Automatically created from the header {@link Chunker}-Message (where the serialNo==-1)
|
||||
*/
|
||||
static class PacketChunkReceiver {
|
||||
@NotNull
|
||||
public final UUID uuid;
|
||||
public final int chunkCount;
|
||||
@NotNull
|
||||
private final FriendlyByteBuf networkedBuf;
|
||||
@Nullable
|
||||
private final DataHandlerDescriptor descriptor;
|
||||
|
||||
private static final List<PacketChunkReceiver> active = new ArrayList<>(1);
|
||||
|
||||
private static PacketChunkReceiver newReceiver(@NotNull UUID uuid, int chunkCount, ResourceLocation origin) {
|
||||
DataHandlerDescriptor desc = DataExchange.getDescriptor(origin);
|
||||
final PacketChunkReceiver r = new PacketChunkReceiver(uuid, chunkCount, desc);
|
||||
active.add(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
private static PacketChunkReceiver getOrCreate(@NotNull UUID uuid, int chunkCount, ResourceLocation origin) {
|
||||
return active.stream()
|
||||
.filter(r -> r.uuid.equals(uuid))
|
||||
.findFirst()
|
||||
.orElse(newReceiver(uuid, chunkCount, origin));
|
||||
}
|
||||
|
||||
public static PacketChunkReceiver get(@NotNull UUID uuid) {
|
||||
return active.stream().filter(r -> r.uuid.equals(uuid)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private PacketChunkReceiver(@NotNull UUID uuid, int chunkCount, @Nullable DataHandlerDescriptor descriptor) {
|
||||
this.uuid = uuid;
|
||||
this.chunkCount = chunkCount;
|
||||
networkedBuf = PacketByteBufs.create();
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof PacketChunkReceiver)) return false;
|
||||
PacketChunkReceiver that = (PacketChunkReceiver) o;
|
||||
return uuid.equals(that.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(uuid);
|
||||
}
|
||||
|
||||
public boolean testFinished() {
|
||||
ProgressListener listener = ChunkerProgress.getProgressListener();
|
||||
if (listener != null) {
|
||||
listener.progressStagePercentage((100 * receivedCount) / chunkCount);
|
||||
}
|
||||
if (incomingBuffer == null) {
|
||||
return true;
|
||||
}
|
||||
if (lastReadSerial >= chunkCount - 1) {
|
||||
onFinish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addBuffer(FriendlyByteBuf input) {
|
||||
final int size = input.readableBytes();
|
||||
final int cap = networkedBuf.capacity() - networkedBuf.writerIndex();
|
||||
|
||||
if (cap < size) {
|
||||
networkedBuf.capacity(networkedBuf.writerIndex() + size);
|
||||
}
|
||||
input.readBytes(networkedBuf, size);
|
||||
input.clear();
|
||||
}
|
||||
|
||||
protected void onFinish() {
|
||||
incomingBuffer.clear();
|
||||
incomingBuffer = null;
|
||||
|
||||
final BaseDataHandler baseHandler = descriptor.INSTANCE.get();
|
||||
if (baseHandler instanceof DataHandler.FromServer handler) {
|
||||
handler.receiveFromMemory(networkedBuf);
|
||||
}
|
||||
}
|
||||
|
||||
Map<Integer, FriendlyByteBuf> incomingBuffer = new HashMap<>();
|
||||
int lastReadSerial = -1;
|
||||
int receivedCount = 0;
|
||||
|
||||
public void processReceived(FriendlyByteBuf buf, int serialNo, int size) {
|
||||
receivedCount++;
|
||||
|
||||
if (lastReadSerial == serialNo - 1) {
|
||||
addBuffer(buf);
|
||||
lastReadSerial = serialNo;
|
||||
} else {
|
||||
//not sure if order is guaranteed by the underlying system!
|
||||
boolean haveAll = true;
|
||||
for (int nr = lastReadSerial + 1; nr < serialNo - 1; nr++) {
|
||||
if (incomingBuffer.get(nr) == null) {
|
||||
haveAll = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (haveAll) {
|
||||
for (int nr = lastReadSerial + 1; nr < serialNo - 1; nr++) {
|
||||
addBuffer(incomingBuffer.get(nr));
|
||||
incomingBuffer.put(nr, null);
|
||||
}
|
||||
addBuffer(buf);
|
||||
lastReadSerial = serialNo;
|
||||
} else {
|
||||
incomingBuffer.put(serialNo, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for splitting an outgoing ByteBuffer into several smaller Chunks and
|
||||
* send them as seperate messages to the {@link Chunker}-Channel
|
||||
*/
|
||||
public static class PacketChunkSender {
|
||||
private final FriendlyByteBuf networkedBuf;
|
||||
public final UUID uuid;
|
||||
public final int chunkCount;
|
||||
public final int size;
|
||||
public final ResourceLocation origin;
|
||||
|
||||
public PacketChunkSender(FriendlyByteBuf buf, ResourceLocation origin) {
|
||||
networkedBuf = buf;
|
||||
|
||||
size = buf.readableBytes();
|
||||
chunkCount = (int) Math.ceil((double) size / MAX_PAYLOAD_SIZE);
|
||||
uuid = UUID.randomUUID();
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
public void sendChunks(Collection<ServerPlayer> players) {
|
||||
BCLib.LOGGER.info("Sending Request in " + chunkCount + " Packet-Chunks");
|
||||
for (int i = -1; i < chunkCount; i++) {
|
||||
Chunker c = new Chunker(i, uuid, networkedBuf, chunkCount, origin);
|
||||
FriendlyByteBuf buf = PacketByteBufs.create();
|
||||
c.serializeDataOnServer(buf);
|
||||
|
||||
for (ServerPlayer player : players) {
|
||||
ServerPlayNetworking.send(player, DESCRIPTOR.IDENTIFIER, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//header = version + UUID + serialNo + size, see serializeDataOnServer
|
||||
private static final int HEADER_SIZE = 1 + 16 + 4 + 4;
|
||||
|
||||
public static final int MAX_PACKET_SIZE = 1024 * 1024;
|
||||
private static final int MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
|
||||
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
|
||||
new ResourceLocation(
|
||||
BCLib.MOD_ID,
|
||||
"chunker"
|
||||
),
|
||||
Chunker::new,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
private int serialNo;
|
||||
private UUID uuid;
|
||||
private int chunkCount;
|
||||
private FriendlyByteBuf networkedBuf;
|
||||
private ResourceLocation origin;
|
||||
|
||||
protected Chunker(int serialNo, UUID uuid, FriendlyByteBuf networkedBuf, int chunkCount, ResourceLocation origin) {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
this.serialNo = serialNo;
|
||||
this.uuid = uuid;
|
||||
this.networkedBuf = networkedBuf;
|
||||
this.chunkCount = chunkCount;
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
protected Chunker() {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnServer(FriendlyByteBuf buf) {
|
||||
//Sending Header. Make sure to change HEADER_SIZE if you change this!
|
||||
buf.writeByte(0);
|
||||
buf.writeLong(uuid.getMostSignificantBits());
|
||||
buf.writeLong(uuid.getLeastSignificantBits());
|
||||
buf.writeInt(serialNo);
|
||||
|
||||
//sending Payload
|
||||
if (serialNo == -1) {
|
||||
//this is our header-Chunk that transports status information
|
||||
buf.writeInt(chunkCount);
|
||||
writeString(buf, origin.getNamespace());
|
||||
writeString(buf, origin.getPath());
|
||||
} else {
|
||||
//this is an actual payload chunk
|
||||
buf.capacity(MAX_PACKET_SIZE);
|
||||
final int size = Math.min(MAX_PAYLOAD_SIZE, networkedBuf.readableBytes());
|
||||
buf.writeInt(size);
|
||||
networkedBuf.readBytes(buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
private PacketChunkReceiver receiver;
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
|
||||
final int version = buf.readByte();
|
||||
uuid = new UUID(buf.readLong(), buf.readLong());
|
||||
serialNo = buf.readInt();
|
||||
|
||||
if (serialNo == -1) {
|
||||
chunkCount = buf.readInt();
|
||||
final String namespace = readString(buf);
|
||||
final String path = readString(buf);
|
||||
ResourceLocation ident = new ResourceLocation(namespace, path);
|
||||
BCLib.LOGGER.info("Receiving " + chunkCount + " + Packet-Chunks for " + ident);
|
||||
|
||||
receiver = PacketChunkReceiver.getOrCreate(uuid, chunkCount, ident);
|
||||
} else {
|
||||
receiver = PacketChunkReceiver.get(uuid);
|
||||
if (receiver != null) {
|
||||
final int size = buf.readInt();
|
||||
receiver.processReceived(buf, serialNo, size);
|
||||
} else {
|
||||
BCLib.LOGGER.error("Unknown Packet-Chunk Transfer for " + uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runOnClientGameThread(Minecraft client) {
|
||||
if (receiver != null) {
|
||||
receiver.testFinished();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.client.gui.screens.ProgressScreen;
|
||||
|
||||
import net.minecraft.util.ProgressListener;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class ChunkerProgress {
|
||||
private static ProgressScreen progressScreen;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static void setProgressScreen(ProgressScreen scr) {
|
||||
progressScreen = scr;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static ProgressScreen getProgressScreen() {
|
||||
return progressScreen;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public static ProgressListener getProgressListener() {
|
||||
return progressScreen;
|
||||
}
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FileContentWrapper {
|
||||
private byte[] rawContent;
|
||||
private ByteArrayOutputStream outputStream;
|
||||
|
||||
FileContentWrapper(byte[] content) {
|
||||
this.rawContent = content;
|
||||
this.outputStream = null;
|
||||
}
|
||||
|
||||
public byte[] getOriginalContent() {
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
public byte[] getRawContent() {
|
||||
if (outputStream != null) {
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
return rawContent;
|
||||
}
|
||||
|
||||
private void invalidateOutputStream() {
|
||||
if (this.outputStream != null) {
|
||||
try {
|
||||
this.outputStream.close();
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.debug(e);
|
||||
}
|
||||
}
|
||||
this.outputStream = null;
|
||||
}
|
||||
|
||||
public void setRawContent(byte[] rawContent) {
|
||||
this.rawContent = rawContent;
|
||||
invalidateOutputStream();
|
||||
}
|
||||
|
||||
public void syncWithOutputStream() {
|
||||
if (outputStream != null) {
|
||||
try {
|
||||
outputStream.flush();
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.error(e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
setRawContent(getRawContent());
|
||||
invalidateOutputStream();
|
||||
}
|
||||
}
|
||||
|
||||
public ByteArrayInputStream getInputStream() {
|
||||
if (rawContent == null) return new ByteArrayInputStream(new byte[0]);
|
||||
return new ByteArrayInputStream(rawContent);
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream getOrCreateOutputStream() {
|
||||
if (this.outputStream == null) {
|
||||
return this.getEmptyOutputStream();
|
||||
}
|
||||
return this.outputStream;
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream getEmptyOutputStream() {
|
||||
invalidateOutputStream();
|
||||
this.outputStream = new ByteArrayOutputStream(this.rawContent.length);
|
||||
return this.outputStream;
|
||||
}
|
||||
}
|
|
@ -1,544 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.client.gui.screens.ModListScreen;
|
||||
import org.betterx.bclib.client.gui.screens.ProgressScreen;
|
||||
import org.betterx.bclib.client.gui.screens.SyncFilesScreen;
|
||||
import org.betterx.bclib.client.gui.screens.WarnBCLibVersionMismatch;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.config.ServerConfig;
|
||||
import org.betterx.worlds.together.util.ModUtil;
|
||||
import org.betterx.worlds.together.util.ModUtil.ModInfo;
|
||||
import org.betterx.worlds.together.util.PathUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.CommonComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
import net.fabricmc.loader.api.metadata.ModEnvironment;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Sent from the Server to the Client.
|
||||
* <p>
|
||||
* For Details refer to {@link HelloServer}
|
||||
*/
|
||||
public class HelloClient extends DataHandler.FromServer {
|
||||
public record OfferedModInfo(String version, int size, boolean canDownload) {
|
||||
}
|
||||
|
||||
public interface IServerModMap extends Map<String, OfferedModInfo> {
|
||||
}
|
||||
|
||||
public static class ServerModMap extends HashMap<String, OfferedModInfo> implements IServerModMap {
|
||||
}
|
||||
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
|
||||
new ResourceLocation(
|
||||
BCLib.MOD_ID,
|
||||
"hello_client"
|
||||
),
|
||||
HelloClient::new,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
public HelloClient() {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
}
|
||||
|
||||
static String getBCLibVersion() {
|
||||
return ModUtil.getModVersion(BCLib.MOD_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareDataOnServer() {
|
||||
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoSync.loadSyncFolder();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnServer(FriendlyByteBuf buf) {
|
||||
final String vbclib = getBCLibVersion();
|
||||
BCLib.LOGGER.info("Sending Hello to Client. (server=" + vbclib + ")");
|
||||
|
||||
//write BCLibVersion (=protocol version)
|
||||
buf.writeInt(ModUtil.convertModVersion(vbclib));
|
||||
|
||||
if (Configs.SERVER_CONFIG.isOfferingMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()) {
|
||||
List<String> mods = DataExchangeAPI.registeredMods();
|
||||
final List<String> inmods = mods;
|
||||
if (Configs.SERVER_CONFIG.isOfferingAllMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()) {
|
||||
mods = new ArrayList<>(inmods.size());
|
||||
mods.addAll(inmods);
|
||||
mods.addAll(ModUtil
|
||||
.getMods()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue().metadata.getEnvironment() != ModEnvironment.SERVER && !inmods.contains(
|
||||
entry.getKey()))
|
||||
.map(entry -> entry.getKey())
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
mods = mods
|
||||
.stream()
|
||||
.filter(entry -> !Configs.SERVER_CONFIG.get(ServerConfig.EXCLUDED_MODS).contains(entry))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
//write Plugin Versions
|
||||
buf.writeInt(mods.size());
|
||||
for (String modID : mods) {
|
||||
final String ver = ModUtil.getModVersion(modID);
|
||||
int size = 0;
|
||||
|
||||
final ModInfo mi = ModUtil.getModInfo(modID);
|
||||
if (mi != null) {
|
||||
try {
|
||||
size = (int) Files.size(mi.jarPath);
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.error("Unable to get File Size: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
writeString(buf, modID);
|
||||
buf.writeInt(ModUtil.convertModVersion(ver));
|
||||
buf.writeInt(size);
|
||||
final boolean canDownload = size > 0 && Configs.SERVER_CONFIG.isOfferingMods() && (Configs.SERVER_CONFIG.isOfferingAllMods() || inmods.contains(
|
||||
modID));
|
||||
buf.writeBoolean(canDownload);
|
||||
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver + " (size: " + PathUtil.humanReadableFileSize(
|
||||
size) + ", download=" + canDownload + ")");
|
||||
}
|
||||
} else {
|
||||
BCLib.LOGGER.info("Server will not list Mods.");
|
||||
buf.writeInt(0);
|
||||
}
|
||||
|
||||
if (Configs.SERVER_CONFIG.isOfferingFiles() || Configs.SERVER_CONFIG.isOfferingConfigs()) {
|
||||
//do only include files that exist on the server
|
||||
final List<AutoFileSyncEntry> existingAutoSyncFiles = AutoSync.getAutoSyncFiles()
|
||||
.stream()
|
||||
.filter(e -> e.fileName.exists())
|
||||
.filter(e -> (e.isConfigFile() && Configs.SERVER_CONFIG.isOfferingConfigs()) || (e instanceof AutoFileSyncEntry.ForDirectFileRequest && Configs.SERVER_CONFIG.isOfferingFiles()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
//send config Data
|
||||
buf.writeInt(existingAutoSyncFiles.size());
|
||||
for (AutoFileSyncEntry entry : existingAutoSyncFiles) {
|
||||
entry.serialize(buf);
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" - Offering " + (entry.isConfigFile() ? "Config " : "File ") + entry);
|
||||
}
|
||||
} else {
|
||||
BCLib.LOGGER.info("Server will neither offer Files nor Configs.");
|
||||
buf.writeInt(0);
|
||||
}
|
||||
|
||||
if (Configs.SERVER_CONFIG.isOfferingFiles()) {
|
||||
buf.writeInt(AutoSync.syncFolderDescriptions.size());
|
||||
AutoSync.syncFolderDescriptions.forEach(desc -> {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" - Offering Folder " + desc.localFolder + " (allowDelete=" + desc.removeAdditionalFiles + ")");
|
||||
desc.serialize(buf);
|
||||
});
|
||||
} else {
|
||||
BCLib.LOGGER.info("Server will not offer Sync Folders.");
|
||||
buf.writeInt(0);
|
||||
}
|
||||
|
||||
buf.writeBoolean(Configs.SERVER_CONFIG.isOfferingInfosForMods());
|
||||
}
|
||||
|
||||
String bclibVersion = "0.0.0";
|
||||
|
||||
|
||||
IServerModMap modVersion = new ServerModMap();
|
||||
List<AutoSync.AutoSyncTriple> autoSyncedFiles = null;
|
||||
List<SyncFolderDescriptor> autoSynFolders = null;
|
||||
boolean serverPublishedModInfo = false;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
|
||||
//read BCLibVersion (=protocol version)
|
||||
bclibVersion = ModUtil.convertModVersion(buf.readInt());
|
||||
|
||||
//read Plugin Versions
|
||||
modVersion = new ServerModMap();
|
||||
int count = buf.readInt();
|
||||
for (int i = 0; i < count; i++) {
|
||||
final String id = readString(buf);
|
||||
final String version = ModUtil.convertModVersion(buf.readInt());
|
||||
final int size;
|
||||
final boolean canDownload;
|
||||
//since v0.4.1 we also send the size of the mod-File
|
||||
size = buf.readInt();
|
||||
canDownload = buf.readBoolean();
|
||||
modVersion.put(id, new OfferedModInfo(version, size, canDownload));
|
||||
}
|
||||
|
||||
//read config Data
|
||||
count = buf.readInt();
|
||||
autoSyncedFiles = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
//System.out.println("Deserializing ");
|
||||
AutoSync.AutoSyncTriple t = AutoFileSyncEntry.deserializeAndMatch(buf);
|
||||
autoSyncedFiles.add(t);
|
||||
//System.out.println(t.first);
|
||||
}
|
||||
|
||||
|
||||
autoSynFolders = new ArrayList<>(1);
|
||||
//since v0.4.1 we also send the sync folders
|
||||
final int folderCount = buf.readInt();
|
||||
for (int i = 0; i < folderCount; i++) {
|
||||
SyncFolderDescriptor desc = SyncFolderDescriptor.deserialize(buf);
|
||||
autoSynFolders.add(desc);
|
||||
}
|
||||
|
||||
serverPublishedModInfo = buf.readBoolean();
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void processAutoSyncFolder(
|
||||
final List<AutoSyncID> filesToRequest,
|
||||
final List<AutoSyncID.ForDirectFileRequest> filesToRemove
|
||||
) {
|
||||
if (!Configs.CLIENT_CONFIG.isAcceptingFiles()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoSynFolders.size() > 0) {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info("Folders offered by Server:");
|
||||
}
|
||||
|
||||
autoSynFolders.forEach(desc -> {
|
||||
//desc contains the fileCache sent from the server, load the local version to get hold of the actual file cache on the client
|
||||
SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(desc.folderID);
|
||||
if (localDescriptor != null) {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")");
|
||||
localDescriptor.invalidateCache();
|
||||
|
||||
desc.relativeFilesStream()
|
||||
.filter(desc::discardChildElements)
|
||||
.forEach(subFile -> {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.warning(" * " + subFile.relPath + " (REJECTED)");
|
||||
});
|
||||
|
||||
|
||||
if (desc.removeAdditionalFiles) {
|
||||
List<AutoSyncID.ForDirectFileRequest> additionalFiles = localDescriptor.relativeFilesStream()
|
||||
.filter(subFile -> !desc.hasRelativeFile(
|
||||
subFile))
|
||||
.map(desc::mapAbsolute)
|
||||
.filter(desc::acceptChildElements)
|
||||
.map(absPath -> new AutoSyncID.ForDirectFileRequest(
|
||||
desc.folderID,
|
||||
absPath.toFile()
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
additionalFiles.forEach(aid -> BCLib.LOGGER.info(" * " + desc.localFolder.relativize(aid.relFile.toPath()) + " (missing on server)"));
|
||||
filesToRemove.addAll(additionalFiles);
|
||||
}
|
||||
|
||||
desc.relativeFilesStream()
|
||||
.filter(desc::acceptChildElements)
|
||||
.forEach(subFile -> {
|
||||
SyncFolderDescriptor.SubFile localSubFile = localDescriptor.getLocalSubFile(subFile.relPath);
|
||||
if (localSubFile != null) {
|
||||
//the file exists locally, check if the hashes match
|
||||
if (!localSubFile.hash.equals(subFile.hash)) {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" * " + subFile.relPath + " (changed)");
|
||||
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(
|
||||
desc.folderID,
|
||||
new File(subFile.relPath)
|
||||
));
|
||||
} else {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" * " + subFile.relPath);
|
||||
}
|
||||
} else {
|
||||
//the file is missing locally
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" * " + subFile.relPath + " (missing on client)");
|
||||
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(
|
||||
desc.folderID,
|
||||
new File(subFile.relPath)
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
//free some memory
|
||||
localDescriptor.invalidateCache();
|
||||
} else {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" - " + desc.folderID + " (Failed to find)");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void processSingleFileSync(final List<AutoSyncID> filesToRequest) {
|
||||
final boolean debugHashes = Configs.CLIENT_CONFIG.shouldPrintDebugHashes();
|
||||
|
||||
if (autoSyncedFiles.size() > 0) {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info("Files offered by Server:");
|
||||
}
|
||||
|
||||
//Handle single sync files
|
||||
//Single files need to be registered for sync on both client and server
|
||||
//There are no restrictions to the target folder, but the client decides the final
|
||||
//location.
|
||||
for (AutoSync.AutoSyncTriple e : autoSyncedFiles) {
|
||||
String actionString = "";
|
||||
FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent);
|
||||
if (e.localMatch == null) {
|
||||
actionString = "(unknown source -> omitting)";
|
||||
//filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
|
||||
} else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) {
|
||||
actionString = "(prepare update)";
|
||||
//we did not yet receive the new content
|
||||
if (contentWrapper.getRawContent() == null) {
|
||||
filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID));
|
||||
} else {
|
||||
filesToRequest.add(new AutoSyncID.WithContentOverride(
|
||||
e.serverHash.modID,
|
||||
e.serverHash.uniqueID,
|
||||
contentWrapper,
|
||||
e.localMatch.fileName
|
||||
));
|
||||
}
|
||||
}
|
||||
if (Configs.MAIN_CONFIG.verboseLogging()) {
|
||||
BCLib.LOGGER.info(" - " + e + ": " + actionString);
|
||||
if (debugHashes) {
|
||||
BCLib.LOGGER.info(" * " + e.serverHash + " (Server)");
|
||||
BCLib.LOGGER.info(" * " + e.localMatch.getFileHash() + " (Client)");
|
||||
BCLib.LOGGER.info(" * local Content " + (contentWrapper.getRawContent() == null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void processModFileSync(final List<AutoSyncID> filesToRequest, final Set<String> mismatchingMods) {
|
||||
for (Entry<String, OfferedModInfo> e : modVersion.entrySet()) {
|
||||
final String localVersion = ModUtil.convertModVersion(ModUtil.convertModVersion(ModUtil.getModVersion(e.getKey())));
|
||||
final OfferedModInfo serverInfo = e.getValue();
|
||||
|
||||
ModInfo nfo = ModUtil.getModInfo(e.getKey());
|
||||
final boolean clientOnly = nfo != null && nfo.metadata.getEnvironment() == ModEnvironment.CLIENT;
|
||||
final boolean requestMod = !clientOnly && !serverInfo.version.equals(localVersion) && serverInfo.size > 0 && serverInfo.canDownload;
|
||||
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" - " + e.getKey() + " (client=" + localVersion + ", server=" + serverInfo.version + ", size=" + PathUtil.humanReadableFileSize(
|
||||
serverInfo.size) + (requestMod ? ", requesting" : "") + (serverInfo.canDownload
|
||||
? ""
|
||||
: ", not offered") + (clientOnly ? ", client only" : "") + ")");
|
||||
if (requestMod) {
|
||||
filesToRequest.add(new AutoSyncID.ForModFileRequest(e.getKey(), serverInfo.version));
|
||||
}
|
||||
if (!serverInfo.version.equals(localVersion)) {
|
||||
mismatchingMods.add(e.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
mismatchingMods.addAll(ModListScreen.localMissing(modVersion));
|
||||
mismatchingMods.addAll(ModListScreen.serverMissing(modVersion));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isBlocking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void runOnClientGameThread(Minecraft client) {
|
||||
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
|
||||
return;
|
||||
}
|
||||
final String localBclibVersion = getBCLibVersion();
|
||||
BCLib.LOGGER.info("Received Hello from Server. (client=" + localBclibVersion + ", server=" + bclibVersion + ")");
|
||||
|
||||
if (ModUtil.convertModVersion(localBclibVersion) != ModUtil.convertModVersion(bclibVersion)) {
|
||||
showBCLibError(client);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<AutoSyncID> filesToRequest = new ArrayList<>(2);
|
||||
final List<AutoSyncID.ForDirectFileRequest> filesToRemove = new ArrayList<>(2);
|
||||
final Set<String> mismatchingMods = new HashSet<>(2);
|
||||
|
||||
|
||||
processModFileSync(filesToRequest, mismatchingMods);
|
||||
processSingleFileSync(filesToRequest);
|
||||
processAutoSyncFolder(filesToRequest, filesToRemove);
|
||||
|
||||
//Handle folder sync
|
||||
//Both client and server need to know about the folder you want to sync
|
||||
//Files can only get placed within that folder
|
||||
|
||||
if ((filesToRequest.size() > 0 || filesToRemove.size() > 0) && (Configs.CLIENT_CONFIG.isAcceptingMods() || Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles())) {
|
||||
showSyncFilesScreen(client, filesToRequest, filesToRemove);
|
||||
return;
|
||||
} else if (serverPublishedModInfo && mismatchingMods.size() > 0 && Configs.CLIENT_CONFIG.isShowingModInfo()) {
|
||||
client.setScreen(new ModListScreen(
|
||||
client.screen,
|
||||
Component.translatable("title.bclib.modmissmatch"),
|
||||
Component.translatable("message.bclib.modmissmatch"),
|
||||
CommonComponents.GUI_PROCEED,
|
||||
ModUtil.getMods(),
|
||||
modVersion
|
||||
));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void showBCLibError(Minecraft client) {
|
||||
BCLib.LOGGER.error("BCLib differs on client and server.");
|
||||
client.setScreen(new WarnBCLibVersionMismatch((download) -> {
|
||||
if (download) {
|
||||
requestBCLibDownload();
|
||||
|
||||
this.onCloseSyncFilesScreen();
|
||||
} else {
|
||||
Minecraft.getInstance()
|
||||
.setScreen(null);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void showSyncFilesScreen(
|
||||
Minecraft client,
|
||||
List<AutoSyncID> files,
|
||||
final List<AutoSyncID.ForDirectFileRequest> filesToRemove
|
||||
) {
|
||||
int configFiles = 0;
|
||||
int singleFiles = 0;
|
||||
int folderFiles = 0;
|
||||
int modFiles = 0;
|
||||
|
||||
for (AutoSyncID aid : files) {
|
||||
if (aid.isConfigFile()) {
|
||||
configFiles++;
|
||||
} else if (aid instanceof AutoSyncID.ForModFileRequest) {
|
||||
modFiles++;
|
||||
} else if (aid instanceof AutoSyncID.ForDirectFileRequest) {
|
||||
folderFiles++;
|
||||
} else {
|
||||
singleFiles++;
|
||||
}
|
||||
}
|
||||
|
||||
client.setScreen(new SyncFilesScreen(
|
||||
modFiles,
|
||||
configFiles,
|
||||
singleFiles,
|
||||
folderFiles,
|
||||
filesToRemove.size(),
|
||||
modVersion,
|
||||
(downloadMods, downloadConfigs, downloadFiles, removeFiles) -> {
|
||||
if (downloadMods || downloadConfigs || downloadFiles) {
|
||||
BCLib.LOGGER.info("Updating local Files:");
|
||||
List<AutoSyncID.WithContentOverride> localChanges = new ArrayList<>(
|
||||
files.toArray().length);
|
||||
List<AutoSyncID> requestFiles = new ArrayList<>(files.toArray().length);
|
||||
|
||||
files.forEach(aid -> {
|
||||
if (aid.isConfigFile() && downloadConfigs) {
|
||||
processOfferedFile(requestFiles, aid);
|
||||
} else if (aid instanceof AutoSyncID.ForModFileRequest && downloadMods) {
|
||||
processOfferedFile(requestFiles, aid);
|
||||
} else if (downloadFiles) {
|
||||
processOfferedFile(requestFiles, aid);
|
||||
}
|
||||
});
|
||||
|
||||
requestFileDownloads(requestFiles);
|
||||
}
|
||||
if (removeFiles) {
|
||||
filesToRemove.forEach(aid -> {
|
||||
BCLib.LOGGER.info(" - " + aid.relFile + " (removing)");
|
||||
aid.relFile.delete();
|
||||
});
|
||||
}
|
||||
|
||||
this.onCloseSyncFilesScreen();
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void onCloseSyncFilesScreen() {
|
||||
Minecraft.getInstance()
|
||||
.setScreen(ChunkerProgress.getProgressScreen());
|
||||
}
|
||||
|
||||
private void processOfferedFile(List<AutoSyncID> requestFiles, AutoSyncID aid) {
|
||||
if (aid instanceof AutoSyncID.WithContentOverride) {
|
||||
final AutoSyncID.WithContentOverride aidc = (AutoSyncID.WithContentOverride) aid;
|
||||
BCLib.LOGGER.info(" - " + aid + " (updating Content)");
|
||||
|
||||
SendFiles.writeSyncedFile(aid, aidc.contentWrapper.getRawContent(), aidc.localFile);
|
||||
} else {
|
||||
requestFiles.add(aid);
|
||||
BCLib.LOGGER.info(" - " + aid + " (requesting)");
|
||||
}
|
||||
}
|
||||
|
||||
private void requestBCLibDownload() {
|
||||
BCLib.LOGGER.warning("Starting download of BCLib");
|
||||
requestFileDownloads(List.of(new AutoSyncID.ForModFileRequest(BCLib.MOD_ID, bclibVersion)));
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private void requestFileDownloads(List<AutoSyncID> files) {
|
||||
BCLib.LOGGER.info("Starting download of Files:" + files.size());
|
||||
|
||||
final ProgressScreen progress = new ProgressScreen(
|
||||
null,
|
||||
Component.translatable("title.bclib.filesync.progress"),
|
||||
Component.translatable("message.bclib.filesync.progress")
|
||||
);
|
||||
progress.progressStart(Component.translatable("message.bclib.filesync.progress.stage.empty"));
|
||||
ChunkerProgress.setProgressScreen(progress);
|
||||
|
||||
DataExchangeAPI.send(new RequestFiles(files));
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.worlds.together.util.ModUtil;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* This message is sent once a player enters the world. It initiates a sequence of Messages that will sync files between both
|
||||
* client and server.
|
||||
* <table>
|
||||
* <caption>Description</caption>
|
||||
* <tr>
|
||||
* <th>Server</th>
|
||||
* <th></th>
|
||||
* <th>Client</th>
|
||||
* <th></th>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td colspan="4">Player enters World</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td></td>
|
||||
* <td><--</td>
|
||||
* <td>{@link HelloServer}</td>
|
||||
* <td>Sends the current BLib-Version installed on the Client</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link HelloClient}</td>
|
||||
* <td>--></td>
|
||||
* <td></td>
|
||||
* <td>Sends the current BClIb-Version, the Version of all Plugins and data for all AutpoSync-Files
|
||||
* ({@link DataExchangeAPI#addAutoSyncFile(String, File)} on the Server</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td></td>
|
||||
* <td><--</td>
|
||||
* <td>{@link RequestFiles}</td>
|
||||
* <td>Request missing or out of sync Files from the Server</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link SendFiles}</td>
|
||||
* <td>--></td>
|
||||
* <td></td>
|
||||
* <td>Send Files from the Server to the Client</td>
|
||||
* </tr>
|
||||
* </table>
|
||||
*/
|
||||
public class HelloServer extends DataHandler.FromClient {
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
|
||||
new ResourceLocation(
|
||||
BCLib.MOD_ID,
|
||||
"hello_server"
|
||||
),
|
||||
HelloServer::new,
|
||||
true,
|
||||
false
|
||||
);
|
||||
|
||||
protected String bclibVersion = "0.0.0";
|
||||
|
||||
public HelloServer() {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected boolean prepareDataOnClient() {
|
||||
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void serializeDataOnClient(FriendlyByteBuf buf) {
|
||||
BCLib.LOGGER.info("Sending hello to server.");
|
||||
buf.writeInt(ModUtil.convertModVersion(HelloClient.getBCLibVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, Player player, PacketSender responseSender) {
|
||||
bclibVersion = ModUtil.convertModVersion(buf.readInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runOnServerGameThread(MinecraftServer server, Player player) {
|
||||
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
String localBclibVersion = HelloClient.getBCLibVersion();
|
||||
BCLib.LOGGER.info("Received Hello from Client. (server=" + localBclibVersion + ", client=" + bclibVersion + ")");
|
||||
|
||||
if (!server.isPublished()) {
|
||||
BCLib.LOGGER.info("Auto-Sync is disabled for Singleplayer worlds.");
|
||||
return;
|
||||
}
|
||||
|
||||
reply(new HelloClient(), server);
|
||||
}
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RequestFiles extends DataHandler.FromClient {
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
|
||||
new ResourceLocation(
|
||||
BCLib.MOD_ID,
|
||||
"request_files"
|
||||
),
|
||||
RequestFiles::new,
|
||||
false,
|
||||
false
|
||||
);
|
||||
static String currentToken = "";
|
||||
|
||||
protected List<AutoSyncID> files;
|
||||
|
||||
private RequestFiles() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public RequestFiles(List<AutoSyncID> files) {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected boolean prepareDataOnClient() {
|
||||
if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void serializeDataOnClient(FriendlyByteBuf buf) {
|
||||
newToken();
|
||||
writeString(buf, currentToken);
|
||||
|
||||
buf.writeInt(files.size());
|
||||
|
||||
for (AutoSyncID a : files) {
|
||||
a.serializeData(buf);
|
||||
}
|
||||
}
|
||||
|
||||
String receivedToken = "";
|
||||
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, Player player, PacketSender responseSender) {
|
||||
receivedToken = readString(buf);
|
||||
int size = buf.readInt();
|
||||
files = new ArrayList<>(size);
|
||||
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info("Client requested " + size + " Files:");
|
||||
for (int i = 0; i < size; i++) {
|
||||
AutoSyncID asid = AutoSyncID.deserializeData(buf);
|
||||
files.add(asid);
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" - " + asid);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runOnServerGameThread(MinecraftServer server, Player player) {
|
||||
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
|
||||
return;
|
||||
}
|
||||
|
||||
List<AutoFileSyncEntry> syncEntries = files.stream()
|
||||
.map(asid -> AutoFileSyncEntry.findMatching(asid))
|
||||
.filter(e -> e != null)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
reply(new SendFiles(syncEntries, receivedToken), server);
|
||||
}
|
||||
|
||||
public static void newToken() {
|
||||
currentToken = UUID.randomUUID()
|
||||
.toString();
|
||||
}
|
||||
|
||||
static {
|
||||
newToken();
|
||||
}
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandlerDescriptor;
|
||||
import org.betterx.bclib.client.gui.screens.ConfirmRestartScreen;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.bclib.util.Pair;
|
||||
import org.betterx.bclib.util.Triple;
|
||||
import org.betterx.worlds.together.util.PathUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.fabric.api.networking.v1.PacketSender;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SendFiles extends DataHandler.FromServer {
|
||||
public static final DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(
|
||||
new ResourceLocation(
|
||||
BCLib.MOD_ID,
|
||||
"send_files"
|
||||
),
|
||||
SendFiles::new,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
protected List<AutoFileSyncEntry> files;
|
||||
private String token;
|
||||
|
||||
public SendFiles() {
|
||||
this(null, "");
|
||||
}
|
||||
|
||||
public SendFiles(List<AutoFileSyncEntry> files, String token) {
|
||||
super(DESCRIPTOR.IDENTIFIER);
|
||||
this.files = files;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean prepareDataOnServer() {
|
||||
if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) {
|
||||
BCLib.LOGGER.info("Auto-Sync was disabled on the server.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void serializeDataOnServer(FriendlyByteBuf buf) {
|
||||
List<AutoFileSyncEntry> existingFiles = files.stream()
|
||||
.filter(e -> e != null && e.fileName != null && e.fileName.exists())
|
||||
.collect(Collectors.toList());
|
||||
/*
|
||||
//this will try to send a file that was not registered or requested by the client
|
||||
existingFiles.add(new AutoFileSyncEntry("none", new File("D:\\MinecraftPlugins\\BetterNether\\run\\server.properties"),true,(a, b, content) -> {
|
||||
System.out.println("Got Content:" + content.length);
|
||||
return true;
|
||||
}));*/
|
||||
|
||||
/*//this will try to send a folder-file that was not registered or requested by the client
|
||||
existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("test.json"), DataExchange.SYNC_FOLDER.mapAbsolute("test.json").toFile()));*/
|
||||
|
||||
/*//this will try to send a folder-file that was not registered or requested by the client and is outside the base-folder
|
||||
existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("../breakout.json"), DataExchange.SYNC_FOLDER.mapAbsolute("../breakout.json").toFile()));*/
|
||||
|
||||
|
||||
writeString(buf, token);
|
||||
buf.writeInt(existingFiles.size());
|
||||
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:");
|
||||
for (AutoFileSyncEntry entry : existingFiles) {
|
||||
int length = entry.serializeContent(buf);
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" - " + entry + " (" + PathUtil.humanReadableFileSize(length) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private List<Pair<AutoFileSyncEntry, byte[]>> receivedFiles;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
|
||||
if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
|
||||
token = readString(buf);
|
||||
if (!token.equals(RequestFiles.currentToken)) {
|
||||
RequestFiles.newToken();
|
||||
BCLib.LOGGER.error("Unrequested File Transfer!");
|
||||
receivedFiles = new ArrayList<>(0);
|
||||
return;
|
||||
}
|
||||
RequestFiles.newToken();
|
||||
|
||||
int size = buf.readInt();
|
||||
receivedFiles = new ArrayList<>(size);
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info("Server sent " + size + " Files:");
|
||||
for (int i = 0; i < size; i++) {
|
||||
Triple<AutoFileSyncEntry, byte[], AutoSyncID> p = AutoFileSyncEntry.deserializeContent(buf);
|
||||
if (p.first != null) {
|
||||
final String type;
|
||||
if (p.first.isConfigFile() && Configs.CLIENT_CONFIG.isAcceptingConfigs()) {
|
||||
receivedFiles.add(p);
|
||||
type = "Accepted Config ";
|
||||
} else if (p.first instanceof AutoFileSyncEntry.ForModFileRequest && Configs.CLIENT_CONFIG.isAcceptingMods()) {
|
||||
receivedFiles.add(p);
|
||||
type = "Accepted Mod ";
|
||||
} else if (Configs.CLIENT_CONFIG.isAcceptingFiles()) {
|
||||
receivedFiles.add(p);
|
||||
type = "Accepted File ";
|
||||
} else {
|
||||
type = "Ignoring ";
|
||||
}
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.info(" - " + type + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")");
|
||||
} else {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging())
|
||||
BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
@Override
|
||||
protected void runOnClientGameThread(Minecraft client) {
|
||||
if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) {
|
||||
BCLib.LOGGER.info("Writing Files:");
|
||||
|
||||
for (Pair<AutoFileSyncEntry, byte[]> entry : receivedFiles) {
|
||||
final AutoFileSyncEntry e = entry.first;
|
||||
final byte[] data = entry.second;
|
||||
|
||||
writeSyncedFile(e, data, e.fileName);
|
||||
}
|
||||
|
||||
showConfirmRestart(client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
static void writeSyncedFile(AutoSyncID e, byte[] data, File fileName) {
|
||||
if (fileName != null && !PathUtil.isChildOf(PathUtil.GAME_FOLDER, fileName.toPath())) {
|
||||
BCLib.LOGGER.error(fileName + " is not within game folder " + PathUtil.GAME_FOLDER);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PathUtil.MOD_BAK_FOLDER.toFile().exists()) {
|
||||
PathUtil.MOD_BAK_FOLDER.toFile().mkdirs();
|
||||
}
|
||||
|
||||
Path path = fileName != null ? fileName.toPath() : null;
|
||||
Path removeAfter = null;
|
||||
if (e instanceof AutoFileSyncEntry.ForModFileRequest mase) {
|
||||
removeAfter = path;
|
||||
int count = 0;
|
||||
final String prefix = "_bclib_synced_";
|
||||
String name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + ".jar";
|
||||
do {
|
||||
if (path != null) {
|
||||
//move to the same directory as the existing Mod
|
||||
path = path.getParent()
|
||||
.resolve(name);
|
||||
} else {
|
||||
//move to the default mode location
|
||||
path = PathUtil.MOD_FOLDER.resolve(name);
|
||||
}
|
||||
count++;
|
||||
name = prefix + mase.modID + "_" + mase.version.replace(".", "_") + "__" + String.format(
|
||||
"%03d",
|
||||
count
|
||||
) + ".jar";
|
||||
} while (path.toFile().exists());
|
||||
}
|
||||
|
||||
BCLib.LOGGER.info(" - Writing " + path + " (" + PathUtil.humanReadableFileSize(data.length) + ")");
|
||||
try {
|
||||
final File parentFile = path.getParent()
|
||||
.toFile();
|
||||
if (!parentFile.exists()) {
|
||||
parentFile.mkdirs();
|
||||
}
|
||||
Files.write(path, data);
|
||||
if (removeAfter != null) {
|
||||
final String bakFileName = removeAfter.toFile().getName();
|
||||
String collisionFreeName = bakFileName;
|
||||
Path targetPath;
|
||||
int count = 0;
|
||||
do {
|
||||
targetPath = PathUtil.MOD_BAK_FOLDER.resolve(collisionFreeName);
|
||||
count++;
|
||||
collisionFreeName = String.format("%03d", count) + "_" + bakFileName;
|
||||
} while (targetPath.toFile().exists());
|
||||
|
||||
BCLib.LOGGER.info(" - Moving " + removeAfter + " to " + targetPath);
|
||||
removeAfter.toFile().renameTo(targetPath.toFile());
|
||||
}
|
||||
AutoSync.didReceiveFile(e, fileName);
|
||||
|
||||
|
||||
} catch (IOException ioException) {
|
||||
BCLib.LOGGER.error(" --> Writing " + fileName + " failed: " + ioException);
|
||||
}
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
protected void showConfirmRestart(Minecraft client) {
|
||||
client.setScreen(new ConfirmRestartScreen(() -> {
|
||||
Minecraft.getInstance()
|
||||
.setScreen(null);
|
||||
client.stop();
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
|
@ -1,220 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.dataexchange.handler.autosync;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.api.v2.dataexchange.DataHandler;
|
||||
import org.betterx.bclib.api.v2.dataexchange.FileHash;
|
||||
import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSyncID.ForDirectFileRequest;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.worlds.together.util.PathUtil;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class SyncFolderDescriptor {
|
||||
static class SubFile {
|
||||
public final String relPath;
|
||||
public final FileHash hash;
|
||||
|
||||
|
||||
SubFile(String relPath, FileHash hash) {
|
||||
this.relPath = relPath;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return relPath;
|
||||
}
|
||||
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
DataHandler.writeString(buf, relPath);
|
||||
hash.serialize(buf);
|
||||
}
|
||||
|
||||
public static SubFile deserialize(FriendlyByteBuf buf) {
|
||||
final String relPath = DataHandler.readString(buf);
|
||||
FileHash hash = FileHash.deserialize(buf);
|
||||
return new SubFile(relPath, hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o instanceof String) return relPath.equals(o);
|
||||
if (!(o instanceof SubFile)) return false;
|
||||
SubFile subFile = (SubFile) o;
|
||||
return relPath.equals(subFile.relPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return relPath.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public final String folderID;
|
||||
public final boolean removeAdditionalFiles;
|
||||
@NotNull
|
||||
public final Path localFolder;
|
||||
|
||||
private List<SubFile> fileCache;
|
||||
|
||||
public SyncFolderDescriptor(String folderID, Path localFolder, boolean removeAdditionalFiles) {
|
||||
this.removeAdditionalFiles = removeAdditionalFiles;
|
||||
this.folderID = folderID;
|
||||
this.localFolder = localFolder;
|
||||
fileCache = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SyncFolderDescriptor{" + "folderID='" + folderID + '\'' + ", removeAdditionalFiles=" + removeAdditionalFiles + ", localFolder=" + localFolder + ", files=" + (
|
||||
fileCache == null
|
||||
? "?"
|
||||
: fileCache.size()) + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o instanceof String) {
|
||||
return folderID.equals(o);
|
||||
}
|
||||
if (o instanceof ForDirectFileRequest) {
|
||||
return folderID.equals(((ForDirectFileRequest) o).uniqueID);
|
||||
}
|
||||
if (!(o instanceof SyncFolderDescriptor)) return false;
|
||||
SyncFolderDescriptor that = (SyncFolderDescriptor) o;
|
||||
return folderID.equals(that.folderID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return folderID.hashCode();
|
||||
}
|
||||
|
||||
public int fileCount() {
|
||||
return fileCache == null ? 0 : fileCache.size();
|
||||
}
|
||||
|
||||
public void invalidateCache() {
|
||||
fileCache = null;
|
||||
}
|
||||
|
||||
public void loadCache() {
|
||||
if (fileCache == null) {
|
||||
fileCache = new ArrayList<>(8);
|
||||
PathUtil.fileWalker(localFolder.toFile(), p -> fileCache.add(new SubFile(
|
||||
localFolder.relativize(p)
|
||||
.toString(),
|
||||
FileHash.create(p.toFile())
|
||||
)));
|
||||
|
||||
/*//this tests if we can trick the system to load files that are not beneath the base-folder
|
||||
if (!BCLib.isClient()) {
|
||||
fileCache.add(new SubFile("../breakout.json", FileHash.create(mapAbsolute("../breakout.json").toFile())));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
public void serialize(FriendlyByteBuf buf) {
|
||||
final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(AutoSync.SYNC_CATEGORY, "debugHashes", false);
|
||||
loadCache();
|
||||
|
||||
DataHandler.writeString(buf, folderID);
|
||||
buf.writeBoolean(removeAdditionalFiles);
|
||||
buf.writeInt(fileCache.size());
|
||||
fileCache.forEach(fl -> {
|
||||
if (Configs.MAIN_CONFIG.verboseLogging()) {
|
||||
BCLib.LOGGER.info(" - " + fl.relPath);
|
||||
if (debugHashes) {
|
||||
BCLib.LOGGER.info(" " + fl.hash);
|
||||
}
|
||||
}
|
||||
fl.serialize(buf);
|
||||
});
|
||||
}
|
||||
|
||||
public static SyncFolderDescriptor deserialize(FriendlyByteBuf buf) {
|
||||
final String folderID = DataHandler.readString(buf);
|
||||
final boolean remAddFiles = buf.readBoolean();
|
||||
final int count = buf.readInt();
|
||||
SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(folderID);
|
||||
|
||||
final SyncFolderDescriptor desc;
|
||||
if (localDescriptor != null) {
|
||||
desc = new SyncFolderDescriptor(
|
||||
folderID,
|
||||
localDescriptor.localFolder,
|
||||
localDescriptor.removeAdditionalFiles && remAddFiles
|
||||
);
|
||||
desc.fileCache = new ArrayList<>(count);
|
||||
} else {
|
||||
BCLib.LOGGER.warning(BCLib.isClient()
|
||||
? "Client"
|
||||
: "Server" + " does not know Sync-Folder ID '" + folderID + "'");
|
||||
desc = null;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
SubFile relPath = SubFile.deserialize(buf);
|
||||
if (desc != null) desc.fileCache.add(relPath);
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
//Note: make sure loadCache was called before using this
|
||||
boolean hasRelativeFile(String relFile) {
|
||||
return fileCache.stream()
|
||||
.filter(sf -> sf.equals(relFile))
|
||||
.findFirst()
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
//Note: make sure loadCache was called before using this
|
||||
boolean hasRelativeFile(SubFile subFile) {
|
||||
return hasRelativeFile(subFile.relPath);
|
||||
}
|
||||
|
||||
//Note: make sure loadCache was called before using this
|
||||
SubFile getLocalSubFile(String relPath) {
|
||||
return fileCache.stream()
|
||||
.filter(sf -> sf.relPath.equals(relPath))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
Stream<SubFile> relativeFilesStream() {
|
||||
loadCache();
|
||||
return fileCache.stream();
|
||||
}
|
||||
|
||||
public Path mapAbsolute(String relPath) {
|
||||
return this.localFolder.resolve(relPath)
|
||||
.normalize();
|
||||
}
|
||||
|
||||
public Path mapAbsolute(SubFile subFile) {
|
||||
return this.localFolder.resolve(subFile.relPath)
|
||||
.normalize();
|
||||
}
|
||||
|
||||
public boolean acceptChildElements(Path absPath) {
|
||||
return PathUtil.isChildOf(this.localFolder, absPath);
|
||||
}
|
||||
|
||||
public boolean acceptChildElements(SubFile subFile) {
|
||||
return acceptChildElements(mapAbsolute(subFile));
|
||||
}
|
||||
|
||||
public boolean discardChildElements(SubFile subFile) {
|
||||
return !acceptChildElements(subFile);
|
||||
}
|
||||
}
|
|
@ -1,598 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.datafixer;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.client.gui.screens.AtomicProgressListener;
|
||||
import org.betterx.bclib.client.gui.screens.ConfirmFixScreen;
|
||||
import org.betterx.bclib.client.gui.screens.LevelFixErrorScreen;
|
||||
import org.betterx.bclib.client.gui.screens.LevelFixErrorScreen.Listener;
|
||||
import org.betterx.bclib.client.gui.screens.ProgressScreen;
|
||||
import org.betterx.bclib.config.Configs;
|
||||
import org.betterx.worlds.together.util.Logger;
|
||||
import org.betterx.worlds.together.world.WorldConfig;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||
import net.minecraft.client.gui.screens.worldselection.EditWorldScreen;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.zip.ZipException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* API to manage Patches that need to get applied to a world
|
||||
*/
|
||||
public class DataFixerAPI {
|
||||
static final Logger LOGGER = new Logger("DataFixerAPI");
|
||||
|
||||
static class State {
|
||||
public boolean didFail = false;
|
||||
protected ArrayList<String> errors = new ArrayList<>();
|
||||
|
||||
public void addError(String s) {
|
||||
errors.add(s);
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return errors.size() > 0;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errors.stream().reduce("", (a, b) -> a + " - " + b + "\n");
|
||||
}
|
||||
|
||||
public String[] getErrorMessages() {
|
||||
String[] res = new String[errors.size()];
|
||||
return errors.toArray(res);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Callback {
|
||||
void call();
|
||||
}
|
||||
|
||||
private static boolean wrapCall(
|
||||
LevelStorageSource levelSource,
|
||||
String levelID,
|
||||
Function<LevelStorageAccess, Boolean> runWithLevel
|
||||
) {
|
||||
LevelStorageSource.LevelStorageAccess levelStorageAccess;
|
||||
try {
|
||||
levelStorageAccess = levelSource.createAccess(levelID);
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.warning("Failed to read level {} data", levelID, e);
|
||||
SystemToast.onWorldAccessFailure(Minecraft.getInstance(), levelID);
|
||||
Minecraft.getInstance().setScreen(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean returnValue = runWithLevel.apply(levelStorageAccess);
|
||||
|
||||
try {
|
||||
levelStorageAccess.close();
|
||||
} catch (IOException e) {
|
||||
BCLib.LOGGER.warning("Failed to unlock access to level {}", levelID, e);
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will apply necessary Patches to the world.
|
||||
*
|
||||
* @param levelSource The SourceStorage for this Minecraft instance, You can get this using
|
||||
* {@code Minecraft.getInstance().getLevelSource()}
|
||||
* @param levelID The ID of the Level you want to patch
|
||||
* @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
|
||||
* before applying the patches
|
||||
* @param onResume When this method retursn {@code true}, this function will be called when the world is ready
|
||||
* @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
|
||||
* patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
|
||||
*/
|
||||
public static boolean fixData(
|
||||
LevelStorageSource levelSource,
|
||||
String levelID,
|
||||
boolean showUI,
|
||||
Consumer<Boolean> onResume
|
||||
) {
|
||||
return wrapCall(levelSource, levelID, (levelStorageAccess) -> fixData(levelStorageAccess, showUI, onResume));
|
||||
}
|
||||
|
||||
/**
|
||||
* Will apply necessary Patches to the world.
|
||||
*
|
||||
* @param levelStorageAccess The access class of the level you want to patch
|
||||
* @param showUI {@code true}, if you want to present the user with a Screen that offers to backup the world
|
||||
* before applying the patches
|
||||
* @param onResume When this method retursn {@code true}, this function will be called when the world is ready
|
||||
* @return {@code true} if the UI was displayed. The UI is only displayed if {@code showUI} was {@code true} and
|
||||
* patches were enabled in the config and the Guardian did find any patches that need to be applied to the world.
|
||||
*/
|
||||
public static boolean fixData(
|
||||
LevelStorageSource.LevelStorageAccess levelStorageAccess,
|
||||
boolean showUI,
|
||||
Consumer<Boolean> onResume
|
||||
) {
|
||||
File levelPath = levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile();
|
||||
return fixData(levelPath, levelStorageAccess.getLevelId(), showUI, onResume);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the patch level file for new worlds
|
||||
*/
|
||||
public static void initializePatchData() {
|
||||
getMigrationProfile().markApplied();
|
||||
WorldConfig.saveFile(BCLib.MOD_ID);
|
||||
}
|
||||
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private static AtomicProgressListener showProgressScreen() {
|
||||
ProgressScreen ps = new ProgressScreen(
|
||||
Minecraft.getInstance().screen,
|
||||
Component.translatable("title.bclib.datafixer.progress"),
|
||||
Component.translatable("message.bclib.datafixer.progress")
|
||||
);
|
||||
Minecraft.getInstance().setScreen(ps);
|
||||
return ps;
|
||||
}
|
||||
|
||||
private static boolean fixData(File dir, String levelID, boolean showUI, Consumer<Boolean> onResume) {
|
||||
MigrationProfile profile = loadProfileIfNeeded(dir);
|
||||
|
||||
BiConsumer<Boolean, Boolean> runFixes = (createBackup, applyFixes) -> {
|
||||
final AtomicProgressListener progress;
|
||||
if (applyFixes) {
|
||||
if (showUI) {
|
||||
progress = showProgressScreen();
|
||||
} else {
|
||||
progress = new AtomicProgressListener() {
|
||||
private long timeStamp = Util.getMillis();
|
||||
private AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public void incAtomic(int maxProgress) {
|
||||
int percentage = (100 * counter.incrementAndGet()) / maxProgress;
|
||||
if (Util.getMillis() - this.timeStamp >= 1000L) {
|
||||
this.timeStamp = Util.getMillis();
|
||||
BCLib.LOGGER.info("Patching... {}%", percentage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetAtomic() {
|
||||
counter = new AtomicInteger(0);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
}
|
||||
|
||||
public void progressStage(Component component) {
|
||||
BCLib.LOGGER.info("Patcher Stage... {}%", component.getString());
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
progress = null;
|
||||
}
|
||||
|
||||
Supplier<State> runner = () -> {
|
||||
if (createBackup) {
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.waitbackup"));
|
||||
EditWorldScreen.makeBackupAndShowToast(Minecraft.getInstance().getLevelSource(), levelID);
|
||||
}
|
||||
|
||||
if (applyFixes) {
|
||||
return runDataFixes(dir, profile, progress);
|
||||
}
|
||||
|
||||
return new State();
|
||||
};
|
||||
|
||||
if (showUI) {
|
||||
Thread fixerThread = new Thread(() -> {
|
||||
final State state = runner.get();
|
||||
|
||||
Minecraft.getInstance()
|
||||
.execute(() -> {
|
||||
if (profile != null && showUI) {
|
||||
//something went wrong, show the user our error
|
||||
if (state.didFail || state.hasError()) {
|
||||
showLevelFixErrorScreen(state, (markFixed) -> {
|
||||
if (markFixed) {
|
||||
profile.markApplied();
|
||||
}
|
||||
onResume.accept(applyFixes);
|
||||
});
|
||||
} else {
|
||||
onResume.accept(applyFixes);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
fixerThread.start();
|
||||
} else {
|
||||
State state = runner.get();
|
||||
if (state.hasError()) {
|
||||
LOGGER.error("There were Errors while fixing the Level:");
|
||||
LOGGER.error(state.getErrorMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//we have some migrations
|
||||
if (profile != null) {
|
||||
//display the confirm UI.
|
||||
if (showUI) {
|
||||
showBackupWarning(levelID, runFixes);
|
||||
return true;
|
||||
} else {
|
||||
BCLib.LOGGER.warning("Applying Fixes on Level");
|
||||
runFixes.accept(false, true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
private static void showLevelFixErrorScreen(State state, Listener onContinue) {
|
||||
Minecraft.getInstance()
|
||||
.setScreen(new LevelFixErrorScreen(
|
||||
Minecraft.getInstance().screen,
|
||||
state.getErrorMessages(),
|
||||
onContinue
|
||||
));
|
||||
}
|
||||
|
||||
private static MigrationProfile loadProfileIfNeeded(File levelBaseDir) {
|
||||
if (!Configs.MAIN_CONFIG.applyPatches()) {
|
||||
LOGGER.info("World Patches are disabled");
|
||||
return null;
|
||||
}
|
||||
|
||||
MigrationProfile profile = getMigrationProfile();
|
||||
profile.runPrePatches(levelBaseDir);
|
||||
|
||||
if (!profile.hasAnyFixes()) {
|
||||
LOGGER.info("Everything up to date");
|
||||
return null;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static MigrationProfile getMigrationProfile() {
|
||||
final CompoundTag patchConfig = WorldConfig.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
|
||||
MigrationProfile profile = Patch.createMigrationData(patchConfig);
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
static void showBackupWarning(String levelID, BiConsumer<Boolean, Boolean> whenFinished) {
|
||||
Minecraft.getInstance().setScreen(new ConfirmFixScreen(null, whenFinished::accept));
|
||||
}
|
||||
|
||||
private static State runDataFixes(File dir, MigrationProfile profile, AtomicProgressListener progress) {
|
||||
State state = new State();
|
||||
progress.resetAtomic();
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.reading"));
|
||||
List<File> players = getAllPlayers(dir);
|
||||
List<File> regions = getAllRegions(dir, null);
|
||||
final int maxProgress = players.size() + regions.size() + 4;
|
||||
progress.incAtomic(maxProgress);
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.players"));
|
||||
players.parallelStream().forEach((file) -> {
|
||||
fixPlayer(profile, state, file);
|
||||
progress.incAtomic(maxProgress);
|
||||
});
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.level"));
|
||||
fixLevel(profile, state, dir);
|
||||
progress.incAtomic(maxProgress);
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.worlddata"));
|
||||
try {
|
||||
profile.patchWorldData();
|
||||
} catch (PatchDidiFailException e) {
|
||||
state.didFail = true;
|
||||
state.addError("Failed fixing worldconfig (" + e.getMessage() + ")");
|
||||
BCLib.LOGGER.error(e.getMessage());
|
||||
}
|
||||
progress.incAtomic(maxProgress);
|
||||
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.regions"));
|
||||
regions.parallelStream().forEach((file) -> {
|
||||
fixRegion(profile, state, file);
|
||||
progress.incAtomic(maxProgress);
|
||||
});
|
||||
|
||||
if (!state.didFail) {
|
||||
progress.progressStage(Component.translatable("message.bclib.datafixer.progress.saving"));
|
||||
profile.markApplied();
|
||||
WorldConfig.saveFile(BCLib.MOD_ID);
|
||||
}
|
||||
progress.incAtomic(maxProgress);
|
||||
|
||||
progress.stop();
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private static void fixLevel(MigrationProfile profile, State state, File levelBaseDir) {
|
||||
try {
|
||||
LOGGER.info("Inspecting level.dat in " + levelBaseDir);
|
||||
|
||||
//load the level (could already contain patches applied by patchLevelDat)
|
||||
CompoundTag level = profile.getLevelDat(levelBaseDir);
|
||||
boolean[] changed = {profile.isLevelDatChanged()};
|
||||
|
||||
if (profile.getPrePatchException() != null) {
|
||||
throw profile.getPrePatchException();
|
||||
}
|
||||
|
||||
if (level.contains("Data")) {
|
||||
CompoundTag dataTag = (CompoundTag) level.get("Data");
|
||||
if (dataTag.contains("Player")) {
|
||||
CompoundTag player = (CompoundTag) dataTag.get("Player");
|
||||
fixPlayerNbt(player, changed, profile);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed[0]) {
|
||||
LOGGER.warning("Writing '{}'", profile.getLevelDatFile());
|
||||
NbtIo.writeCompressed(level, profile.getLevelDatFile());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BCLib.LOGGER.error("Failed fixing Level-Data.");
|
||||
state.addError("Failed fixing Level-Data in level.dat (" + e.getMessage() + ")");
|
||||
state.didFail = true;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void fixPlayer(MigrationProfile data, State state, File file) {
|
||||
try {
|
||||
LOGGER.info("Inspecting " + file);
|
||||
|
||||
CompoundTag player = readNbt(file);
|
||||
boolean[] changed = {false};
|
||||
fixPlayerNbt(player, changed, data);
|
||||
|
||||
if (changed[0]) {
|
||||
LOGGER.warning("Writing '{}'", file);
|
||||
NbtIo.writeCompressed(player, file);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BCLib.LOGGER.error("Failed fixing Player-Data.");
|
||||
state.addError("Failed fixing Player-Data in " + file.getName() + " (" + e.getMessage() + ")");
|
||||
state.didFail = true;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void fixPlayerNbt(CompoundTag player, boolean[] changed, MigrationProfile data) {
|
||||
//Checking Inventory
|
||||
ListTag inventory = player.getList("Inventory", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(inventory, changed, data, true);
|
||||
|
||||
//Checking EnderChest
|
||||
ListTag enderitems = player.getList("EnderItems", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(enderitems, changed, data, true);
|
||||
|
||||
//Checking ReceipBook
|
||||
if (player.contains("recipeBook")) {
|
||||
CompoundTag recipeBook = player.getCompound("recipeBook");
|
||||
changed[0] |= fixStringIDList(recipeBook, "recipes", data);
|
||||
changed[0] |= fixStringIDList(recipeBook, "toBeDisplayed", data);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean fixStringIDList(CompoundTag root, String name, MigrationProfile data) {
|
||||
boolean _changed = false;
|
||||
if (root.contains(name)) {
|
||||
ListTag items = root.getList(name, Tag.TAG_STRING);
|
||||
ListTag newItems = new ListTag();
|
||||
|
||||
for (Tag tag : items) {
|
||||
final StringTag str = (StringTag) tag;
|
||||
final String replace = data.replaceStringFromIDs(str.getAsString());
|
||||
if (replace != null) {
|
||||
_changed = true;
|
||||
newItems.add(StringTag.valueOf(replace));
|
||||
} else {
|
||||
newItems.add(tag);
|
||||
}
|
||||
}
|
||||
if (_changed) {
|
||||
root.put(name, newItems);
|
||||
}
|
||||
}
|
||||
return _changed;
|
||||
}
|
||||
|
||||
private static void fixRegion(MigrationProfile data, State state, File file) {
|
||||
try {
|
||||
Path path = file.toPath();
|
||||
LOGGER.info("Inspecting " + path);
|
||||
boolean[] changed = new boolean[1];
|
||||
RegionFile region = new RegionFile(path, path.getParent(), true);
|
||||
|
||||
for (int x = 0; x < 32; x++) {
|
||||
for (int z = 0; z < 32; z++) {
|
||||
ChunkPos pos = new ChunkPos(x, z);
|
||||
changed[0] = false;
|
||||
if (region.hasChunk(pos) && !state.didFail) {
|
||||
DataInputStream input = region.getChunkDataInputStream(pos);
|
||||
CompoundTag root = NbtIo.read(input);
|
||||
// if ((root.toString().contains("betternether:chest") || root.toString().contains("bclib:chest"))) {
|
||||
// NbtIo.write(root, new File(file.toString() + "-" + x + "-" + z + ".nbt"));
|
||||
// }
|
||||
input.close();
|
||||
|
||||
//Checking TileEntities
|
||||
ListTag tileEntities = root.getCompound("Level")
|
||||
.getList("TileEntities", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(tileEntities, changed, data, true);
|
||||
|
||||
//Checking Entities
|
||||
ListTag entities = root.getList("Entities", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(entities, changed, data, true);
|
||||
|
||||
//Checking Block Palette
|
||||
ListTag sections = root.getCompound("Level")
|
||||
.getList("Sections", Tag.TAG_COMPOUND);
|
||||
sections.forEach((tag) -> {
|
||||
ListTag palette = ((CompoundTag) tag).getList("Palette", Tag.TAG_COMPOUND);
|
||||
palette.forEach((blockTag) -> {
|
||||
CompoundTag blockTagCompound = ((CompoundTag) blockTag);
|
||||
changed[0] |= data.replaceStringFromIDs(blockTagCompound, "Name");
|
||||
});
|
||||
|
||||
try {
|
||||
changed[0] |= data.patchBlockState(
|
||||
palette,
|
||||
((CompoundTag) tag).getList(
|
||||
"BlockStates",
|
||||
Tag.TAG_LONG
|
||||
)
|
||||
);
|
||||
} catch (PatchDidiFailException e) {
|
||||
BCLib.LOGGER.error("Failed fixing BlockState in " + pos);
|
||||
state.addError("Failed fixing BlockState in " + pos + " (" + e.getMessage() + ")");
|
||||
state.didFail = true;
|
||||
changed[0] = false;
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
if (changed[0]) {
|
||||
LOGGER.warning("Writing '{}': {}/{}", file, x, z);
|
||||
// NbtIo.write(root, new File(file.toString() + "-" + x + "-" + z + "-changed.nbt"));
|
||||
DataOutputStream output = region.getChunkDataOutputStream(pos);
|
||||
NbtIo.write(root, output);
|
||||
output.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
region.close();
|
||||
} catch (Exception e) {
|
||||
BCLib.LOGGER.error("Failed fixing Region.");
|
||||
state.addError("Failed fixing Region in " + file.getName() + " (" + e.getMessage() + ")");
|
||||
state.didFail = true;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static CompoundTag patchConfTag = null;
|
||||
|
||||
static CompoundTag getPatchData() {
|
||||
if (patchConfTag == null) {
|
||||
patchConfTag = WorldConfig.getCompoundTag(BCLib.MOD_ID, Configs.MAIN_PATCH_CATEGORY);
|
||||
}
|
||||
return patchConfTag;
|
||||
}
|
||||
|
||||
static void fixItemArrayWithID(ListTag items, boolean[] changed, MigrationProfile data, boolean recursive) {
|
||||
items.forEach(inTag -> {
|
||||
fixID((CompoundTag) inTag, changed, data, recursive);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static void fixID(CompoundTag inTag, boolean[] changed, MigrationProfile data, boolean recursive) {
|
||||
final CompoundTag tag = inTag;
|
||||
|
||||
changed[0] |= data.replaceStringFromIDs(tag, "id");
|
||||
if (tag.contains("Item")) {
|
||||
CompoundTag item = (CompoundTag) tag.get("Item");
|
||||
fixID(item, changed, data, recursive);
|
||||
}
|
||||
|
||||
if (recursive && tag.contains("Items")) {
|
||||
fixItemArrayWithID(tag.getList("Items", Tag.TAG_COMPOUND), changed, data, true);
|
||||
}
|
||||
if (recursive && tag.contains("Inventory")) {
|
||||
ListTag inventory = tag.getList("Inventory", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(inventory, changed, data, true);
|
||||
}
|
||||
if (tag.contains("tag")) {
|
||||
CompoundTag entityTag = (CompoundTag) tag.get("tag");
|
||||
if (entityTag.contains("BlockEntityTag")) {
|
||||
CompoundTag blockEntityTag = (CompoundTag) entityTag.get("BlockEntityTag");
|
||||
fixID(blockEntityTag, changed, data, recursive);
|
||||
/*ListTag items = blockEntityTag.getList("Items", Tag.TAG_COMPOUND);
|
||||
fixItemArrayWithID(items, changed, data, recursive);*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<File> getAllPlayers(File dir) {
|
||||
List<File> list = new ArrayList<>();
|
||||
dir = new File(dir, "playerdata");
|
||||
if (!dir.exists() || !dir.isDirectory()) {
|
||||
return list;
|
||||
}
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isFile() && file.getName().endsWith(".dat")) {
|
||||
list.add(file);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<File> getAllRegions(File dir, List<File> list) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
getAllRegions(file, list);
|
||||
} else if (file.isFile() && file.getName().endsWith(".mca")) {
|
||||
list.add(file);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* register a new Patch
|
||||
*
|
||||
* @param patch A #Supplier that will instantiate the new Patch Object
|
||||
*/
|
||||
public static void registerPatch(Supplier<Patch> patch) {
|
||||
Patch.getALL().add(patch.get());
|
||||
}
|
||||
|
||||
private static CompoundTag readNbt(File file) throws IOException {
|
||||
try {
|
||||
return NbtIo.readCompressed(file);
|
||||
} catch (ZipException | EOFException e) {
|
||||
return NbtIo.read(file);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.datafixer;
|
||||
|
||||
import org.betterx.bclib.interfaces.PatchBiFunction;
|
||||
import org.betterx.bclib.interfaces.PatchFunction;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
/**
|
||||
* A Patch for level.dat that is always executed no matter what Patchlevel is set in a world.
|
||||
*/
|
||||
public abstract class ForcedLevelPatch extends Patch {
|
||||
protected ForcedLevelPatch(@NotNull String modID, String version) {
|
||||
super(modID, version, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Map<String, String> getIDReplacements() {
|
||||
return new HashMap<String, String>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PatchFunction<CompoundTag, Boolean> getWorldDataPatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final PatchBiFunction<ListTag, ListTag, Boolean> getBlockStatePatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final List<String> getWorldDataIDPaths() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() {
|
||||
return this::runLevelDatPatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called with the contents of level.dat in {@code root}
|
||||
*
|
||||
* @param root The contents of level.dat
|
||||
* @param profile The active migration profile
|
||||
* @return true, if the run did change the contents of root
|
||||
*/
|
||||
abstract protected Boolean runLevelDatPatch(CompoundTag root, MigrationProfile profile);
|
||||
}
|
||||
|
|
@ -1,374 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.datafixer;
|
||||
|
||||
import org.betterx.bclib.BCLib;
|
||||
import org.betterx.bclib.interfaces.PatchBiFunction;
|
||||
import org.betterx.bclib.interfaces.PatchFunction;
|
||||
import org.betterx.worlds.together.util.ModUtil;
|
||||
import org.betterx.worlds.together.world.WorldConfig;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.Tag;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class MigrationProfile {
|
||||
final Set<String> mods;
|
||||
final Map<String, String> idReplacements;
|
||||
final List<PatchFunction<CompoundTag, Boolean>> levelPatchers;
|
||||
final List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatchers;
|
||||
final List<Patch> worldDataPatchers;
|
||||
final Map<String, List<String>> worldDataIDPaths;
|
||||
|
||||
private final CompoundTag config;
|
||||
private CompoundTag level;
|
||||
private File levelBaseDir;
|
||||
private boolean prePatchChangedLevelDat;
|
||||
private boolean didRunPrePatch;
|
||||
private Exception prePatchException;
|
||||
|
||||
MigrationProfile(CompoundTag config, boolean applyAll) {
|
||||
this.config = config;
|
||||
|
||||
this.mods = Collections.unmodifiableSet(Patch.getALL()
|
||||
.stream()
|
||||
.map(p -> p.modID)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
HashMap<String, String> replacements = new HashMap<String, String>();
|
||||
List<PatchFunction<CompoundTag, Boolean>> levelPatches = new LinkedList<>();
|
||||
List<Patch> worldDataPatches = new LinkedList<>();
|
||||
List<PatchBiFunction<ListTag, ListTag, Boolean>> statePatches = new LinkedList<>();
|
||||
HashMap<String, List<String>> worldDataIDPaths = new HashMap<>();
|
||||
for (String modID : mods) {
|
||||
|
||||
Patch.getALL()
|
||||
.stream()
|
||||
.filter(p -> p.modID.equals(modID))
|
||||
.forEach(patch -> {
|
||||
List<String> paths = patch.getWorldDataIDPaths();
|
||||
if (paths != null) worldDataIDPaths.put(modID, paths);
|
||||
|
||||
if (applyAll || currentPatchLevel(modID) < patch.level || patch.alwaysApply) {
|
||||
replacements.putAll(patch.getIDReplacements());
|
||||
if (patch.getLevelDatPatcher() != null)
|
||||
levelPatches.add(patch.getLevelDatPatcher());
|
||||
if (patch.getWorldDataPatcher() != null)
|
||||
worldDataPatches.add(patch);
|
||||
if (patch.getBlockStatePatcher() != null)
|
||||
statePatches.add(patch.getBlockStatePatcher());
|
||||
DataFixerAPI.LOGGER.info("Applying " + patch);
|
||||
} else {
|
||||
DataFixerAPI.LOGGER.info("Ignoring " + patch);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.worldDataIDPaths = Collections.unmodifiableMap(worldDataIDPaths);
|
||||
this.idReplacements = Collections.unmodifiableMap(replacements);
|
||||
this.levelPatchers = Collections.unmodifiableList(levelPatches);
|
||||
this.worldDataPatchers = Collections.unmodifiableList(worldDataPatches);
|
||||
this.statePatchers = Collections.unmodifiableList(statePatches);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
|
||||
* available in Developer-Mode
|
||||
*/
|
||||
public static void fixCustomFolder(File dir) {
|
||||
if (!BCLib.isDevEnvironment()) return;
|
||||
MigrationProfile profile = Patch.createMigrationData();
|
||||
List<File> nbts = getAllNbts(dir, null);
|
||||
nbts.parallelStream().forEach((file) -> {
|
||||
DataFixerAPI.LOGGER.info("Loading NBT " + file);
|
||||
try {
|
||||
CompoundTag root = NbtIo.readCompressed(file);
|
||||
boolean[] changed = {false};
|
||||
int spawnerIdx = -1;
|
||||
if (root.contains("palette")) {
|
||||
ListTag items = root.getList("palette", Tag.TAG_COMPOUND);
|
||||
for (int idx = 0; idx < items.size(); idx++) {
|
||||
final CompoundTag tag = (CompoundTag) items.get(idx);
|
||||
if (tag.contains("Name") && tag.getString("Name").equals("minecraft:spawner"))
|
||||
spawnerIdx = idx;
|
||||
if (tag.contains("Name") && (tag.getString("Name").equals("minecraft:") || tag.getString("Name")
|
||||
.equals(""))) {
|
||||
System.out.println("Empty Name");
|
||||
}
|
||||
if (tag.contains("id") && (tag.getString("id").equals("minecraft:") || tag.getString("id")
|
||||
.equals(""))) {
|
||||
System.out.println("Empty ID");
|
||||
}
|
||||
changed[0] |= profile.replaceStringFromIDs(tag, "Name");
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnerIdx >= 0 && root.contains("blocks")) {
|
||||
ListTag items = root.getList("blocks", Tag.TAG_COMPOUND);
|
||||
for (int idx = 0; idx < items.size(); idx++) {
|
||||
final CompoundTag blockTag = (CompoundTag) items.get(idx);
|
||||
if (blockTag.contains("state") && blockTag.getInt("state") == spawnerIdx && blockTag.contains(
|
||||
"nbt")) {
|
||||
CompoundTag nbt = blockTag.getCompound("nbt");
|
||||
if (nbt.contains("SpawnData")) {
|
||||
final CompoundTag entity = nbt.getCompound("SpawnData");
|
||||
if (!entity.contains("entity")) {
|
||||
CompoundTag data = new CompoundTag();
|
||||
data.put("entity", entity);
|
||||
nbt.put("SpawnData", data);
|
||||
|
||||
changed[0] = true;
|
||||
}
|
||||
}
|
||||
if (nbt.contains("SpawnPotentials")) {
|
||||
ListTag pots = nbt.getList("SpawnPotentials", Tag.TAG_COMPOUND);
|
||||
for (Tag potItemIn : pots) {
|
||||
final CompoundTag potItem = (CompoundTag) potItemIn;
|
||||
if (potItem.contains("Weight")) {
|
||||
int weight = potItem.getInt("Weight");
|
||||
potItem.putInt("weight", weight);
|
||||
potItem.remove("Weight");
|
||||
|
||||
changed[0] = true;
|
||||
}
|
||||
|
||||
if (potItem.contains("Entity")) {
|
||||
CompoundTag entity = potItem.getCompound("Entity");
|
||||
CompoundTag data = new CompoundTag();
|
||||
data.put("entity", entity);
|
||||
|
||||
potItem.put("data", data);
|
||||
potItem.remove("Entity");
|
||||
|
||||
changed[0] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (changed[0]) {
|
||||
DataFixerAPI.LOGGER.info("Writing NBT " + file);
|
||||
NbtIo.writeCompressed(root, file);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static List<File> getAllNbts(File dir, List<File> list) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
}
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isDirectory()) {
|
||||
getAllNbts(file, list);
|
||||
} else if (file.isFile() && file.getName().endsWith(".nbt")) {
|
||||
list.add(file);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
final public CompoundTag getLevelDat(File levelBaseDir) {
|
||||
if (level == null || this.levelBaseDir == null || !this.levelBaseDir.equals(levelBaseDir)) {
|
||||
runPrePatches(levelBaseDir);
|
||||
}
|
||||
return level;
|
||||
}
|
||||
|
||||
final public boolean isLevelDatChanged() {
|
||||
return prePatchChangedLevelDat;
|
||||
}
|
||||
|
||||
final public File getLevelDatFile() {
|
||||
return new File(levelBaseDir, "level.dat");
|
||||
}
|
||||
|
||||
final public Exception getPrePatchException() {
|
||||
return prePatchException;
|
||||
}
|
||||
|
||||
|
||||
final public void runPrePatches(File levelBaseDir) {
|
||||
if (didRunPrePatch) {
|
||||
BCLib.LOGGER.warning("Already did run PrePatches for " + this.levelBaseDir + ".");
|
||||
}
|
||||
BCLib.LOGGER.info("Running Pre Patchers on " + levelBaseDir);
|
||||
|
||||
this.levelBaseDir = levelBaseDir;
|
||||
this.level = null;
|
||||
this.prePatchException = null;
|
||||
didRunPrePatch = true;
|
||||
|
||||
this.prePatchChangedLevelDat = runPreLevelPatches(getLevelDatFile());
|
||||
}
|
||||
|
||||
private boolean runPreLevelPatches(File levelDat) {
|
||||
try {
|
||||
level = NbtIo.readCompressed(levelDat);
|
||||
|
||||
boolean changed = patchLevelDat(level);
|
||||
return changed;
|
||||
} catch (IOException | PatchDidiFailException e) {
|
||||
prePatchException = e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final public void markApplied() {
|
||||
for (String modID : mods) {
|
||||
DataFixerAPI.LOGGER.info(
|
||||
"Updating Patch-Level for '{}' from {} to {}",
|
||||
modID,
|
||||
ModUtil.convertModVersion(currentPatchLevel(modID)),
|
||||
ModUtil.convertModVersion(Patch.maxPatchLevel(modID))
|
||||
);
|
||||
if (config != null)
|
||||
config.putString(modID, Patch.maxPatchVersion(modID));
|
||||
}
|
||||
}
|
||||
|
||||
public String currentPatchVersion(@NotNull String modID) {
|
||||
if (config == null || !config.contains(modID)) return "0.0.0";
|
||||
return config.getString(modID);
|
||||
}
|
||||
|
||||
public int currentPatchLevel(@NotNull String modID) {
|
||||
return ModUtil.convertModVersion(currentPatchVersion(modID));
|
||||
}
|
||||
|
||||
public boolean hasAnyFixes() {
|
||||
boolean hasLevelDatPatches;
|
||||
if (didRunPrePatch != false) {
|
||||
hasLevelDatPatches = prePatchChangedLevelDat;
|
||||
} else {
|
||||
hasLevelDatPatches = levelPatchers.size() > 0;
|
||||
}
|
||||
|
||||
return idReplacements.size() > 0 || hasLevelDatPatches || worldDataPatchers.size() > 0;
|
||||
}
|
||||
|
||||
public String replaceStringFromIDs(@NotNull String val) {
|
||||
final String replace = idReplacements.get(val);
|
||||
return replace;
|
||||
}
|
||||
|
||||
public boolean replaceStringFromIDs(@NotNull CompoundTag tag, @NotNull String key) {
|
||||
if (!tag.contains(key)) return false;
|
||||
|
||||
final String val = tag.getString(key);
|
||||
final String replace = idReplacements.get(val);
|
||||
|
||||
if (replace != null) {
|
||||
DataFixerAPI.LOGGER.warning("Replacing ID '{}' with '{}'.", val, replace);
|
||||
tag.putString(key, replace);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean replaceIDatPath(@NotNull ListTag list, @NotNull String[] parts, int level) {
|
||||
boolean[] changed = {false};
|
||||
if (level == parts.length - 1) {
|
||||
DataFixerAPI.fixItemArrayWithID(list, changed, this, true);
|
||||
} else {
|
||||
list.forEach(inTag -> changed[0] |= replaceIDatPath((CompoundTag) inTag, parts, level + 1));
|
||||
}
|
||||
return changed[0];
|
||||
}
|
||||
|
||||
private boolean replaceIDatPath(@NotNull CompoundTag tag, @NotNull String[] parts, int level) {
|
||||
boolean changed = false;
|
||||
for (int i = level; i < parts.length - 1; i++) {
|
||||
final String part = parts[i];
|
||||
if (tag.contains(part)) {
|
||||
final byte type = tag.getTagType(part);
|
||||
if (type == Tag.TAG_LIST) {
|
||||
ListTag list = tag.getList(part, Tag.TAG_COMPOUND);
|
||||
return replaceIDatPath(list, parts, i);
|
||||
} else if (type == Tag.TAG_COMPOUND) {
|
||||
tag = tag.getCompound(part);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tag != null && parts.length > 0) {
|
||||
final String key = parts[parts.length - 1];
|
||||
final byte type = tag.getTagType(key);
|
||||
if (type == Tag.TAG_LIST) {
|
||||
final ListTag list = tag.getList(key, Tag.TAG_COMPOUND);
|
||||
final boolean[] _changed = {false};
|
||||
if (list.size() == 0) {
|
||||
_changed[0] = DataFixerAPI.fixStringIDList(tag, key, this);
|
||||
} else {
|
||||
DataFixerAPI.fixItemArrayWithID(list, _changed, this, true);
|
||||
}
|
||||
return _changed[0];
|
||||
} else if (type == Tag.TAG_STRING) {
|
||||
return replaceStringFromIDs(tag, key);
|
||||
} else if (type == Tag.TAG_COMPOUND) {
|
||||
final CompoundTag cTag = tag.getCompound(key);
|
||||
boolean[] _changed = {false};
|
||||
DataFixerAPI.fixID(cTag, _changed, this, true);
|
||||
return _changed[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean replaceIDatPath(@NotNull CompoundTag root, @NotNull String path) {
|
||||
String[] parts = path.split("\\.");
|
||||
return replaceIDatPath(root, parts, 0);
|
||||
}
|
||||
|
||||
public boolean patchLevelDat(@NotNull CompoundTag level) throws PatchDidiFailException {
|
||||
boolean changed = false;
|
||||
for (PatchFunction<CompoundTag, Boolean> f : levelPatchers) {
|
||||
changed |= f.apply(level, this);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void patchWorldData() throws PatchDidiFailException {
|
||||
for (Patch patch : worldDataPatchers) {
|
||||
CompoundTag root = WorldConfig.getRootTag(patch.modID);
|
||||
boolean changed = patch.getWorldDataPatcher().apply(root, this);
|
||||
if (changed) {
|
||||
WorldConfig.saveFile(patch.modID);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : worldDataIDPaths.entrySet()) {
|
||||
CompoundTag root = WorldConfig.getRootTag(entry.getKey());
|
||||
boolean[] changed = {false};
|
||||
entry.getValue().forEach(path -> {
|
||||
changed[0] |= replaceIDatPath(root, path);
|
||||
});
|
||||
|
||||
if (changed[0]) {
|
||||
WorldConfig.saveFile(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean patchBlockState(ListTag palette, ListTag states) throws PatchDidiFailException {
|
||||
boolean changed = false;
|
||||
for (PatchBiFunction<ListTag, ListTag, Boolean> f : statePatchers) {
|
||||
changed |= f.apply(palette, states, this);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.datafixer;
|
||||
|
||||
import org.betterx.bclib.interfaces.PatchBiFunction;
|
||||
import org.betterx.bclib.interfaces.PatchFunction;
|
||||
import org.betterx.worlds.together.util.ModUtil;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class Patch {
|
||||
private static final List<Patch> ALL = new ArrayList<>(10);
|
||||
|
||||
/**
|
||||
* The Patch-Level derived from {@link #version}
|
||||
*/
|
||||
public final int level;
|
||||
|
||||
/**
|
||||
* The Patch-Version string
|
||||
*/
|
||||
public final String version;
|
||||
|
||||
/**
|
||||
* The Mod-ID that registered this Patch
|
||||
*/
|
||||
|
||||
@NotNull
|
||||
public final String modID;
|
||||
|
||||
/**
|
||||
* This Mod is tested for each level start
|
||||
*/
|
||||
public final boolean alwaysApply;
|
||||
|
||||
static List<Patch> getALL() {
|
||||
return ALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest Patch-Version that is available for the given mod. If no patches were
|
||||
* registerd for the mod, this will return 0.0.0
|
||||
*
|
||||
* @param modID The ID of the mod you want to query
|
||||
* @return The highest Patch-Version that was found
|
||||
*/
|
||||
public static String maxPatchVersion(@NotNull String modID) {
|
||||
return ALL.stream().filter(p -> p.modID.equals(modID)).map(p -> p.version).reduce((p, c) -> c).orElse("0.0.0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest patch-level that is available for the given mod. If no patches were
|
||||
* registerd for the mod, this will return 0
|
||||
*
|
||||
* @param modID The ID of the mod you want to query
|
||||
* @return The highest Patch-Level that was found
|
||||
*/
|
||||
public static int maxPatchLevel(@NotNull String modID) {
|
||||
return ALL.stream().filter(p -> p.modID.equals(modID)).mapToInt(p -> p.level).max().orElse(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by inheriting classes.
|
||||
* <p>
|
||||
* Performs some sanity checks on the values and might throw a #RuntimeException if any
|
||||
* inconsistencies are found.
|
||||
*
|
||||
* @param modID The ID of the Mod you want to register a patch for. This should be your
|
||||
* ModID only. The ModID can not be {@code null} or an empty String.
|
||||
* @param version The mod-version that introduces the patch. This needs Semantic-Version String
|
||||
* like x.x.x. Developers are responsible for registering their patches in the correct
|
||||
* order (with increasing versions). You are not allowed to register a new
|
||||
* Patch with a version lower or equal than
|
||||
* {@link Patch#maxPatchVersion(String)}
|
||||
*/
|
||||
protected Patch(@NotNull String modID, String version) {
|
||||
this(modID, version, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal Constructor used to create patches that can allways run (no matter what patchlevel a level has)
|
||||
*
|
||||
* @param modID The ID of the Mod
|
||||
* @param version The mod-version that introduces the patch. When {@Code runAllways} is set, this version will
|
||||
* determine the patchlevel that is written to the level
|
||||
* @param alwaysApply When true, this patch is always active, no matter the patchlevel of the world.
|
||||
* This should be used sparingly and just for patches that apply to level.dat (as they only take
|
||||
* effect when changes are detected). Use {@link ForcedLevelPatch} to instatiate.
|
||||
*/
|
||||
Patch(@NotNull String modID, String version, boolean alwaysApply) {
|
||||
//Patchlevels need to be unique and registered in ascending order
|
||||
if (modID == null || modID.isEmpty()) {
|
||||
throw new RuntimeException("[INTERNAL ERROR] Patches need a valid modID!");
|
||||
}
|
||||
|
||||
if (version == null || version.isEmpty()) {
|
||||
throw new RuntimeException("Invalid Mod-Version");
|
||||
}
|
||||
|
||||
this.version = version;
|
||||
this.alwaysApply = alwaysApply;
|
||||
this.level = ModUtil.convertModVersion(version);
|
||||
if (!ALL.stream().filter(p -> p.modID.equals(modID)).noneMatch(p -> p.level >= this.level) || this.level <= 0) {
|
||||
throw new RuntimeException(
|
||||
"[INTERNAL ERROR] Patch-levels need to be created in ascending order beginning with 1.");
|
||||
}
|
||||
|
||||
this.modID = modID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Patch{" + modID + ':' + version + ':' + level + '}';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return block data fixes. Fixes will be applied on world load if current patch-level for
|
||||
* the linked mod is lower than the {@link #level}.
|
||||
* <p>
|
||||
* The default implementation of this method returns an empty map.
|
||||
*
|
||||
* @return The returned Map should contain the replacements. All occurences of the
|
||||
* {@code KeySet} are replaced with the associated value.
|
||||
*/
|
||||
public Map<String, String> getIDReplacements() {
|
||||
return new HashMap<String, String>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PatchFunction} that is called with the content of <i>level.dat</i>.
|
||||
* <p>
|
||||
* The function needs to return {@code true}, if changes were made to the data.
|
||||
* If an error occurs, the method should throw a {@link PatchDidiFailException}
|
||||
* <p>
|
||||
* The default implementation of this method returns null.
|
||||
*
|
||||
* @return {@code true} if changes were applied and we need to save the data
|
||||
*/
|
||||
public PatchFunction<CompoundTag, Boolean> getLevelDatPatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PatchFunction} that is called with the content from the
|
||||
* {@link org.betterx.worlds.together.world.WorldConfig} for this Mod.
|
||||
* The function needs to return {@code true}, if changes were made to the data.
|
||||
* If an error occurs, the method should throw a {@link PatchDidiFailException}
|
||||
* <p>
|
||||
* The default implementation of this method returns null.
|
||||
*
|
||||
* @return {@code true} if changes were applied and we need to save the data
|
||||
*/
|
||||
public PatchFunction<CompoundTag, Boolean> getWorldDataPatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link PatchBiFunction} that is called with pallette and blockstate of
|
||||
* each chunk in every region. This method is called AFTER all ID replacements
|
||||
* from {@link #getIDReplacements()} were applied to the pallete.
|
||||
* <p>
|
||||
* The first parameter is the palette and the second is the blockstate.
|
||||
* <p>
|
||||
* The function needs to return {@code true}, if changes were made to the data.
|
||||
* If an error occurs, the method should throw a {@link PatchDidiFailException}
|
||||
* <p>
|
||||
* The default implementation of this method returns null.
|
||||
*
|
||||
* @return {@code true} if changes were applied and we need to save the data
|
||||
*/
|
||||
public PatchBiFunction<ListTag, ListTag, Boolean> getBlockStatePatcher() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ready to use data for all currently registered patches. The list of
|
||||
* patches is selected by the current patch-level of the world.
|
||||
* <p>
|
||||
* A {@link #Patch} with a given {@link #level} is only included if the patch-level of the
|
||||
* world is less
|
||||
*
|
||||
* @param config The current patch-level configuration*
|
||||
* @return a new {@link MigrationProfile} Object.
|
||||
*/
|
||||
static MigrationProfile createMigrationData(CompoundTag config) {
|
||||
return new MigrationProfile(config, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is supposed to be used by developers to apply id-patches to custom nbt structures. It is only
|
||||
* available in Developer-Mode
|
||||
*/
|
||||
static MigrationProfile createMigrationData() {
|
||||
return new MigrationProfile(null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of paths where your mod stores IDs in your {@link org.betterx.worlds.together.world.WorldConfig}-File.
|
||||
* <p>
|
||||
* {@link DataFixerAPI} will use information from the latest patch that returns a non-null-result. This list is used
|
||||
* to automatically fix changed IDs from all active patches (see {@link Patch#getIDReplacements()}
|
||||
* <p>
|
||||
* The end of the path can either be a {@link net.minecraft.nbt.StringTag}, a {@link net.minecraft.nbt.ListTag} or
|
||||
* a {@link CompoundTag}. If the Path contains a non-leaf {@link net.minecraft.nbt.ListTag}, all members of that
|
||||
* list will be processed. For example:
|
||||
* <pre>
|
||||
* - global +
|
||||
* | - key (String)
|
||||
* | - items (List) +
|
||||
* | - { id (String) }
|
||||
* | - { id (String) }
|
||||
* </pre>
|
||||
* The path <b>global.items.id</b> will fix all <i>id</i>-entries in the <i>items</i>-list, while the path
|
||||
* <b>global.key</b> will only fix the <i>key</i>-entry.
|
||||
* <p>
|
||||
* if the leaf-entry (= the last part of the path, which would be <i>items</i> in <b>global.items</b>) is a
|
||||
* {@link CompoundTag}, the system will fix any <i>id</i> entry. If the {@link CompoundTag} contains an <i>item</i>
|
||||
* or <i>tag.BlockEntityTag</i> entry, the system will recursivley continue with those. If an <i>items</i>
|
||||
* or <i>inventory</i>-{@link net.minecraft.nbt.ListTag} was found, the system will continue recursivley with
|
||||
* every item of that list.
|
||||
* <p>
|
||||
* if the leaf-entry is a {@link net.minecraft.nbt.ListTag}, it is handle the same as a child <i>items</i> entry
|
||||
* of a {@link CompoundTag}.
|
||||
*
|
||||
* @return {@code null} if nothing changes or a list of Paths in your {@link org.betterx.worlds.together.world.WorldConfig}-File.
|
||||
* Paths are dot-seperated (see {@link org.betterx.worlds.together.world.WorldConfig#getCompoundTag(String, String)}).
|
||||
*/
|
||||
public List<String> getWorldDataIDPaths() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package org.betterx.bclib.api.v2.datafixer;
|
||||
|
||||
public class PatchDidiFailException extends Exception {
|
||||
public PatchDidiFailException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PatchDidiFailException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue