Compare commits

..

20 commits
3.0.11 ... 1.17

Author SHA1 Message Date
Frank Bauer
7d01f14808 Merge commit 'befdd2c10a' into 1.17 2021-07-05 15:39:37 +02:00
Frank Bauer
67c4decf8e Revert "Added automatic tool configuration for blocks"
This reverts commit 70b109758d.
2021-07-04 23:55:14 +02:00
Frank Bauer
70b109758d Added automatic tool configuration for blocks 2021-07-04 16:33:39 +02:00
Frank Bauer
e7841b1b27 blockStateis null if it can not be placed 2021-06-29 23:32:31 +02:00
Frank Bauer
447a199379 Revert "re-enable custom tick functions for furnaces"
This reverts commit b54b14782e.
2021-06-29 08:09:22 +02:00
Frank Bauer
b54b14782e re-enable custom tick functions for furnaces 2021-06-28 21:16:58 +02:00
Frank Bauer
1d64b215f4 Make sure *Furnace*-Blocks tick 2021-06-28 21:15:35 +02:00
Frank Bauer
622f611624 this.minecraft is not available in the constructor, moved to init 2021-06-28 18:05:19 +02:00
Frank Bauer
3725ed9367 Removed code for custom sign-models 2021-06-28 17:47:23 +02:00
Frank Bauer
ad40784871 texOffs before addBox 2021-06-28 17:38:11 +02:00
Aleksey
51aa7b5aff Merge branch 'main' into quiqueck_1.17 2021-06-27 17:40:58 +03:00
Frank Bauer
ec4b105f09 HEIGHTMAP_SQUARE has index 17 2021-06-25 17:48:58 +02:00
Frank Bauer
5340a50f0c Merge branch '1.17' of github.com-quiqueck:quiqueck/BCLib into 1.17 2021-06-25 13:43:46 +02:00
Frank Bauer
3313663a3f Merged changes from upstream 2021-06-25 13:42:30 +02:00
Aleksey
9a378bb69c
Merge branch '1.17' into 1.17 2021-06-25 14:41:48 +03:00
Frank Bauer
986dc2c7a0 Removed wildcard imports 2021-06-25 12:39:55 +02:00
Frank Bauer
307486a32c Changes to TagLoader 2021-06-25 11:34:32 +02:00
Frank Bauer
3c5661aed9 Chest rendering 2021-06-25 11:34:24 +02:00
Frank Bauer
cf2e0e012d Generalized SignModel handling 2021-06-25 10:50:38 +02:00
Frank Bauer
a39989b331 Signs with custom Models 2021-06-25 10:23:06 +02:00
1029 changed files with 18554 additions and 69094 deletions

View file

@ -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

View file

@ -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

View file

@ -1 +0,0 @@
blank_issues_enabled: true

5
.gitignore vendored
View file

@ -27,10 +27,5 @@ bin/
# fabric
run/
run-client/
run-server/
output/
*.log
/CHANGES.md
/src/main/generated/.cache/
/modrinth.json

View file

@ -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>

View file

@ -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.

View file

@ -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
View file

@ -1,146 +1,41 @@
[![](https://jitpack.io/v/quiqueck/BCLib.svg)](https://jitpack.io/#quiqueck/BCLib)
[![](https://jitpack.io/v/paulevsGitch/BCLib.svg)](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

View file

@ -1,8 +0,0 @@
plugins {
id 'idea'
id 'eclipse'
id 'fabric-loom'
id 'maven-publish'
}
apply from: "bclib.gradle"

View file

@ -1,184 +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}"
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
}

View file

@ -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
}
}
}
}

View file

@ -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.83.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.11
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

Binary file not shown.

View file

@ -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
View 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
View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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'
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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"
}
}
]
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -1,6 +0,0 @@
{
"replace": false,
"values": [
"minecraft:end_barrens"
]
}

View file

@ -1,6 +0,0 @@
{
"replace": false,
"values": [
"minecraft:the_end"
]
}

View file

@ -1,7 +0,0 @@
{
"replace": false,
"values": [
"minecraft:end_highlands",
"minecraft:end_midlands"
]
}

View file

@ -1,6 +0,0 @@
{
"replace": false,
"values": [
"minecraft:end_midlands"
]
}

View file

@ -1,10 +0,0 @@
{
"replace": false,
"values": [
"minecraft:small_end_islands",
{
"id": "nullscape:void_barrens",
"required": false
}
]
}

View file

@ -1,8 +0,0 @@
{
"replace": false,
"values": [
"bclib:normal",
"bclib:amplified",
"bclib:large"
]
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 {
}

View file

@ -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
);
}
}
}

View file

@ -1,200 +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.core.HolderLookup;
import net.minecraft.data.registries.VanillaRegistries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.dimension.LevelStem;
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 {
private static final String NETHER_BIOME_SOURCE = "bclib:nether_biome_source";
private static final String END_BIOME_SOURCE = "bclib:end_biome_source";
private static final String MC_NETHER = "minecraft:the_nether";
private static final String MC_END = "minecraft:the_end";
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();
final CompoundTag worldGenSettings = root.getCompound("Data").getCompound("WorldGenSettings");
final CompoundTag dimensions = worldGenSettings.getCompound("dimensions");
final HolderLookup.Provider registryAccess = VanillaRegistries.createLookup();
final RegistryOps<Tag> registryOps = RegistryOps.create(NbtOps.INSTANCE, registryAccess);
boolean result = false;
result |= checkDimension(worldGenSettings, dimensions, registryAccess, registryOps, LevelStem.NETHER);
result |= checkDimension(worldGenSettings, dimensions, registryAccess, registryOps, LevelStem.END);
System.out.println("Dimensions:" + dimensions);
return result;
// if (root != null) return false;
//
// boolean result = false;
//
// if (GeneratorOptions.fixNetherBiomeSource()) {
// if (!dimensions.contains(MC_NETHER) || !isBCLibEntry(dimensions.getCompound(MC_NETHER))) {
// CompoundTag dimRoot = new CompoundTag();
// dimRoot.put("generator", makeNetherGenerator(seed));
// dimRoot.putString("type", MC_NETHER);
// dimensions.put(MC_NETHER, dimRoot);
// result = true;
// }
// }
//
// if (GeneratorOptions.fixEndBiomeSource()) {
// if (!dimensions.contains(MC_END) || !isBCLibEntry(dimensions.getCompound(MC_END))) {
// CompoundTag dimRoot = new CompoundTag();
// dimRoot.put("generator", makeEndGenerator(seed));
// dimRoot.putString("type", MC_END);
// dimensions.put(MC_END, dimRoot);
// result = true;
// }
// }
//
// return result;
}
private boolean checkDimension(
CompoundTag worldGenSettings,
CompoundTag dimensions,
HolderLookup.Provider registryAccess,
RegistryOps<Tag> registryOps,
ResourceKey<LevelStem> dimensionKey
) {
boolean result = false;
// final long seed = worldGenSettings.contains("seed")
// ? worldGenSettings.getLong("seed")
// : MHelper.RANDOM.nextLong();
//
// final boolean genStructures = !worldGenSettings.contains("generate_features") || worldGenSettings.getBoolean(
// "generate_features");
//
// final boolean genBonusChest = worldGenSettings.contains("bonus_chest") && worldGenSettings.getBoolean(
// "bonus_chest");
//
//
// CompoundTag dimensionTag = dimensions.getCompound(dimensionKey.location().toString());
// Optional<WorldGenSettings> oWorldGen = WorldGenSettings.CODEC
// .parse(new Dynamic<>(registryOps, worldGenSettings))
// .result();
//
// Optional<LevelStem> oLevelStem = LevelStem.CODEC
// .parse(new Dynamic<>(registryOps, dimensionTag))
// .resultOrPartial(BCLib.LOGGER::error);
//
// Optional<ChunkGenerator> netherGenerator = oLevelStem.map(l -> l.generator());
// int biomeSourceVersion = LevelGenUtil.getBiomeVersionForGenerator(netherGenerator.orElse(null));
// int targetVersion = LevelGenUtil.getBiomeVersionForCurrentWorld(dimensionKey);
// if (biomeSourceVersion != targetVersion) {
// Optional<Holder<LevelStem>> refLevelStem = LevelGenUtil.referenceStemForVersion(
// dimensionKey,
// targetVersion,
// registryAccess,
// oWorldGen.map(g -> g.seed()).orElse(seed),
// oWorldGen.map(g -> g.generateStructures()).orElse(genStructures),
// oWorldGen.map(g -> g.generateBonusChest()).orElse(genBonusChest)
// );
//
// BCLib.LOGGER.warning("The world uses the BiomeSource Version " + biomeSourceVersion + " but should have " + targetVersion + ".");
// BCLib.LOGGER.warning("Dimension: " + dimensionKey);
// BCLib.LOGGER.warning("Found: " + netherGenerator);
// BCLib.LOGGER.warning("Should: " + refLevelStem.map(l -> l.value().generator()));
//
// if (refLevelStem.isPresent()) {
// var levelStem = refLevelStem.get();
// BCLib.LOGGER.warning("Repairing level.dat in order to ensure world continuity.");
// var codec = LevelStem.CODEC.orElse(levelStem.value());
// var encodeResult = codec.encodeStart(registryOps, levelStem.value());
// if (encodeResult.result().isPresent()) {
// dimensions.put(dimensionKey.location().toString(), encodeResult.result().get());
// result = true;
// } else {
// BCLib.LOGGER.error("Unable to encode '" + dimensionKey + "' generator for level.dat.");
// }
// } else {
// BCLib.LOGGER.error("Unable to update '" + dimensionKey + "' generator in level.dat.");
// }
// }
return result;
}
private boolean isBCLibEntry(CompoundTag dimRoot) {
String type = dimRoot.getCompound("generator").getCompound("biome_source").getString("type");
if (type.isEmpty() || type.length() < 5) {
return false;
}
return type.startsWith("bclib");
}
public static CompoundTag makeNetherGenerator(long seed) {
CompoundTag generator = new CompoundTag();
generator.putString("type", "minecraft:noise");
generator.putString("settings", "minecraft:nether");
generator.putLong("seed", seed);
CompoundTag biomeSource = new CompoundTag();
biomeSource.putString("type", NETHER_BIOME_SOURCE);
biomeSource.putLong("seed", seed);
generator.put("biome_source", biomeSource);
return generator;
}
public static CompoundTag makeEndGenerator(long seed) {
CompoundTag generator = new CompoundTag();
generator.putString("type", "minecraft:noise");
generator.putString("settings", "minecraft:end");
generator.putLong("seed", seed);
CompoundTag biomeSource = new CompoundTag();
biomeSource.putString("type", END_BIOME_SOURCE);
biomeSource.putLong("seed", seed);
generator.put("biome_source", biomeSource);
return generator;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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
);
}
}

View file

@ -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;
}
}

View file

@ -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!");
}
}
}
}
}

View file

@ -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);
}
}

View file

@ -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
);
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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!");
}
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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));
}
}

View 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);
}
}
});
}
}

View file

@ -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);
}
}

View file

@ -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.");
}
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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>&lt;--</td>
* <td>{@link HelloServer}</td>
* <td>Sends the current BLib-Version installed on the Client</td>
* </tr>
* <tr>
* <td>{@link HelloClient}</td>
* <td>--&gt;</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>&lt;--</td>
* <td>{@link RequestFiles}</td>
* <td>Request missing or out of sync Files from the Server</td>
* </tr>
* <tr>
* <td>{@link SendFiles}</td>
* <td>--&gt;</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);
}
}

View file

@ -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();
}
}

View file

@ -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();
}));
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View 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);
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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