Compare commits

..

20 commits

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
836 changed files with 18568 additions and 56225 deletions

View file

@ -1,75 +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: 2.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.5x.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.19
- 1.18.2
- 1.18.1
- 1.18
- 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

1
.gitignore vendored
View file

@ -27,6 +27,5 @@ bin/
# fabric
run/
run-client/
output/
*.log

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>

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,241 +0,0 @@
buildscript {
dependencies {
classpath 'org.kohsuke:github-api:1.114'
}
repositories {
gradlePluginPortal()
}
}
apply from: "gradle/bclib-common.gradle"
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' }
}
archivesBaseName = project.archives_base_name
version = project.mod_version
loom {
accessWidenerPath = file("src/main/resources/bclib.accesswidener")
}
moduleDependencies(project, ["together-v1"])
repositories {
maven { url 'https://maven.terraformersmc.com/releases' }
}
dependencies {
modCompileOnly "com.terraformersmc:modmenu:${project.modmenu_version}"
}
allprojects {
group = project.maven_group
apply plugin: "java-library"
apply plugin: "fabric-loom"
tasks.withType(GenerateModuleMetadata) {
enabled = false
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
it.options.release = 17
}
java {
// Must be added before the split source sets are setup.
withSourcesJar()
}
allprojects.each { p ->
loom.mods.register(p.name) {
sourceSet p.sourceSets.main
}
}
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}"
}
loom {
shareRemapCaches = true
}
tasks.withType(ProcessResources).configureEach {
inputs.property "version", project.version
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
tasks.withType(AbstractArchiveTask) {
preserveFileTimestamps = false
reproducibleFileOrder = true
}
jar {
from "../LICENSE"
}
}
subprojects {
javadoc.enabled = false
}
javadoc {
options {
source = "17"
encoding = "UTF-8"
charSet = "UTF-8"
memberLevel = JavadocMemberLevel.PACKAGE
addStringOption("Xdoclint:none", "-quiet")
}
allprojects.each {
if (it.name == "deprecated") return
source(it.sourceSets.main.allJava.srcDirs)
}
classpath = files(sourceSets.main.compileClasspath)
include("**")
failOnError false
}
task javadocJar(type: Jar) {
dependsOn javadoc
from javadoc.destinationDir
//Set as `fatjavadoc` to prevent an ide form trying to use this javadoc, over using the modules javadoc
archiveClassifier = "fatjavadoc"
}
build.dependsOn javadocJar
loom {}
processResources {
println "Version: ${project.mod_version}"
inputs.property "version", project.mod_version
filesMatching("fabric.mod.json") {
expand "version": project.mod_version
}
}
subprojects.each {
remapJar.dependsOn("${it.path}:remapJar")
}
dependencies {
afterEvaluate {
subprojects.each {
api project(path: "${it.path}", configuration: "namedElements")
}
}
}
remapJar {
afterEvaluate {
subprojects.each {
nestedJars.from project("${it.path}").tasks.getByName("remapJar")
}
}
}
// 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");
}
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("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,8 +1,164 @@
plugins {
id 'idea'
id 'eclipse'
id 'fabric-loom' version "${loom_version}"
id 'maven-publish'
buildscript {
dependencies {
classpath 'org.kohsuke:github-api:1.114'
}
}
apply from: "bclib.gradle"
plugins {
id 'idea'
id 'eclipse'
id 'fabric-loom' version '0.8-SNAPSHOT'
id 'maven-publish'
}
sourceCompatibility = JavaVersion.VERSION_16
targetCompatibility = JavaVersion.VERSION_16
archivesBaseName = project.archives_base_name
version = project.mod_version
group = project.maven_group
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' }
}
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}"
}
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"
}
}
}
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'
from(sourceSets.main.resources.srcDirs) {
include "fabric.mod.json"
expand "version": project.version
}
from(sourceSets.main.resources.srcDirs) {
exclude "fabric.mod.json"
}
}
// 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"
}
javadoc {
options.tags = [ "reason" ]
}
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"
}
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,16 +1,18 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx2G
#Loom
loom_version=0.12-SNAPSHOT
# Fabric Properties
# check these on https://fabricmc.net/versions.html
minecraft_version=1.19
loader_version=0.14.8
fabric_version=0.57.0+1.19
# check these on https://fabricmc.net/use
minecraft_version= 1.17
yarn_mappings= 6
loader_version= 0.11.6
# Mod Properties
mod_version=2.0.10
together-v1-version=2.0.10
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=4.0.0
# 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

View file

@ -1,21 +0,0 @@
def getSubprojectVersion(project) {
// Get the version from the gradle.properties file
return project.properties["${project.name}-version"]
}
def moduleDependencies(project, List<String> depNames) {
def deps = depNames.iterator().collect { project.dependencies.project(path: ":$it", configuration: 'namedElements') }
project.dependencies {
deps.each {
api it
}
}
}
ext {
getSubprojectVersion = this.&getSubprojectVersion
moduleDependencies = this.&moduleDependencies
compileOnlyDependencies = this.&compileOnlyDependencies
}

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

@ -4,10 +4,6 @@ pluginManagement {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenLocal()
gradlePluginPortal()
}
}
include("together-v1")

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,159 +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.BCLBiome;
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeBuilder;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.bclib.api.v2.levelgen.structures.TemplatePiece;
import org.betterx.bclib.api.v2.levelgen.surface.rules.Conditions;
import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates;
import org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers;
import org.betterx.bclib.commands.CommandRegistry;
import org.betterx.bclib.config.Configs;
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.worlds.together.WorldsTogether;
import org.betterx.worlds.together.tag.v3.TagManager;
import org.betterx.worlds.together.util.Logger;
import org.betterx.worlds.together.world.WorldConfig;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Blocks;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer;
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);
@Override
public void onInitialize() {
LevelGenEvents.register();
BlockPredicates.ensureStaticInitialization();
BaseRegistry.register();
GeneratorOptions.init();
BaseBlockEntities.register();
BCLibEndBiomeSource.register();
BCLibNetherBiomeSource.register();
TagManager.ensureStaticallyLoaded();
CraftingRecipes.init();
WorldConfig.registerModCache(MOD_ID);
DataExchangeAPI.registerMod(MOD_ID);
AnvilRecipe.register();
Conditions.registerAll();
CommandRegistry.register();
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();
if (false && isDevEnvironment()) {
BCLBiome theYellow = BCLBiomeBuilder
.start(makeID("the_yellow"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0xFFFF00)
.waterColor(0x777700)
.waterFogColor(0xFFFF00)
.skyColor(0xAAAA00)
.addNetherClimateParamater(-1, 1)
.surface(Blocks.YELLOW_CONCRETE)
.build();
BiomeAPI.registerEndLandBiome(theYellow);
BCLBiome theBlue = BCLBiomeBuilder
.start(makeID("the_blue"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0x0000FF)
.waterColor(0x000077)
.waterFogColor(0x0000FF)
.skyColor(0x0000AA)
.addNetherClimateParamater(-1, 1)
.surface(Blocks.LIGHT_BLUE_CONCRETE)
.build();
BiomeAPI.registerEndLandBiome(theBlue);
BCLBiome theGray = BCLBiomeBuilder
.start(makeID("the_gray"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0xFFFFFF)
.waterColor(0x777777)
.waterFogColor(0xFFFFFF)
.skyColor(0xAAAAAA)
.addNetherClimateParamater(-1, 1)
.surface(Blocks.GRAY_CONCRETE)
.build();
BiomeAPI.registerEndVoidBiome(theGray);
BCLBiome theOrange = BCLBiomeBuilder
.start(makeID("the_orange"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0xFF7700)
.waterColor(0x773300)
.waterFogColor(0xFF7700)
.skyColor(0xAA7700)
.addNetherClimateParamater(-1, 1.1f)
.surface(Blocks.ORANGE_CONCRETE)
.build();
BiomeAPI.registerNetherBiome(theOrange);
BCLBiome thePurple = BCLBiomeBuilder
.start(makeID("the_purple"))
.precipitation(Biome.Precipitation.NONE)
.temperature(1.0f)
.wetness(1.0f)
.fogColor(0xFF00FF)
.waterColor(0x770077)
.waterFogColor(0xFF00FF)
.skyColor(0xAA00AA)
.addNetherClimateParamater(-1.1f, 1)
.surface(Blocks.PURPLE_CONCRETE)
.build();
BiomeAPI.registerNetherBiome(thePurple);
}
}
public static boolean isDevEnvironment() {
return FabricLoader.getInstance().isDevelopmentEnvironment();
}
public static boolean isClient() {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
}
public static ResourceLocation makeID(String path) {
return new ResourceLocation(MOD_ID, path);
}
}

View file

@ -1,183 +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.generator.GeneratorOptions;
import org.betterx.bclib.api.v2.levelgen.LevelGenUtil;
import org.betterx.bclib.config.Configs;
import net.minecraft.core.RegistryAccess;
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;
public final class BCLibPatch {
public static void register() {
// TODO separate values in config on client side (config screen)
if (Configs.MAIN_CONFIG.repairBiomes() && (GeneratorOptions.fixEndBiomeSource() || GeneratorOptions.fixNetherBiomeSource())) {
DataFixerAPI.registerPatch(BiomeSourcePatch::new);
}
}
}
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 RegistryAccess registryAccess = RegistryAccess.builtinCopy();
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,
RegistryAccess 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,201 +0,0 @@
package org.betterx.bclib.api.v2;
import org.betterx.bclib.util.BlocksHelper;
import org.betterx.bclib.util.WeightedList;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
public class BonemealAPI {
private static final Map<ResourceLocation, Map<Block, WeightedList<BiConsumer<Level, BlockPos>>>> WATER_GRASS_BIOMES = Maps.newHashMap();
private static final Map<ResourceLocation, Map<Block, WeightedList<BiConsumer<Level, BlockPos>>>> LAND_GRASS_BIOMES = Maps.newHashMap();
private static final Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> WATER_GRASS_TYPES = Maps.newHashMap();
private static final Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> LAND_GRASS_TYPES = Maps.newHashMap();
private static final Map<Block, Block> SPREADABLE_BLOCKS = Maps.newHashMap();
private static final Set<Block> TERRAIN_TO_SPREAD = Sets.newHashSet();
private static final Set<Block> TERRAIN = Sets.newHashSet();
public static void addSpreadableBlock(Block spreadableBlock, Block surfaceForSpread) {
SPREADABLE_BLOCKS.put(spreadableBlock, surfaceForSpread);
TERRAIN_TO_SPREAD.add(surfaceForSpread);
TERRAIN.add(surfaceForSpread);
}
public static boolean isTerrain(Block block) {
return TERRAIN.contains(block);
}
public static boolean isSpreadableTerrain(Block block) {
return TERRAIN_TO_SPREAD.contains(block);
}
public static Block getSpreadable(Block block) {
return SPREADABLE_BLOCKS.get(block);
}
public static void addLandGrass(Block plant, Block... terrain) {
addLandGrass(makeConsumer(plant), terrain);
}
public static void addLandGrass(BiConsumer<Level, BlockPos> plant, Block... terrain) {
for (Block block : terrain) {
addLandGrass(plant, block, 1F);
}
}
public static void addLandGrass(ResourceLocation biome, Block plant, Block... terrain) {
addLandGrass(biome, makeConsumer(plant), terrain);
}
public static void addLandGrass(ResourceLocation biome, BiConsumer<Level, BlockPos> plant, Block... terrain) {
for (Block block : terrain) {
addLandGrass(biome, plant, block, 1F);
}
}
public static void addLandGrass(Block plant, Block terrain, float chance) {
addLandGrass(makeConsumer(plant), terrain, chance);
}
public static void addLandGrass(BiConsumer<Level, BlockPos> plant, Block terrain, float chance) {
WeightedList<BiConsumer<Level, BlockPos>> list = LAND_GRASS_TYPES.get(terrain);
if (list == null) {
list = new WeightedList<>();
LAND_GRASS_TYPES.put(terrain, list);
}
TERRAIN.add(terrain);
list.add(plant, chance);
}
public static void addLandGrass(ResourceLocation biome, Block plant, Block terrain, float chance) {
addLandGrass(biome, makeConsumer(plant), terrain, chance);
}
public static void addLandGrass(
ResourceLocation biome,
BiConsumer<Level, BlockPos> plant,
Block terrain,
float chance
) {
Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> map = LAND_GRASS_BIOMES.get(biome);
if (map == null) {
map = Maps.newHashMap();
LAND_GRASS_BIOMES.put(biome, map);
}
WeightedList<BiConsumer<Level, BlockPos>> list = map.get(terrain);
if (list == null) {
list = new WeightedList<>();
map.put(terrain, list);
}
TERRAIN.add(terrain);
list.add(plant, chance);
}
public static void addWaterGrass(Block plant, Block... terrain) {
addWaterGrass(makeConsumer(plant), terrain);
}
public static void addWaterGrass(BiConsumer<Level, BlockPos> plant, Block... terrain) {
for (Block block : terrain) {
addWaterGrass(plant, block, 1F);
}
}
public static void addWaterGrass(ResourceLocation biome, Block plant, Block... terrain) {
addWaterGrass(biome, makeConsumer(plant), terrain);
}
public static void addWaterGrass(ResourceLocation biome, BiConsumer<Level, BlockPos> plant, Block... terrain) {
for (Block block : terrain) {
addWaterGrass(biome, plant, block, 1F);
}
}
public static void addWaterGrass(Block plant, Block terrain, float chance) {
addWaterGrass(makeConsumer(plant), terrain, chance);
}
public static void addWaterGrass(BiConsumer<Level, BlockPos> plant, Block terrain, float chance) {
WeightedList<BiConsumer<Level, BlockPos>> list = WATER_GRASS_TYPES.get(terrain);
if (list == null) {
list = new WeightedList<>();
WATER_GRASS_TYPES.put(terrain, list);
}
TERRAIN.add(terrain);
list.add(plant, chance);
}
public static void addWaterGrass(ResourceLocation biome, Block plant, Block terrain, float chance) {
addWaterGrass(biome, makeConsumer(plant), terrain, chance);
}
public static void addWaterGrass(
ResourceLocation biome,
BiConsumer<Level, BlockPos> plant,
Block terrain,
float chance
) {
Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> map = WATER_GRASS_BIOMES.get(biome);
if (map == null) {
map = Maps.newHashMap();
WATER_GRASS_BIOMES.put(biome, map);
}
WeightedList<BiConsumer<Level, BlockPos>> list = map.get(terrain);
if (list == null) {
list = new WeightedList<>();
map.put(terrain, list);
}
TERRAIN.add(terrain);
list.add(plant, chance);
}
public static BiConsumer<Level, BlockPos> getLandGrass(
ResourceLocation biomeID,
Block terrain,
RandomSource random
) {
Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> map = LAND_GRASS_BIOMES.get(biomeID);
WeightedList<BiConsumer<Level, BlockPos>> list;
if (map != null) {
list = map.get(terrain);
if (list == null) {
list = LAND_GRASS_TYPES.get(terrain);
}
} else {
list = LAND_GRASS_TYPES.get(terrain);
}
return list == null ? null : list.get(random);
}
public static BiConsumer<Level, BlockPos> getWaterGrass(
ResourceLocation biomeID,
Block terrain,
RandomSource random
) {
Map<Block, WeightedList<BiConsumer<Level, BlockPos>>> map = WATER_GRASS_BIOMES.get(biomeID);
WeightedList<BiConsumer<Level, BlockPos>> list;
if (map != null) {
list = map.get(terrain);
if (list == null) {
list = WATER_GRASS_TYPES.get(terrain);
}
} else {
list = WATER_GRASS_TYPES.get(terrain);
}
return list == null ? null : list.get(random);
}
private static BiConsumer<Level, BlockPos> makeConsumer(Block block) {
return (level, pos) -> BlocksHelper.setWithoutUpdate(level, pos, block);
}
}

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,145 +0,0 @@
package org.betterx.bclib.api.v2;
import org.betterx.bclib.api.v2.datafixer.DataFixerAPI;
import net.minecraft.core.Registry;
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(Registry.BIOME_REGISTRY);
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,149 +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.blocks.BaseBarrelBlock;
import org.betterx.bclib.blocks.BaseChestBlock;
import org.betterx.bclib.blocks.BaseFurnaceBlock;
import org.betterx.bclib.blocks.BaseSignBlock;
import org.betterx.bclib.client.render.BCLRenderLayer;
import org.betterx.bclib.client.render.BaseChestBlockEntityRenderer;
import org.betterx.bclib.client.render.BaseSignBlockEntityRenderer;
import org.betterx.bclib.config.Configs;
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.registry.BaseBlockEntities;
import org.betterx.worlds.together.tag.v3.MineableTags;
import org.betterx.worlds.together.tag.v3.TagManager;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.Registry;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
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) {
Registry.BLOCK.forEach(block -> {
processBlockCommon(block);
if (isClient) {
processBlockClient(block);
}
});
Registry.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();
}
@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);
} else if (block instanceof BaseSignBlock) {
BaseSignBlockEntityRenderer.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();
}
}
private static void processBlockCommon(Block block) {
if (block instanceof PostInitable) {
((PostInitable) block).postInit();
}
if (block instanceof BaseChestBlock) {
BaseBlockEntities.CHEST.registerBlock(block);
} else if (block instanceof BaseSignBlock) {
BaseBlockEntities.SIGN.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) {
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 TagProvider) {
((TagProvider) block).addTags(blockTags, itemTags);
blockTags.forEach(tag -> TagManager.BLOCKS.add(tag, block));
itemTags.forEach(tag -> TagManager.ITEMS.add(tag, block.asItem()));
blockTags.clear();
itemTags.clear();
}
}
}

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,77 +0,0 @@
package org.betterx.bclib.api.v2;
import org.betterx.worlds.together.world.WorldConfig;
import net.minecraft.nbt.CompoundTag;
import java.io.File;
/**
* @deprecated Implementation moved to {@link WorldConfig}
*/
@Deprecated(forRemoval = true)
public class WorldDataAPI {
/**
* @deprecated use {@link WorldConfig#load(File)} instead
*/
@Deprecated(forRemoval = true)
public static void load(File dataDir) {
WorldConfig.load(dataDir);
}
/**
* @deprecated use {@link WorldConfig#registerModCache(String)} instead
*/
@Deprecated(forRemoval = true)
public static void registerModCache(String modID) {
WorldConfig.registerModCache(modID);
}
/**
* @deprecated use {@link WorldConfig#getRootTag(String)} instead
*/
@Deprecated(forRemoval = true)
public static CompoundTag getRootTag(String modID) {
return WorldConfig.getRootTag(modID);
}
/**
* @deprecated use {@link WorldConfig#hasMod(String)} instead
*/
@Deprecated(forRemoval = true)
public static boolean hasMod(String modID) {
return WorldConfig.hasMod(modID);
}
/**
* @deprecated use {@link WorldConfig#getCompoundTag(String, String)} instead
*/
@Deprecated(forRemoval = true)
public static CompoundTag getCompoundTag(String modID, String path) {
return WorldConfig.getCompoundTag(modID, path);
}
/**
* @deprecated use {@link WorldConfig#saveFile(String)} instead
*/
@Deprecated(forRemoval = true)
public static void saveFile(String modID) {
WorldConfig.saveFile(modID);
}
/**
* @deprecated use {@link WorldConfig#getModVersion(String)} instead
*/
@Deprecated(forRemoval = true)
public static String getModVersion(String modID) {
return WorldConfig.getModVersion(modID);
}
/**
* @deprecated use {@link WorldConfig#getIntModVersion(String)} instead
*/
@Deprecated(forRemoval = true)
public static int getIntModVersion(String modID) {
return WorldConfig.getIntModVersion(modID);
}
}

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,217 +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.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 {
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,530 +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);
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);
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 -> {
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) {
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) {
BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")");
localDescriptor.invalidateCache();
desc.relativeFilesStream()
.filter(desc::discardChildElements)
.forEach(subFile -> {
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());
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)) {
BCLib.LOGGER.info(" * " + subFile.relPath + " (changed)");
filesToRequest.add(new AutoSyncID.ForDirectFileRequest(
desc.folderID,
new File(subFile.relPath)
));
} else {
BCLib.LOGGER.info(" * " + subFile.relPath);
}
} else {
//the file is missing locally
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 {
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) {
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
));
}
}
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;
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,109 +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);
BCLib.LOGGER.info("Client requested " + size + " Files:");
for (int i = 0; i < size; i++) {
AutoSyncID asid = AutoSyncID.deserializeData(buf);
files.add(asid);
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,225 +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());
BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:");
for (AutoFileSyncEntry entry : existingFiles) {
int length = entry.serializeContent(buf);
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);
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 ";
}
BCLib.LOGGER.info(" - " + type + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")");
} else {
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,218 +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 -> {
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);
}
}

View file

@ -1,124 +0,0 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.worlds.together.biomesource.BiomeSourceFromRegistry;
import org.betterx.worlds.together.biomesource.MergeableBiomeSource;
import org.betterx.worlds.together.world.BiomeSourceWithNoiseRelatedSettings;
import org.betterx.worlds.together.world.BiomeSourceWithSeed;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import com.google.common.collect.Sets;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
public abstract class BCLBiomeSource extends BiomeSource implements BiomeSourceWithSeed, MergeableBiomeSource<BCLBiomeSource>, BiomeSourceWithNoiseRelatedSettings, BiomeSourceFromRegistry {
protected final Registry<Biome> biomeRegistry;
protected long currentSeed;
protected int maxHeight;
private static List<Holder<Biome>> preInit(Registry<Biome> biomeRegistry, List<Holder<Biome>> biomes) {
biomes = biomes.stream().sorted(Comparator.comparing(holder -> holder.unwrapKey()
.get()
.location()
.toString()))
.toList();
biomes.forEach(biome -> BiomeAPI.sortBiomeFeatures(biome));
return biomes;
}
protected BCLBiomeSource(
Registry<Biome> biomeRegistry,
List<Holder<Biome>> list,
long seed
) {
super(preInit(biomeRegistry, list));
this.biomeRegistry = biomeRegistry;
this.currentSeed = seed;
}
final public void setSeed(long seed) {
if (seed != currentSeed) {
System.out.println(this + " set Seed: " + seed);
this.currentSeed = seed;
initMap(seed);
}
}
/**
* Set world height
*
* @param maxHeight height of the World.
*/
final public void setMaxHeight(int maxHeight) {
if (this.maxHeight != maxHeight) {
System.out.println(this + " set Max Height: " + maxHeight);
this.maxHeight = maxHeight;
onHeightChange(maxHeight);
}
}
protected final void initMap(long seed) {
System.out.println(this + " updates Map");
onInitMap(seed);
}
protected abstract void onInitMap(long newSeed);
protected abstract void onHeightChange(int newHeight);
public BCLBiomeSource createCopyForDatapack(Set<Holder<Biome>> datapackBiomes) {
Set<Holder<Biome>> mutableSet = Sets.newHashSet();
mutableSet.addAll(datapackBiomes);
return cloneForDatapack(mutableSet);
}
protected abstract BCLBiomeSource cloneForDatapack(Set<Holder<Biome>> datapackBiomes);
public interface ValidBiomePredicate {
boolean isValid(Holder<Biome> biome, ResourceLocation location);
}
protected static List<Holder<Biome>> getBiomes(
Registry<Biome> biomeRegistry,
List<String> exclude,
List<String> include,
BCLibNetherBiomeSource.ValidBiomePredicate test
) {
return biomeRegistry.stream()
.filter(biome -> biomeRegistry.getResourceKey(biome).isPresent())
.map(biome -> biomeRegistry.getOrCreateHolderOrThrow(biomeRegistry.getResourceKey(biome)
.get()))
.filter(biome -> {
ResourceLocation location = biome.unwrapKey().orElseThrow().location();
final String strLocation = location.toString();
if (exclude.contains(strLocation)) return false;
if (include.contains(strLocation)) return true;
return test.isValid(biome, location);
})
.toList();
}
@Override
public BCLBiomeSource mergeWithBiomeSource(BiomeSource inputBiomeSource) {
final Set<Holder<Biome>> datapackBiomes = inputBiomeSource.possibleBiomes();
return this.createCopyForDatapack(datapackBiomes);
}
public void onLoadGeneratorSettings(NoiseGeneratorSettings generator) {
this.setMaxHeight(generator.noiseSettings().height());
}
public Registry<Biome> getBiomeRegistry() {
return biomeRegistry;
}
}

View file

@ -1,227 +0,0 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.levelgen.LevelGenUtil;
import org.betterx.bclib.interfaces.NoiseGeneratorSettingsProvider;
import org.betterx.bclib.mixin.common.ChunkGeneratorAccessor;
import org.betterx.worlds.together.WorldsTogether;
import org.betterx.worlds.together.biomesource.MergeableBiomeSource;
import org.betterx.worlds.together.chunkgenerator.EnforceableChunkGenerator;
import org.betterx.worlds.together.chunkgenerator.InjectableSurfaceRules;
import org.betterx.worlds.together.chunkgenerator.RestorableBiomeSource;
import org.betterx.worlds.together.world.BiomeSourceWithNoiseRelatedSettings;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeGenerationSettings;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.FeatureSorter;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.WorldGenSettings;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.synth.NormalNoise;
import com.google.common.base.Suppliers;
import java.util.List;
import java.util.function.Function;
public class BCLChunkGenerator extends NoiseBasedChunkGenerator implements RestorableBiomeSource<BCLChunkGenerator>, InjectableSurfaceRules<BCLChunkGenerator>, EnforceableChunkGenerator<BCLChunkGenerator> {
public static final Codec<BCLChunkGenerator> CODEC = RecordCodecBuilder
.create((RecordCodecBuilder.Instance<BCLChunkGenerator> builderInstance) -> {
final RecordCodecBuilder<BCLChunkGenerator, Registry<NormalNoise.NoiseParameters>> noiseGetter = RegistryOps
.retrieveRegistry(
Registry.NOISE_REGISTRY)
.forGetter(
BCLChunkGenerator::getNoises);
RecordCodecBuilder<BCLChunkGenerator, BiomeSource> biomeSourceCodec = BiomeSource.CODEC
.fieldOf("biome_source")
.forGetter((BCLChunkGenerator generator) -> generator.biomeSource);
RecordCodecBuilder<BCLChunkGenerator, Holder<NoiseGeneratorSettings>> settingsCodec = NoiseGeneratorSettings.CODEC
.fieldOf("settings")
.forGetter((BCLChunkGenerator generator) -> generator.settings);
return NoiseBasedChunkGenerator
.commonCodec(builderInstance)
.and(builderInstance.group(noiseGetter, biomeSourceCodec, settingsCodec))
.apply(builderInstance, builderInstance.stable(BCLChunkGenerator::new));
});
public final BiomeSource initialBiomeSource;
public BCLChunkGenerator(
Registry<StructureSet> registry,
Registry<NormalNoise.NoiseParameters> registry2,
BiomeSource biomeSource,
Holder<NoiseGeneratorSettings> holder
) {
super(registry, registry2, biomeSource, holder);
initialBiomeSource = biomeSource;
if (biomeSource instanceof BiomeSourceWithNoiseRelatedSettings bcl) {
bcl.onLoadGeneratorSettings(holder.value());
}
if (WorldsTogether.RUNS_TERRABLENDER) {
BCLib.LOGGER.info("Make sure features are loaded from terrablender for " + biomeSource);
//terrablender is invalidating the feature initialization
//we redo it at this point, otherwise we will get blank biomes
rebuildFeaturesPerStep(biomeSource);
}
System.out.println("Chunk Generator: " + this + " (biomeSource: " + biomeSource + ")");
}
private void rebuildFeaturesPerStep(BiomeSource biomeSource) {
if (this instanceof ChunkGeneratorAccessor acc) {
Function<Holder<Biome>, BiomeGenerationSettings> function = (Holder<Biome> hh) -> hh.value()
.getGenerationSettings();
acc.bcl_setFeaturesPerStep(Suppliers.memoize(() -> FeatureSorter.buildFeaturesPerStep(
List.copyOf(biomeSource.possibleBiomes()),
(hh) -> function.apply(hh).features(),
true
)));
}
}
/**
* Other Mods like TerraBlender might inject new BiomeSources. We und that change after the world setup did run.
*
* @param dimensionKey The Dimension where this ChunkGenerator is used from
*/
@Override
public void restoreInitialBiomeSource(ResourceKey<LevelStem> dimensionKey) {
if (initialBiomeSource != getBiomeSource()) {
if (this instanceof ChunkGeneratorAccessor acc) {
if (initialBiomeSource instanceof MergeableBiomeSource bs) {
acc.bcl_setBiomeSource(bs.mergeWithBiomeSource(getBiomeSource()));
}
rebuildFeaturesPerStep(getBiomeSource());
}
}
}
@Override
protected Codec<? extends ChunkGenerator> codec() {
return CODEC;
}
private Registry<NormalNoise.NoiseParameters> getNoises() {
if (this instanceof NoiseGeneratorSettingsProvider p) {
return p.bclib_getNoises();
}
return null;
}
@Override
public String toString() {
return "BCLib - Chunk Generator (" + Integer.toHexString(hashCode()) + ")";
}
// This method is injected by Terrablender.
// We make sure terrablender does not rewrite the feature-set for our ChunkGenerator by overwriting the
// Mixin-Method with an empty implementation
public void appendFeaturesPerStep() {
}
public static RandomState createRandomState(ServerLevel level, ChunkGenerator generator) {
if (generator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
return RandomState.create(
noiseBasedChunkGenerator.generatorSettings().value(),
level.registryAccess().registryOrThrow(Registry.NOISE_REGISTRY),
level.getSeed()
);
} else {
return RandomState.create(level.registryAccess(), NoiseGeneratorSettings.OVERWORLD, level.getSeed());
}
}
@Override
public WorldGenSettings enforceGeneratorInWorldGenSettings(
RegistryAccess access,
ResourceKey<LevelStem> dimensionKey,
ResourceKey<DimensionType> dimensionTypeKey,
ChunkGenerator loadedChunkGenerator,
WorldGenSettings settings
) {
BCLib.LOGGER.info("Enforcing Correct Generator for " + dimensionKey.location().toString() + ".");
ChunkGenerator referenceGenerator = this;
if (loadedChunkGenerator instanceof org.betterx.bclib.interfaces.ChunkGeneratorAccessor generator) {
if (loadedChunkGenerator instanceof NoiseGeneratorSettingsProvider noiseProvider) {
if (referenceGenerator instanceof NoiseGeneratorSettingsProvider referenceProvider) {
final BiomeSource bs;
if (referenceGenerator.getBiomeSource() instanceof MergeableBiomeSource mbs) {
bs = mbs.mergeWithBiomeSource(loadedChunkGenerator.getBiomeSource());
} else {
bs = referenceGenerator.getBiomeSource();
}
referenceGenerator = new BCLChunkGenerator(
generator.bclib_getStructureSetsRegistry(),
noiseProvider.bclib_getNoises(),
bs,
buildGeneratorSettings(
referenceProvider.bclib_getNoiseGeneratorSettingHolders(),
noiseProvider.bclib_getNoiseGeneratorSettingHolders(),
bs
)
);
}
}
}
return LevelGenUtil.replaceGenerator(
dimensionKey,
dimensionTypeKey,
access,
settings,
referenceGenerator
);
}
private static Holder<NoiseGeneratorSettings> buildGeneratorSettings(
Holder<NoiseGeneratorSettings> reference,
Holder<NoiseGeneratorSettings> settings,
BiomeSource biomeSource
) {
return settings;
// NoiseGeneratorSettings old = settings.value();
// NoiseGeneratorSettings noise = new NoiseGeneratorSettings(
// old.noiseSettings(),
// old.defaultBlock(),
// old.defaultFluid(),
// old.noiseRouter(),
// SurfaceRuleRegistry.mergeSurfaceRulesFromBiomes(old.surfaceRule(), biomeSource),
// //SurfaceRuleUtil.addRulesForBiomeSource(old.surfaceRule(), biomeSource),
// old.spawnTarget(),
// old.seaLevel(),
// old.disableMobGeneration(),
// old.aquifersEnabled(),
// old.oreVeinsEnabled(),
// old.useLegacyRandomSource()
// );
//
//
// return Holder.direct(noise);
}
}

View file

@ -1,362 +0,0 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig;
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.worlds.together.biomesource.BiomeSourceWithConfig;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Holder;
import net.minecraft.core.QuartPos;
import net.minecraft.core.Registry;
import net.minecraft.core.SectionPos;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BiomeTags;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.levelgen.DensityFunction;
import java.awt.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import org.jetbrains.annotations.NotNull;
public class BCLibEndBiomeSource extends BCLBiomeSource implements BiomeSourceWithConfig<BCLibEndBiomeSource, BCLEndBiomeSourceConfig> {
public static Codec<BCLibEndBiomeSource> CODEC
= RecordCodecBuilder.create((instance) -> instance.group(
RegistryOps
.retrieveRegistry(Registry.BIOME_REGISTRY)
.forGetter((theEndBiomeSource) -> theEndBiomeSource.biomeRegistry),
Codec
.LONG
.fieldOf("seed")
.stable()
.forGetter(source -> source.currentSeed),
BCLEndBiomeSourceConfig
.CODEC
.fieldOf("config")
.orElse(BCLEndBiomeSourceConfig.DEFAULT)
.forGetter(o -> o.config)
)
.apply(
instance,
instance.stable(BCLibEndBiomeSource::new)
)
);
private final Point pos;
private final BiFunction<Point, Integer, Boolean> endLandFunction;
private BiomeMap mapLand;
private BiomeMap mapVoid;
private BiomeMap mapCenter;
private BiomeMap mapBarrens;
private final BiomePicker endLandBiomePicker;
private BiomePicker endVoidBiomePicker;
private BiomePicker endCenterBiomePicker;
private BiomePicker endBarrensBiomePicker;
private BCLEndBiomeSourceConfig config;
public BCLibEndBiomeSource(Registry<Biome> biomeRegistry, long seed, BCLEndBiomeSourceConfig config) {
this(biomeRegistry, seed, config, true);
}
public BCLibEndBiomeSource(Registry<Biome> biomeRegistry, BCLEndBiomeSourceConfig config) {
this(biomeRegistry, 0, config, false);
}
private BCLibEndBiomeSource(
Registry<Biome> biomeRegistry,
long seed,
BCLEndBiomeSourceConfig config,
boolean initMaps
) {
this(biomeRegistry, getBiomes(biomeRegistry), seed, config, initMaps);
}
private BCLibEndBiomeSource(
Registry<Biome> biomeRegistry,
List<Holder<Biome>> list,
long seed,
BCLEndBiomeSourceConfig config,
boolean initMaps
) {
super(biomeRegistry, list, seed);
this.config = config;
var includeMap = Configs.BIOMES_CONFIG.getBiomeIncludeMap();
var excludeList = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END);
endLandBiomePicker = new BiomePicker(biomeRegistry);
endVoidBiomePicker = new BiomePicker(biomeRegistry);
endCenterBiomePicker = new BiomePicker(biomeRegistry);
endBarrensBiomePicker = new BiomePicker(biomeRegistry);
Map<BiomeAPI.BiomeType, BiomePicker> pickerMap = new HashMap<>();
pickerMap.put(BiomeAPI.BiomeType.END_LAND, endLandBiomePicker);
pickerMap.put(BiomeAPI.BiomeType.END_VOID, endVoidBiomePicker);
pickerMap.put(BiomeAPI.BiomeType.END_CENTER, endCenterBiomePicker);
pickerMap.put(BiomeAPI.BiomeType.END_BARRENS, endBarrensBiomePicker);
this.possibleBiomes().forEach(biome -> {
ResourceKey<Biome> key = biome.unwrapKey().orElseThrow();
ResourceLocation biomeID = key.location();
String biomeStr = biomeID.toString();
//exclude everything that was listed
if (excludeList != null && excludeList.contains(biomeStr)) return;
if (!biome.isBound()) {
BCLib.LOGGER.warning("Biome " + biomeStr + " is requested but not yet bound.");
return;
}
final BCLBiome bclBiome;
if (!BiomeAPI.hasBiome(biomeID)) {
bclBiome = new BCLBiome(biomeID, biome.value());
} else {
bclBiome = BiomeAPI.getBiome(biomeID);
}
if (bclBiome != null || bclBiome != BiomeAPI.EMPTY_BIOME) {
if (bclBiome.getParentBiome() == null) {
//ignore small islands when void biomes are disabled
if (!config.withVoidBiomes) {
if (biomeID.equals(Biomes.SMALL_END_ISLANDS.location())) {
return;
}
}
//force include biomes
boolean didForceAdd = false;
for (var entry : pickerMap.entrySet()) {
var includeList = includeMap == null ? null : includeMap.get(entry.getKey());
if (includeList != null && includeList.contains(biomeStr)) {
entry.getValue().addBiome(bclBiome);
didForceAdd = true;
}
}
if (!didForceAdd) {
if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_IGNORE)) {
//we should not add this biome anywhere, so just ignore it
} else if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_CENTER)
|| TheEndBiomesHelper.canGenerateAsMainIslandBiome(key)) {
endCenterBiomePicker.addBiome(bclBiome);
} else if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_LAND)
|| TheEndBiomesHelper.canGenerateAsHighlandsBiome(key)) {
if (!config.withVoidBiomes) endVoidBiomePicker.addBiome(bclBiome);
endLandBiomePicker.addBiome(bclBiome);
} else if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_BARRENS)
|| TheEndBiomesHelper.canGenerateAsEndBarrens(key)) {
endBarrensBiomePicker.addBiome(bclBiome);
} else if (BiomeAPI.wasRegisteredAs(biomeID, BiomeAPI.BiomeType.END_VOID)
|| TheEndBiomesHelper.canGenerateAsSmallIslandsBiome(key)) {
endVoidBiomePicker.addBiome(bclBiome);
} else {
BCLib.LOGGER.info("Found End Biome " + biomeStr + " that was not registers with fabric or bclib. Assuming end-land Biome...");
endLandBiomePicker.addBiome(bclBiome);
}
}
}
}
});
endLandBiomePicker.rebuild();
endVoidBiomePicker.rebuild();
endBarrensBiomePicker.rebuild();
endCenterBiomePicker.rebuild();
if (endVoidBiomePicker.isEmpty()) {
BCLib.LOGGER.info("No Void Biomes found. Disabling by using barrens");
endVoidBiomePicker = endBarrensBiomePicker;
}
if (endBarrensBiomePicker.isEmpty()) {
BCLib.LOGGER.info("No Barrens Biomes found. Disabling by using land Biomes");
endBarrensBiomePicker = endLandBiomePicker;
endVoidBiomePicker = endLandBiomePicker;
}
if (endCenterBiomePicker.isEmpty()) {
BCLib.LOGGER.warning("No Center Island Biomes found. Forcing use of vanilla center.");
endCenterBiomePicker.addBiome(BiomeAPI.THE_END);
endCenterBiomePicker.rebuild();
if (endCenterBiomePicker.isEmpty()) {
BCLib.LOGGER.error("Unable to force vanilla central Island. Falling back to land Biomes...");
endCenterBiomePicker = endLandBiomePicker;
}
}
this.endLandFunction = GeneratorOptions.getEndLandFunction();
this.pos = new Point();
if (initMaps) {
initMap(seed);
}
}
protected BCLBiomeSource cloneForDatapack(Set<Holder<Biome>> datapackBiomes) {
datapackBiomes.addAll(getBclBiomes(this.biomeRegistry));
return new BCLibEndBiomeSource(
this.biomeRegistry,
datapackBiomes.stream().toList(),
this.currentSeed,
this.config,
true
);
}
private static List<Holder<Biome>> getBclBiomes(Registry<Biome> biomeRegistry) {
return getBiomes(
biomeRegistry,
Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END),
Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.END),
BCLibEndBiomeSource::isValidNonVanillaEndBiome
);
}
private static List<Holder<Biome>> getBiomes(Registry<Biome> biomeRegistry) {
return getBiomes(
biomeRegistry,
Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.END),
Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.END),
BCLibEndBiomeSource::isValidEndBiome
);
}
private static boolean isValidEndBiome(Holder<Biome> biome, ResourceLocation location) {
return biome.is(BiomeTags.IS_END) ||
BiomeAPI.wasRegisteredAsEndBiome(location) ||
TheEndBiomesHelper.canGenerateInEnd(biome.unwrapKey().orElse(null));
}
private static boolean isValidNonVanillaEndBiome(Holder<Biome> biome, ResourceLocation location) {
return biome.is(BiomeTags.IS_END) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_LAND) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_VOID) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_CENTER) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_END_BARRENS) ||
TheEndBiomesHelper.canGenerateInEnd(biome.unwrapKey().orElse(null));
}
public static void register() {
Registry.register(Registry.BIOME_SOURCE, BCLib.makeID("end_biome_source"), CODEC);
}
@Override
protected void onInitMap(long seed) {
this.mapLand = config.mapVersion.mapBuilder.apply(
seed,
config.landBiomesSize,
endLandBiomePicker
);
this.mapVoid = config.mapVersion.mapBuilder.apply(
seed,
config.voidBiomesSize,
endVoidBiomePicker
);
this.mapCenter = config.mapVersion.mapBuilder.apply(
seed,
config.centerBiomesSize,
endCenterBiomePicker
);
this.mapBarrens = config.mapVersion.mapBuilder.apply(
seed,
config.barrensBiomesSize,
endBarrensBiomePicker
);
}
@Override
protected void onHeightChange(int newHeight) {
}
@Override
public Holder<Biome> getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.@NotNull Sampler sampler) {
if (mapLand == null || mapVoid == null || mapCenter == null || mapBarrens == null)
return this.possibleBiomes().stream().findFirst().orElseThrow();
int posX = QuartPos.toBlock(biomeX);
int posY = QuartPos.toBlock(biomeY);
int posZ = QuartPos.toBlock(biomeZ);
long dist = Math.abs(posX) + Math.abs(posZ) > (long) config.innerVoidRadiusSquared
? ((long) config.innerVoidRadiusSquared + 1)
: (long) posX * (long) posX + (long) posZ * (long) posZ;
if ((biomeX & 63) == 0 || (biomeZ & 63) == 0) {
mapLand.clearCache();
mapVoid.clearCache();
mapCenter.clearCache();
mapVoid.clearCache();
}
if (config.generatorVersion == BCLEndBiomeSourceConfig.EndBiomeGeneratorType.VANILLA || endLandFunction == null) {
if (dist <= (long) config.innerVoidRadiusSquared) {
return mapCenter.getBiome(posX, biomeY << 2, posZ).biome;
}
int x = (SectionPos.blockToSectionCoord(posX) * 2 + 1) * 8;
int z = (SectionPos.blockToSectionCoord(posZ) * 2 + 1) * 8;
double d = sampler.erosion().compute(new DensityFunction.SinglePointContext(x, posY, z));
if (d > 0.25) {
return mapLand.getBiome(posX, biomeY << 2, posZ).biome; //highlands
} else if (d >= -0.0625) {
return mapLand.getBiome(posX, biomeY << 2, posZ).biome; //midlands
} else {
return d < -0.21875
? mapVoid.getBiome(posX, biomeY << 2, posZ).biome //small islands
: (config.withVoidBiomes ? mapBarrens : mapLand).getBiome(
posX,
biomeY << 2,
posZ
).biome; //barrens
}
} else {
pos.setLocation(biomeX, biomeZ);
if (endLandFunction.apply(pos, maxHeight)) {
return (dist <= (long) config.innerVoidRadiusSquared ? mapCenter : mapLand)
.getBiome(posX, biomeY << 2, posZ).biome;
} else {
return (dist <= (long) config.innerVoidRadiusSquared ? mapBarrens : mapVoid)
.getBiome(posX, biomeY << 2, posZ).biome;
}
}
}
@Override
protected Codec<? extends BiomeSource> codec() {
return CODEC;
}
@Override
public String toString() {
return "BCLib - The End BiomeSource (" + Integer.toHexString(hashCode()) + ", config=" + config + ", seed=" + currentSeed + ", height=" + maxHeight + ", customLand=" + (endLandFunction != null) + ", biomes=" + possibleBiomes().size() + ")";
}
@Override
public BCLEndBiomeSourceConfig getTogetherConfig() {
return config;
}
@Override
public void setTogetherConfig(BCLEndBiomeSourceConfig newConfig) {
this.config = newConfig;
this.initMap(currentSeed);
}
}

View file

@ -1,216 +0,0 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.generator.config.BCLNetherBiomeSourceConfig;
import org.betterx.bclib.api.v2.generator.map.MapStack;
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.bclib.config.Configs;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.util.TriFunction;
import org.betterx.worlds.together.biomesource.BiomeSourceWithConfig;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BiomeTags;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.Climate;
import net.fabricmc.fabric.api.biome.v1.NetherBiomes;
import java.util.List;
import java.util.Set;
public class BCLibNetherBiomeSource extends BCLBiomeSource implements BiomeSourceWithConfig<BCLibNetherBiomeSource, BCLNetherBiomeSourceConfig> {
public static final Codec<BCLibNetherBiomeSource> CODEC = RecordCodecBuilder
.create(instance -> instance
.group(
RegistryOps
.retrieveRegistry(Registry.BIOME_REGISTRY)
.forGetter(source -> source.biomeRegistry),
Codec
.LONG
.fieldOf("seed")
.stable()
.forGetter(source -> {
return source.currentSeed;
}),
BCLNetherBiomeSourceConfig
.CODEC
.fieldOf("config")
.orElse(BCLNetherBiomeSourceConfig.DEFAULT)
.forGetter(o -> o.config)
)
.apply(instance, instance.stable(BCLibNetherBiomeSource::new))
);
private BiomeMap biomeMap;
private final BiomePicker biomePicker;
private BCLNetherBiomeSourceConfig config;
public BCLibNetherBiomeSource(Registry<Biome> biomeRegistry, BCLNetherBiomeSourceConfig config) {
this(biomeRegistry, 0, config, false);
}
public BCLibNetherBiomeSource(Registry<Biome> biomeRegistry, long seed, BCLNetherBiomeSourceConfig config) {
this(biomeRegistry, seed, config, true);
}
private BCLibNetherBiomeSource(
Registry<Biome> biomeRegistry,
long seed,
BCLNetherBiomeSourceConfig config,
boolean initMaps
) {
this(biomeRegistry, getBiomes(biomeRegistry), seed, config, initMaps);
}
private BCLibNetherBiomeSource(
Registry<Biome> biomeRegistry,
List<Holder<Biome>> list,
long seed,
BCLNetherBiomeSourceConfig config,
boolean initMaps
) {
super(biomeRegistry, list, seed);
this.config = config;
biomePicker = new BiomePicker(biomeRegistry);
this.possibleBiomes().forEach(biome -> {
ResourceLocation biomeID = biome.unwrapKey().orElseThrow().location();
if (!biome.isBound()) {
BCLib.LOGGER.warning("Biome " + biomeID.toString() + " is requested but not yet bound.");
return;
}
if (!BiomeAPI.hasBiome(biomeID)) {
BCLBiome bclBiome = new BCLBiome(biomeID, biome.value());
biomePicker.addBiome(bclBiome);
} else {
BCLBiome bclBiome = BiomeAPI.getBiome(biomeID);
if (bclBiome != BiomeAPI.EMPTY_BIOME) {
if (bclBiome.getParentBiome() == null) {
biomePicker.addBiome(bclBiome);
}
}
}
});
biomePicker.rebuild();
if (initMaps) {
initMap(seed);
}
}
protected BCLBiomeSource cloneForDatapack(Set<Holder<Biome>> datapackBiomes) {
datapackBiomes.addAll(getBclBiomes(this.biomeRegistry));
return new BCLibNetherBiomeSource(
this.biomeRegistry,
datapackBiomes.stream().toList(),
this.currentSeed,
config,
true
);
}
private static List<Holder<Biome>> getBclBiomes(Registry<Biome> biomeRegistry) {
List<String> include = Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.NETHER);
List<String> exclude = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.NETHER);
return getBiomes(biomeRegistry, exclude, include, BCLibNetherBiomeSource::isValidNonVanillaNetherBiome);
}
private static List<Holder<Biome>> getBiomes(Registry<Biome> biomeRegistry) {
List<String> include = Configs.BIOMES_CONFIG.getIncludeMatching(BiomeAPI.BiomeType.NETHER);
List<String> exclude = Configs.BIOMES_CONFIG.getExcludeMatching(BiomeAPI.BiomeType.NETHER);
return getBiomes(biomeRegistry, exclude, include, BCLibNetherBiomeSource::isValidNetherBiome);
}
private static boolean isValidNetherBiome(Holder<Biome> biome, ResourceLocation location) {
return NetherBiomes.canGenerateInNether(biome.unwrapKey().get()) ||
biome.is(BiomeTags.IS_NETHER) ||
BiomeAPI.wasRegisteredAsNetherBiome(location);
}
private static boolean isValidNonVanillaNetherBiome(Holder<Biome> biome, ResourceLocation location) {
return (
!"minecraft".equals(location.getNamespace()) &&
NetherBiomes.canGenerateInNether(biome.unwrapKey().get())) ||
BiomeAPI.wasRegisteredAs(location, BiomeAPI.BiomeType.BCL_NETHER);
}
public static <T> void debug(Object el, Registry<T> reg) {
System.out.println("Unknown " + el + " in " + reg);
}
public static void register() {
Registry.register(Registry.BIOME_SOURCE, BCLib.makeID("nether_biome_source"), CODEC);
}
@Override
public Holder<Biome> getNoiseBiome(int biomeX, int biomeY, int biomeZ, Climate.Sampler var4) {
if (biomeMap == null)
return this.possibleBiomes().stream().findFirst().get();
if ((biomeX & 63) == 0 && (biomeZ & 63) == 0) {
biomeMap.clearCache();
}
BiomePicker.ActualBiome bb = biomeMap.getBiome(biomeX << 2, biomeY << 2, biomeZ << 2);
return bb.biome;
}
@Override
protected Codec<? extends BiomeSource> codec() {
return CODEC;
}
@Override
protected void onInitMap(long seed) {
TriFunction<Long, Integer, BiomePicker, BiomeMap> mapConstructor = config.mapVersion.mapBuilder;
if (maxHeight > config.biomeSizeVertical * 1.5 && config.useVerticalBiomes) {
this.biomeMap = new MapStack(
seed,
config.biomeSize,
biomePicker,
config.biomeSizeVertical,
maxHeight,
mapConstructor
);
} else {
this.biomeMap = mapConstructor.apply(
seed,
config.biomeSize,
biomePicker
);
}
}
@Override
protected void onHeightChange(int newHeight) {
initMap(currentSeed);
}
@Override
public String toString() {
return "BCLib - Nether BiomeSource (" + Integer.toHexString(hashCode()) + ", config=" + config + ", seed=" + currentSeed + ", height=" + maxHeight + ", biomes=" + possibleBiomes().size() + ")";
}
@Override
public BCLNetherBiomeSourceConfig getTogetherConfig() {
return config;
}
@Override
public void setTogetherConfig(BCLNetherBiomeSourceConfig newConfig) {
this.config = newConfig;
initMap(currentSeed);
}
}

View file

@ -1,164 +0,0 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import org.betterx.bclib.util.WeighTree;
import org.betterx.bclib.util.WeightedList;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import com.google.common.collect.Lists;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class BiomePicker {
private final Map<BCLBiome, ActualBiome> all = new HashMap<>();
public final Registry<Biome> biomeRegistry;
private final List<ActualBiome> biomes = Lists.newArrayList();
private final List<String> allowedBiomes;
public final ActualBiome fallbackBiome;
private WeighTree<ActualBiome> tree;
public BiomePicker(Registry<Biome> biomeRegistry) {
this(biomeRegistry, null);
}
public BiomePicker(Registry<Biome> biomeRegistry, List<Holder<Biome>> allowedBiomes) {
this.biomeRegistry = biomeRegistry;
this.allowedBiomes = allowedBiomes != null ? allowedBiomes
.stream()
.map(h -> h.unwrapKey())
.filter(o -> o.isPresent())
.map(o -> o.get().location().toString()).toList() : null;
this.fallbackBiome = create(BiomeAPI.EMPTY_BIOME);
}
private boolean isAllowed(BCLBiome b) {
if (allowedBiomes == null) return true;
return allowedBiomes.contains(b.getID().toString());
}
private ActualBiome create(BCLBiome bclBiome) {
ActualBiome e = all.get(bclBiome);
if (e != null) return e;
return new ActualBiome(bclBiome);
}
public void addBiome(BCLBiome biome) {
biomes.add(create(biome));
}
public ActualBiome getBiome(WorldgenRandom random) {
return biomes.isEmpty() ? fallbackBiome : tree.get(random);
}
public boolean isEmpty() {
return biomes.isEmpty();
}
public void rebuild() {
WeightedList<ActualBiome> list = new WeightedList<>();
biomes.forEach(biome -> {
if (biome.isValid)
list.add(biome, biome.bclBiome.getGenChance());
});
//only a single biome, we need to add the edges as well
if (list.size() == 1) {
ActualBiome biome = list.get(0);
if (biome.getEdge() != null) {
float defaultBiomeSize = 128;
float edgeSize = (biome.bclBiome.getEdgeSize() * list.getWeight(0)) / defaultBiomeSize;
list.add(biome.getEdge(), edgeSize);
}
}
//no Biome, make sure we add at least one, otherwise bad things will happen
if (list.isEmpty()) {
list.add(create(BiomeAPI.EMPTY_BIOME), 1);
}
tree = new WeighTree<>(list);
}
public class ActualBiome {
public final BCLBiome bclBiome;
public final Holder<Biome> biome;
public final ResourceKey<Biome> key;
private final WeightedList<ActualBiome> subbiomes = new WeightedList<>();
private final ActualBiome edge;
private final ActualBiome parent;
public final boolean isValid;
private ActualBiome(BCLBiome bclBiome) {
all.put(bclBiome, this);
this.bclBiome = bclBiome;
this.key = biomeRegistry.getResourceKey(biomeRegistry.get(bclBiome.getID())).orElse(null);
this.biome = key != null ? biomeRegistry.getOrCreateHolderOrThrow(key) : null;
this.isValid = key != null && biome != null && biome.isBound();
bclBiome.forEachSubBiome((b, w) -> {
if (isAllowed(b))
subbiomes.add(create(b), w);
});
if (bclBiome.getEdge() != null && isAllowed(bclBiome.getEdge())) {
edge = create(bclBiome.getEdge());
} else {
edge = null;
}
parent = bclBiome.getParentBiome() != null ? create(bclBiome.getParentBiome()) : null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ActualBiome entry = (ActualBiome) o;
return bclBiome.equals(entry.bclBiome);
}
@Override
public int hashCode() {
return Objects.hash(bclBiome);
}
public ActualBiome getSubBiome(WorldgenRandom random) {
return subbiomes.get(random);
}
public ActualBiome getEdge() {
return edge;
}
public ActualBiome getParentBiome() {
return parent;
}
public boolean isSame(ActualBiome e) {
return bclBiome.isSame(e.bclBiome);
}
@Override
public String toString() {
return "ActualBiome{" +
"key=" + key.location() +
", subbiomes=" + subbiomes.size() +
", edge=" + (edge != null ? edge.key.location() : "null") +
", parent=" + (parent != null ? parent.key.location() : "null") +
", isValid=" + isValid +
'}';
}
}
}

View file

@ -1,5 +0,0 @@
package org.betterx.bclib.api.v2.generator;
public enum BiomeType {
LAND, VOID
}

View file

@ -1,103 +0,0 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.config.Configs;
import java.awt.*;
import java.util.function.BiFunction;
import java.util.function.Function;
public class GeneratorOptions {
private static BiFunction<Point, Integer, Boolean> endLandFunction;
private static boolean fixEndBiomeSource = true;
private static boolean fixNetherBiomeSource = true;
public static void init() {
fixEndBiomeSource = Configs.GENERATOR_CONFIG.getBoolean("options.biomeSource", "fixEndBiomeSource", true);
fixNetherBiomeSource = Configs.GENERATOR_CONFIG.getBoolean("options.biomeSource", "fixNetherBiomeSource", true);
}
@Deprecated(forRemoval = true)
public static int getBiomeSizeNether() {
return 256;
}
@Deprecated(forRemoval = true)
public static int getVerticalBiomeSizeNether() {
return 86;
}
@Deprecated(forRemoval = true)
public static int getBiomeSizeEndLand() {
return 256;
}
@Deprecated(forRemoval = true)
public static int getBiomeSizeEndVoid() {
return 256;
}
/**
* @param endLandFunction
* @deprecated use {@link #setEndLandFunction(BiFunction)} instead
*/
@Deprecated(forRemoval = true)
public static void setEndLandFunction(Function<Point, Boolean> endLandFunction) {
GeneratorOptions.endLandFunction = (p, h) -> endLandFunction.apply(p);
}
public static void setEndLandFunction(BiFunction<Point, Integer, Boolean> endLandFunction) {
GeneratorOptions.endLandFunction = endLandFunction;
}
public static BiFunction<Point, Integer, Boolean> getEndLandFunction() {
return endLandFunction;
}
@Deprecated(forRemoval = true)
public static long getFarEndBiomes() {
return 1000000;
}
/**
* Set distance of far End biomes generation, in blocks
*
* @param distance
*/
@Deprecated(forRemoval = true)
public static void setFarEndBiomes(int distance) {
}
/**
* Set distance of far End biomes generation, in blocks^2
*
* @param distanceSqr the distance squared
*/
@Deprecated(forRemoval = true)
public static void setFarEndBiomesSqr(long distanceSqr) {
}
@Deprecated(forRemoval = true)
public static boolean customNetherBiomeSource() {
return true;
}
@Deprecated(forRemoval = true)
public static boolean customEndBiomeSource() {
return true;
}
@Deprecated(forRemoval = true)
public static boolean useVerticalBiomes() {
return true;
}
public static boolean fixEndBiomeSource() {
return fixEndBiomeSource;
}
public static boolean fixNetherBiomeSource() {
return fixNetherBiomeSource;
}
}

View file

@ -1,92 +0,0 @@
package org.betterx.bclib.api.v2.generator;
import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
/**
* Helper class until FAPI integrates <a href="https://github.com/FabricMC/fabric/pull/2369">this PR</a>
*/
public class TheEndBiomesHelper {
@ApiStatus.Internal
private static Map<BiomeAPI.BiomeType, Set<ResourceKey<Biome>>> END_BIOMES = new HashMap<>();
@ApiStatus.Internal
public static void add(BiomeAPI.BiomeType type, ResourceKey<Biome> biome) {
if (biome == null) return;
END_BIOMES.computeIfAbsent(type, t -> new HashSet<>()).add(biome);
}
private static boolean has(BiomeAPI.BiomeType type, ResourceKey<Biome> biome) {
if (biome == null) return false;
Set<ResourceKey<Biome>> set = END_BIOMES.get(type);
if (set == null) return false;
return set.contains(biome);
}
/**
* Returns true if the given biome was added as a main end Biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsMainIslandBiome(ResourceKey<Biome> biome) {
return has(BiomeAPI.BiomeType.END_CENTER, biome);
}
/**
* Returns true if the given biome was added as a small end islands Biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsSmallIslandsBiome(ResourceKey<Biome> biome) {
return has(BiomeAPI.BiomeType.END_VOID, biome);
}
/**
* Returns true if the given biome was added as a Highland Biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsHighlandsBiome(ResourceKey<Biome> biome) {
return has(BiomeAPI.BiomeType.END_LAND, biome);
}
/**
* Returns true if the given biome was added as midland biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End as midland biome by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsEndMidlands(ResourceKey<Biome> biome) {
return false;
}
/**
* Returns true if the given biome was added as barrens biome in the end, considering the Vanilla end biomes,
* and any biomes added to the End as barrens biome by mods.
*
* @param biome The biome to search for
*/
public static boolean canGenerateAsEndBarrens(ResourceKey<Biome> biome) {
return has(BiomeAPI.BiomeType.END_BARRENS, biome);
}
public static boolean canGenerateInEnd(ResourceKey<Biome> biome) {
return canGenerateAsHighlandsBiome(biome)
|| canGenerateAsEndBarrens(biome)
|| canGenerateAsEndMidlands(biome)
|| canGenerateAsSmallIslandsBiome(biome)
|| canGenerateAsMainIslandBiome(biome);
}
}

View file

@ -1,215 +0,0 @@
package org.betterx.bclib.api.v2.generator.config;
import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.api.v2.generator.map.hex.HexBiomeMap;
import org.betterx.bclib.api.v2.generator.map.square.SquareBiomeMap;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.util.TriFunction;
import org.betterx.worlds.together.biomesource.config.BiomeSourceConfig;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.util.Mth;
import net.minecraft.util.StringRepresentable;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
public class BCLEndBiomeSourceConfig implements BiomeSourceConfig<BCLibEndBiomeSource> {
public static final BCLEndBiomeSourceConfig VANILLA = new BCLEndBiomeSourceConfig(
EndBiomeMapType.VANILLA,
EndBiomeGeneratorType.VANILLA,
true,
4096,
128,
128,
128,
128
);
public static final BCLEndBiomeSourceConfig MINECRAFT_17 = new BCLEndBiomeSourceConfig(
EndBiomeMapType.SQUARE,
EndBiomeGeneratorType.PAULEVS,
true,
VANILLA.innerVoidRadiusSquared * 16 * 16,
256,
256,
256,
256
);
public static final BCLEndBiomeSourceConfig MINECRAFT_18 = new BCLEndBiomeSourceConfig(
EndBiomeMapType.HEX,
EndBiomeGeneratorType.PAULEVS,
true,
MINECRAFT_17.innerVoidRadiusSquared,
MINECRAFT_17.centerBiomesSize,
MINECRAFT_17.voidBiomesSize,
MINECRAFT_17.landBiomesSize,
MINECRAFT_17.barrensBiomesSize
);
public static final BCLEndBiomeSourceConfig DEFAULT = MINECRAFT_18;
public static final Codec<BCLEndBiomeSourceConfig> CODEC = RecordCodecBuilder.create(instance -> instance
.group(
EndBiomeMapType.CODEC
.fieldOf("map_type")
.orElse(DEFAULT.mapVersion)
.forGetter(o -> o.mapVersion),
EndBiomeGeneratorType.CODEC
.fieldOf("generator_version")
.orElse(DEFAULT.generatorVersion)
.forGetter(o -> o.generatorVersion),
Codec.BOOL
.fieldOf("with_void_biomes")
.orElse(DEFAULT.withVoidBiomes)
.forGetter(o -> o.withVoidBiomes),
Codec.INT
.fieldOf("inner_void_radius_squared")
.orElse(DEFAULT.innerVoidRadiusSquared)
.forGetter(o -> o.innerVoidRadiusSquared),
Codec.INT
.fieldOf("center_biomes_size")
.orElse(DEFAULT.centerBiomesSize)
.forGetter(o -> o.centerBiomesSize),
Codec.INT
.fieldOf("void_biomes_size")
.orElse(DEFAULT.voidBiomesSize)
.forGetter(o -> o.voidBiomesSize),
Codec.INT
.fieldOf("land_biomes_size")
.orElse(DEFAULT.landBiomesSize)
.forGetter(o -> o.landBiomesSize),
Codec.INT
.fieldOf("barrens_biomes_size")
.orElse(DEFAULT.barrensBiomesSize)
.forGetter(o -> o.barrensBiomesSize)
)
.apply(instance, BCLEndBiomeSourceConfig::new));
public BCLEndBiomeSourceConfig(
@NotNull EndBiomeMapType mapVersion,
@NotNull EndBiomeGeneratorType generatorVersion,
boolean withVoidBiomes,
int innerVoidRadiusSquared,
int centerBiomesSize,
int voidBiomesSize,
int landBiomesSize,
int barrensBiomesSize
) {
this.mapVersion = mapVersion;
this.generatorVersion = generatorVersion;
this.withVoidBiomes = withVoidBiomes;
this.innerVoidRadiusSquared = innerVoidRadiusSquared;
this.barrensBiomesSize = Mth.clamp(barrensBiomesSize, 1, 8192);
this.voidBiomesSize = Mth.clamp(voidBiomesSize, 1, 8192);
this.centerBiomesSize = Mth.clamp(centerBiomesSize, 1, 8192);
this.landBiomesSize = Mth.clamp(landBiomesSize, 1, 8192);
}
public enum EndBiomeMapType implements StringRepresentable {
VANILLA("vanilla", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker)),
SQUARE("square", (seed, biomeSize, picker) -> new SquareBiomeMap(seed, biomeSize, picker)),
HEX("hex", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker));
public static final Codec<EndBiomeMapType> CODEC = StringRepresentable.fromEnum(EndBiomeMapType::values);
public final String name;
public final @NotNull TriFunction<Long, Integer, BiomePicker, BiomeMap> mapBuilder;
EndBiomeMapType(String name, @NotNull TriFunction<Long, Integer, BiomePicker, BiomeMap> mapBuilder) {
this.name = name;
this.mapBuilder = mapBuilder;
}
@Override
public String getSerializedName() {
return name;
}
@Override
public String toString() {
return name;
}
}
public enum EndBiomeGeneratorType implements StringRepresentable {
VANILLA("vanilla"),
PAULEVS("paulevs");
public static final Codec<EndBiomeGeneratorType> CODEC = StringRepresentable.fromEnum(EndBiomeGeneratorType::values);
public final String name;
EndBiomeGeneratorType(String name) {
this.name = name;
}
@Override
public String getSerializedName() {
return name;
}
@Override
public String toString() {
return name;
}
}
public final @NotNull EndBiomeMapType mapVersion;
public final @NotNull EndBiomeGeneratorType generatorVersion;
public final boolean withVoidBiomes;
public final int innerVoidRadiusSquared;
public final int voidBiomesSize;
public final int centerBiomesSize;
public final int landBiomesSize;
public final int barrensBiomesSize;
@Override
public String toString() {
return "BCLEndBiomeSourceConfig{" +
"mapVersion=" + mapVersion +
", generatorVersion=" + generatorVersion +
", withVoidBiomes=" + withVoidBiomes +
", innerVoidRadiusSquared=" + innerVoidRadiusSquared +
", voidBiomesSize=" + voidBiomesSize +
", centerBiomesSize=" + centerBiomesSize +
", landBiomesSize=" + landBiomesSize +
", barrensBiomesSize=" + barrensBiomesSize +
'}';
}
@Override
public boolean couldSetWithoutRepair(BiomeSourceConfig<?> input) {
if (input instanceof BCLEndBiomeSourceConfig cfg) {
return withVoidBiomes == cfg.withVoidBiomes && mapVersion == cfg.mapVersion;
}
return false;
}
@Override
public boolean sameConfig(BiomeSourceConfig<?> input) {
return this.equals(input);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BCLEndBiomeSourceConfig that = (BCLEndBiomeSourceConfig) o;
return withVoidBiomes == that.withVoidBiomes && innerVoidRadiusSquared == that.innerVoidRadiusSquared && voidBiomesSize == that.voidBiomesSize && centerBiomesSize == that.centerBiomesSize && landBiomesSize == that.landBiomesSize && barrensBiomesSize == that.barrensBiomesSize && mapVersion == that.mapVersion && generatorVersion == that.generatorVersion;
}
@Override
public int hashCode() {
return Objects.hash(
mapVersion,
generatorVersion,
withVoidBiomes,
innerVoidRadiusSquared,
voidBiomesSize,
centerBiomesSize,
landBiomesSize,
barrensBiomesSize
);
}
}

View file

@ -1,130 +0,0 @@
package org.betterx.bclib.api.v2.generator.config;
import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.api.v2.generator.map.hex.HexBiomeMap;
import org.betterx.bclib.api.v2.generator.map.square.SquareBiomeMap;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.util.TriFunction;
import org.betterx.worlds.together.biomesource.config.BiomeSourceConfig;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.util.Mth;
import net.minecraft.util.StringRepresentable;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
public class BCLNetherBiomeSourceConfig implements BiomeSourceConfig<BCLibNetherBiomeSource> {
public static final BCLNetherBiomeSourceConfig VANILLA = new BCLNetherBiomeSourceConfig(
NetherBiomeMapType.VANILLA,
256,
86,
false
);
public static final BCLNetherBiomeSourceConfig MINECRAFT_17 = new BCLNetherBiomeSourceConfig(
NetherBiomeMapType.SQUARE,
256,
86,
true
);
public static final BCLNetherBiomeSourceConfig MINECRAFT_18 = new BCLNetherBiomeSourceConfig(
NetherBiomeMapType.HEX,
MINECRAFT_17.biomeSize,
MINECRAFT_17.biomeSizeVertical,
MINECRAFT_17.useVerticalBiomes
);
public static final BCLNetherBiomeSourceConfig DEFAULT = MINECRAFT_18;
public static final Codec<BCLNetherBiomeSourceConfig> CODEC = RecordCodecBuilder.create(instance -> instance
.group(
BCLNetherBiomeSourceConfig.NetherBiomeMapType.CODEC
.fieldOf("map_type")
.orElse(DEFAULT.mapVersion)
.forGetter(o -> o.mapVersion),
Codec.INT.fieldOf("biome_size").orElse(DEFAULT.biomeSize).forGetter(o -> o.biomeSize),
Codec.INT.fieldOf("biome_size_vertical")
.orElse(DEFAULT.biomeSizeVertical)
.forGetter(o -> o.biomeSizeVertical),
Codec.BOOL.fieldOf("use_vertical_biomes")
.orElse(DEFAULT.useVerticalBiomes)
.forGetter(o -> o.useVerticalBiomes)
)
.apply(instance, BCLNetherBiomeSourceConfig::new));
public final @NotNull NetherBiomeMapType mapVersion;
public final int biomeSize;
public final int biomeSizeVertical;
public final boolean useVerticalBiomes;
public BCLNetherBiomeSourceConfig(
@NotNull NetherBiomeMapType mapVersion,
int biomeSize,
int biomeSizeVertical,
boolean useVerticalBiomes
) {
this.mapVersion = mapVersion;
this.biomeSize = Mth.clamp(biomeSize, 1, 8192);
this.biomeSizeVertical = Mth.clamp(biomeSizeVertical, 1, 8192);
this.useVerticalBiomes = useVerticalBiomes;
}
@Override
public String toString() {
return "BCLibNetherBiomeSourceConfig{" +
"mapVersion=" + mapVersion +
'}';
}
@Override
public boolean couldSetWithoutRepair(BiomeSourceConfig<?> input) {
if (input instanceof BCLNetherBiomeSourceConfig cfg) {
return mapVersion == cfg.mapVersion;
}
return false;
}
@Override
public boolean sameConfig(BiomeSourceConfig<?> input) {
return this.equals(input);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BCLNetherBiomeSourceConfig)) return false;
BCLNetherBiomeSourceConfig that = (BCLNetherBiomeSourceConfig) o;
return mapVersion == that.mapVersion;
}
@Override
public int hashCode() {
return Objects.hash(mapVersion);
}
public enum NetherBiomeMapType implements StringRepresentable {
VANILLA("vanilla", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker)),
SQUARE("square", (seed, biomeSize, picker) -> new SquareBiomeMap(seed, biomeSize, picker)),
HEX("hex", (seed, biomeSize, picker) -> new HexBiomeMap(seed, biomeSize, picker));
public static final Codec<NetherBiomeMapType> CODEC = StringRepresentable.fromEnum(NetherBiomeMapType::values);
public final String name;
public final TriFunction<Long, Integer, BiomePicker, BiomeMap> mapBuilder;
NetherBiomeMapType(String name, TriFunction<Long, Integer, BiomePicker, BiomeMap> mapBuilder) {
this.name = name;
this.mapBuilder = mapBuilder;
}
@Override
public String getSerializedName() {
return name;
}
@Override
public String toString() {
return name;
}
}
}

View file

@ -1,113 +0,0 @@
package org.betterx.bclib.api.v2.generator.map;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.interfaces.TriConsumer;
import org.betterx.bclib.noise.OpenSimplexNoise;
import org.betterx.bclib.util.TriFunction;
import net.minecraft.util.Mth;
import java.util.Random;
public class MapStack implements BiomeMap {
private final OpenSimplexNoise noise;
private final BiomeMap[] maps;
private final double layerDistortion;
private final int worldHeight;
private final int minValue;
private final int maxValue;
private final int maxIndex;
public MapStack(
long seed,
int size,
BiomePicker picker,
int mapHeight,
int worldHeight,
TriFunction<Long, Integer, BiomePicker, BiomeMap> mapConstructor
) {
final int mapCount = Mth.ceil((float) worldHeight / mapHeight);
this.maxIndex = mapCount - 1;
this.worldHeight = worldHeight;
this.layerDistortion = mapHeight * 0.1;
minValue = Mth.floor(mapHeight * 0.5F + 0.5F);
maxValue = Mth.floor(worldHeight - mapHeight * 0.5F + 0.5F);
maps = new BiomeMap[mapCount];
Random random = new Random(seed);
for (int i = 0; i < mapCount; i++) {
maps[i] = mapConstructor.apply(random.nextLong(), size, picker);
maps[i].setChunkProcessor(this::onChunkCreation);
}
noise = new OpenSimplexNoise(random.nextInt());
}
@Override
public void clearCache() {
for (BiomeMap map : maps) {
map.clearCache();
}
}
@Override
public void setChunkProcessor(TriConsumer<Integer, Integer, Integer> processor) {
}
@Override
public BiomeChunk getChunk(int cx, int cz, boolean update) {
return null;
}
@Override
public BiomePicker.ActualBiome getBiome(double x, double y, double z) {
int mapIndex;
if (y < minValue) {
mapIndex = 0;
} else if (y > maxValue) {
mapIndex = maxIndex;
} else {
mapIndex = Mth.floor((y + noise.eval(
x * 0.03,
z * 0.03
) * layerDistortion) / worldHeight * maxIndex + 0.5F);
mapIndex = Mth.clamp(mapIndex, 0, maxIndex);
}
return maps[mapIndex].getBiome(x, y, z);
}
private void onChunkCreation(int cx, int cz, int side) {
BiomePicker.ActualBiome[][] biomeMap = new BiomePicker.ActualBiome[side][side];
BiomeChunk[] chunks = new BiomeChunk[maps.length];
boolean isNoEmpty = false;
for (int i = 0; i < maps.length; i++) {
chunks[i] = maps[i].getChunk(cx, cz, false);
for (int x = 0; x < side; x++) {
for (int z = 0; z < side; z++) {
if (biomeMap[x][z] == null) {
BiomePicker.ActualBiome biome = chunks[i].getBiome(x, z);
if (biome.bclBiome.isVertical()) {
biomeMap[x][z] = biome;
isNoEmpty = true;
}
}
}
}
}
if (isNoEmpty) {
for (int i = 0; i < maps.length; i++) {
for (int x = 0; x < side; x++) {
for (int z = 0; z < side; z++) {
if (biomeMap[x][z] != null) {
chunks[i].setBiome(x, z, biomeMap[x][z]);
}
}
}
}
}
}
}

View file

@ -1,160 +0,0 @@
package org.betterx.bclib.api.v2.generator.map.hex;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import java.util.Arrays;
public class HexBiomeChunk implements BiomeChunk {
private static final short SIDE = 32;
private static final byte SIDE_PRE = 4;
private static final short SIZE = SIDE * SIDE;
private static final short MAX_SIDE = SIZE - SIDE;
private static final byte SCALE_PRE = SIDE / SIDE_PRE;
private static final byte SIZE_PRE = SIDE_PRE * SIDE_PRE;
private static final byte SIDE_MASK = SIDE - 1;
private static final byte SIDE_PRE_MASK = SIDE_PRE - 1;
private static final byte SIDE_OFFSET = (byte) Math.round(Math.log(SIDE) / Math.log(2));
private static final byte SIDE_PRE_OFFSET = (byte) Math.round(Math.log(SIDE_PRE) / Math.log(2));
private static final short[][] NEIGHBOURS;
private final BiomePicker.ActualBiome[] biomes = new BiomePicker.ActualBiome[SIZE];
public HexBiomeChunk(WorldgenRandom random, BiomePicker picker) {
BiomePicker.ActualBiome[][] buffers = new BiomePicker.ActualBiome[2][SIZE];
for (BiomePicker.ActualBiome[] buffer : buffers) {
Arrays.fill(buffer, null);
}
for (byte index = 0; index < SIZE_PRE; index++) {
byte px = (byte) (index >> SIDE_PRE_OFFSET);
byte pz = (byte) (index & SIDE_PRE_MASK);
px = (byte) (px * SCALE_PRE + random.nextInt(SCALE_PRE));
pz = (byte) (pz * SCALE_PRE + random.nextInt(SCALE_PRE));
circle(buffers[0], getIndex(px, pz), picker.getBiome(random), null);
}
boolean hasEmptyCells = true;
byte bufferIndex = 0;
while (hasEmptyCells) {
BiomePicker.ActualBiome[] inBuffer = buffers[bufferIndex];
bufferIndex = (byte) ((bufferIndex + 1) & 1);
BiomePicker.ActualBiome[] outBuffer = buffers[bufferIndex];
hasEmptyCells = false;
for (short index = SIDE; index < MAX_SIDE; index++) {
byte z = (byte) (index & SIDE_MASK);
if (z == 0 || z == SIDE_MASK) {
continue;
}
if (inBuffer[index] != null) {
outBuffer[index] = inBuffer[index];
short[] neighbours = getNeighbours(index & SIDE_MASK);
short indexSide = (short) (index + neighbours[random.nextInt(6)]);
if (indexSide >= 0 && indexSide < SIZE && outBuffer[indexSide] == null) {
outBuffer[indexSide] = inBuffer[index];
}
} else {
hasEmptyCells = true;
}
}
}
BiomePicker.ActualBiome[] outBuffer = buffers[bufferIndex];
byte preN = (byte) (SIDE_MASK - 2);
for (byte index = 0; index < SIDE; index++) {
outBuffer[getIndex(index, (byte) 0)] = outBuffer[getIndex(index, (byte) 2)];
outBuffer[getIndex((byte) 0, index)] = outBuffer[getIndex((byte) 2, index)];
outBuffer[getIndex(index, SIDE_MASK)] = outBuffer[getIndex(index, preN)];
outBuffer[getIndex(SIDE_MASK, index)] = outBuffer[getIndex(preN, index)];
}
for (short index = 0; index < SIZE; index++) {
if (outBuffer[index] == null) {
outBuffer[index] = picker.getBiome(random);
} else if (random.nextInt(4) == 0) {
circle(outBuffer, index, outBuffer[index].getSubBiome(random), outBuffer[index]);
}
}
System.arraycopy(outBuffer, 0, this.biomes, 0, SIZE);
}
private void circle(
BiomePicker.ActualBiome[] buffer,
short center,
BiomePicker.ActualBiome biome,
BiomePicker.ActualBiome mask
) {
if (buffer[center] == mask) {
buffer[center] = biome;
}
short[] neighbours = getNeighbours(center & SIDE_MASK);
for (short i : neighbours) {
short index = (short) (center + i);
if (index >= 0 && index < SIZE && buffer[index] == mask) {
buffer[index] = biome;
}
}
}
private static byte wrap(int value) {
return (byte) (value & SIDE_MASK);
}
private short getIndex(byte x, byte z) {
return (short) ((short) x << SIDE_OFFSET | z);
}
@Override
public BiomePicker.ActualBiome getBiome(int x, int z) {
return biomes[getIndex(wrap(x), wrap(z))];
}
@Override
public void setBiome(int x, int z, BiomePicker.ActualBiome biome) {
biomes[getIndex(wrap(x), wrap(z))] = biome;
}
@Override
public int getSide() {
return SIDE;
}
public static int scaleCoordinate(int value) {
return value >> SIDE_OFFSET;
}
public static boolean isBorder(int value) {
return wrap(value) == SIDE_MASK;
}
private short[] getNeighbours(int z) {
return NEIGHBOURS[z & 1];
}
public static float scaleMap(float size) {
return size / (SIDE >> 2);
}
static {
NEIGHBOURS = new short[2][6];
NEIGHBOURS[0][0] = 1;
NEIGHBOURS[0][1] = -1;
NEIGHBOURS[0][2] = SIDE;
NEIGHBOURS[0][3] = -SIDE;
NEIGHBOURS[0][4] = SIDE + 1;
NEIGHBOURS[0][5] = SIDE - 1;
NEIGHBOURS[1][0] = 1;
NEIGHBOURS[1][1] = -1;
NEIGHBOURS[1][2] = SIDE;
NEIGHBOURS[1][3] = -SIDE;
NEIGHBOURS[1][4] = -SIDE + 1;
NEIGHBOURS[1][5] = -SIDE - 1;
}
}

View file

@ -1,187 +0,0 @@
package org.betterx.bclib.api.v2.generator.map.hex;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.interfaces.TriConsumer;
import org.betterx.bclib.noise.OpenSimplexNoise;
import org.betterx.bclib.util.MHelper;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.Random;
public class HexBiomeMap implements BiomeMap {
private static final float RAD_INNER = (float) Math.sqrt(3.0) * 0.5F;
private static final float COEF = 0.25F * (float) Math.sqrt(3.0);
private static final float COEF_HALF = COEF * 0.5F;
private static final float SIN = (float) Math.sin(0.4);
private static final float COS = (float) Math.cos(0.4);
private static final float[] EDGE_CIRCLE_X;
private static final float[] EDGE_CIRCLE_Z;
private final Map<ChunkPos, HexBiomeChunk> chunks = Maps.newConcurrentMap();
private final BiomePicker picker;
private final OpenSimplexNoise[] noises = new OpenSimplexNoise[2];
private TriConsumer<Integer, Integer, Integer> processor;
private final byte noiseIterations;
private final float scale;
private final int seed;
public HexBiomeMap(long seed, int size, BiomePicker picker) {
this.picker = picker;
this.scale = HexBiomeChunk.scaleMap(size);
Random random = new Random(seed);
noises[0] = new OpenSimplexNoise(random.nextInt());
noises[1] = new OpenSimplexNoise(random.nextInt());
noiseIterations = (byte) Math.min(Math.ceil(Math.log(scale) / Math.log(2)), 5);
this.seed = random.nextInt();
}
@Override
public void clearCache() {
if (chunks.size() > 127) {
chunks.clear();
}
}
@Override
public BiomePicker.ActualBiome getBiome(double x, double y, double z) {
BiomePicker.ActualBiome biome = getRawBiome(x, z);
BiomePicker.ActualBiome edge = biome.getEdge();
int size = biome.bclBiome.getEdgeSize();
if (edge == null && biome.getParentBiome() != null) {
edge = biome.getParentBiome().getEdge();
size = biome.getParentBiome().bclBiome.getEdgeSize();
}
if (edge == null) {
return biome;
}
for (byte i = 0; i < 8; i++) {
if (!getRawBiome(x + size * EDGE_CIRCLE_X[i], z + size * EDGE_CIRCLE_Z[i]).isSame(biome)) {
return edge;
}
}
return biome;
}
@Override
public BiomeChunk getChunk(final int cx, final int cz, final boolean update) {
final ChunkPos pos = new ChunkPos(cx, cz);
HexBiomeChunk chunk = chunks.get(pos);
if (chunk == null) {
WorldgenRandom random = new WorldgenRandom(RandomSource.create(MHelper.getSeed(seed, cx, cz)));
chunk = new HexBiomeChunk(random, picker);
if (update && processor != null) {
processor.accept(cx, cz, chunk.getSide());
}
chunks.put(pos, chunk);
}
return chunk;
}
@Override
public void setChunkProcessor(TriConsumer<Integer, Integer, Integer> processor) {
this.processor = processor;
}
private BiomePicker.ActualBiome getRawBiome(double x, double z) {
double px = x / scale * RAD_INNER;
double pz = z / scale;
double dx = rotateX(px, pz);
double dz = rotateZ(px, pz);
px = dx;
pz = dz;
dx = getNoise(px, pz, (byte) 0) * 0.2F;
dz = getNoise(pz, px, (byte) 1) * 0.2F;
px += dx;
pz += dz;
int cellZ = (int) Math.floor(pz);
boolean offset = (cellZ & 1) == 1;
if (offset) {
px += 0.5;
}
int cellX = (int) Math.floor(px);
float pointX = (float) (px - cellX - 0.5);
float pointZ = (float) (pz - cellZ - 0.5);
if (Math.abs(pointZ) < 0.3333F) {
return getChunkBiome(cellX, cellZ);
}
if (insideHexagon(0, 0, 1.1555F, pointZ * RAD_INNER, pointX)) {
return getChunkBiome(cellX, cellZ);
}
cellX = pointX < 0 ? (offset ? cellX - 1 : cellX) : (offset ? cellX : cellX + 1);
cellZ = pointZ < 0 ? cellZ - 1 : cellZ + 1;
return getChunkBiome(cellX, cellZ);
}
private BiomePicker.ActualBiome getChunkBiome(int x, int z) {
int cx = HexBiomeChunk.scaleCoordinate(x);
int cz = HexBiomeChunk.scaleCoordinate(z);
if (((z >> 2) & 1) == 0 && HexBiomeChunk.isBorder(x)) {
x = 0;
cx += 1;
} else if (((x >> 2) & 1) == 0 && HexBiomeChunk.isBorder(z)) {
z = 0;
cz += 1;
}
return getChunk(cx, cz, true).getBiome(x, z);
}
private boolean insideHexagon(float centerX, float centerZ, float radius, float x, float z) {
double dx = Math.abs(x - centerX) / radius;
double dy = Math.abs(z - centerZ) / radius;
return (dy <= COEF) && (COEF * dx + 0.25F * dy <= COEF_HALF);
}
private double getNoise(double x, double z, byte state) {
double result = 0;
for (byte i = 1; i <= noiseIterations; i++) {
OpenSimplexNoise noise = noises[state];
state = (byte) ((state + 1) & 1);
result += noise.eval(x * i, z * i) / i;
}
return result;
}
private double rotateX(double x, double z) {
return x * COS - z * SIN;
}
private double rotateZ(double x, double z) {
return x * SIN + z * COS;
}
static {
EDGE_CIRCLE_X = new float[8];
EDGE_CIRCLE_Z = new float[8];
for (byte i = 0; i < 8; i++) {
float angle = i / 4F * (float) Math.PI;
EDGE_CIRCLE_X[i] = (float) Math.sin(angle);
EDGE_CIRCLE_Z[i] = (float) Math.cos(angle);
}
}
}

View file

@ -1,66 +0,0 @@
package org.betterx.bclib.api.v2.generator.map.square;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import net.minecraft.world.level.levelgen.WorldgenRandom;
public class SquareBiomeChunk implements BiomeChunk {
private static final int BIT_OFFSET = 4;
protected static final int WIDTH = 1 << BIT_OFFSET;
private static final int SM_WIDTH = WIDTH >> 1;
private static final int SM_BIT_OFFSET = BIT_OFFSET >> 1;
private static final int MASK_OFFSET = SM_WIDTH - 1;
protected static final int MASK_WIDTH = WIDTH - 1;
private static final int SM_CAPACITY = SM_WIDTH * SM_WIDTH;
private static final int CAPACITY = WIDTH * WIDTH;
private final BiomePicker.ActualBiome[] biomes;
public SquareBiomeChunk(WorldgenRandom random, BiomePicker picker) {
BiomePicker.ActualBiome[] PreBio = new BiomePicker.ActualBiome[SM_CAPACITY];
biomes = new BiomePicker.ActualBiome[CAPACITY];
for (int x = 0; x < SM_WIDTH; x++) {
int offset = x << SM_BIT_OFFSET;
for (int z = 0; z < SM_WIDTH; z++) {
PreBio[offset | z] = picker.getBiome(random);
}
}
for (int x = 0; x < WIDTH; x++) {
int offset = x << BIT_OFFSET;
for (int z = 0; z < WIDTH; z++) {
biomes[offset | z] = PreBio[getSmIndex(offsetXZ(x, random), offsetXZ(z, random))].getSubBiome(random);
}
}
}
@Override
public BiomePicker.ActualBiome getBiome(int x, int z) {
return biomes[getIndex(x & MASK_WIDTH, z & MASK_WIDTH)];
}
@Override
public void setBiome(int x, int z, BiomePicker.ActualBiome biome) {
biomes[getIndex(x & MASK_WIDTH, z & MASK_WIDTH)] = biome;
}
@Override
public int getSide() {
return WIDTH;
}
private int offsetXZ(int x, WorldgenRandom random) {
return ((x + random.nextInt(2)) >> 1) & MASK_OFFSET;
}
private int getIndex(int x, int z) {
return x << BIT_OFFSET | z;
}
private int getSmIndex(int x, int z) {
return x << SM_BIT_OFFSET | z;
}
}

View file

@ -1,143 +0,0 @@
package org.betterx.bclib.api.v2.generator.map.square;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.interfaces.BiomeChunk;
import org.betterx.bclib.interfaces.BiomeMap;
import org.betterx.bclib.interfaces.TriConsumer;
import org.betterx.bclib.noise.OpenSimplexNoise;
import org.betterx.bclib.util.MHelper;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.levelgen.LegacyRandomSource;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import com.google.common.collect.Maps;
import java.util.Map;
public class SquareBiomeMap implements BiomeMap {
private final Map<ChunkPos, SquareBiomeChunk> maps = Maps.newHashMap();
private final OpenSimplexNoise noiseX;
private final OpenSimplexNoise noiseZ;
private final WorldgenRandom random;
private final BiomePicker picker;
private final int sizeXZ;
private final int depth;
private final int size;
private TriConsumer<Integer, Integer, Integer> processor;
public SquareBiomeMap(long seed, int size, BiomePicker picker) {
random = new WorldgenRandom(new LegacyRandomSource(seed));
noiseX = new OpenSimplexNoise(random.nextLong());
noiseZ = new OpenSimplexNoise(random.nextLong());
this.sizeXZ = size;
depth = (int) Math.ceil(Math.log(size) / Math.log(2)) - 2;
this.size = 1 << depth;
this.picker = picker;
}
@Override
public void clearCache() {
if (maps.size() > 32) {
maps.clear();
}
}
@Override
public BiomePicker.ActualBiome getBiome(double x, double y, double z) {
BiomePicker.ActualBiome biome = getRawBiome(x, z);
if (biome.getEdge() != null || (biome.getParentBiome() != null && biome.getParentBiome().getEdge() != null)) {
BiomePicker.ActualBiome search = biome;
if (biome.getParentBiome() != null) {
search = biome.getParentBiome();
}
int size = search.bclBiome.getEdgeSize();
boolean edge = !search.isSame(getRawBiome(x + size, z));
edge = edge || !search.isSame(getRawBiome(x - size, z));
edge = edge || !search.isSame(getRawBiome(x, z + size));
edge = edge || !search.isSame(getRawBiome(x, z - size));
edge = edge || !search.isSame(getRawBiome(x - 1, z - 1));
edge = edge || !search.isSame(getRawBiome(x - 1, z + 1));
edge = edge || !search.isSame(getRawBiome(x + 1, z - 1));
edge = edge || !search.isSame(getRawBiome(x + 1, z + 1));
if (edge) {
biome = search.getEdge();
}
}
return biome;
}
@Override
public void setChunkProcessor(TriConsumer<Integer, Integer, Integer> processor) {
this.processor = processor;
}
@Override
public BiomeChunk getChunk(int cx, int cz, boolean update) {
ChunkPos cpos = new ChunkPos(cx, cz);
SquareBiomeChunk chunk = maps.get(cpos);
if (chunk == null) {
synchronized (random) {
random.setLargeFeatureWithSalt(0, cpos.x, cpos.z, 0);
chunk = new SquareBiomeChunk(random, picker);
}
maps.put(cpos, chunk);
if (update && processor != null) {
processor.accept(cx, cz, chunk.getSide());
}
}
return chunk;
}
private BiomePicker.ActualBiome getRawBiome(double bx, double bz) {
double x = bx * size / sizeXZ;
double z = bz * size / sizeXZ;
double px = bx * 0.2;
double pz = bz * 0.2;
for (int i = 0; i < depth; i++) {
double nx = (x + noiseX.eval(px, pz)) / 2F;
double nz = (z + noiseZ.eval(px, pz)) / 2F;
x = nx;
z = nz;
px = px / 2 + i;
pz = pz / 2 + i;
}
int ix = MHelper.floor(x);
int iz = MHelper.floor(z);
if ((ix & SquareBiomeChunk.MASK_WIDTH) == SquareBiomeChunk.MASK_WIDTH) {
x += (iz / 2) & 1;
}
if ((iz & SquareBiomeChunk.MASK_WIDTH) == SquareBiomeChunk.MASK_WIDTH) {
z += (ix / 2) & 1;
}
ChunkPos cpos = new ChunkPos(
MHelper.floor(x / SquareBiomeChunk.WIDTH),
MHelper.floor(z / SquareBiomeChunk.WIDTH)
);
SquareBiomeChunk chunk = maps.get(cpos);
if (chunk == null) {
synchronized (random) {
random.setLargeFeatureWithSalt(0, cpos.x, cpos.z, 0);
chunk = new SquareBiomeChunk(random, picker);
}
maps.put(cpos, chunk);
}
return chunk.getBiome(MHelper.floor(x), MHelper.floor(z));
}
}

View file

@ -1,161 +0,0 @@
package org.betterx.bclib.api.v2.levelgen;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.LifeCycleAPI;
import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI;
import org.betterx.bclib.api.v2.datafixer.DataFixerAPI;
import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource;
import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig;
import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI;
import org.betterx.bclib.api.v2.tag.TagAPI;
import org.betterx.bclib.registry.PresetsRegistry;
import org.betterx.worlds.together.tag.v3.TagManager;
import org.betterx.worlds.together.world.WorldConfig;
import org.betterx.worlds.together.world.event.WorldEvents;
import org.betterx.worlds.together.worldPreset.TogetherWorldPreset;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagLoader;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.WorldGenSettings;
import net.minecraft.world.level.levelgen.presets.WorldPreset;
import net.minecraft.world.level.storage.LevelStorageSource;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
public class LevelGenEvents {
public static void setupWorld() {
InternalBiomeAPI.prepareNewLevel();
DataExchangeAPI.prepareServerside();
}
public static void register() {
WorldEvents.BEFORE_WORLD_LOAD.on(LevelGenEvents::prepareWorld);
WorldEvents.BEFORE_SERVER_WORLD_LOAD.on(LevelGenEvents::prepareServerWorld);
WorldEvents.ON_WORLD_LOAD.on(LevelGenEvents::onWorldLoad);
WorldEvents.WORLD_REGISTRY_READY.on(LevelGenEvents::onRegistryReady);
WorldEvents.ON_FINALIZE_LEVEL_STEM.on(LevelGenEvents::finalizeStem);
WorldEvents.PATCH_WORLD.on(LevelGenEvents::patchExistingWorld);
WorldEvents.ADAPT_WORLD_PRESET.on(LevelGenEvents::adaptWorldPresetSettings);
WorldEvents.BEFORE_ADDING_TAGS.on(LevelGenEvents::appplyTags);
}
private static void appplyTags(
String directory,
Map<ResourceLocation, List<TagLoader.EntryWithSource>> tagsMap
) {
//make sure we include Tags registered by the deprecated API
TagAPI.apply(directory, tagsMap);
if (directory.equals(TagManager.BIOMES.directory)) {
InternalBiomeAPI._runBiomeTagAdders();
}
}
private static boolean patchExistingWorld(
LevelStorageSource.LevelStorageAccess storageAccess,
Consumer<Boolean> allDone
) {
return DataFixerAPI.fixData(storageAccess, true, allDone);
}
private static Optional<Holder<WorldPreset>> adaptWorldPresetSettings(
Optional<Holder<WorldPreset>> currentPreset,
WorldGenSettings worldGenSettings
) {
LevelStem endStem = worldGenSettings.dimensions().get(LevelStem.END);
//We probably loaded a Datapack for the End
if (!(endStem.generator().getBiomeSource() instanceof BCLibEndBiomeSource)) {
if (currentPreset.isPresent()) {
if (currentPreset.get().value() instanceof TogetherWorldPreset worldPreset) {
ResourceKey worldPresetKey = currentPreset.get().unwrapKey().orElse(null);
//user did not configure/change the Preset!
if (PresetsRegistry.BCL_WORLD.equals(worldPresetKey)
|| PresetsRegistry.BCL_WORLD_17.equals(worldPresetKey)) {
BCLib.LOGGER.info("Detected Datapack for END.");
LevelStem configuredEndStem = worldPreset.getDimension(LevelStem.END);
if (configuredEndStem.generator().getBiomeSource() instanceof BCLibEndBiomeSource endSource) {
BCLib.LOGGER.info("Changing Default WorldPreset Settings for Datapack use.");
BCLEndBiomeSourceConfig inputConfig = endSource.getTogetherConfig();
endSource.setTogetherConfig(new BCLEndBiomeSourceConfig(
inputConfig.mapVersion,
BCLEndBiomeSourceConfig.EndBiomeGeneratorType.VANILLA,
false,
inputConfig.innerVoidRadiusSquared,
inputConfig.centerBiomesSize,
inputConfig.voidBiomesSize,
inputConfig.landBiomesSize,
inputConfig.barrensBiomesSize
));
}
}
}
}
}
return currentPreset;
}
private static void onRegistryReady(RegistryAccess a) {
InternalBiomeAPI.initRegistry(a);
}
private static void prepareWorld(
LevelStorageSource.LevelStorageAccess storageAccess,
Map<ResourceKey<LevelStem>, ChunkGenerator> dimensions,
boolean isNewWorld
) {
setupWorld();
if (isNewWorld) {
WorldConfig.saveFile(BCLib.MOD_ID);
DataFixerAPI.initializePatchData();
} else {
LevelGenUtil.migrateGeneratorSettings();
}
}
private static void prepareServerWorld(
LevelStorageSource.LevelStorageAccess storageAccess,
Map<ResourceKey<LevelStem>, ChunkGenerator> dimensions,
boolean isNewWorld
) {
setupWorld();
if (isNewWorld) {
WorldConfig.saveFile(BCLib.MOD_ID);
DataFixerAPI.initializePatchData();
} else {
LevelGenUtil.migrateGeneratorSettings();
DataFixerAPI.fixData(storageAccess, false, (didFix) -> {/* not called when showUI==false */});
}
}
private static void onWorldLoad() {
LifeCycleAPI._runBeforeLevelLoad();
}
private static void finalizeStem(
WorldGenSettings settings,
ResourceKey<LevelStem> dimension,
LevelStem levelStem
) {
InternalBiomeAPI.applyModifications(levelStem.generator().getBiomeSource(), dimension);
}
}

View file

@ -1,206 +0,0 @@
package org.betterx.bclib.api.v2.levelgen;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.generator.BCLChunkGenerator;
import org.betterx.bclib.api.v2.generator.BCLibEndBiomeSource;
import org.betterx.bclib.api.v2.generator.BCLibNetherBiomeSource;
import org.betterx.bclib.api.v2.generator.config.BCLEndBiomeSourceConfig;
import org.betterx.bclib.api.v2.generator.config.BCLNetherBiomeSourceConfig;
import org.betterx.bclib.registry.PresetsRegistry;
import org.betterx.worlds.together.levelgen.WorldGenUtil;
import org.betterx.worlds.together.util.ModUtil;
import org.betterx.worlds.together.world.WorldConfig;
import org.betterx.worlds.together.worldPreset.TogetherWorldPreset;
import com.mojang.serialization.Lifecycle;
import net.minecraft.core.Holder;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.WorldGenSettings;
import net.minecraft.world.level.levelgen.presets.WorldPreset;
import net.minecraft.world.level.levelgen.presets.WorldPresets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
public class LevelGenUtil {
private static final String TAG_VERSION = "version";
private static final String TAG_BN_GEN_VERSION = "generator_version";
@NotNull
public static LevelStem getBCLNetherLevelStem(WorldGenUtil.Context context, BCLNetherBiomeSourceConfig config) {
BCLibNetherBiomeSource netherSource = new BCLibNetherBiomeSource(context.biomes, config);
return new LevelStem(
context.dimension,
new BCLChunkGenerator(
context.structureSets,
context.noiseParameters,
netherSource,
context.generatorSettings
)
);
}
public static LevelStem getBCLEndLevelStem(WorldGenUtil.Context context, BCLEndBiomeSourceConfig config) {
BCLibEndBiomeSource endSource = new BCLibEndBiomeSource(context.biomes, config);
return new LevelStem(
context.dimension,
new BCLChunkGenerator(
context.structureSets,
context.noiseParameters,
endSource,
context.generatorSettings
)
);
}
public static WorldGenSettings replaceGenerator(
ResourceKey<LevelStem> dimensionKey,
ResourceKey<DimensionType> dimensionTypeKey,
RegistryAccess registryAccess,
WorldGenSettings worldGenSettings,
ChunkGenerator generator
) {
Registry<DimensionType> dimensionTypeRegistry = registryAccess.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY);
Registry<LevelStem> newDimensions = withDimension(
dimensionKey,
dimensionTypeKey,
dimensionTypeRegistry,
worldGenSettings.dimensions(),
generator
);
return new WorldGenSettings(
worldGenSettings.seed(),
worldGenSettings.generateStructures(),
worldGenSettings.generateBonusChest(),
newDimensions
);
}
public static Registry<LevelStem> withDimension(
ResourceKey<LevelStem> dimensionKey,
ResourceKey<DimensionType> dimensionTypeKey,
Registry<DimensionType> dimensionTypeRegistry,
Registry<LevelStem> inputDimensions,
ChunkGenerator generator
) {
LevelStem levelStem = inputDimensions.get(dimensionKey);
Holder<DimensionType> dimensionType = levelStem == null
? dimensionTypeRegistry.getOrCreateHolderOrThrow(dimensionTypeKey)
: levelStem.typeHolder();
return withDimension(dimensionKey, inputDimensions, new LevelStem(dimensionType, generator));
}
public static Registry<LevelStem> withDimension(
ResourceKey<LevelStem> dimensionKey,
Registry<LevelStem> inputDimensions,
LevelStem levelStem
) {
MappedRegistry<LevelStem> writableRegistry = new MappedRegistry<>(
Registry.LEVEL_STEM_REGISTRY,
Lifecycle.experimental(),
null
);
writableRegistry.register(
dimensionKey,
levelStem,
Lifecycle.stable()
);
for (Map.Entry<ResourceKey<LevelStem>, LevelStem> entry : inputDimensions.entrySet()) {
ResourceKey<LevelStem> resourceKey = entry.getKey();
if (resourceKey == dimensionKey) continue;
writableRegistry.register(
resourceKey,
entry.getValue(),
inputDimensions.lifecycle(entry.getValue())
);
}
return writableRegistry;
}
public static void migrateGeneratorSettings() {
final CompoundTag settingsNbt = WorldGenUtil.getPresetsNbt();
if (settingsNbt.size() == 0) {
CompoundTag oldGen = WorldGenUtil.getGeneratorNbt();
if (oldGen != null) {
if (oldGen.contains("type")) {
BCLib.LOGGER.info("Found World with beta generator Settings.");
if ("bclib:bcl_world_preset_settings".equals(oldGen.getString("type"))) {
int netherVersion = 18;
int endVersion = 18;
if (oldGen.contains("minecraft:the_nether"))
netherVersion = oldGen.getInt("minecraft:the_nether");
if (oldGen.contains("minecraft:the_end"))
endVersion = oldGen.getInt("minecraft:the_end");
if (netherVersion == 18) netherVersion = 0;
else if (netherVersion == 17) netherVersion = 1;
else netherVersion = 2;
if (endVersion == 18) endVersion = 0;
else if (endVersion == 17) endVersion = 1;
else endVersion = 2;
var presets = List.of(
TogetherWorldPreset.getDimensionsMap(PresetsRegistry.BCL_WORLD),
TogetherWorldPreset.getDimensionsMap(PresetsRegistry.BCL_WORLD_17),
TogetherWorldPreset.getDimensionsMap(WorldPresets.NORMAL)
);
Map<ResourceKey<LevelStem>, ChunkGenerator> dimensions = new HashMap<>();
dimensions.put(LevelStem.OVERWORLD, presets.get(0).get(LevelStem.OVERWORLD));
dimensions.put(LevelStem.NETHER, presets.get(netherVersion).get(LevelStem.NETHER));
dimensions.put(LevelStem.END, presets.get(endVersion).get(LevelStem.END));
TogetherWorldPreset.writeWorldPresetSettingsDirect(dimensions);
}
return;
}
}
BCLib.LOGGER.info("Found World without generator Settings. Setting up data...");
ResourceKey<WorldPreset> biomeSourceVersion = PresetsRegistry.BCL_WORLD;
final CompoundTag bclRoot = WorldConfig.getRootTag(BCLib.MOD_ID);
String bclVersion = "0.0.0";
if (bclRoot.contains(TAG_VERSION)) {
bclVersion = bclRoot.getString(TAG_VERSION);
}
boolean isPre18 = !ModUtil.isLargerOrEqualVersion(bclVersion, "1.0.0");
if (isPre18) {
BCLib.LOGGER.info("World was create pre 1.18!");
biomeSourceVersion = PresetsRegistry.BCL_WORLD_17;
}
if (WorldConfig.hasMod("betternether")) {
BCLib.LOGGER.info("Found Data from BetterNether, using for migration.");
final CompoundTag bnRoot = WorldConfig.getRootTag("betternether");
biomeSourceVersion = "1.17".equals(bnRoot.getString(TAG_BN_GEN_VERSION))
? PresetsRegistry.BCL_WORLD_17
: PresetsRegistry.BCL_WORLD;
}
Registry<LevelStem> dimensions = TogetherWorldPreset.getDimensions(biomeSourceVersion);
if (dimensions != null) {
BCLib.LOGGER.info("Set world to BiomeSource Version " + biomeSourceVersion);
TogetherWorldPreset.writeWorldPresetSettings(dimensions);
} else {
BCLib.LOGGER.error("Failed to set world to BiomeSource Version " + biomeSourceVersion);
}
}
}
}

View file

@ -1,363 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.util.WeightedList;
import net.minecraft.core.Registry;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
public class BCLBiome extends BCLBiomeSettings {
private final Set<TagKey<Biome>> biomeTags = Sets.newHashSet();
private final WeightedList<BCLBiome> subbiomes = new WeightedList<>();
private final Map<String, Object> customData = Maps.newHashMap();
private final ResourceLocation biomeID;
private final ResourceKey<Biome> biomeKey;
final Biome biomeToRegister;
private final List<Climate.ParameterPoint> parameterPoints = Lists.newArrayList();
private BCLBiome biomeParent;
/**
* Create wrapper for existing biome using its {@link ResourceLocation} identifier.
*
* @param biomeKey {@link ResourceKey} for the {@link Biome}.
*/
protected BCLBiome(ResourceKey<Biome> biomeKey) {
this(biomeKey.location());
}
/**
* Create wrapper for existing biome using its {@link ResourceLocation} identifier.
*
* @param biomeID {@link ResourceLocation} biome ID.
*/
protected BCLBiome(ResourceLocation biomeID) {
this(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID), null);
}
/**
* Create wrapper for existing biome using biome instance from {@link BuiltinRegistries}.
*
* @param biomeToRegister {@link Biome} to wrap.
*/
@Deprecated(forRemoval = true)
protected BCLBiome(Biome biomeToRegister) {
this(biomeToRegister, null);
}
/**
* Create wrapper for existing biome using biome instance from {@link BuiltinRegistries}.
*
* @param biomeToRegister {@link Biome} to wrap.
* @param settings The Settings for this Biome or {@code null} if you want to apply default settings
*/
@Deprecated(forRemoval = true)
protected BCLBiome(Biome biomeToRegister, VanillaBiomeSettings settings) {
this(BiomeAPI.getBiomeID(biomeToRegister), biomeToRegister, settings);
}
/**
* Create wrapper for existing biome using biome instance from {@link BuiltinRegistries}.
*
* @param biomeToRegister {@link Biome} to wrap.
* @param biomeID Teh ResoureLocation for this Biome
*/
@Deprecated(forRemoval = true)
public BCLBiome(ResourceLocation biomeID, Biome biomeToRegister) {
this(biomeID, biomeToRegister, null);
}
/**
* Create a new Biome
*
* @param biomeID {@link ResourceLocation} biome ID.
* @param biomeToRegister {@link Biome} to wrap.
* @param defaults The Settings for this Biome or null if you want to apply the defaults
*/
protected BCLBiome(ResourceLocation biomeID, Biome biomeToRegister, BCLBiomeSettings defaults) {
this(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID), biomeToRegister, defaults);
}
/**
* Create a new Biome
*
* @param biomeKey {@link ResourceKey<Biome>} of the wrapped Biome
* @param defaults The Settings for this Biome or null if you want to apply the defaults
*/
protected BCLBiome(ResourceKey<Biome> biomeKey, BCLBiomeSettings defaults) {
this(biomeKey, null, defaults);
}
/**
* Create a new Biome
*
* @param biomeKey {@link ResourceKey<Biome>} of the wrapped Biome
* @param biomeToRegister The biome you want to use when this instance gets registered through the {@link BiomeAPI}
* @param defaults The Settings for this Biome or null if you want to apply the defaults
*/
protected BCLBiome(ResourceKey<Biome> biomeKey, Biome biomeToRegister, BCLBiomeSettings defaults) {
this.biomeToRegister = biomeToRegister;
this.subbiomes.add(this, 1.0F);
this.biomeID = biomeKey.location();
this.biomeKey = biomeKey;
if (defaults != null) {
defaults.applyWithDefaults(this);
}
}
/**
* Get current biome edge.
*
* @return {@link BCLBiome} edge.
*/
@Nullable
public BCLBiome getEdge() {
return edge;
}
/**
* Set biome edge for this biome instance.
*
* @param edge {@link BCLBiome} as the edge biome.
* @return same {@link BCLBiome}.
*/
BCLBiome setEdge(BCLBiome edge) {
this.edge = edge;
edge.biomeParent = this;
return this;
}
/**
* Set biome edge for this biome instance. If there is already an edge, the
* biome is added as subBiome to the current edge-biome
*
* @param edge The new edge
* @return same {@link BCLBiome}.
*/
public BCLBiome addEdge(BCLBiome edge) {
if (this.edge != null) {
this.edge.addSubBiome(edge);
} else {
this.setEdge(edge);
}
return this;
}
/**
* Adds sub-biome into this biome instance. Biome chance will be interpreted as a sub-biome generation chance.
* Biome itself has chance 1.0 compared to all its sub-biomes.
*
* @param biome {@link Random} to be added.
* @return same {@link BCLBiome}.
*/
public BCLBiome addSubBiome(BCLBiome biome) {
biome.biomeParent = this;
subbiomes.add(biome, biome.getGenChance());
return this;
}
/**
* Checks if specified biome is a sub-biome of this one.
*
* @param biome {@link Random}.
* @return true if this instance contains specified biome as a sub-biome.
*/
public boolean containsSubBiome(BCLBiome biome) {
return subbiomes.contains(biome);
}
/**
* Getter for a random sub-biome from all existing sub-biomes. Will return biome itself if there are no sub-biomes.
*
* @param random {@link Random}.
* @return {@link BCLBiome}.
*/
public BCLBiome getSubBiome(WorldgenRandom random) {
return subbiomes.get(random);
}
public void forEachSubBiome(BiConsumer<BCLBiome, Float> consumer) {
for (int i = 0; i < subbiomes.size(); i++)
consumer.accept(subbiomes.get(i), subbiomes.getWeight(i));
}
/**
* Getter for parent {@link BCLBiome} or null if there are no parent biome.
*
* @return {@link BCLBiome} or null.
*/
@Nullable
public BCLBiome getParentBiome() {
return this.biomeParent;
}
/**
* Compares biome instances (directly) and their parents. Used in custom world generator.
*
* @param biome {@link BCLBiome}
* @return true if biome or its parent is same.
*/
public boolean isSame(BCLBiome biome) {
return biome == this || (biome.biomeParent != null && biome.biomeParent == this);
}
/**
* Getter for biome identifier.
*
* @return {@link ResourceLocation}
*/
public ResourceLocation getID() {
return biomeID;
}
/**
* Getter for biome from buil-in registry. For datapack biomes will be same as actual biome.
*
* @return {@link Biome}.
*/
@Deprecated(forRemoval = true)
public Biome getBiome() {
if (biomeToRegister != null) return biomeToRegister;
return BiomeAPI.getFromBuiltinRegistry(biomeKey).value();
}
/**
* Getter for biomeKey
*
* @return {@link ResourceKey<Biome>}.
*/
public ResourceKey<Biome> getBiomeKey() {
return biomeKey;
}
/**
* For internal use from BiomeAPI only
*/
void afterRegistration() {
}
/**
* Getter for custom data. Will get custom data object or null if object doesn't exists.
*
* @param name {@link String} name of data object.
* @return object value or null.
*/
@Nullable
@SuppressWarnings("unchecked")
@Deprecated(forRemoval = true)
public <T> T getCustomData(String name) {
return (T) customData.get(name);
}
/**
* Getter for custom data. Will get custom data object or default value if object doesn't exists.
*
* @param name {@link String} name of data object.
* @param defaultValue object default value.
* @return object value or default value.
*/
@SuppressWarnings("unchecked")
@Deprecated(forRemoval = true)
public <T> T getCustomData(String name, T defaultValue) {
return (T) customData.getOrDefault(name, defaultValue);
}
/**
* Adds custom data object to this biome instance.
*
* @param name {@link String} name of data object.
* @param obj any data to add.
* @return same {@link BCLBiome}.
*/
public BCLBiome addCustomData(String name, Object obj) {
customData.put(name, obj);
return this;
}
/**
* Adds custom data object to this biome instance.
*
* @param data a {@link Map} with custom data.
* @return same {@link BCLBiome}.
*/
public BCLBiome addCustomData(Map<String, Object> data) {
customData.putAll(data);
return this;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
BCLBiome biome = (BCLBiome) obj;
return biome != null && biomeID.equals(biome.biomeID);
}
@Override
public int hashCode() {
return biomeID.hashCode();
}
@Override
public String toString() {
return biomeID.toString();
}
/**
* Adds structures to this biome. For internal use only.
* Used inside {@link BCLBiomeBuilder}.
*/
void addClimateParameters(List<Climate.ParameterPoint> params) {
this.parameterPoints.addAll(params);
}
public void forEachClimateParameter(Consumer<Climate.ParameterPoint> consumer) {
this.parameterPoints.forEach(consumer);
}
/**
* Returns the group used in the config Files for this biome
* <p>
* Example: {@code Configs.BIOMES_CONFIG.getFloat(configGroup(), "generation_chance", 1.0);}
*
* @return The group name
*/
public String configGroup() {
return biomeID.getNamespace() + "." + biomeID.getPath();
}
private final boolean didLoadConfig = false;
public boolean isEdgeBiome() {
if (getParentBiome() == null) return false;
return getParentBiome().edge == this;
}
boolean allowFabricRegistration() {
return !isEdgeBiome();
}
}

View file

@ -1,858 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.api.v2.levelgen.features.BCLFeature;
import org.betterx.bclib.api.v2.levelgen.structures.BCLStructure;
import org.betterx.bclib.api.v2.levelgen.surface.SurfaceRuleBuilder;
import org.betterx.bclib.entity.BCLEntityWrapper;
import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor;
import org.betterx.bclib.util.CollectionsUtil;
import org.betterx.bclib.util.ColorUtil;
import org.betterx.bclib.util.Pair;
import org.betterx.bclib.util.TriFunction;
import org.betterx.worlds.together.surfaceRules.SurfaceRuleRegistry;
import org.betterx.worlds.together.tag.v3.TagManager;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.particles.ParticleOptions;
import net.minecraft.data.worldgen.BiomeDefaultFeatures;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.Music;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.tags.TagKey;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.level.biome.*;
import net.minecraft.world.level.biome.Biome.BiomeBuilder;
import net.minecraft.world.level.biome.Biome.Precipitation;
import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.Noises;
import net.minecraft.world.level.levelgen.SurfaceRules;
import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
public class BCLBiomeBuilder {
@FunctionalInterface
public interface BiomeSupplier<T> extends TriFunction<ResourceLocation, Biome, BCLBiomeSettings, T> {
}
private static final BCLBiomeBuilder INSTANCE = new BCLBiomeBuilder();
private static final SurfaceRules.ConditionSource SURFACE_NOISE = SurfaceRules.noiseCondition(
Noises.SOUL_SAND_LAYER,
-0.012
);
private final List<Pair<GenerationStep.Carving, Holder<? extends ConfiguredWorldCarver<?>>>> carvers = new ArrayList<>(
1);
private BiomeGenerationSettings.Builder generationSettings;
private BiomeSpecialEffects.Builder effectsBuilder;
private MobSpawnSettings.Builder spawnSettings;
private SurfaceRules.RuleSource surfaceRule;
private Precipitation precipitation;
private ResourceLocation biomeID;
private final Set<TagKey<Biome>> tags = Sets.newHashSet();
private final List<Climate.ParameterPoint> parameters = Lists.newArrayList();
private float temperature;
private float fogDensity;
private float genChance;
private float downfall;
private float height;
private int edgeSize;
private BCLBiome edge;
private boolean vertical;
private BiomeAPI.BiomeType biomeType;
/**
* Starts new biome building process.
*
* @param biomeID {@link ResourceLocation} biome identifier.
* @return prepared {@link BCLBiomeBuilder} instance.
*/
public static BCLBiomeBuilder start(ResourceLocation biomeID) {
INSTANCE.biomeID = biomeID;
INSTANCE.precipitation = Precipitation.NONE;
INSTANCE.generationSettings = null;
INSTANCE.effectsBuilder = null;
INSTANCE.spawnSettings = null;
INSTANCE.temperature = 1.0F;
INSTANCE.fogDensity = 1.0F;
INSTANCE.edgeSize = 0;
INSTANCE.downfall = 1.0F;
INSTANCE.genChance = 1.0F;
INSTANCE.height = 0.1F;
INSTANCE.vertical = false;
INSTANCE.edge = null;
INSTANCE.carvers.clear();
INSTANCE.parameters.clear();
INSTANCE.tags.clear();
INSTANCE.biomeType = null;
return INSTANCE;
}
public BCLBiomeBuilder addNetherClimateParamater(float temperature, float humidity) {
parameters.add(Climate.parameters(temperature, humidity, 0, 0, 0, 0, 0));
return this;
}
/**
* Set the type for this Biome. If the type was set, the Biome can be registered.
*
* @param type selected Type
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder type(BiomeAPI.BiomeType type) {
this.biomeType = type;
return this;
}
/**
* Set biome {@link Precipitation}. Affect biome visual effects (rain, snow, none).
*
* @param precipitation {@link Precipitation}
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder precipitation(Precipitation precipitation) {
this.precipitation = precipitation;
return this;
}
/**
* Set biome temperature, affect plant color, biome generation and ice formation.
*
* @param temperature biome temperature.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder temperature(float temperature) {
this.temperature = temperature;
return this;
}
/**
* Set biome wetness (same as downfall). Affect plant color and biome generation.
*
* @param wetness biome wetness (downfall).
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder wetness(float wetness) {
this.downfall = wetness;
return this;
}
/**
* Adds mob spawning to biome.
*
* @param entityType {@link EntityType} mob type.
* @param weight spawn weight.
* @param minGroupCount minimum mobs in group.
* @param maxGroupCount maximum mobs in group.
* @return same {@link BCLBiomeBuilder} instance.
*/
public <M extends Mob> BCLBiomeBuilder spawn(
EntityType<M> entityType,
int weight,
int minGroupCount,
int maxGroupCount
) {
getSpawns().addSpawn(
entityType.getCategory(),
new SpawnerData(entityType, weight, minGroupCount, maxGroupCount)
);
return this;
}
/**
* Adds mob spawning to biome.
*
* @param wrapper {@link BCLEntityWrapper} mob type.
* @param weight spawn weight.
* @param minGroupCount minimum mobs in group.
* @param maxGroupCount maximum mobs in group.
* @return same {@link BCLBiomeBuilder} instance.
*/
public <M extends Mob> BCLBiomeBuilder spawn(
BCLEntityWrapper<M> wrapper,
int weight,
int minGroupCount,
int maxGroupCount
) {
if (wrapper.canSpawn()) {
return spawn(wrapper.type(), weight, minGroupCount, maxGroupCount);
}
return this;
}
/**
* Adds ambient particles to thr biome.
*
* @param particle {@link ParticleOptions} particles (or {@link net.minecraft.core.particles.ParticleType}).
* @param probability particle spawn probability, should have low value (example: 0.01F).
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder particles(ParticleOptions particle, float probability) {
getEffects().ambientParticle(new AmbientParticleSettings(particle, probability));
return this;
}
/**
* Sets sky color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder skyColor(int color) {
getEffects().skyColor(color);
return this;
}
/**
* Sets sky color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder skyColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return skyColor(ColorUtil.color(red, green, blue));
}
/**
* Sets fog color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder fogColor(int color) {
getEffects().fogColor(color);
return this;
}
/**
* Sets fog color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder fogColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return fogColor(ColorUtil.color(red, green, blue));
}
/**
* Sets fog density for the biome.
*
* @param density fog density as a float, default value is 1.0F.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder fogDensity(float density) {
this.fogDensity = density;
return this;
}
/**
* Sets generation chance for this biome.
*
* @param genChance
* @return same {@link BCLBiomeBuilder}.
*/
public BCLBiomeBuilder genChance(float genChance) {
this.genChance = genChance;
return this;
}
/**
* Sets edge size for this biome.
*
* @param edgeSize size of the Edge (in Blocks)
* @return same {@link BCLBiomeBuilder}.
*/
public BCLBiomeBuilder edgeSize(int edgeSize) {
this.edgeSize = edgeSize;
return this;
}
/**
* Sets edge-Biome for this biome.
*
* @param edge The Edge Biome
* @return same {@link BCLBiomeBuilder}.
*/
public BCLBiomeBuilder edge(BCLBiome edge) {
this.edge = edge;
return this;
}
/**
* Sets edge-Biome for this biome.
*
* @param edge The Edge Biome
* @param edgeSize size of the Edge (in Blocks)
* @return same {@link BCLBiomeBuilder}.
*/
public BCLBiomeBuilder edge(BCLBiome edge, int edgeSize) {
this.edge(edge);
this.edgeSize(edgeSize);
return this;
}
/**
* Sets water color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterColor(int color) {
getEffects().waterColor(color);
return this;
}
/**
* Sets water color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return waterColor(ColorUtil.color(red, green, blue));
}
/**
* Sets underwater fog color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterFogColor(int color) {
getEffects().waterFogColor(color);
return this;
}
/**
* Sets underwater fog color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterFogColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return waterFogColor(ColorUtil.color(red, green, blue));
}
/**
* Sets water and underwater fig color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterAndFogColor(int color) {
return waterColor(color).waterFogColor(color);
}
/**
* Sets water and underwater fig color for the biome. Color is in ARGB int format.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder waterAndFogColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return waterAndFogColor(ColorUtil.color(red, green, blue));
}
/**
* Sets grass color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder grassColor(int color) {
getEffects().grassColorOverride(color);
return this;
}
/**
* Sets grass color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder grassColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return grassColor(ColorUtil.color(red, green, blue));
}
/**
* Sets leaves and plants color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder foliageColor(int color) {
getEffects().foliageColorOverride(color);
return this;
}
/**
* Sets leaves and plants color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder foliageColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return foliageColor(ColorUtil.color(red, green, blue));
}
/**
* Sets grass, leaves and all plants color for the biome. Color is in ARGB int format.
*
* @param color ARGB color as integer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder plantsColor(int color) {
return grassColor(color).foliageColor(color);
}
/**
* Sets grass, leaves and all plants color for the biome. Color represented as red, green and blue channel values.
*
* @param red red color component [0-255]
* @param green green color component [0-255]
* @param blue blue color component [0-255]
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder plantsColor(int red, int green, int blue) {
red = Mth.clamp(red, 0, 255);
green = Mth.clamp(green, 0, 255);
blue = Mth.clamp(blue, 0, 255);
return plantsColor(ColorUtil.color(red, green, blue));
}
/**
* Sets biome music, used for biomes in the Nether and End.
*
* @param music {@link Music} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder music(Music music) {
getEffects().backgroundMusic(music);
return this;
}
/**
* Sets biome music, used for biomes in the Nether and End.
*
* @param music {@link SoundEvent} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder music(SoundEvent music) {
return music(new Music(music, 600, 2400, true));
}
/**
* Sets biome ambient loop sound. Can be used for biome environment.
*
* @param loopSound {@link SoundEvent} to use as a loop.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder loop(SoundEvent loopSound) {
getEffects().ambientLoopSound(loopSound);
return this;
}
/**
* Sets biome mood sound. Can be used for biome environment.
*
* @param mood {@link SoundEvent} to use as a mood.
* @param tickDelay delay between sound events in ticks.
* @param blockSearchExtent block search radius (for area available for sound).
* @param soundPositionOffset offset in sound.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder mood(SoundEvent mood, int tickDelay, int blockSearchExtent, float soundPositionOffset) {
getEffects().ambientMoodSound(new AmbientMoodSettings(mood, tickDelay, blockSearchExtent, soundPositionOffset));
return this;
}
/**
* Sets biome mood sound. Can be used for biome environment.
*
* @param mood {@link SoundEvent} to use as a mood.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder mood(SoundEvent mood) {
return mood(mood, 6000, 8, 2.0F);
}
/**
* Sets biome additionsl ambient sounds.
*
* @param additions {@link SoundEvent} to use.
* @param intensity sound intensity. Default is 0.0111F.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder additions(SoundEvent additions, float intensity) {
getEffects().ambientAdditionsSound(new AmbientAdditionsSettings(additions, intensity));
return this;
}
/**
* Sets biome additionsl ambient sounds.
*
* @param additions {@link SoundEvent} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder additions(SoundEvent additions) {
return additions(additions, 0.0111F);
}
/**
* Adds new feature to the biome.
*
* @param decoration {@link Decoration} feature step.
* @param feature {@link PlacedFeature}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(Decoration decoration, Holder<PlacedFeature> feature) {
getGeneration().addFeature(decoration, feature);
return this;
}
/**
* Adds vanilla Mushrooms.
*
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder defaultMushrooms() {
return feature(BiomeDefaultFeatures::addDefaultMushrooms);
}
/**
* Adds vanilla Nether Ores.
*
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder netherDefaultOres() {
return feature(BiomeDefaultFeatures::addNetherDefaultOres);
}
/**
* Will add features into biome, used for vanilla feature adding functions.
*
* @param featureAdd {@link Consumer} with {@link BiomeGenerationSettings.Builder}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(Consumer<BiomeGenerationSettings.Builder> featureAdd) {
featureAdd.accept(getGeneration());
return this;
}
/**
* Adds new feature to the biome.
*
* @param feature {@link BCLFeature}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(BCLFeature feature) {
return feature(feature.getDecoration(), feature.getPlacedFeature());
}
/**
* Adds new feature to the biome.
*
* @param feature {@link BCLFeature}.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder feature(org.betterx.bclib.api.v3.levelgen.features.BCLFeature feature) {
return feature(feature.decoration, feature.placedFeature);
}
/**
* Adds new structure feature into the biome.
*
* @param structureTag {@link TagKey} to add.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder structure(TagKey<Biome> structureTag) {
tags.add(structureTag);
return this;
}
/**
* Adds new structure feature into thr biome. Will add building biome into the structure list.
*
* @param structure {@link BCLStructure} to add.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder structure(BCLStructure structure) {
structure.addInternalBiome(biomeID);
return structure(structure.biomeTag);
}
/**
* Adds new world carver into the biome.
*
* @param carver {@link ConfiguredWorldCarver} to add.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder carver(GenerationStep.Carving step, Holder<? extends ConfiguredWorldCarver<?>> carver) {
final ResourceLocation immutableID = biomeID;
var oKey = carver.unwrapKey();
if (oKey.isPresent()) {
BiomeModifications.addCarver(
ctx -> ctx.getBiomeKey().location().equals(immutableID),
step,
(ResourceKey<ConfiguredWorldCarver<?>>) oKey.get()
);
}
//carvers.add(new Pair<>(step, carver));
return this;
}
/**
* Adds new world surface rule for the given block
*
* @param surfaceBlock {@link Block} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder surface(Block surfaceBlock) {
return surface(surfaceBlock.defaultBlockState());
}
/**
* Adds new world surface rule for the given block
*
* @param surfaceBlock {@link BlockState} to use.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder surface(BlockState surfaceBlock) {
return surface(SurfaceRuleBuilder.start().surface(surfaceBlock).build());
}
/**
* Adds blocks to the biome surface and below it (with specified depth).
*
* @param surfaceBlock {@link Block} that will cover biome.
* @param subterrainBlock {@link Block} below it with specified depth.
* @param depth thickness of bottom block layer.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder surface(Block surfaceBlock, Block subterrainBlock, int depth) {
return surface(SurfaceRuleBuilder
.start()
.surface(surfaceBlock.defaultBlockState())
.subsurface(subterrainBlock.defaultBlockState(), depth)
.build());
}
/**
* Adds surface rule to this biome.
*
* @param newSurfaceRule {link SurfaceRules.RuleSource} surface rule.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder surface(SurfaceRules.RuleSource newSurfaceRule) {
this.surfaceRule = newSurfaceRule;
return this;
}
public BCLBiomeBuilder tag(TagKey<Biome>... tag) {
for (TagKey<Biome> t : tag) {
tags.add(t);
}
return this;
}
/**
* Set terrain height for the biome. Can be used in custom generators, doesn't change vanilla biome distribution or generation.
*
* @param height a relative float terrain height value.
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder terrainHeight(float height) {
this.height = height;
return this;
}
/**
* Make this a vertical Biome
*
* @return same {@link BCLBiomeBuilder} instance.
*/
public BCLBiomeBuilder vertical() {
this.vertical = vertical;
return this;
}
private static BiomeGenerationSettings fixGenerationSettings(BiomeGenerationSettings settings) {
//Fabric Biome Modification API can not handle an empty carver map, thus we will create one with
//an empty HolderSet for every possible step:
//https://github.com/FabricMC/fabric/issues/2079
//TODO: Remove, once fabric gets fixed
if (settings instanceof BiomeGenerationSettingsAccessor acc) {
Map<GenerationStep.Carving, HolderSet<ConfiguredWorldCarver<?>>> carvers = CollectionsUtil.getMutable(acc.bclib_getCarvers());
for (GenerationStep.Carving step : GenerationStep.Carving.values()) {
carvers.computeIfAbsent(step, __ -> HolderSet.direct(Lists.newArrayList()));
}
acc.bclib_setCarvers(carvers);
}
return settings;
}
/**
* Get or create {@link BiomeSpecialEffects.Builder} for biome visual effects.
* For internal usage only.
* For internal usage only.
*
* @return new or same {@link BiomeSpecialEffects.Builder} instance.
*/
private BiomeSpecialEffects.Builder getEffects() {
if (effectsBuilder == null) {
effectsBuilder = new BiomeSpecialEffects.Builder();
}
return effectsBuilder;
}
/**
* Get or create {@link MobSpawnSettings.Builder} for biome mob spawning.
* For internal usage only.
*
* @return new or same {@link MobSpawnSettings.Builder} instance.
*/
private MobSpawnSettings.Builder getSpawns() {
if (spawnSettings == null) {
spawnSettings = new MobSpawnSettings.Builder();
}
return spawnSettings;
}
/**
* Get or create {@link BiomeGenerationSettings.Builder} for biome features and generation.
* For internal usage only.
*
* @return new or same {@link BiomeGenerationSettings.Builder} instance.
*/
private BiomeGenerationSettings.Builder getGeneration() {
if (generationSettings == null) {
generationSettings = new BiomeGenerationSettings.Builder();
}
return generationSettings;
}
/**
* Finalize biome creation.
*
* @return created {@link BCLBiome} instance.
*/
public BCLBiome build() {
return build((BiomeSupplier<BCLBiome>) BCLBiome::new);
}
/**
* Finalize biome creation.
*
* @param biomeConstructor {@link BiFunction} biome constructor.
* @return created {@link BCLBiome} instance.
* @deprecated Replaced with {@link #build(BiomeSupplier)}
*/
@Deprecated(forRemoval = true)
public <T extends BCLBiome> T build(BiFunction<ResourceLocation, Biome, T> biomeConstructor) {
return build((id, biome, settings) -> biomeConstructor.apply(id, biome));
}
/**
* Finalize biome creation.
*
* @param biomeConstructor {@link BiomeSupplier} biome constructor.
* @return created {@link BCLBiome} instance.
*/
public <T extends BCLBiome> T build(BiomeSupplier<T> biomeConstructor) {
BiomeBuilder builder = new BiomeBuilder()
.precipitation(precipitation)
.temperature(temperature)
.downfall(downfall);
builder.mobSpawnSettings(getSpawns().build());
builder.specialEffects(getEffects().build());
builder.generationSettings(fixGenerationSettings(getGeneration().build()));
BCLBiomeSettings settings = BCLBiomeSettings.createBCL()
.setTerrainHeight(height)
.setFogDensity(fogDensity)
.setGenChance(genChance)
.setEdgeSize(edgeSize)
.setEdge(edge)
.setVertical(vertical)
.build();
final Biome biome = builder.build();
final T res = biomeConstructor.apply(biomeID, biome, settings);
tags.forEach(tagKey -> TagManager.BIOMES.add(tagKey, res.getBiomeKey()));
//res.addBiomeTags(tags);
//res.setSurface(surfaceRule);
SurfaceRuleRegistry.registerRule(biomeID, surfaceRule, biomeID);
res.addClimateParameters(parameters);
//carvers.forEach(cfg -> BiomeAPI.addBiomeCarver(biome, cfg.second, cfg.first));
return res;
}
}

View file

@ -1,202 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.api.v2.generator.BiomePicker;
import org.betterx.bclib.config.Configs;
import net.minecraft.world.level.biome.Biome;
public class BCLBiomeSettings {
public static Builder createBCL() {
return new Builder();
}
public static class Builder extends CommonBuilder<BCLBiomeSettings, Builder> {
public Builder() {
super(new BCLBiomeSettings());
}
}
public static class CommonBuilder<T extends BCLBiomeSettings, R extends CommonBuilder> {
private final T storage;
CommonBuilder(T storage) {
this.storage = storage;
}
public T build() {
return storage;
}
/**
* Set gen chance for this biome, default value is 1.0.
*
* @param genChance chance of this biome to be generated.
* @return same {@link BCLBiomeSettings}.
*/
public R setGenChance(float genChance) {
storage.genChance = genChance;
return (R) this;
}
/**
* Setter for terrain height, can be used in custom terrain generator.
*
* @param terrainHeight a relative float terrain height value.
* @return same {@link Builder}.
*/
public R setTerrainHeight(float terrainHeight) {
storage.terrainHeight = terrainHeight;
return (R) this;
}
/**
* Set biome vertical distribution (for tall Nether only).
*
* @return same {@link Builder}.
*/
public R setVertical() {
return setVertical(true);
}
/**
* Set biome vertical distribution (for tall Nether only).
*
* @param vertical {@code boolean} value.
* @return same {@link Builder}.
*/
public R setVertical(boolean vertical) {
storage.vertical = vertical;
return (R) this;
}
/**
* Set edges size for this biome. Size is in blocks.
*
* @param size as a float value.
* @return same {@link Builder}.
*/
public R setEdgeSize(int size) {
storage.edgeSize = size;
return (R) this;
}
/**
* Set edges:biome for this biome.
*
* @param edge The {@link Biome}.
* @return same {@link Builder}.
*/
public R setEdge(BCLBiome edge) {
storage.edge = edge;
return (R) this;
}
/**
* Sets fog density for this biome.
*
* @param fogDensity
* @return same {@link Builder}.
*/
public R setFogDensity(float fogDensity) {
storage.fogDensity = fogDensity;
return (R) this;
}
}
protected BCLBiomeSettings() {
this.terrainHeight = 0.1F;
this.fogDensity = 1.0F;
this.genChance = 1.0F;
this.edgeSize = 0;
this.vertical = false;
this.edge = null;
}
float terrainHeight;
float fogDensity;
float genChance;
int edgeSize;
boolean vertical;
BCLBiome edge;
/**
* Getter for biome generation chance, used in {@link BiomePicker} and in custom generators.
*
* @return biome generation chance as float.
*/
public float getGenChance() {
return this.genChance;
}
/**
* Checks if biome is vertical, for tall Nether only (or for custom generators).
*
* @return is biome vertical or not.
*/
public boolean isVertical() {
return vertical;
}
/**
* Getter for terrain height, can be used in custom terrain generator.
*
* @return terrain height.
*/
public float getTerrainHeight() {
return terrainHeight;
}
/**
* Getter for fog density, used in custom for renderer.
*
* @return fog density as a float.
*/
public float getFogDensity() {
return fogDensity;
}
/**
* Getter for biome edge size.
*
* @return edge size in blocks.
*/
public int getEdgeSize() {
return edgeSize;
}
/**
* Getter for edge-biome.
*
* @return The assigned edge biome.
*/
public BCLBiome getEdge() {
return edge;
}
/**
* Load values from Config and apply to the passed Biome. The Default values for the loaded settings
* are derifed from the values store in this object
*
* @param biome {@link BCLBiome} to assign values to
*/
public void applyWithDefaults(BCLBiome biome) {
final String group = biome.configGroup();
biome.genChance = Configs.BIOMES_CONFIG.getFloat(group, "generation_chance", this.genChance);
if (edge != null) {
biome.edgeSize = Configs.BIOMES_CONFIG.getInt(group, "edge_size", this.edgeSize);
if (edgeSize > 0) {
biome.setEdge(edge);
}
}
if (!(this instanceof VanillaBiomeSettings)) {
biome.fogDensity = Configs.BIOMES_CONFIG.getFloat(group, "fog_density", this.fogDensity);
biome.vertical = Configs.BIOMES_CONFIG.getBoolean(group, "vertical", this.vertical);
biome.terrainHeight = Configs.BIOMES_CONFIG.getFloat(group, "terrain_height", this.terrainHeight);
}
Configs.BIOMES_CONFIG.saveChanges();
}
}

View file

@ -1,965 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.levelgen.features.BCLFeature;
import org.betterx.bclib.interfaces.SurfaceMaterialProvider;
import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor;
import org.betterx.bclib.mixin.common.MobSpawnSettingsAccessor;
import org.betterx.bclib.util.CollectionsUtil;
import org.betterx.worlds.together.tag.v3.CommonBiomeTags;
import org.betterx.worlds.together.tag.v3.TagManager;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BiomeTags;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.PalettedContainerRO;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.biome.v1.NetherBiomes;
import net.fabricmc.fabric.api.biome.v1.TheEndBiomes;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
public class BiomeAPI {
public static class BiomeType {
public static final BiomeType NONE = new BiomeType("NONE");
public static final BiomeType OVERWORLD = new BiomeType("OVERWORLD");
public static final BiomeType NETHER = new BiomeType("NETHER");
public static final BiomeType BCL_NETHER = new BiomeType("BCL_NETHER", NETHER);
public static final BiomeType END = new BiomeType("END");
public static final BiomeType END_IGNORE = new BiomeType("END_IGNORE", END);
public static final BiomeType END_LAND = new BiomeType("END_LAND", END);
public static final BiomeType END_VOID = new BiomeType("END_VOID", END);
public static final BiomeType END_CENTER = new BiomeType("END_CENTER", END);
public static final BiomeType END_BARRENS = new BiomeType("END_BARRENS", END);
public static final BiomeType BCL_END_LAND = new BiomeType("BCL_END_LAND", END_LAND);
public static final BiomeType BCL_END_VOID = new BiomeType("BCL_END_VOID", END_VOID);
public static final BiomeType BCL_END_CENTER = new BiomeType("BCL_END_CENTER", END_CENTER);
public static final BiomeType BCL_END_BARRENS = new BiomeType("BCL_END_BARRENS", END_BARRENS);
static final Map<ResourceLocation, BiomeType> BIOME_TYPE_MAP = Maps.newHashMap();
public final BiomeType parentOrNull;
private final String name;
public BiomeType(String name) {
this(name, null);
}
public BiomeType(String name, BiomeType parentOrNull) {
this.parentOrNull = parentOrNull;
this.name = name;
}
public boolean is(BiomeType d) {
if (d == this) return true;
if (parentOrNull != null) return parentOrNull.is(d);
return false;
}
public String getName() {
return name;
}
@Override
public String toString() {
String str = name;
if (parentOrNull != null) str += " -> " + parentOrNull;
return str;
}
}
/**
* Empty biome used as default value if requested biome doesn't exist or linked. Shouldn't be registered anywhere to prevent bugs.
* Have {@code Biomes.THE_VOID} as the reference biome.
*/
public static final BCLBiome EMPTY_BIOME = new BCLBiome(Biomes.THE_VOID.location());
private static final Map<ResourceLocation, BCLBiome> ID_MAP = Maps.newHashMap();
public static final BCLBiome NETHER_WASTES_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.NETHER_WASTES,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome CRIMSON_FOREST_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.CRIMSON_FOREST,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome WARPED_FOREST_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.WARPED_FOREST,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome SOUL_SAND_VALLEY_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.SOUL_SAND_VALLEY,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome BASALT_DELTAS_BIOME = InternalBiomeAPI.wrapNativeBiome(
Biomes.BASALT_DELTAS,
InternalBiomeAPI.OTHER_NETHER
);
public static final BCLBiome THE_END = InternalBiomeAPI.wrapNativeBiome(
Biomes.THE_END,
0.5F,
InternalBiomeAPI.OTHER_END_CENTER
);
public static final BCLBiome END_MIDLANDS = InternalBiomeAPI.wrapNativeBiome(
Biomes.END_MIDLANDS,
0.5F,
InternalBiomeAPI.OTHER_END_LAND
);
public static final BCLBiome END_HIGHLANDS = InternalBiomeAPI.wrapNativeBiome(
Biomes.END_HIGHLANDS,
END_MIDLANDS,
8,
0.5F,
InternalBiomeAPI.OTHER_END_LAND
);
public static final BCLBiome END_BARRENS = InternalBiomeAPI.wrapNativeBiome(
Biomes.END_BARRENS,
InternalBiomeAPI.OTHER_END_BARRENS
);
public static final BCLBiome SMALL_END_ISLANDS = InternalBiomeAPI.wrapNativeBiome(
Biomes.SMALL_END_ISLANDS,
InternalBiomeAPI.OTHER_END_VOID
);
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
*
* @param bclbiome {@link BCLBiome}
* @param dim The Dimension fo rthis Biome
* @return {@link BCLBiome}
*/
public static BCLBiome registerBiome(BCLBiome bclbiome, BiomeType dim) {
return registerBiome(bclbiome, dim, BuiltinRegistries.BIOME);
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
*
* @param bclbiome {@link BCLBiome}
* @param dim The Dimension fo rthis Biome
* @return {@link BCLBiome}
*/
static BCLBiome registerBiome(BCLBiome bclbiome, BiomeType dim, Registry<Biome> registryOrNull) {
if (registryOrNull != null
&& bclbiome.biomeToRegister != null
&& registryOrNull.get(bclbiome.getID()) == null) {
Registry.register(registryOrNull, bclbiome.getBiomeKey(), bclbiome.biomeToRegister);
}
ID_MAP.put(bclbiome.getID(), bclbiome);
BiomeType.BIOME_TYPE_MAP.put(bclbiome.getID(), dim);
if (dim != null && dim.is(BiomeType.NETHER)) {
TagManager.BIOMES.add(BiomeTags.IS_NETHER, bclbiome.getBiomeKey());
TagManager.BIOMES.add(CommonBiomeTags.IN_NETHER, bclbiome.getBiomeKey());
} else if (dim != null && dim.is(BiomeType.END)) {
TagManager.BIOMES.add(BiomeTags.IS_END, bclbiome.getBiomeKey());
TagManager.BIOMES.add(CommonBiomeTags.IN_END, bclbiome.getBiomeKey());
}
bclbiome.afterRegistration();
return bclbiome;
}
public static BCLBiome registerSubBiome(BCLBiome parent, BCLBiome subBiome) {
return registerSubBiome(
parent,
subBiome,
BiomeType.BIOME_TYPE_MAP.getOrDefault(parent.getID(), BiomeType.NONE)
);
}
public static BCLBiome registerSubBiome(BCLBiome parent, Biome subBiome, float genChance) {
return registerSubBiome(
parent,
subBiome,
genChance,
BiomeType.BIOME_TYPE_MAP.getOrDefault(parent.getID(), BiomeType.NONE)
);
}
public static BCLBiome registerSubBiome(BCLBiome parent, BCLBiome subBiome, BiomeType dim) {
registerBiome(subBiome, dim);
parent.addSubBiome(subBiome);
return subBiome;
}
public static BCLBiome registerSubBiome(BCLBiome parent, Biome biome, float genChance, BiomeType dim) {
BCLBiome subBiome = new BCLBiome(biome, VanillaBiomeSettings.createVanilla().setGenChance(genChance).build());
return registerSubBiome(parent, subBiome, dim);
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndLandBiome(BCLBiome biome) {
registerBiome(biome, BiomeType.BCL_END_LAND);
float weight = biome.getGenChance();
ResourceKey<Biome> key = biome.getBiomeKey();
if (biome.allowFabricRegistration()) {
if (biome.isEdgeBiome()) {
ResourceKey<Biome> parentKey = biome.getParentBiome().getBiomeKey();
TheEndBiomes.addMidlandsBiome(parentKey, key, weight);
} else {
TheEndBiomes.addHighlandsBiome(key, weight);
}
}
return biome;
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndVoidBiome(BCLBiome biome) {
registerBiome(biome, BiomeType.BCL_END_VOID);
float weight = biome.getGenChance();
ResourceKey<Biome> key = biome.getBiomeKey();
if (biome.allowFabricRegistration()) {
TheEndBiomes.addSmallIslandsBiome(key, weight);
}
return biome;
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a center island
* biome (will generate only on the center island).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndCenterBiome(BCLBiome biome) {
registerBiome(biome, BiomeType.BCL_END_CENTER);
float weight = biome.getGenChance();
ResourceKey<Biome> key = biome.getBiomeKey();
if (biome.allowFabricRegistration()) {
TheEndBiomes.addMainIslandBiome(key, weight);
}
return biome;
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a barrens island
* biome (will generate on the edge of midland biomes on the larger islands).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerEndBarrensBiome(BCLBiome highlandBiome, BCLBiome biome) {
registerBiome(biome, BiomeType.BCL_END_BARRENS);
float weight = biome.getGenChance();
ResourceKey<Biome> key = biome.getBiomeKey();
if (biome.allowFabricRegistration()) {
ResourceKey<Biome> parentKey = highlandBiome.getBiomeKey();
TheEndBiomes.addBarrensBiome(parentKey, key, weight);
}
return biome;
}
public static BCLBiome registerEndBiome(Holder<Biome> biome) {
BCLBiome bclBiome = new BCLBiome(biome.value(), null);
registerBiome(bclBiome, BiomeType.END);
return bclBiome;
}
public static BCLBiome registerCenterBiome(Holder<Biome> biome) {
BCLBiome bclBiome = new BCLBiome(biome.value(), null);
registerBiome(bclBiome, BiomeType.END_CENTER);
return bclBiome;
}
/**
* Get {@link BCLBiome} from {@link Biome} instance on server. Used to convert world biomes to BCLBiomes.
*
* @param biome - {@link Holder<Biome>} from world.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
@Deprecated(forRemoval = true)
public static BCLBiome getFromBiome(Holder<Biome> biome) {
if (InternalBiomeAPI.biomeRegistry == null) {
return EMPTY_BIOME;
}
return ID_MAP.getOrDefault(biome.unwrapKey().orElseThrow().location(), EMPTY_BIOME);
}
/**
* Get {@link BCLBiome} from biome on client. Used in fog rendering.
*
* @param biome - {@link Biome} from client world.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
@Environment(EnvType.CLIENT)
public static BCLBiome getRenderBiome(Biome biome) {
BCLBiome endBiome = InternalBiomeAPI.CLIENT.get(biome);
if (endBiome == null) {
Minecraft minecraft = Minecraft.getInstance();
ResourceLocation id = minecraft.level.registryAccess()
.registryOrThrow(Registry.BIOME_REGISTRY)
.getKey(biome);
endBiome = id == null ? EMPTY_BIOME : ID_MAP.getOrDefault(id, EMPTY_BIOME);
InternalBiomeAPI.CLIENT.put(biome, endBiome);
}
return endBiome;
}
/**
* Get biome {@link ResourceKey} from given {@link Biome}.
*
* @param biome - {@link Biome} from server world.
* @return biome {@link ResourceKey} or {@code null}.
*/
@Nullable
public static ResourceKey getBiomeKey(Biome biome) {
if (InternalBiomeAPI.biomeRegistry != null) {
Optional<ResourceKey<Biome>> key = InternalBiomeAPI.biomeRegistry.getResourceKey(biome);
if (key.isPresent()) return key.get();
}
return BuiltinRegistries.BIOME
.getResourceKey(biome)
.orElseGet(null);
}
/**
* Get biome {@link ResourceLocation} from given {@link Biome}.
*
* @param biome - {@link Biome} from server world.
* @return biome {@link ResourceLocation}.
*/
public static ResourceLocation getBiomeID(Biome biome) {
ResourceLocation id = null;
if (InternalBiomeAPI.biomeRegistry != null) {
id = InternalBiomeAPI.biomeRegistry.getKey(biome);
}
if (id == null) {
id = BuiltinRegistries.BIOME.getKey(biome);
}
if (id == null) {
BCLib.LOGGER.error("Unable to get ID for " + biome + ". Falling back to empty Biome...");
id = EMPTY_BIOME.getID();
}
return id;
}
/**
* Get biome {@link ResourceLocation} from given {@link Biome}.
*
* @param biome - {@link Holder<Biome>} from server world.
* @return biome {@link ResourceLocation}.
*/
public static ResourceLocation getBiomeID(Holder<Biome> biome) {
var oKey = biome.unwrapKey();
if (oKey.isPresent()) {
return oKey.get().location();
}
return null;
}
public static ResourceKey getBiomeKey(Holder<Biome> biome) {
return biome.unwrapKey().orElse(null);
}
public static ResourceKey getBiomeKeyOrThrow(Holder<Biome> biome) {
return biome.unwrapKey().orElseThrow();
}
public static Holder<Biome> getBiomeHolder(BCLBiome biome) {
return getBiomeHolder(biome.getBiomeKey());
}
public static Holder<Biome> getBiomeHolder(Biome biome) {
Optional<ResourceKey<Biome>> key = Optional.empty();
if (InternalBiomeAPI.biomeRegistry != null) {
key = InternalBiomeAPI.biomeRegistry.getResourceKey(biome);
} else {
key = BuiltinRegistries.BIOME.getResourceKey(biome);
}
return getBiomeHolder(key.orElseThrow());
}
public static Holder<Biome> getBiomeHolder(ResourceKey<Biome> biomeKey) {
if (InternalBiomeAPI.biomeRegistry != null) {
return InternalBiomeAPI.biomeRegistry.getOrCreateHolderOrThrow(biomeKey);
}
return BuiltinRegistries.BIOME.getOrCreateHolderOrThrow(biomeKey);
}
public static Holder<Biome> getBiomeHolder(ResourceLocation biome) {
return getBiomeHolder(ResourceKey.create(Registry.BIOME_REGISTRY, biome));
}
/**
* Get {@link BCLBiome} from given {@link ResourceLocation}.
*
* @param biomeID - biome {@link ResourceLocation}.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
public static BCLBiome getBiome(ResourceLocation biomeID) {
return ID_MAP.getOrDefault(biomeID, EMPTY_BIOME);
}
/**
* Get {@link BCLBiome} from given {@link Biome}.
*
* @param biome - biome {@link Biome}.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
public static BCLBiome getBiome(Biome biome) {
return getBiome(BiomeAPI.getBiomeID(biome));
}
/**
* Get {@link BCLBiome} from given {@link Biome}.
*
* @param biome - biome {@link Biome}.
* @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}.
*/
public static BCLBiome getBiome(Holder<Biome> biome) {
return getBiome(BiomeAPI.getBiomeID(biome));
}
/**
* Check if biome with {@link ResourceLocation} exists in API registry.
*
* @param biomeID - biome {@link ResourceLocation}.
* @return {@code true} if biome exists in API registry and {@code false} if not.
*/
public static boolean hasBiome(ResourceLocation biomeID) {
return ID_MAP.containsKey(biomeID);
}
public static Holder<Biome> getFromRegistry(ResourceLocation biomeID) {
if (InternalBiomeAPI.biomeRegistry != null)
return InternalBiomeAPI.biomeRegistry.getHolder(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID))
.orElseThrow();
return getFromBuiltinRegistry(biomeID);
}
@Nullable
public static Holder<Biome> getFromRegistry(ResourceKey<Biome> key) {
if (InternalBiomeAPI.biomeRegistry != null)
return InternalBiomeAPI.biomeRegistry.getHolder(key).orElseThrow();
return getFromBuiltinRegistry(key);
}
@Nullable
public static Holder<Biome> getFromBuiltinRegistry(ResourceLocation biomeID) {
return BuiltinRegistries.BIOME.getHolder(ResourceKey.create(Registry.BIOME_REGISTRY, biomeID)).orElse(null);
}
@Nullable
public static Holder<Biome> getFromBuiltinRegistry(ResourceKey<Biome> key) {
return BuiltinRegistries.BIOME.getHolder(key).orElse(null);
}
@Deprecated(forRemoval = true)
public static boolean registryContains(ResourceKey<Biome> key) {
if (InternalBiomeAPI.biomeRegistry != null)
return InternalBiomeAPI.biomeRegistry.containsKey(key);
return builtinRegistryContains(key);
}
@Nullable
@Deprecated(forRemoval = true)
public static boolean builtinRegistryContains(ResourceKey<Biome> key) {
return BuiltinRegistries.BIOME.containsKey(key);
}
public static boolean isDatapackBiome(ResourceLocation biomeID) {
return getFromBuiltinRegistry(biomeID) == null;
}
public static boolean wasRegisteredAs(ResourceLocation biomeID, BiomeType dim) {
if (BiomeType.BIOME_TYPE_MAP.containsKey(biomeID) && BiomeType.BIOME_TYPE_MAP.get(biomeID).is(dim)) return true;
BCLBiome biome = getBiome(biomeID);
if (biome != null && biome != BiomeAPI.EMPTY_BIOME && biome.getParentBiome() != null) {
return wasRegisteredAs(biome.getParentBiome().getID(), dim);
}
return false;
}
public static boolean wasRegisteredAsNetherBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.NETHER);
}
public static boolean wasRegisteredAsEndBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END);
}
public static boolean wasRegisteredAsEndLandBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END_LAND);
}
public static boolean wasRegisteredAsEndVoidBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END_VOID);
}
public static boolean wasRegisteredAsEndCenterBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END_CENTER);
}
public static boolean wasRegisteredAsEndBarrensBiome(ResourceLocation biomeID) {
return wasRegisteredAs(biomeID, BiomeType.END_BARRENS);
}
/**
* Registers new biome modification for specified dimension. Will work both for mod and datapack biomes.
*
* @param dimensionID {@link ResourceLocation} dimension ID, example: Level.OVERWORLD or "minecraft:overworld".
* @param modification {@link BiConsumer} with {@link ResourceKey} biome ID and {@link Biome} parameters.
*/
public static void registerBiomeModification(
ResourceKey<LevelStem> dimensionID,
BiConsumer<ResourceLocation, Holder<Biome>> modification
) {
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = InternalBiomeAPI.MODIFICATIONS.computeIfAbsent(
dimensionID,
k -> Lists.newArrayList()
);
modifications.add(modification);
}
/**
* Registers new biome modification for the Overworld. Will work both for mod and datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerOverworldBiomeModification(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
registerBiomeModification(LevelStem.OVERWORLD, modification);
}
/**
* Registers new biome modification for the Nether. Will work both for mod and datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerNetherBiomeModification(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
registerBiomeModification(LevelStem.NETHER, modification);
}
/**
* Registers new biome modification for the End. Will work both for mod and datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void registerEndBiomeModification(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
registerBiomeModification(LevelStem.END, modification);
}
/**
* Registers new biome modification for specified dimension that is executed when all
* BiomeTags are finalized by the game during level load. Will work both for mod and
* datapack biomes.
*
* @param dimensionID {@link ResourceLocation} dimension ID, example: Level.OVERWORLD or "minecraft:overworld".
* @param modification {@link BiConsumer} with {@link ResourceKey} biome ID and {@link Biome} parameters.
*/
public static void onFinishingBiomeTags(
ResourceKey dimensionID,
BiConsumer<ResourceLocation, Holder<Biome>> modification
) {
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = InternalBiomeAPI.TAG_ADDERS.computeIfAbsent(
dimensionID,
k -> Lists.newArrayList()
);
modifications.add(modification);
}
/**
* Registers new biome modification for the Nether dimension that is executed when all
* BiomeTags are finalized by the game during level load. Will work both for mod and
* datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void onFinishingNetherBiomeTags(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
onFinishingBiomeTags(Level.NETHER, modification);
}
/**
* Registers new biome modification for the End that is executed when all
* BiomeTags are finalized by the game during level load. Will work both for mod and
* datapack biomes.
*
* @param modification {@link BiConsumer} with {@link ResourceLocation} biome ID and {@link Biome} parameters.
*/
public static void onFinishingEndBiomeTags(BiConsumer<ResourceLocation, Holder<Biome>> modification) {
onFinishingBiomeTags(Level.END, modification);
}
/**
* Create a unique sort order for all Features of the Biome. This method is automatically called for each Biome
* after all biome Modifications were executed.
*
* @param biome The {@link Biome} to sort the features for
*/
public static void sortBiomeFeatures(Holder<Biome> biome) {
sortBiomeFeatures(biome.value());
}
static void sortBiomeFeatures(Biome biome) {
// BiomeGenerationSettings settings = biome.getGenerationSettings();
// BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) settings;
// List<HolderSet<PlacedFeature>> featureList = CollectionsUtil.getMutable(accessor.bclib_getFeatures());
// final int size = featureList.size();
// for (int i = 0; i < size; i++) {
// List<Holder<PlacedFeature>> features = getFeaturesListCopy(featureList, i);
// sortFeatures(features);
// featureList.set(i, HolderSet.direct(features));
// }
// accessor.bclib_setFeatures(featureList);
}
/**
* Adds new features to existing biome.
*
* @param biome {@link Biome} to add features in.
* @param feature {@link ConfiguredFeature} to add.
*/
public static void addBiomeFeature(Holder<Biome> biome, BCLFeature feature) {
addBiomeFeature(biome, feature.getDecoration(), feature.getPlacedFeature());
}
/**
* Adds new features to existing biome.
*
* @param biome {@link Biome} to add features in.
* @param feature {@link ConfiguredFeature} to add.
*/
public static void addBiomeFeature(
Holder<Biome> biome,
org.betterx.bclib.api.v3.levelgen.features.BCLFeature feature
) {
addBiomeFeature(biome, feature.getDecoration(), feature.getPlacedFeature());
}
/**
* Adds new features to existing biome.
*
* @param biome {@link Biome} to add features in.
* @param step a {@link Decoration} step for the feature.
* @param featureList {@link ConfiguredFeature} to add.
*/
public static void addBiomeFeature(Holder<Biome> biome, Decoration step, Holder<PlacedFeature>... featureList) {
addBiomeFeature(biome, step, List.of(featureList));
}
/**
* Adds new features to existing biome.
*
* @param biome {@link Biome} to add features in.
* @param step a {@link Decoration} step for the feature.
* @param additionalFeatures List of {@link ConfiguredFeature} to add.
*/
private static void addBiomeFeature(
Holder<Biome> biome,
Decoration step,
List<Holder<PlacedFeature>> additionalFeatures
) {
BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) biome.value()
.getGenerationSettings();
List<HolderSet<PlacedFeature>> allFeatures = CollectionsUtil.getMutable(accessor.bclib_getFeatures());
List<Holder<PlacedFeature>> features = getFeaturesListCopy(allFeatures, step);
for (var feature : additionalFeatures) {
if (!features.contains(feature))
features.add(feature);
}
allFeatures.set(step.ordinal(), HolderSet.direct(features));
final Supplier<List<ConfiguredFeature<?, ?>>> flowerFeatures = Suppliers.memoize(() -> allFeatures.stream()
.flatMap(
HolderSet::stream)
.map(Holder::value)
.flatMap(
PlacedFeature::getFeatures)
.filter(configuredFeature -> configuredFeature.feature() == Feature.FLOWER)
.collect(
ImmutableList.toImmutableList()));
final Supplier<Set<PlacedFeature>> featureSet = Suppliers.memoize(() -> allFeatures.stream()
.flatMap(HolderSet::stream)
.map(Holder::value)
.collect(Collectors.toSet()));
accessor.bclib_setFeatures(allFeatures);
accessor.bclib_setFeatureSet(featureSet);
accessor.bclib_setFlowerFeatures(flowerFeatures);
}
/**
* Adds mob spawning to specified biome.
*
* @param biome {@link Biome} to add mob spawning.
* @param entityType {@link EntityType} mob type.
* @param weight spawn weight.
* @param minGroupCount minimum mobs in group.
* @param maxGroupCount maximum mobs in group.
*/
public static <M extends Mob> void addBiomeMobSpawn(
Holder<Biome> biome,
EntityType<M> entityType,
int weight,
int minGroupCount,
int maxGroupCount
) {
final MobCategory category = entityType.getCategory();
MobSpawnSettingsAccessor accessor = (MobSpawnSettingsAccessor) biome.value().getMobSettings();
Map<MobCategory, WeightedRandomList<SpawnerData>> spawners = CollectionsUtil.getMutable(accessor.bcl_getSpawners());
List<SpawnerData> mobs = spawners.containsKey(category)
? CollectionsUtil.getMutable(spawners.get(category)
.unwrap())
: Lists.newArrayList();
mobs.add(new SpawnerData(entityType, weight, minGroupCount, maxGroupCount));
spawners.put(category, WeightedRandomList.create(mobs));
accessor.bcl_setSpawners(spawners);
}
public static Optional<BlockState> findTopMaterial(WorldGenLevel world, BlockPos pos) {
return findTopMaterial(getBiome(world.getBiome(pos)));
}
public static Optional<BlockState> findTopMaterial(Holder<Biome> biome) {
return findTopMaterial(getBiome(biome.value()));
}
public static Optional<BlockState> findTopMaterial(Biome biome) {
return findTopMaterial(getBiome(biome));
}
public static Optional<BlockState> findTopMaterial(BCLBiome biome) {
if (biome instanceof SurfaceMaterialProvider smp) {
return Optional.of(smp.getTopMaterial());
}
return Optional.empty();
}
public static Optional<BlockState> findUnderMaterial(Holder<Biome> biome) {
return findUnderMaterial(getBiome(biome.value()));
}
public static Optional<BlockState> findUnderMaterial(BCLBiome biome) {
if (biome instanceof SurfaceMaterialProvider smp) {
return Optional.of(smp.getUnderMaterial());
}
return Optional.empty();
}
/**
* Set biome in chunk at specified position.
*
* @param chunk {@link ChunkAccess} chunk to set biome in.
* @param pos {@link BlockPos} biome position.
* @param biome {@link Holder<Biome>} instance. Should be biome from world.
*/
public static void setBiome(ChunkAccess chunk, BlockPos pos, Holder<Biome> biome) {
int sectionY = (pos.getY() - chunk.getMinBuildHeight()) >> 4;
PalettedContainerRO<Holder<Biome>> biomes = chunk.getSection(sectionY).getBiomes();
if (biomes instanceof PalettedContainer<Holder<Biome>> palette) {
palette.set((pos.getX() & 15) >> 2, (pos.getY() & 15) >> 2, (pos.getZ() & 15) >> 2, biome);
} else {
BCLib.LOGGER.warning("Unable to change Biome at " + pos);
}
}
/**
* Set biome in world at specified position.
*
* @param level {@link LevelAccessor} world to set biome in.
* @param pos {@link BlockPos} biome position.
* @param biome {@link Holder<Biome>} instance. Should be biome from world.
*/
public static void setBiome(LevelAccessor level, BlockPos pos, Holder<Biome> biome) {
ChunkAccess chunk = level.getChunk(pos);
setBiome(chunk, pos, biome);
}
private static void sortFeatures(List<Holder<PlacedFeature>> features) {
// InternalBiomeAPI.initFeatureOrder();
//
// Set<Holder<PlacedFeature>> featuresWithoutDuplicates = Sets.newHashSet();
// features.forEach(holder -> featuresWithoutDuplicates.add(holder));
//
// if (featuresWithoutDuplicates.size() != features.size()) {
// features.clear();
// featuresWithoutDuplicates.forEach(feature -> features.add(feature));
// }
//
// features.forEach(feature -> {
// InternalBiomeAPI.FEATURE_ORDER.computeIfAbsent(
// feature,
// f -> InternalBiomeAPI.FEATURE_ORDER_ID.getAndIncrement()
// );
// });
//
// features.sort((f1, f2) -> {
// int v1 = InternalBiomeAPI.FEATURE_ORDER.getOrDefault(f1, 70000);
// int v2 = InternalBiomeAPI.FEATURE_ORDER.getOrDefault(f2, 70000);
// return Integer.compare(v1, v2);
// });
}
private static List<Holder<PlacedFeature>> getFeaturesListCopy(
List<HolderSet<PlacedFeature>> features,
Decoration step
) {
return getFeaturesListCopy(features, step.ordinal());
}
private static List<Holder<PlacedFeature>> getFeaturesListCopy(List<HolderSet<PlacedFeature>> features, int index) {
while (features.size() <= index) {
features.add(HolderSet.direct(Lists.newArrayList()));
}
return features.get(index).stream().collect(Collectors.toList());
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib Nether Biome Generator and into Fabric Biome API.
*
* @param bclBiome {@link BCLBiome}
* @return {@link BCLBiome}
*/
public static BCLBiome registerNetherBiome(BCLBiome bclBiome) {
registerBiome(bclBiome, BiomeType.BCL_NETHER);
ResourceKey<Biome> key = bclBiome.getBiomeKey();
if (bclBiome.allowFabricRegistration()) {
bclBiome.forEachClimateParameter(p -> NetherBiomes.addNetherBiome(key, p));
}
return bclBiome;
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib Nether Biome Generator and into Fabric Biome API.
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
public static BCLBiome registerNetherBiome(Biome biome) {
return InternalBiomeAPI.wrapNativeBiome(biome, -1, InternalBiomeAPI.OTHER_NETHER);
}
@Deprecated(forRemoval = true)
public static BCLBiome registerEndLandBiome(Holder<Biome> biome) {
return InternalBiomeAPI.wrapNativeBiome(biome.unwrapKey().orElseThrow(), InternalBiomeAPI.OTHER_END_LAND);
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands).
*
* @param biome {@link BCLBiome}
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
public static BCLBiome registerEndVoidBiome(Holder<Biome> biome) {
return InternalBiomeAPI.wrapNativeBiome(biome.unwrapKey().orElseThrow(), InternalBiomeAPI.OTHER_END_VOID);
}
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biome {@link BCLBiome};
* @param genChance float generation chance.
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
public static BCLBiome registerEndLandBiome(Holder<Biome> biome, float genChance) {
return InternalBiomeAPI.wrapNativeBiome(
biome.unwrapKey().orElseThrow(),
genChance,
InternalBiomeAPI.OTHER_END_LAND
);
}
/**
* Register {@link BCLBiome} instance and its {@link Biome} if necessary.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a void biome (will generate only in the End void - between islands).
*
* @param biome {@link BCLBiome}.
* @param genChance float generation chance.
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
public static BCLBiome registerEndVoidBiome(Holder<Biome> biome, float genChance) {
return InternalBiomeAPI.wrapNativeBiome(
biome.unwrapKey().orElseThrow(),
genChance,
InternalBiomeAPI.OTHER_END_VOID
);
}
}

View file

@ -1,383 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.interfaces.BiomeSourceAccessor;
import org.betterx.bclib.interfaces.NoiseGeneratorSettingsProvider;
import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor;
import org.betterx.worlds.together.surfaceRules.SurfaceRuleProvider;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.mutable.MutableInt;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public class InternalBiomeAPI {
public static final BiomeAPI.BiomeType OTHER_NETHER = new BiomeAPI.BiomeType(
"OTHER_NETHER",
BiomeAPI.BiomeType.NETHER
);
public static final BiomeAPI.BiomeType OTHER_END_LAND = new BiomeAPI.BiomeType(
"OTHER_END_LAND",
BiomeAPI.BiomeType.END_LAND
);
public static final BiomeAPI.BiomeType OTHER_END_VOID = new BiomeAPI.BiomeType(
"OTHER_END_VOID",
BiomeAPI.BiomeType.END_VOID
);
public static final BiomeAPI.BiomeType OTHER_END_CENTER = new BiomeAPI.BiomeType(
"OTHER_END_CENTER",
BiomeAPI.BiomeType.END_CENTER
);
public static final BiomeAPI.BiomeType OTHER_END_BARRENS = new BiomeAPI.BiomeType(
"OTHER_END_BARRENS",
BiomeAPI.BiomeType.END_BARRENS
);
static final Map<Biome, BCLBiome> CLIENT = Maps.newHashMap();
static final Map<Holder<PlacedFeature>, Integer> FEATURE_ORDER = Maps.newHashMap();
static final MutableInt FEATURE_ORDER_ID = new MutableInt(0);
static final Map<ResourceKey<LevelStem>, List<BiConsumer<ResourceLocation, Holder<Biome>>>> MODIFICATIONS = Maps.newHashMap();
static final Map<ResourceKey, List<BiConsumer<ResourceLocation, Holder<Biome>>>> TAG_ADDERS = Maps.newHashMap();
static Registry<Biome> biomeRegistry;
static RegistryAccess registryAccess;
static void initFeatureOrder() {
if (!FEATURE_ORDER.isEmpty()) {
return;
}
BuiltinRegistries.BIOME
.entrySet()
.stream()
.filter(entry -> entry
.getKey()
.location()
.getNamespace()
.equals("minecraft"))
.map(Map.Entry::getValue)
.map(biome -> (BiomeGenerationSettingsAccessor) biome.getGenerationSettings())
.map(BiomeGenerationSettingsAccessor::bclib_getFeatures)
.forEach(stepFeatureSuppliers -> stepFeatureSuppliers.forEach(step -> step.forEach(feature -> {
FEATURE_ORDER.computeIfAbsent(feature, f -> FEATURE_ORDER_ID.getAndIncrement());
})));
}
public static RegistryAccess worldRegistryAccess() {
return registryAccess;
}
/**
* Initialize registry for current server.
*
* @param access - The new, active {@link RegistryAccess} for the current session.
*/
public static void initRegistry(RegistryAccess access) {
if (access != registryAccess) {
registryAccess = access;
Registry<Biome> biomeRegistry = access.registry(Registry.BIOME_REGISTRY).orElse(null);
if (biomeRegistry != InternalBiomeAPI.biomeRegistry) {
InternalBiomeAPI.biomeRegistry = biomeRegistry;
CLIENT.clear();
BIOMES_TO_SORT.forEach(id -> {
Biome b = biomeRegistry.get(id);
if (b != null) {
BCLib.LOGGER.info("Found non fabric/bclib Biome: " + id + "(" + b + ")");
BiomeAPI.sortBiomeFeatures(b);
} else {
BCLib.LOGGER.info("Unknown Biome: " + id);
}
});
}
}
}
/**
* For internal use only.
* <p>
* This method gets called before a world is loaded/created to flush cashes we build.
*/
public static void prepareNewLevel() {
BIOMES_TO_SORT.clear();
}
/**
* Load biomes from Fabric API. For internal usage only.
*/
public static void loadFabricAPIBiomes() {
// FabricBiomesData.NETHER_BIOMES.forEach((key) -> {
// if (!BiomeAPI.hasBiome(key.location())) {
// Optional<Holder<Biome>> optional = BuiltinRegistries.BIOME.getHolder(key);
// if (optional.isPresent()) {
// BiomeAPI.registerNetherBiome(optional.get().value());
// }
// }
// });
//
// FabricBiomesData.END_LAND_BIOMES.forEach((key, weight) -> {
// if (!BiomeAPI.hasBiome(key.location())) {
// Optional<Holder<Biome>> optional = BuiltinRegistries.BIOME.getHolder(key);
// if (optional.isPresent()) {
// BiomeAPI.registerEndLandBiome(optional.get(), weight);
// }
// }
// });
//
// FabricBiomesData.END_VOID_BIOMES.forEach((key, weight) -> {
// if (!BiomeAPI.hasBiome(key.location())) {
// Optional<Holder<Biome>> optional = BuiltinRegistries.BIOME.getHolder(key);
// if (optional.isPresent()) {
// BiomeAPI.registerEndVoidBiome(optional.get(), weight);
// }
// }
// });
}
/**
* For internal use only
*/
public static void _runBiomeTagAdders() {
for (var mod : TAG_ADDERS.entrySet()) {
Stream<ResourceLocation> s = null;
if (mod.getKey() == Level.NETHER) s = BiomeAPI.BiomeType.BIOME_TYPE_MAP.entrySet()
.stream()
.filter(e -> e.getValue()
.is(BiomeAPI.BiomeType.NETHER))
.map(e -> e.getKey());
else if (mod.getKey() == Level.END) s = BiomeAPI.BiomeType.BIOME_TYPE_MAP.entrySet()
.stream()
.filter(e -> e.getValue().is(
BiomeAPI.BiomeType.END))
.map(e -> e.getKey());
if (s != null) {
s.forEach(id -> {
Holder<Biome> biomeHolder = BiomeAPI.getFromRegistry(id);
if (biomeHolder != null && biomeHolder.isBound()) {
mod.getValue().forEach(c -> c.accept(id, biomeHolder));
} else {
BCLib.LOGGER.info("No Holder for " + id);
}
});
}
}
}
@Deprecated(forRemoval = true)
public static void applyModificationsDeprecated(ServerLevel level) {
//TODO: Now Disabled, because we fix the settings when everything gets loaded
if (level != null) return;
NoiseGeneratorSettings noiseGeneratorSettings = null;
final ChunkGenerator chunkGenerator = level.getChunkSource().getGenerator();
final BiomeSource source = chunkGenerator.getBiomeSource();
final Set<Holder<Biome>> biomes = source.possibleBiomes();
if (chunkGenerator instanceof NoiseGeneratorSettingsProvider gen)
noiseGeneratorSettings = gen.bclib_getNoiseGeneratorSettings();
// Datapacks (like Amplified Nether)will change the GeneratorSettings upon load, so we will
// only use the default Setting for Nether/End if we were unable to find a settings object
if (noiseGeneratorSettings == null) {
if (level.dimension() == Level.NETHER) {
noiseGeneratorSettings = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.NETHER);
} else if (level.dimension() == Level.END) {
noiseGeneratorSettings = BuiltinRegistries.NOISE_GENERATOR_SETTINGS.get(NoiseGeneratorSettings.END);
}
}
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = MODIFICATIONS.get(level
.dimensionTypeRegistration()
.unwrapKey()
.orElseThrow());
for (Holder<Biome> biomeHolder : biomes) {
if (biomeHolder.isBound()) {
applyModificationsAndUpdateFeatures(modifications, biomeHolder);
}
}
if (noiseGeneratorSettings != null) {
final SurfaceRuleProvider provider = SurfaceRuleProvider.class.cast(noiseGeneratorSettings);
// Multiple Biomes can use the same generator. So we need to keep track of all Biomes that are
// Provided by all the BiomeSources that use the same generator.
// This happens for example when using the MiningDimensions, which reuses the generator for the
// Nethering Dimension
//MODIFIED_SURFACE_PROVIDERS.add(provider);
//provider.bclib_addBiomeSource(source);
} else {
BCLib.LOGGER.warning("No generator for " + source);
}
((BiomeSourceAccessor) source).bclRebuildFeatures();
}
public static void applyModifications(BiomeSource source, ResourceKey<LevelStem> dimension) {
BCLib.LOGGER.info("Apply Modifications for " + dimension.location() + " BiomeSource " + source);
/*if (dimension.location().equals(LevelStem.NETHER)){
if (source instanceof BCLBiomeSource s) {
NetherBiomes.useLegacyGeneration = s.biomeSourceVersion==BCLBiomeSource.BIOME_SOURCE_VERSION_SQUARE;
}
}*/
final Set<Holder<Biome>> biomes = source.possibleBiomes();
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications = MODIFICATIONS.get(dimension);
for (Holder<Biome> biomeHolder : biomes) {
if (biomeHolder.isBound()) {
applyModificationsAndUpdateFeatures(modifications, biomeHolder);
}
}
}
private static void applyModificationsAndUpdateFeatures(
List<BiConsumer<ResourceLocation, Holder<Biome>>> modifications,
Holder<Biome> biome
) {
ResourceLocation biomeID = BiomeAPI.getBiomeID(biome);
if (modifications != null) {
modifications.forEach(consumer -> {
consumer.accept(biomeID, biome);
});
}
BiomeAPI.sortBiomeFeatures(biome);
}
private static final Set<ResourceLocation> BIOMES_TO_SORT = Sets.newHashSet();
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biomeKey The source biome to wrap
* @return {@link BCLBiome}
*/
public static BCLBiome wrapNativeBiome(ResourceKey<Biome> biomeKey, BiomeAPI.BiomeType type) {
return wrapNativeBiome(biomeKey, -1, type);
}
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biomeKey The source biome to wrap
* @param genChance generation chance. If &lt;0 the default genChance is used
* @return {@link BCLBiome}
*/
public static BCLBiome wrapNativeBiome(ResourceKey<Biome> biomeKey, float genChance, BiomeAPI.BiomeType type) {
return wrapNativeBiome(
biomeKey,
genChance < 0 ? null : VanillaBiomeSettings.createVanilla().setGenChance(genChance).build(),
type
);
}
public static BCLBiome wrapNativeBiome(
ResourceKey<Biome> biomeKey,
BCLBiome edgeBiome,
int edgeBiomeSize,
float genChance,
BiomeAPI.BiomeType type
) {
VanillaBiomeSettings.Builder settings = VanillaBiomeSettings.createVanilla();
if (genChance >= 0) settings.setGenChance(genChance);
settings.setEdge(edgeBiome);
settings.setEdgeSize(edgeBiomeSize);
return wrapNativeBiome(biomeKey, settings.build(), type);
}
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biomeKey The source biome to wrap
* @param setings the {@link VanillaBiomeSettings} to use
* @return {@link BCLBiome}
*/
private static BCLBiome wrapNativeBiome(
ResourceKey<Biome> biomeKey,
VanillaBiomeSettings setings,
BiomeAPI.BiomeType type
) {
BCLBiome bclBiome = BiomeAPI.getBiome(biomeKey.location());
if (bclBiome == BiomeAPI.EMPTY_BIOME) {
bclBiome = new BCLBiome(biomeKey, setings);
}
BiomeAPI.registerBiome(bclBiome, type);
return bclBiome;
}
/**
* Register {@link BCLBiome} wrapper for {@link Biome}.
* After that biome will be added to BCLib End Biome Generator and into Fabric Biome API as a land biome (will generate only on islands).
*
* @param biome The source biome to wrap
* @param genChance generation chance.
* @return {@link BCLBiome}
*/
@Deprecated(forRemoval = true)
static BCLBiome wrapNativeBiome(Biome biome, float genChance, BiomeAPI.BiomeType type) {
BCLBiome bclBiome = BiomeAPI.getBiome(biome);
if (bclBiome == BiomeAPI.EMPTY_BIOME) {
bclBiome = new BCLBiome(
biome,
genChance < 0 ? null : VanillaBiomeSettings.createVanilla().setGenChance(genChance).build()
);
}
BiomeAPI.registerBiome(bclBiome, type, null);
return bclBiome;
}
static {
DynamicRegistrySetupCallback.EVENT.register(registryManager -> {
Optional<? extends Registry<Biome>> oBiomeRegistry = registryManager.registry(Registry.BIOME_REGISTRY);
RegistryEntryAddedCallback
.event(oBiomeRegistry.get())
.register((rawId, id, biome) -> {
BCLBiome b = BiomeAPI.getBiome(id);
if (!"minecraft".equals(id.getNamespace()) && (b == null || b == BiomeAPI.EMPTY_BIOME)) {
//BCLib.LOGGER.info(" #### " + rawId + ", " + biome + ", " + id);
BIOMES_TO_SORT.add(id);
}
});
});
}
public static boolean registryContainsBound(ResourceKey<Biome> key) {
Registry<Biome> reg = biomeRegistry;
if (reg == null) reg = BuiltinRegistries.BIOME;
if (reg.containsKey(key)) {
return reg.getOrCreateHolderOrThrow(key).isBound();
}
return false;
}
}

View file

@ -1,14 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.biomes;
public class VanillaBiomeSettings extends BCLBiomeSettings {
public static class Builder extends BCLBiomeSettings.CommonBuilder<VanillaBiomeSettings, VanillaBiomeSettings.Builder> {
public Builder() {
super(new VanillaBiomeSettings());
}
}
public static Builder createVanilla() {
return new Builder();
}
}

View file

@ -1,166 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration;
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
import net.minecraft.world.level.levelgen.structure.templatesystem.BlockMatchTest;
@Deprecated(forRemoval = true)
public class BCLCommonFeatures {
/**
* Will create a basic plant feature.
*
* @param id {@link ResourceLocation} feature ID.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @param density iterations per chunk.
* @return new BCLFeature instance.
*/
public static BCLFeature makeVegetationFeature(
ResourceLocation id,
Feature<NoneFeatureConfiguration> feature,
int density
) {
return makeVegetationFeature(id, feature, density, false);
}
/**
* Will create a basic plant feature.
*
* @param id {@link ResourceLocation} feature ID.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @param density iterations per chunk.
* @param allHeight if {@code true} will generate plant on all layers, if {@code false} - only on surface.
* @return new BCLFeature instance.
*/
public static BCLFeature makeVegetationFeature(
ResourceLocation id,
Feature<NoneFeatureConfiguration> feature,
int density,
boolean allHeight
) {
if (allHeight) {
return BCLFeatureBuilder
.start(id, feature)
.countLayers(density)
.squarePlacement()
.onlyInBiome()
.buildAndRegister();
} else {
return BCLFeatureBuilder
.start(id, feature)
.countMax(density)
.squarePlacement()
.heightmap()
.onlyInBiome()
.buildAndRegister();
}
}
/**
* Will create feature which will be generated once in each chunk.
*
* @param id {@link ResourceLocation} feature ID.
* @param decoration {@link Decoration} feature step.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @return new BCLFeature instance.
*/
public static BCLFeature makeChunkFeature(
ResourceLocation id,
Decoration decoration,
Feature<NoneFeatureConfiguration> feature
) {
return BCLFeatureBuilder.start(id, feature).decoration(decoration).count(1).onlyInBiome().buildAndRegister();
}
/**
* Will create feature with chanced decoration, chance for feature to generate per chunk is 1 / chance.
*
* @param id {@link ResourceLocation} feature ID.
* @param decoration {@link Decoration} feature step.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @param chance chance for feature to be generated in.
* @return new BCLFeature instance.
*/
public static BCLFeature makeChancedFeature(
ResourceLocation id,
Decoration decoration,
Feature<NoneFeatureConfiguration> feature,
int chance
) {
return BCLFeatureBuilder.start(id, feature)
.decoration(decoration)
.onceEvery(chance)
.squarePlacement()
.onlyInBiome()
.buildAndRegister();
}
/**
* Will create feature with specified generation iterations per chunk.
*
* @param id {@link ResourceLocation} feature ID.
* @param decoration {@link Decoration} feature step.
* @param feature {@link Feature} with {@link NoneFeatureConfiguration} config.
* @param count iterations steps.
* @return new BCLFeature instance.
*/
public static BCLFeature makeCountFeature(
ResourceLocation id,
Decoration decoration,
Feature<NoneFeatureConfiguration> feature,
int count
) {
return BCLFeatureBuilder.start(id, feature)
.decoration(decoration)
.count(count)
.squarePlacement()
.onlyInBiome()
.buildAndRegister();
}
/**
* Will create a basic ore feature.
*
* @param id {@link ResourceLocation} feature ID.
* @param blockOre {@link Decoration} feature step.
* @param hostBlock {@link Block} to generate feature in.
* @param veins iterations per chunk.
* @param veinSize size of ore vein.
* @param airDiscardChance chance that this orge gets discarded when it is exposed to air
* @param placement {@link net.minecraft.world.level.levelgen.placement.PlacementModifier} for the ore distribution,
* for example {@code PlacementUtils.FULL_RANGE}, {@code PlacementUtils.RANGE_10_10}
* @param rare when true, this is placed as a rare resource
* @return new BCLFeature instance.
*/
public static BCLFeature makeOreFeature(
ResourceLocation id,
Block blockOre,
Block hostBlock,
int veins,
int veinSize,
float airDiscardChance,
PlacementModifier placement,
boolean rare
) {
BCLFeatureBuilder builder = BCLFeatureBuilder.start(id, Feature.ORE).decoration(Decoration.UNDERGROUND_ORES);
if (rare) {
builder.onceEvery(veins);
} else {
builder.count(veins);
}
builder.modifier(placement).squarePlacement().onlyInBiome();
return builder.buildAndRegister(new OreConfiguration(
new BlockMatchTest(hostBlock),
blockOre.defaultBlockState(),
veinSize,
airDiscardChance
));
}
}

View file

@ -1,290 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.api.v2.levelgen.features.config.ScatterFeatureConfig;
import org.betterx.bclib.api.v2.levelgen.features.features.ScatterFeature;
import org.betterx.bclib.api.v2.levelgen.features.features.WeightedRandomSelectorFeature;
import org.betterx.bclib.api.v3.levelgen.features.BCLConfigureFeature;
import org.betterx.bclib.api.v3.levelgen.features.BCLFeatureBuilder;
import org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature;
import org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig;
import org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig;
import org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig;
import org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.Registry;
import net.minecraft.data.BuiltinRegistries;
import net.minecraft.data.worldgen.features.FeatureUtils;
import net.minecraft.data.worldgen.placement.PlacementUtils;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.RandomFeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.RandomPatchConfiguration;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
import java.util.Map;
import java.util.Optional;
/**
* @param <F>
* @param <FC>
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature} instead
*/
@Deprecated(forRemoval = true)
public class BCLFeature<F extends Feature<FC>, FC extends FeatureConfiguration> {
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#PLACE_BLOCK}
*/
@Deprecated(forRemoval = true)
public static final Feature<PlaceFacingBlockConfig> PLACE_BLOCK = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.PLACE_BLOCK;
@Deprecated(forRemoval = true)
public static final Feature<ScatterFeatureConfig.OnSolid> SCATTER_ON_SOLID = register(
BCLib.makeID("scatter_on_solid"),
new ScatterFeature<>(ScatterFeatureConfig.OnSolid.CODEC)
);
@Deprecated(forRemoval = true)
public static final Feature<ScatterFeatureConfig.ExtendTop> SCATTER_EXTEND_TOP = register(
BCLib.makeID("scatter_extend_top"),
new ScatterFeature<>(ScatterFeatureConfig.ExtendTop.CODEC)
);
@Deprecated(forRemoval = true)
public static final Feature<ScatterFeatureConfig.ExtendBottom> SCATTER_EXTEND_BOTTOM = register(
BCLib.makeID("scatter_extend_bottom"),
new ScatterFeature<>(ScatterFeatureConfig.ExtendBottom.CODEC)
);
@Deprecated(forRemoval = true)
public static final Feature<RandomFeatureConfiguration> RANDOM_SELECTOR = register(
BCLib.makeID("random_select"),
new WeightedRandomSelectorFeature()
);
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#TEMPLATE}
*/
@Deprecated(forRemoval = true)
public static final Feature<TemplateFeatureConfig> TEMPLATE = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.TEMPLATE;
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#MARK_POSTPROCESSING}
*/
@Deprecated(forRemoval = true)
public static final Feature<NoneFeatureConfiguration> MARK_POSTPROCESSING = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.MARK_POSTPROCESSING;
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#SEQUENCE}
*/
@Deprecated(forRemoval = true)
public static final Feature<SequenceFeatureConfig> SEQUENCE = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.SEQUENCE;
/**
* @deprecated Replace by {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#CONDITION}
*/
@Deprecated(forRemoval = true)
public static final Feature<ConditionFeatureConfig> CONDITION = org.betterx.bclib.api.v3.levelgen.features.BCLFeature.CONDITION;
public final ResourceLocation id;
org.betterx.bclib.api.v3.levelgen.features.BCLFeature<F, FC> proxy;
@Deprecated(forRemoval = true)
public BCLFeature(
ResourceLocation id,
F feature,
Decoration featureStep,
FC configuration,
PlacementModifier[] modifiers
) {
this(id, feature, featureStep, configuration, buildPlacedFeature(id, feature, configuration, modifiers));
}
private static <E> boolean containsObj(Registry<E> registry, E obj) {
Optional<Map.Entry<ResourceKey<E>, E>> optional = registry
.entrySet()
.stream()
.filter(entry -> entry.getValue() == obj)
.findAny();
return optional.isPresent();
}
private static <F extends Feature<FC>, FC extends FeatureConfiguration> org.betterx.bclib.api.v3.levelgen.features.BCLFeature<F, FC> build(
ResourceLocation id,
F feature,
Decoration featureStep,
FC configuration,
Holder<PlacedFeature> placedFeature
) {
BCLConfigureFeature<F, FC> cfg = BCLFeatureBuilder.start(id, feature)
.configuration(configuration)
.build();
if (!BuiltinRegistries.PLACED_FEATURE.containsKey(id)) {
Registry.register(BuiltinRegistries.PLACED_FEATURE, id, placedFeature.value());
}
if (!Registry.FEATURE.containsKey(id) && !containsObj(Registry.FEATURE, feature)) {
Registry.register(Registry.FEATURE, id, feature);
}
return new org.betterx.bclib.api.v3.levelgen.features.BCLFeature<>(cfg, placedFeature, featureStep);
}
@Deprecated(forRemoval = true)
public BCLFeature(
ResourceLocation id,
F feature,
Decoration featureStep,
FC configuration,
Holder<PlacedFeature> placedFeature
) {
this(build(id, feature, featureStep, configuration, placedFeature));
}
@Deprecated(forRemoval = true)
public BCLFeature(org.betterx.bclib.api.v3.levelgen.features.BCLFeature proxy) {
this.proxy = proxy;
this.id = proxy.configuredFeature.id;
}
private static <FC extends FeatureConfiguration, F extends Feature<FC>> Holder<PlacedFeature> buildPlacedFeature(
ResourceLocation id,
F feature,
FC configuration,
PlacementModifier[] modifiers
) {
Holder<ConfiguredFeature<?, ?>> configuredFeature;
if (!BuiltinRegistries.CONFIGURED_FEATURE.containsKey(id)) {
configuredFeature = (Holder<ConfiguredFeature<?, ?>>) (Object) FeatureUtils.register(
id.toString(),
feature,
configuration
);
} else {
configuredFeature = BuiltinRegistries.CONFIGURED_FEATURE
.getHolder(ResourceKey.create(
BuiltinRegistries.CONFIGURED_FEATURE.key(),
id
))
.orElseThrow();
}
if (!BuiltinRegistries.PLACED_FEATURE.containsKey(id)) {
return PlacementUtils.register(id.toString(), configuredFeature, modifiers);
} else {
return BuiltinRegistries.PLACED_FEATURE.getHolder(ResourceKey.create(
BuiltinRegistries.PLACED_FEATURE.key(),
id
)).orElseThrow();
}
}
/**
* @param string
* @param feature
* @param <C>
* @param <F>
* @return
* @deprecated Use {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeature#register(ResourceLocation, Feature)} instead
*/
@Deprecated(forRemoval = true)
public static <C extends FeatureConfiguration, F extends Feature<C>> F register(
ResourceLocation string,
F feature
) {
return org.betterx.bclib.api.v3.levelgen.features.BCLFeature.register(string, feature);
}
/**
* Get raw feature.
*
* @return {@link Feature}.
*/
public F getFeature() {
return proxy.getFeature();
}
public BCLConfigureFeature<F, FC> getConfFeature() {
return proxy.configuredFeature;
}
/**
* Get configured feature.
*
* @return {@link PlacedFeature}.
*/
public Holder<PlacedFeature> getPlacedFeature() {
return proxy.getPlacedFeature();
}
/**
* Get feature decoration step.
*
* @return {@link Decoration}.
*/
public Decoration getDecoration() {
return proxy.getDecoration();
}
public FC getConfiguration() {
return proxy.getConfiguration();
}
public boolean place(ServerLevel level, BlockPos pos, RandomSource random) {
return place(this.getFeature(), this.getConfiguration(), level, pos, random);
}
private static boolean placeUnbound(
Feature<?> feature,
FeatureConfiguration config,
ServerLevel level,
BlockPos pos,
RandomSource random
) {
if (config instanceof RandomPatchConfiguration rnd) {
var configured = rnd.feature().value().feature().value();
feature = configured.feature();
config = configured.config();
}
if (feature instanceof UserGrowableFeature growable) {
return growable.grow(level, pos, random, config);
}
FeaturePlaceContext context = new FeaturePlaceContext(
Optional.empty(),
level,
level.getChunkSource().getGenerator(),
random,
pos,
config
);
return feature.place(context);
}
public static boolean place(
Feature<NoneFeatureConfiguration> feature,
ServerLevel level,
BlockPos pos,
RandomSource random
) {
return placeUnbound(feature, FeatureConfiguration.NONE, level, pos, random);
}
public static <FC extends FeatureConfiguration> boolean place(
Feature<FC> feature,
FC config,
ServerLevel level,
BlockPos pos,
RandomSource random
) {
return placeUnbound(feature, config, level, pos, random);
}
}

View file

@ -1,402 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features;
import org.betterx.bclib.api.v2.levelgen.features.placement.IsEmptyAboveSampledFilter;
import org.betterx.bclib.api.v2.levelgen.features.placement.MinEmptyFilter;
import org.betterx.bclib.api.v2.levelgen.features.placement.Stencil;
import org.betterx.bclib.api.v3.levelgen.features.placement.*;
import org.betterx.worlds.together.tag.v3.CommonBlockTags;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
import net.minecraft.data.worldgen.placement.PlacementUtils;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.valueproviders.IntProvider;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.GenerationStep.Decoration;
import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.SimpleBlockFeature;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.SimpleBlockConfiguration;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
import net.minecraft.world.level.levelgen.placement.*;
import net.minecraft.world.level.material.Material;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* @param <FC>
* @param <F>
* @deprecated please use {@link org.betterx.bclib.api.v3.levelgen.features.BCLFeatureBuilder} instead
*/
@Deprecated(forRemoval = true)
public class BCLFeatureBuilder<FC extends FeatureConfiguration, F extends Feature<FC>> {
private final List<PlacementModifier> modifications = new ArrayList<>(5);
private final ResourceLocation featureID;
private Decoration decoration = Decoration.VEGETAL_DECORATION;
private final F feature;
private BlockStateProvider provider;
private BCLFeatureBuilder(ResourceLocation featureID, F feature) {
this.featureID = featureID;
this.feature = feature;
}
/**
* Starts a new {@link BCLFeature} builder.
*
* @param featureID {@link ResourceLocation} feature identifier.
* @param feature {@link Feature} to construct.
* @return {@link BCLFeatureBuilder} instance.
*/
public static BCLFeatureBuilder start(ResourceLocation featureID, Feature<?> feature) {
return new BCLFeatureBuilder(featureID, feature);
}
public static BCLFeatureBuilder<SimpleBlockConfiguration, SimpleBlockFeature> start(
ResourceLocation featureID,
Block block
) {
return start(featureID, BlockStateProvider.simple(block));
}
public static BCLFeatureBuilder<SimpleBlockConfiguration, SimpleBlockFeature> start(
ResourceLocation featureID,
BlockState state
) {
return start(featureID, BlockStateProvider.simple(state));
}
public static BCLFeatureBuilder<SimpleBlockConfiguration, SimpleBlockFeature> start(
ResourceLocation featureID,
BlockStateProvider provider
) {
BCLFeatureBuilder<SimpleBlockConfiguration, SimpleBlockFeature> builder = new BCLFeatureBuilder(
featureID,
Feature.SIMPLE_BLOCK
);
builder.provider = provider;
return builder;
}
/**
* Set generation step for the feature. Default is {@code VEGETAL_DECORATION}.
*
* @param decoration {@link Decoration} step.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder decoration(Decoration decoration) {
this.decoration = decoration;
return this;
}
/**
* Add feature placement modifier. Used as a condition for feature how to generate.
*
* @param modifier {@link PlacementModifier}.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder modifier(PlacementModifier modifier) {
modifications.add(modifier);
return this;
}
public BCLFeatureBuilder modifier(List<PlacementModifier> modifiers) {
modifications.addAll(modifiers);
return this;
}
/**
* Generate feature in certain iterations (per chunk).
*
* @param count how many times feature will be generated in chunk.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder count(int count) {
return modifier(CountPlacement.of(count));
}
/**
* Generate feature in certain iterations (per chunk). Count can be between 0 and max value.
*
* @param count maximum amount of iterations per chunk.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder countMax(int count) {
return modifier(CountPlacement.of(UniformInt.of(0, count)));
}
public BCLFeatureBuilder countRange(int min, int max) {
return modifier(CountPlacement.of(UniformInt.of(min, max)));
}
/**
* Generate feature in certain iterations (per chunk).
* Feature will be generated on all layers (example - Nether plants).
*
* @param count how many times feature will be generated in chunk layers.
* @return same {@link BCLFeatureBuilder} instance.
*/
@SuppressWarnings("deprecation")
public BCLFeatureBuilder countLayers(int count) {
return modifier(CountOnEveryLayerPlacement.of(count));
}
/**
* Generate feature in certain iterations (per chunk). Count can be between 0 and max value.
* Feature will be generated on all layers (example - Nether plants).
*
* @param count maximum amount of iterations per chunk layers.
* @return same {@link BCLFeatureBuilder} instance.
*/
@SuppressWarnings("deprecation")
public BCLFeatureBuilder countLayersMax(int count) {
return modifier(CountOnEveryLayerPlacement.of(UniformInt.of(0, count)));
}
/**
* Will place feature once every n-th attempts (in average).
*
* @param n amount of attempts.
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder onceEvery(int n) {
return modifier(RarityFilter.onAverageOnceEvery(n));
}
/**
* Restricts feature generation only to biome where feature was added.
*
* @return same {@link BCLFeatureBuilder} instance.
*/
public BCLFeatureBuilder onlyInBiome() {
return modifier(BiomeFilter.biome());
}
public BCLFeatureBuilder squarePlacement() {
return modifier(InSquarePlacement.spread());
}
public BCLFeatureBuilder stencil() {
return modifier(Stencil.all());
}
public BCLFeatureBuilder all() {
return modifier(All.simple());
}
public BCLFeatureBuilder stencilOneIn4() {
return modifier(Stencil.oneIn4());
}
/**
* Select random height that is 10 above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight10FromFloorCeil() {
return modifier(PlacementUtils.RANGE_10_10);
}
/**
* Select random height that is 4 above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight4FromFloorCeil() {
return modifier(PlacementUtils.RANGE_4_4);
}
/**
* Select random height that is 8 above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight8FromFloorCeil() {
return modifier(PlacementUtils.RANGE_8_8);
}
/**
* Select random height that is above min Build height and 10 below max generation height
*
* @return The instance it was called on
*/
public BCLFeatureBuilder randomHeight() {
return modifier(PlacementUtils.FULL_RANGE);
}
public BCLFeatureBuilder isEmptyAbove4() {
return modifier(IsEmptyAboveSampledFilter.emptyAbove4());
}
public BCLFeatureBuilder isEmptyAbove2() {
return modifier(IsEmptyAboveSampledFilter.emptyAbove2());
}
public BCLFeatureBuilder isEmptyAbove() {
return modifier(IsEmptyAboveSampledFilter.emptyAbove());
}
public BCLFeatureBuilder isEmptyBelow4() {
return modifier(IsEmptyAboveSampledFilter.emptyBelow4());
}
public BCLFeatureBuilder isEmptyBelow2() {
return modifier(IsEmptyAboveSampledFilter.emptyBelow2());
}
public BCLFeatureBuilder isEmptyBelow() {
return modifier(IsEmptyAboveSampledFilter.emptyBelow());
}
public BCLFeatureBuilder isEmptyAbove(int d1, int d2) {
return modifier(new IsEmptyAboveSampledFilter(d1, d2));
}
public BCLFeatureBuilder onEveryLayer() {
return modifier(OnEveryLayer.simple());
}
public BCLFeatureBuilder underEveryLayer() {
return modifier(UnderEveryLayer.simple());
}
public BCLFeatureBuilder spreadHorizontal(IntProvider p) {
return modifier(RandomOffsetPlacement.horizontal(p));
}
public BCLFeatureBuilder spreadVertical(IntProvider p) {
return modifier(RandomOffsetPlacement.horizontal(p));
}
public BCLFeatureBuilder spread(IntProvider horizontal, IntProvider vertical) {
return modifier(RandomOffsetPlacement.of(horizontal, vertical));
}
public BCLFeatureBuilder offset(Direction dir) {
return modifier(Offset.inDirection(dir));
}
public BCLFeatureBuilder offset(Vec3i dir) {
return modifier(new Offset(dir));
}
/**
* Cast a downward ray with max {@code distance} length to find the next solid Block.
*
* @param distance The maximum search Distance
* @return The instance it was called on
* @see #findSolidSurface(Direction, int) for Details
*/
public BCLFeatureBuilder findSolidFloor(int distance) {
return modifier(FindSolidInDirection.down(distance));
}
public BCLFeatureBuilder noiseBasedCount(float noiseLevel, int belowNoiseCount, int aboveNoiseCount) {
return modifier(NoiseThresholdCountPlacement.of(noiseLevel, belowNoiseCount, aboveNoiseCount));
}
public BCLFeatureBuilder extendDown(int min, int max) {
return modifier(new Extend(Direction.DOWN, UniformInt.of(min, max)));
}
public BCLFeatureBuilder inBasinOf(BlockPredicate... predicates) {
return modifier(new IsBasin(BlockPredicate.anyOf(predicates)));
}
public BCLFeatureBuilder inOpenBasinOf(BlockPredicate... predicates) {
return modifier(IsBasin.openTop(BlockPredicate.anyOf(predicates)));
}
public BCLFeatureBuilder is(BlockPredicate... predicates) {
return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.empty()));
}
public BCLFeatureBuilder isAbove(BlockPredicate... predicates) {
return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.of(Direction.DOWN.getNormal())));
}
public BCLFeatureBuilder isUnder(BlockPredicate... predicates) {
return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.of(Direction.UP.getNormal())));
}
public BCLFeatureBuilder findSolidCeil(int distance) {
return modifier(FindSolidInDirection.up(distance));
}
public BCLFeatureBuilder hasMinimumDownwardSpace() {
return modifier(MinEmptyFilter.down());
}
public BCLFeatureBuilder hasMinimumUpwardSpace() {
return modifier(MinEmptyFilter.up());
}
/**
* Cast a ray with max {@code distance} length to find the next solid Block. The ray will travel through replaceable
* Blocks (see {@link Material#isReplaceable()}) and will be accepted if it hits a block with the
* {@link CommonBlockTags#TERRAIN}-tag
*
* @param dir The direction the ray is cast
* @param distance The maximum search Distance
* @return The instance it was called on
* @see #findSolidSurface(Direction, int) for Details
*/
public BCLFeatureBuilder findSolidSurface(Direction dir, int distance) {
return modifier(new FindSolidInDirection(dir, distance, 0));
}
public BCLFeatureBuilder findSolidSurface(List<Direction> dir, int distance, boolean randomSelect) {
return modifier(new FindSolidInDirection(dir, distance, randomSelect, 0));
}
public BCLFeatureBuilder heightmap() {
return modifier(PlacementUtils.HEIGHTMAP);
}
public BCLFeatureBuilder heightmapTopSolid() {
return modifier(PlacementUtils.HEIGHTMAP_TOP_SOLID);
}
public BCLFeatureBuilder heightmapWorldSurface() {
return modifier(PlacementUtils.HEIGHTMAP_WORLD_SURFACE);
}
/**
* Builds a new {@link BCLFeature} instance. Features will be registered during this process.
*
* @param configuration any {@link FeatureConfiguration} for provided {@link Feature}.
* @return created {@link BCLFeature} instance.
*/
public BCLFeature buildAndRegister(FC configuration) {
PlacementModifier[] modifiers = modifications.toArray(new PlacementModifier[modifications.size()]);
return new BCLFeature(featureID, feature, decoration, configuration, modifiers);
}
/**
* Builds a new {@link BCLFeature} instance with {@code NONE} {@link FeatureConfiguration}.
* Features will be registered during this process.
*
* @return created {@link BCLFeature} instance.
*/
public BCLFeature buildAndRegister() {
if (this.feature == Feature.SIMPLE_BLOCK && provider != null)
return buildAndRegister((FC) new SimpleBlockConfiguration(provider));
return buildAndRegister((FC) FeatureConfiguration.NONE);
}
@Deprecated(forRemoval = true)
public BCLFeature build(FC configuration) {
return buildAndRegister(configuration);
}
@Deprecated(forRemoval = true)
public BCLFeature build() {
return buildAndRegister();
}
}

View file

@ -1,255 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features;
@Deprecated(forRemoval = true)
public class FastFeatures {
// @Deprecated(forRemoval = true)
// public static RandomPatchConfiguration grassPatch(BlockStateProvider stateProvider, int tries) {
// return FeatureUtils.simpleRandomPatchConfiguration(
// tries,
// PlacementUtils.onlyWhenEmpty(Feature.SIMPLE_BLOCK, new SimpleBlockConfiguration(stateProvider))
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature<ScatterFeature<ScatterFeatureConfig.OnSolid>, ScatterFeatureConfig.OnSolid> vine(
// ResourceLocation location,
// boolean onFloor,
// boolean sparse,
// ScatterFeatureConfig.Builder builder
// ) {
// return scatter(location, onFloor, sparse, builder,
// org.betterx.bclib.api.v3.levelgen.features.BCLFeature.SCATTER_ON_SOLID
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature scatter(
// ResourceLocation location,
// boolean onFloor,
// boolean sparse,
// ScatterFeatureConfig.Builder builder,
// Feature scatterFeature
// ) {
// BCLFeatureBuilder fBuilder = BCLFeatureBuilder.start(location, scatterFeature);
// if (onFloor) {
// fBuilder.findSolidFloor(3).isEmptyAbove2();
// builder.onFloor();
// } else {
// fBuilder.findSolidCeil(3).isEmptyBelow2();
// builder.onCeil();
// }
// if (sparse) {
// fBuilder.onceEvery(3);
// }
//
// return fBuilder
// .is(BlockPredicate.ONLY_IN_AIR_PREDICATE)
// .buildAndRegister(builder.build());
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature patch(ResourceLocation location, Block block) {
// return patch(location, block, 96, 7, 3);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(ResourceLocation location, Block block, int attempts, int xzSpread, int ySpread) {
// return patch(
// location,
// attempts,
// xzSpread,
// ySpread,
// Feature.SIMPLE_BLOCK,
// new SimpleBlockConfiguration(BlockStateProvider.simple(block))
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(ResourceLocation location, BlockStateProvider provider, int attempts, int xzSpread, int ySpread) {
// return patch(
// location,
// attempts,
// xzSpread,
// ySpread,
// Feature.SIMPLE_BLOCK,
// new SimpleBlockConfiguration(provider)
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature patchWitRandomInt(ResourceLocation location, Block block, IntegerProperty prop) {
// return patchWitRandomInt(location, block, prop, 96, 7, 3);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patchWitRandomInt(
// ResourceLocation location,
// Block block,
// IntegerProperty prop,
// int attempts,
// int xzSpread,
// int ySpread
// ) {
// return patch(
// location,
// attempts,
// xzSpread,
// ySpread,
// simple(location, ySpread, false, block.defaultBlockState(), prop)
// );
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// simple(
// ResourceLocation location,
// int searchDist,
// boolean rare,
// Feature<NoneFeatureConfiguration> feature
// ) {
// return simple(location, searchDist, rare, feature, NoneFeatureConfiguration.NONE);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// single(ResourceLocation location, Block block) {
// return single(location, BlockStateProvider.simple(block));
//
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// single(ResourceLocation location, BlockStateProvider provider) {
// return BCLFeatureBuilder
// .start(location, provider)
// .buildAndRegister();
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// simple(ResourceLocation location, Feature<NoneFeatureConfiguration> feature) {
// return BCLFeatureBuilder
// .start(location, feature)
// .buildAndRegister();
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// simple(
// ResourceLocation location,
// int searchDist,
// boolean rare,
// BlockState baseState,
// IntegerProperty property
// ) {
// int min = Integer.MAX_VALUE;
// int max = Integer.MIN_VALUE;
//
// for (Integer i : property.getPossibleValues()) {
// if (i < min) min = i;
// if (i > max) max = i;
// }
//
// return simple(
// location,
// searchDist,
// rare,
// Feature.SIMPLE_BLOCK,
// new SimpleBlockConfiguration(new RandomizedIntStateProvider(
// BlockStateProvider.simple(baseState),
// property,
// UniformInt.of(min, max)
// ))
// );
// }
//
// @Deprecated(forRemoval = true)
//
// public static <FC extends FeatureConfiguration> BCLFeature<Feature<FC>, FC>
// simple(
// ResourceLocation location,
// int searchDist,
// boolean rare,
// Feature<FC> feature,
// FC config
// ) {
// BCLFeatureBuilder builder = BCLFeatureBuilder
// .start(location, feature)
// .findSolidFloor(Math.min(12, searchDist))
// .is(BlockPredicate.ONLY_IN_AIR_PREDICATE);
// if (rare) {
// builder.onceEvery(4);
// }
// return builder.buildAndRegister(config);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(ResourceLocation location, Feature<NoneFeatureConfiguration> feature) {
// return patch(location, 96, 7, 3, feature, FeatureConfiguration.NONE);
// }
//
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(
// ResourceLocation location,
// int attempts,
// int xzSpread,
// int ySpread,
// Feature<NoneFeatureConfiguration> feature
// ) {
// return patch(location, attempts, xzSpread, ySpread, feature, FeatureConfiguration.NONE);
// }
//
// @Deprecated(forRemoval = true)
// public static <FC extends FeatureConfiguration> BCLFeature
// patch(
// ResourceLocation location,
// int attempts,
// int xzSpread,
// int ySpread,
// Feature<FC> feature,
// FC config
// ) {
// final BCLFeature SINGLE = simple(location, ySpread, false, feature, config);
// return patch(location, attempts, xzSpread, ySpread, SINGLE);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// wallPatch(
// ResourceLocation location,
// Block block,
// int attempts,
// int xzSpread,
// int ySpread
// ) {
// final BCLFeature SINGLE = simple(location, ySpread, false,
// org.betterx.bclib.api.v3.levelgen.features.BCLFeature.PLACE_BLOCK,
// new PlaceFacingBlockConfig(block, PlaceFacingBlockConfig.HORIZONTAL)
// );
// return patch(location, attempts, xzSpread, ySpread, SINGLE);
// }
//
// @Deprecated(forRemoval = true)
// public static BCLFeature
// patch(
// ResourceLocation location,
// int attempts,
// int xzSpread,
// int ySpread,
// BCLFeature single
// ) {
// ResourceLocation patchLocation = new ResourceLocation(location.getNamespace(), location.getPath() + "_patch");
//
// return BCLFeatureBuilder
// .start(patchLocation, Feature.RANDOM_PATCH)
// .buildAndRegister(new RandomPatchConfiguration(attempts, xzSpread, ySpread, single.getPlacedFeature()));
// }
//
}

View file

@ -1,11 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
/**
* @param <FC>
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature} instead
*/
@Deprecated(forRemoval = true)
public interface UserGrowableFeature<FC extends FeatureConfiguration> extends org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature<FC> {
}

View file

@ -1,37 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.blockpredicates;
import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates;
import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.IsFullShape;
import com.mojang.serialization.Codec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate;
import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicateType;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates} instead
*/
@Deprecated(forRemoval = true)
public class Types {
/**
* @deprecated Please use {@link BlockPredicates#FULL_SHAPE} instead
*/
@Deprecated(forRemoval = true)
public static final BlockPredicateType<IsFullShape> FULL_SHAPE = BlockPredicates.FULL_SHAPE;
/**
* @param location
* @param codec
* @param <P>
* @return
* @deprecated Please use {@link BlockPredicates#register(ResourceLocation, Codec)} instead
*/
@Deprecated(forRemoval = true)
public static <P extends BlockPredicate> BlockPredicateType<P> register(ResourceLocation location, Codec<P> codec) {
return BlockPredicates.register(location, codec);
}
public static void ensureStaticInitialization() {
}
}

View file

@ -1,50 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import org.betterx.bclib.api.v2.levelgen.features.BCLFeature;
import net.minecraft.core.Holder;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import net.minecraft.world.level.levelgen.placement.PlacementFilter;
import net.minecraft.world.level.levelgen.placement.PlacementModifier;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig} instead
*/
@Deprecated(forRemoval = true)
public class ConditionFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig {
public ConditionFeatureConfig(@NotNull PlacementFilter filter, @NotNull BCLFeature okFeature) {
super(filter, okFeature);
}
public ConditionFeatureConfig(
@NotNull PlacementFilter filter,
@NotNull BCLFeature okFeature,
@NotNull BCLFeature failFeature
) {
super(filter, okFeature, failFeature);
}
public ConditionFeatureConfig(@NotNull PlacementFilter filter, @NotNull Holder<PlacedFeature> okFeature) {
super(filter, okFeature);
}
public ConditionFeatureConfig(
@NotNull PlacementFilter filter,
@NotNull Holder<PlacedFeature> okFeature,
@NotNull Holder<PlacedFeature> failFeature
) {
super(filter, okFeature, failFeature);
}
protected ConditionFeatureConfig(
@NotNull PlacementModifier filter,
@NotNull Holder<PlacedFeature> okFeature,
@NotNull Optional<Holder<PlacedFeature>> failFeature
) {
super(filter, okFeature, failFeature);
}
}

View file

@ -1,32 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import net.minecraft.util.random.SimpleWeightedRandomList;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
import java.util.List;
@Deprecated(forRemoval = true)
public abstract class PlaceBlockFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.PlaceBlockFeatureConfig {
public PlaceBlockFeatureConfig(Block block) {
super(block);
}
public PlaceBlockFeatureConfig(BlockState state) {
super(state);
}
public PlaceBlockFeatureConfig(List<BlockState> states) {
super(states);
}
public PlaceBlockFeatureConfig(SimpleWeightedRandomList<BlockState> blocks) {
super(blocks);
}
public PlaceBlockFeatureConfig(BlockStateProvider blocks) {
super(blocks);
}
}

View file

@ -1,37 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import net.minecraft.core.Direction;
import net.minecraft.util.random.SimpleWeightedRandomList;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
import java.util.List;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig} instead
*/
@Deprecated(forRemoval = true)
public class PlaceFacingBlockConfig extends org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig {
public PlaceFacingBlockConfig(Block block, List<Direction> dir) {
super(block, dir);
}
public PlaceFacingBlockConfig(BlockState state, List<Direction> dir) {
super(state, dir);
}
public PlaceFacingBlockConfig(List<BlockState> states, List<Direction> dir) {
super(states, dir);
}
public PlaceFacingBlockConfig(SimpleWeightedRandomList<BlockState> blocks, List<Direction> dir) {
super(blocks, dir);
}
public PlaceFacingBlockConfig(BlockStateProvider blocks, List<Direction> dir) {
super(blocks, dir);
}
}

View file

@ -1,548 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import org.betterx.bclib.BCLib;
import org.betterx.bclib.blocks.BlockProperties;
import org.betterx.bclib.util.BlocksHelper;
import com.mojang.datafixers.util.Function15;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.util.valueproviders.ConstantInt;
import net.minecraft.util.valueproviders.IntProvider;
import net.minecraft.util.valueproviders.UniformInt;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider;
import java.util.Optional;
public abstract class ScatterFeatureConfig implements FeatureConfiguration {
public interface Instancer<T extends ScatterFeatureConfig> extends Function15<BlockStateProvider, Optional<BlockStateProvider>, Optional<BlockStateProvider>, Optional<BlockState>, Float, Float, Float, Float, Integer, Integer, Float, Float, Float, Boolean, IntProvider, T> {
}
public final BlockStateProvider clusterBlock;
public final BlockStateProvider tipBlock;
public final BlockStateProvider bottomBlock;
public final Optional<BlockState> baseState;
public final float baseReplaceChance;
public final float chanceOfDirectionalSpread;
public final float chanceOfSpreadRadius2;
public final float chanceOfSpreadRadius3;
public final int minHeight;
public final int maxHeight;
public final float maxSpread;
public final float sizeVariation;
public final float floorChance;
public final IntProvider spreadCount;
public final boolean growWhileFree;
public ScatterFeatureConfig(
BlockStateProvider clusterBlock,
Optional<BlockStateProvider> tipBlock,
Optional<BlockStateProvider> bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount
) {
this.clusterBlock = clusterBlock;
this.tipBlock = tipBlock.orElse(clusterBlock);
this.bottomBlock = bottomBlock.orElse(clusterBlock);
this.baseState = baseState;
this.baseReplaceChance = baseReplaceChance;
this.chanceOfDirectionalSpread = chanceOfDirectionalSpread;
this.chanceOfSpreadRadius2 = chanceOfSpreadRadius2;
this.chanceOfSpreadRadius3 = chanceOfSpreadRadius3;
this.minHeight = minHeight;
this.maxHeight = maxHeight;
this.maxSpread = maxSpread;
this.sizeVariation = sizeVariation;
this.floorChance = floorChance;
this.growWhileFree = growWhileFree;
this.spreadCount = spreadCount;
}
public boolean isFloor(RandomSource random) {
return random.nextFloat() < floorChance;
}
public abstract boolean isValidBase(BlockState state);
public abstract BlockState createBlock(int height, int maxHeight, RandomSource random, BlockPos pos);
public static <T extends ScatterFeatureConfig> Codec<T> buildCodec(Instancer<T> instancer) {
return RecordCodecBuilder.create((instance) -> instance
.group(
BlockStateProvider.CODEC
.fieldOf("cluster_block")
.forGetter((T cfg) -> cfg.clusterBlock),
BlockStateProvider.CODEC
.optionalFieldOf("tip_block")
.orElse(Optional.empty())
.forGetter((T cfg) -> cfg.tipBlock == cfg.clusterBlock
? Optional.empty()
: Optional.of(cfg.tipBlock)),
BlockStateProvider.CODEC
.optionalFieldOf("bottom_block")
.orElse(Optional.empty())
.forGetter((T cfg) -> cfg.bottomBlock == cfg.clusterBlock
? Optional.empty()
: Optional.of(cfg.bottomBlock)),
BlockState.CODEC
.optionalFieldOf("base_state")
.forGetter((T cfg) -> cfg.baseState),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("baseReplaceChance")
.orElse(1.0F)
.forGetter((T cfg) -> cfg.baseReplaceChance),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("chance_of_directional_spread")
.orElse(0.7F)
.forGetter((T cfg) -> cfg.chanceOfDirectionalSpread),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("chance_of_spread_radius2")
.orElse(0.5F)
.forGetter((T cfg) -> cfg.chanceOfSpreadRadius2),
Codec
.floatRange(0.0F, 1.0F)
.fieldOf("chance_of_spread_radius3")
.orElse(0.5F)
.forGetter((T cfg) -> cfg.chanceOfSpreadRadius3),
Codec
.intRange(1, 64)
.fieldOf("min_height")
.orElse(2)
.forGetter((T cfg) -> cfg.minHeight),
Codec
.intRange(1, 64)
.fieldOf("max_height")
.orElse(7)
.forGetter((T cfg) -> cfg.maxHeight),
Codec
.floatRange(0, 16)
.fieldOf("max_spread")
.orElse(2f)
.forGetter((T cfg) -> cfg.maxSpread),
Codec
.floatRange(0, 1)
.fieldOf("size_variation")
.orElse(0.7f)
.forGetter((T cfg) -> cfg.sizeVariation),
Codec
.floatRange(0, 1)
.fieldOf("floor_chance")
.orElse(0.5f)
.forGetter((T cfg) -> cfg.floorChance),
Codec
.BOOL
.fieldOf("grow_while_empty")
.orElse(false)
.forGetter((T cfg) -> cfg.growWhileFree),
IntProvider.codec(0, 64)
.fieldOf("length")
.orElse(UniformInt.of(0, 3))
.forGetter(cfg -> cfg.spreadCount)
)
.apply(instance, instancer)
);
}
public static class Builder<T extends ScatterFeatureConfig> {
private BlockStateProvider clusterBlock;
private BlockStateProvider tipBlock;
private BlockStateProvider bottomBlock;
private Optional<BlockState> baseState = Optional.empty();
private float baseReplaceChance = 0;
private float chanceOfDirectionalSpread = 0;
private float chanceOfSpreadRadius2 = 0;
private float chanceOfSpreadRadius3 = 0;
private int minHeight = 2;
private int maxHeight = 12;
private float maxSpread = 0;
private float sizeVariation = 0;
private float floorChance = 0.5f;
private boolean growWhileFree = false;
public IntProvider spreadCount = ConstantInt.of(0);
private final Instancer<T> instancer;
public Builder(Instancer<T> instancer) {
this.instancer = instancer;
}
public static <T extends ScatterFeatureConfig> Builder<T> start(Instancer<T> instancer) {
return new Builder<>(instancer);
}
public Builder<T> block(Block b) {
return block(b.defaultBlockState());
}
public Builder<T> singleBlock(Block b) {
return block(b.defaultBlockState()).heightRange(1, 1).spread(0, 0, ConstantInt.of(0));
}
public Builder<T> block(BlockState s) {
this.clusterBlock = BlockStateProvider.simple(s);
if (tipBlock == null) tipBlock = BlockStateProvider.simple(s);
if (bottomBlock == null) bottomBlock = BlockStateProvider.simple(s);
return this;
}
public Builder<T> tipBlock(BlockState s) {
tipBlock = BlockStateProvider.simple(s);
return this;
}
public Builder<T> bottomBlock(BlockState s) {
bottomBlock = BlockStateProvider.simple(s);
return this;
}
public Builder<T> tripleShape(Block s) {
return tripleShape(s.defaultBlockState());
}
public Builder<T> tripleShape(BlockState s) {
block(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.MIDDLE));
tipBlock(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.TOP));
bottomBlock(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.BOTTOM));
return this;
}
public Builder<T> tripleShapeCeil(Block s) {
return tripleShapeCeil(s.defaultBlockState());
}
public Builder<T> tripleShapeCeil(BlockState s) {
block(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.MIDDLE));
tipBlock(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.BOTTOM));
bottomBlock(s.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.TOP));
return this;
}
public Builder<T> block(BlockStateProvider s) {
this.clusterBlock = s;
if (tipBlock == null) tipBlock = s;
if (bottomBlock == null) bottomBlock = s;
return this;
}
public Builder<T> tipBlock(BlockStateProvider s) {
tipBlock = s;
return this;
}
public Builder<T> bottomBlock(BlockStateProvider s) {
bottomBlock = s;
return this;
}
public Builder<T> heightRange(int min, int max) {
minHeight = min;
maxHeight = max;
return this;
}
public Builder<T> growWhileFree() {
growWhileFree = true;
return this;
}
public Builder<T> minHeight(int h) {
minHeight = h;
return this;
}
public Builder<T> maxHeight(int h) {
maxHeight = h;
return this;
}
public Builder<T> generateBaseBlock(BlockState baseState) {
return generateBaseBlock(baseState, 1, 0, 0, 0);
}
public Builder<T> generateBaseBlock(BlockState baseState, float baseReplaceChance) {
return generateBaseBlock(baseState, baseReplaceChance, 0, 0, 0);
}
public Builder<T> generateBaseBlock(
BlockState baseState,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3
) {
return generateBaseBlock(
baseState,
1,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3
);
}
public Builder<T> generateBaseBlock(
BlockState baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3
) {
if (this.baseState.isPresent() && this.baseReplaceChance == 0) {
BCLib.LOGGER.error("Base generation was already selected.");
}
this.baseState = Optional.of(baseState);
this.baseReplaceChance = baseReplaceChance;
this.chanceOfDirectionalSpread = chanceOfDirectionalSpread;
this.chanceOfSpreadRadius2 = chanceOfSpreadRadius2;
this.chanceOfSpreadRadius3 = chanceOfSpreadRadius3;
return this;
}
public Builder<T> noSpread() {
return spread(0, 0, ConstantInt.of(0));
}
public Builder<T> spread(float maxSpread, float sizeVariation) {
return spread(maxSpread, sizeVariation, ConstantInt.of((int) Math.min(16, 4 * maxSpread * maxSpread)));
}
public Builder<T> spread(float maxSpread, float sizeVariation, IntProvider spreadCount) {
this.spreadCount = spreadCount; //
this.maxSpread = maxSpread;
this.sizeVariation = sizeVariation;
return this;
}
public Builder<T> floorChance(float chance) {
this.floorChance = chance;
return this;
}
public Builder<T> onFloor() {
this.floorChance = 1;
return this;
}
public Builder<T> onCeil() {
this.floorChance = 0;
return this;
}
public T build() {
return instancer.apply(
this.clusterBlock,
Optional.of(this.tipBlock),
Optional.of(this.bottomBlock),
this.baseState,
this.baseReplaceChance,
this.chanceOfDirectionalSpread,
this.chanceOfSpreadRadius2,
this.chanceOfSpreadRadius3,
this.minHeight,
this.maxHeight,
this.maxSpread,
this.sizeVariation,
this.floorChance,
this.growWhileFree,
this.spreadCount
);
}
}
public static Builder<OnSolid> startOnSolid() {
return Builder.start(OnSolid::new);
}
public static class OnSolid extends ScatterFeatureConfig {
public static final Codec<OnSolid> CODEC = buildCodec(OnSolid::new);
protected OnSolid(
BlockStateProvider clusterBlock,
Optional<BlockStateProvider> tipBlock,
Optional<BlockStateProvider> bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount
) {
super(
clusterBlock,
tipBlock,
bottomBlock,
baseState,
baseReplaceChance,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3,
minHeight,
maxHeight,
maxSpread,
sizeVariation,
floorChance,
growWhileFree,
spreadCount
);
}
@Override
public boolean isValidBase(BlockState state) {
return BlocksHelper.isTerrain(state)
|| baseState.map(s -> state.is(s.getBlock())).orElse(false);
}
@Override
public BlockState createBlock(int height, int maxHeight, RandomSource random, BlockPos pos) {
if (height == 0) return this.bottomBlock.getState(random, pos);
return height == maxHeight
? this.tipBlock.getState(random, pos)
: this.clusterBlock.getState(random, pos);
}
}
public static Builder<ExtendTop> startExtendTop() {
return Builder.start(ExtendTop::new);
}
public static class ExtendTop extends ScatterFeatureConfig {
public static final Codec<ExtendTop> CODEC = buildCodec(ExtendTop::new);
protected ExtendTop(
BlockStateProvider clusterBlock,
Optional<BlockStateProvider> tipBlock,
Optional<BlockStateProvider> bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount
) {
super(
clusterBlock,
tipBlock,
bottomBlock,
baseState,
baseReplaceChance,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3,
minHeight,
maxHeight,
maxSpread,
sizeVariation,
floorChance,
growWhileFree,
spreadCount
);
}
@Override
public boolean isValidBase(BlockState state) {
return BlocksHelper.isTerrain(state)
|| baseState.map(s -> state.is(s.getBlock())).orElse(false);
}
@Override
public BlockState createBlock(int height, int maxHeight, RandomSource random, BlockPos pos) {
if (height == 0) return this.bottomBlock.getState(random, pos);
if (height == 1) return this.clusterBlock.getState(random, pos);
return this.tipBlock.getState(random, pos);
}
}
public static Builder<ExtendBottom> startExtendBottom() {
return Builder.start(ExtendBottom::new);
}
public static class ExtendBottom extends ScatterFeatureConfig {
public static final Codec<ExtendBottom> CODEC = buildCodec(ExtendBottom::new);
protected ExtendBottom(
BlockStateProvider clusterBlock,
Optional<BlockStateProvider> tipBlock,
Optional<BlockStateProvider> bottomBlock,
Optional<BlockState> baseState,
float baseReplaceChance,
float chanceOfDirectionalSpread,
float chanceOfSpreadRadius2,
float chanceOfSpreadRadius3,
int minHeight,
int maxHeight,
float maxSpread,
float sizeVariation,
float floorChance,
boolean growWhileFree,
IntProvider spreadCount
) {
super(
clusterBlock,
tipBlock,
bottomBlock,
baseState,
baseReplaceChance,
chanceOfDirectionalSpread,
chanceOfSpreadRadius2,
chanceOfSpreadRadius3,
minHeight,
maxHeight,
maxSpread,
sizeVariation,
floorChance,
growWhileFree,
spreadCount
);
}
@Override
public boolean isValidBase(BlockState state) {
return BlocksHelper.isTerrain(state)
|| baseState.map(s -> state.is(s.getBlock())).orElse(false);
}
@Override
public BlockState createBlock(int height, int maxHeight, RandomSource random, BlockPos pos) {
if (height == maxHeight) return this.tipBlock.getState(random, pos);
if (height == maxHeight - 1) return this.clusterBlock.getState(random, pos);
return this.bottomBlock.getState(random, pos);
}
}
}

View file

@ -1,17 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import net.minecraft.core.Holder;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import java.util.List;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig instead}
*/
@Deprecated(forRemoval = true)
public class SequenceFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig {
public SequenceFeatureConfig(List<Holder<PlacedFeature>> features) {
super(features);
}
}

View file

@ -1,23 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.config;
import org.betterx.bclib.api.v2.levelgen.structures.StructurePlacementType;
import org.betterx.bclib.api.v2.levelgen.structures.StructureWorldNBT;
import net.minecraft.resources.ResourceLocation;
import java.util.List;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig} instead
*/
@Deprecated(forRemoval = true)
public class TemplateFeatureConfig extends org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig {
public TemplateFeatureConfig(ResourceLocation location, int offsetY, StructurePlacementType type) {
super(location, offsetY, type);
}
public TemplateFeatureConfig(List<StructureWorldNBT> structures) {
super(structures);
}
}

View file

@ -1,8 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.ConditionFeature} instead.
*/
@Deprecated(forRemoval = true)
public class ConditionFeature extends org.betterx.bclib.api.v3.levelgen.features.features.ConditionFeature {
}

View file

@ -1,45 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.util.BlocksHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap.Types;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
public abstract class DefaultFeature extends Feature<NoneFeatureConfiguration> {
public static final BlockState AIR = Blocks.AIR.defaultBlockState();
public static final BlockState WATER = Blocks.WATER.defaultBlockState();
public DefaultFeature() {
super(NoneFeatureConfiguration.CODEC);
}
public static int getYOnSurface(WorldGenLevel world, int x, int z) {
return world.getHeight(Types.WORLD_SURFACE, x, z);
}
public static int getYOnSurfaceWG(WorldGenLevel world, int x, int z) {
return world.getHeight(Types.WORLD_SURFACE_WG, x, z);
}
public static BlockPos getPosOnSurface(WorldGenLevel world, BlockPos pos) {
return world.getHeightmapPos(Types.WORLD_SURFACE, pos);
}
public static BlockPos getPosOnSurfaceWG(WorldGenLevel world, BlockPos pos) {
return world.getHeightmapPos(Types.WORLD_SURFACE_WG, pos);
}
public static BlockPos getPosOnSurfaceRaycast(WorldGenLevel world, BlockPos pos) {
return getPosOnSurfaceRaycast(world, pos, 256);
}
public static BlockPos getPosOnSurfaceRaycast(WorldGenLevel world, BlockPos pos, int dist) {
int h = BlocksHelper.downRay(world, pos, dist);
return pos.below(h);
}
}

View file

@ -1,8 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.MarkPostProcessingFeature} instead.
*/
@Deprecated(forRemoval = true)
public class MarkPostProcessingFeature extends org.betterx.bclib.api.v3.levelgen.features.features.MarkPostProcessingFeature {
}

View file

@ -1,16 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.api.v3.levelgen.features.config.PlaceBlockFeatureConfig;
import com.mojang.serialization.Codec;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.PlaceBlockFeature} instead.
*/
@Deprecated(forRemoval = true)
public class PlaceBlockFeature<FC extends PlaceBlockFeatureConfig> extends org.betterx.bclib.api.v3.levelgen.features.features.PlaceBlockFeature<FC> {
public PlaceBlockFeature(Codec<FC> codec) {
super(codec);
}
}

View file

@ -1,249 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.api.v2.levelgen.features.config.ScatterFeatureConfig;
import org.betterx.bclib.api.v3.levelgen.features.UserGrowableFeature;
import org.betterx.bclib.util.BlocksHelper;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import java.util.Optional;
@Deprecated(forRemoval = true)
public class ScatterFeature<FC extends ScatterFeatureConfig>
extends Feature<FC> implements UserGrowableFeature<FC> {
public ScatterFeature(Codec<FC> configCodec) {
super(configCodec);
}
@Override
public boolean place(FeaturePlaceContext<FC> featurePlaceContext) {
final WorldGenLevel level = featurePlaceContext.level();
final BlockPos origin = featurePlaceContext.origin();
final RandomSource random = featurePlaceContext.random();
ScatterFeatureConfig config = featurePlaceContext.config();
Optional<Direction> direction = getTipDirection(level, origin, random, config);
if (direction.isEmpty()) {
return false;
}
BlockPos basePos = origin.relative(direction.get(), -1);
int i = (int) (random.nextFloat() * (1 + config.maxHeight - config.minHeight) + config.minHeight);
growCenterPillar(level, origin, basePos, direction.get(), i, config, random);
return true;
}
protected void growCenterPillar(
LevelAccessor level,
BlockPos origin,
BlockPos basePos,
Direction direction,
int centerHeight,
ScatterFeatureConfig config,
RandomSource random
) {
if (config.isValidBase(level.getBlockState(basePos))) {
final Direction surfaceDirection = direction.getOpposite();
BlockPos.MutableBlockPos POS = new BlockPos.MutableBlockPos();
int adaptedHeight = freeHeight(level, direction, centerHeight, config, origin);
buildPillarWithBase(level, origin, basePos, direction, adaptedHeight, config, random, false);
final double distNormalizer = (config.maxSpread * Math.sqrt(2));
final int tryCount = config.spreadCount.sample(random);
for (int i = 0; i < tryCount; i++) {
int x = origin.getX() + (int) (random.nextGaussian() * config.maxSpread);
int z = origin.getZ() + (int) (random.nextGaussian() * config.maxSpread);
POS.set(x, basePos.getY(), z);
if (BlocksHelper.findSurroundingSurface(level, POS, surfaceDirection, 4, config::isValidBase)) {
int myHeight = freeHeight(
level,
direction,
centerHeight,
config,
POS
);
int dx = x - POS.getX();
int dz = z - POS.getZ();
float sizeFactor = (1 - (float) (Math.sqrt(dx * dx + dz * dz) / distNormalizer));
sizeFactor = (1 - (random.nextFloat() * config.sizeVariation)) * sizeFactor;
myHeight = (int) Math.min(Math.max(
config.minHeight,
config.minHeight + sizeFactor * (myHeight - config.minHeight)
), config.maxHeight);
BlockState baseState = level.getBlockState(POS.relative(direction.getOpposite()));
if (!config.isValidBase(baseState)) {
System.out.println("Starting from " + baseState + " at " + POS.relative(direction.getOpposite()));
}
buildPillarWithBase(level,
POS,
POS.relative(direction.getOpposite()),
direction,
myHeight,
config,
random, false
);
}
}
}
}
private int freeHeight(
LevelAccessor level,
Direction direction,
int defaultHeight,
ScatterFeatureConfig config,
BlockPos POS
) {
int myHeight;
if (config.growWhileFree) {
myHeight = BlocksHelper.blockCount(
level,
POS,
direction,
config.maxHeight,
BlocksHelper::isFree
);
} else {
myHeight = defaultHeight;
}
return Math.max(config.minHeight, myHeight);
}
private void buildPillarWithBase(
LevelAccessor level,
BlockPos origin,
BlockPos basePos,
Direction direction,
int height,
ScatterFeatureConfig config,
RandomSource random,
boolean force
) {
if (force || BlocksHelper.isFreeSpace(level, origin, direction, height, BlocksHelper::isFree)) {
createPatchOfBaseBlocks(level, random, basePos, config);
BlockState bottom = config.bottomBlock.getState(random, origin);
if (bottom.canSurvive(level, origin)) {
buildPillar(level, origin, direction, height, config, random);
}
}
}
private void buildPillar(
LevelAccessor level,
BlockPos origin,
Direction direction,
int height,
ScatterFeatureConfig config,
RandomSource random
) {
final BlockPos.MutableBlockPos POS = origin.mutable();
for (int size = 0; size < height; size++) {
BlockState state = config.createBlock(size, height - 1, random, POS);
BlocksHelper.setWithoutUpdate(level, POS, state);
POS.move(direction);
}
}
private Optional<Direction> getTipDirection(
LevelAccessor levelAccessor,
BlockPos blockPos,
RandomSource randomSource,
ScatterFeatureConfig config
) {
boolean onCeil = config.floorChance < 1 && config.isValidBase(levelAccessor.getBlockState(blockPos.above()));
boolean onFloor = config.floorChance > 0 && config.isValidBase(levelAccessor.getBlockState(blockPos.below()));
if (onCeil && onFloor) {
return Optional.of(config.isFloor(randomSource) ? Direction.DOWN : Direction.UP);
}
if (onCeil) {
return Optional.of(Direction.DOWN);
}
if (onFloor) {
return Optional.of(Direction.UP);
}
return Optional.empty();
}
private void createPatchOfBaseBlocks(
LevelAccessor levelAccessor,
RandomSource randomSource,
BlockPos blockPos,
ScatterFeatureConfig config
) {
if (config.baseState.isPresent() && config.baseReplaceChance > 0 && randomSource.nextFloat() < config.baseReplaceChance) {
final BlockState baseState = config.baseState.get();
BlockPos pos;
for (Direction direction : Direction.Plane.HORIZONTAL) {
if (randomSource.nextFloat() > config.chanceOfDirectionalSpread) continue;
pos = blockPos.relative(direction);
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
if (randomSource.nextFloat() > config.chanceOfSpreadRadius2) continue;
pos = pos.relative(Direction.getRandom(randomSource));
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
if (randomSource.nextFloat() > config.chanceOfSpreadRadius3) continue;
pos = pos.relative(Direction.getRandom(randomSource));
placeBaseBlockIfPossible(levelAccessor, pos, baseState);
}
placeBaseBlockIfPossible(levelAccessor, blockPos, baseState);
}
}
protected void placeBaseBlockIfPossible(
LevelAccessor levelAccessor,
BlockPos blockPos,
BlockState baseState
) {
BlockState blockState = levelAccessor.getBlockState(blockPos);
if (BlocksHelper.isTerrain(blockState)) {
levelAccessor.setBlock(blockPos, baseState, 2);
}
}
@Override
public boolean grow(
ServerLevelAccessor level,
BlockPos origin,
RandomSource random,
FC config
) {
Optional<Direction> oDirection = getTipDirection(level, origin, random, config);
if (oDirection.isEmpty()) {
return false;
}
Direction direction = oDirection.get();
BlockPos basePos = origin.relative(direction, -1);
if (config.isValidBase(level.getBlockState(basePos))) {
int centerHeight = (int) (random.nextFloat() * (1 + config.maxHeight - config.minHeight) + config.minHeight);
centerHeight = freeHeight(
level,
direction,
centerHeight,
config,
origin.relative(direction, 1)
) + 1;
buildPillarWithBase(level, origin, basePos, direction, centerHeight, config, random, true);
}
return false;
}
}

View file

@ -1,9 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.SequenceFeature} instead.
*/
@Deprecated(forRemoval = true)
public class SequenceFeature extends org.betterx.bclib.api.v3.levelgen.features.features.SequenceFeature {
}

View file

@ -1,51 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.util.BlocksHelper;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration;
import java.util.Optional;
@Deprecated(forRemoval = true)
public abstract class SurfaceFeature<T extends FeatureConfiguration> extends Feature<T> {
public static abstract class DefaultConfiguration extends SurfaceFeature<NoneFeatureConfiguration> {
protected DefaultConfiguration() {
super(NoneFeatureConfiguration.CODEC);
}
}
protected SurfaceFeature(Codec<T> codec) {
super(codec);
}
protected abstract boolean isValidSurface(BlockState state);
protected int minHeight(FeaturePlaceContext<T> ctx) {
return ctx.chunkGenerator().getSeaLevel();
}
@Override
public boolean place(FeaturePlaceContext<T> ctx) {
Optional<BlockPos> pos = BlocksHelper.findSurfaceBelow(
ctx.level(),
ctx.origin(),
minHeight(ctx),
this::isValidSurface
);
if (pos.isPresent()) {
generate(pos.get(), ctx);
return true;
}
return false;
}
protected abstract void generate(BlockPos centerPos, FeaturePlaceContext<T> ctx);
}

View file

@ -1,16 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig;
import com.mojang.serialization.Codec;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.features.TemplateFeature} instead.
*/
@Deprecated(forRemoval = true)
public class TemplateFeature<FC extends TemplateFeatureConfig> extends org.betterx.bclib.api.v3.levelgen.features.features.TemplateFeature<FC> {
public TemplateFeature(Codec<FC> codec) {
super(codec);
}
}

View file

@ -1,40 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.features;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.WeightedPlacedFeature;
import net.minecraft.world.level.levelgen.feature.configurations.RandomFeatureConfiguration;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
@Deprecated(forRemoval = true)
public class WeightedRandomSelectorFeature extends Feature<RandomFeatureConfiguration> {
public WeightedRandomSelectorFeature() {
super(RandomFeatureConfiguration.CODEC);
}
public boolean place(FeaturePlaceContext<RandomFeatureConfiguration> ctx) {
final WorldGenLevel level = ctx.level();
final ChunkGenerator generator = ctx.chunkGenerator();
final RandomFeatureConfiguration cfg = ctx.config();
final RandomSource random = ctx.random();
final BlockPos pos = ctx.origin();
PlacedFeature selected = cfg.defaultFeature.value();
if (!cfg.features.isEmpty()) {
final float totalWeight = cfg.features.stream().map(w -> w.chance).reduce(0.0f, (p, c) -> p + c);
float bar = random.nextFloat() * totalWeight;
for (WeightedPlacedFeature f : cfg.features) {
selected = f.feature.value();
bar -= f.chance;
if (bar < 0) break;
}
}
return selected.place(level, generator, random, pos);
}
}

View file

@ -1,9 +0,0 @@
package org.betterx.bclib.api.v2.levelgen.features.placement;
/**
* @deprecated Please use {@link org.betterx.bclib.api.v3.levelgen.features.placement.All} instead.
*/
@Deprecated(forRemoval = true)
public class All extends org.betterx.bclib.api.v3.levelgen.features.placement.All {
}

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