diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml new file mode 100644 index 00000000..0e617b67 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -0,0 +1,76 @@ +name: Report a Bug +description: File a bug report +title: "[Bug] " +labels: [ "bug" ] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a Bug in BCLib! + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: markdown + attributes: + value: | + ## Versions + - type: input + id: bn_version + attributes: + label: BCLib + description: What version of BCLib are you running? + placeholder: 3.x.x + validations: + required: true + - type: input + id: fabric_api_version + attributes: + label: Fabric API + description: What version of Fabric API is installed + placeholder: 0.8x.x + validations: + required: false + - type: input + id: fabric_loader_version + attributes: + label: Fabric Loader + description: What version of Fabric Loader do you use + placeholder: 0.14.x + validations: + required: false + - type: dropdown + id: mc_version + attributes: + label: Minecraft + description: What version of Minecraft is installed? + options: + - 1.20.1 + - 1.20.0 + - 1.19.4 + - 1.19.3 [unsupported] + - 1.19.2 + - Older + validations: + required: true + - type: markdown + attributes: + value: | + ## Additional Information + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: textarea + id: other_mods + attributes: + label: Other Mods + description: If you can, please supply a list of installed Mods (besides BetterNether and BCLib). This information may already be included in the log above. + render: shell diff --git a/.github/ISSUE_TEMPLATE/SUGGEST_FROM.yml b/.github/ISSUE_TEMPLATE/SUGGEST_FROM.yml new file mode 100644 index 00000000..8b40a8bb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/SUGGEST_FROM.yml @@ -0,0 +1,18 @@ +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 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..a49eab2f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6019df58..a17985c9 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,10 @@ bin/ # fabric run/ +run-client/ +run-server/ output/ *.log +/CHANGES.md +/src/main/generated/.cache/ +/modrinth.json diff --git a/BetterX_CodeFormat.xml b/BetterX_CodeFormat.xml new file mode 100644 index 00000000..ee85f79d --- /dev/null +++ b/BetterX_CodeFormat.xml @@ -0,0 +1,40 @@ + + \ No newline at end of file diff --git a/LICENSE b/LICENSE index 419a3b02..9caa064f 100644 --- a/LICENSE +++ b/LICENSE @@ -19,3 +19,7 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +___________________________ +Some of our Assets (see LICENSE.ASSETS for a Listing) is licensed under CC BY-NC-SA 4.0 +See https://creativecommons.org/licenses/by-nc-sa/4.0/ for Details. diff --git a/LICENSE.ASSETS b/LICENSE.ASSETS new file mode 100644 index 00000000..f2ef219a --- /dev/null +++ b/LICENSE.ASSETS @@ -0,0 +1,14 @@ +Some of our Assets (see List below) is licensed under CC BY-NC-SA 4.0 +See https://creativecommons.org/licenses/by-nc-sa/4.0/ for Details. + +Please use the Attribution "Team BetterX". + +The following Files are distributed under this License: + * src/main/resources/assets/bclib/lang/de_de.json + * src/main/resources/assets/bclib/textures/* + * src/main/resources/assets/bclib/betterx.png + * src/main/resources/assets/bclib/header.jpg + * src/main/resources/assets/bclib/icon_betterend.png + * src/main/resources/assets/bclib/icon_betternether.png + * src/main/resources/assets/bclib/icon_bright.png + * src/main/resources/assets/bclib/icon_updater.png \ No newline at end of file diff --git a/README.md b/README.md index 814d89b5..1afe21c6 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,146 @@ -[![](https://jitpack.io/v/paulevsGitch/BCLib.svg)](https://jitpack.io/#paulevsGitch/BCLib) +[![](https://jitpack.io/v/quiqueck/BCLib.svg)](https://jitpack.io/#quiqueck/BCLib) + # BCLib -BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.16.5 -## Features: -### API: -* Simple Mod Integration API; -* Structure Features API; -* World Data API; -* Bonemeal API; -* Features API; -* Biome API; -* Tag API. - -### Libs: -* Spline library (simple); -* Recipe manager; -* Noise library; -* Math library; -* SDF library. - -### Helpers And Utils: -* Custom surface builders; -* Translation helper; -* Weighted list; -* Block helper. - -### Rendering: -* Procedural block models (from paterns or from code); -* Block render layer interface. +BCLib is a library mod for BetterX team mods, developed for Fabric, MC 1.19 ## Importing: -* Clone repo -* Edit gradle.properties if necessary -* Run command line in folder: gradlew genSources eclipse (or Another-IDE-Name) -* Import project to IDE + +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. + +## 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. + +### 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. + +### Helpers And Utils: + +* 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; + +### 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). ## Building: + * Clone repo * Run command line in folder: gradlew build * Mod .jar will be in ./build/libs diff --git a/bclib-composit.gradle b/bclib-composit.gradle new file mode 100644 index 00000000..3d9f10e6 --- /dev/null +++ b/bclib-composit.gradle @@ -0,0 +1,8 @@ +plugins { + id 'idea' + id 'eclipse' + id 'fabric-loom' + id 'maven-publish' +} + +apply from: "bclib.gradle" \ No newline at end of file diff --git a/bclib.gradle b/bclib.gradle new file mode 100644 index 00000000..4f72e087 --- /dev/null +++ b/bclib.gradle @@ -0,0 +1,186 @@ +buildscript { + dependencies { + classpath 'org.kohsuke:github-api:1.114' + } + + repositories { + gradlePluginPortal() + } +} +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + +archivesBaseName = project.archives_base_name +version = project.mod_version +group = project.maven_group + +repositories { + maven { url "https://maven.dblsaiko.net/" } + maven { url "https://maven.fabricmc.net/" } + maven { url "https://maven.shedaniel.me/" } + maven { url 'https://maven.blamejared.com' } + maven { url 'https://jitpack.io' } + maven { url 'https://maven.terraformersmc.com/releases' } + maven { url 'https://maven.terraformersmc.com' } +} + +def local_wunderlib = findProject(':WunderLib') != null + +loom { + accessWidenerPath = file("src/main/resources/bclib.accesswidener") + runs { + // This adds a new gradle task that runs the datagen API: "gradlew runDatagenClient" + datagenClient { + inherit client + name "Data Generation" + vmArg "-Dfabric-api.datagen" + vmArg "-Dfabric-api.datagen.output-dir=${file("src/main/generated")}" + vmArg "-Dfabric-api.datagen.strict-validation" + + runDir "build/datagen" + } + } +} + + +sourceSets { + main { + // Add the datagenned files into the jar. + resources { + srcDirs += [ + 'src/main/generated' + ] + } + } +} + +dependencies { + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings loom.officialMojangMappings() + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + //make sure we are compatible to the old model API + modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_version}" + modCompileOnly "com.terraformersmc:modmenu:${project.modmenu_version}" + + modCompileOnly "dev.emi:emi-fabric:${emi_version}:api" + modLocalRuntime "dev.emi:emi-fabric:${emi_version}" + + println "Using local WunderLib: ${local_wunderlib}" + if (local_wunderlib) { + implementation project(path: ":WunderLib", configuration: 'dev') + include project(path: ":WunderLib", configuration: 'dev') + } else { + modApi "com.github.quiqueck:WunderLib:${project.wunderlib_version}" + include "com.github.quiqueck:WunderLib:${project.wunderlib_version}" + } +} + +processResources { + println "Version: ${project.mod_version}" + inputs.property "version", project.mod_version + + filesMatching("fabric.mod.json") { + expand "version": project.mod_version + } +} + +// ensure that the encoding is set to UTF-8, no matter what the system default is +// this fixes some edge cases with special characters not displaying correctly +// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" + it.options.release = 17 +} + +javadoc { + options.tags = ["reason"] + options.stylesheetFile = new File(projectDir, "javadoc.css"); +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + archiveClassifier = '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) { + archiveClassifier = 'sources' + from sourceSets.main.allSource +} + +jar { + from "LICENSE" + from "LICENSE.ASSETS" +} + +artifacts { + archives sourcesJar + archives javadocJar +} + +def env = System.getenv() + +import org.kohsuke.github.GHReleaseBuilder +import org.kohsuke.github.GitHub + +task release(dependsOn: [remapJar, sourcesJar, javadocJar]) { + onlyIf { + env.GITHUB_TOKEN + } + + doLast { + def github = GitHub.connectUsingOAuth(env.GITHUB_TOKEN as String) + def repository = github.getRepository("quiqueck/BCLib") + + def releaseBuilder = new GHReleaseBuilder(repository, version as String) + releaseBuilder.name("${archivesBaseName}-${version}") + releaseBuilder.body("A changelog can be found at https://github.com/quiqueck/BCLib/commits") + releaseBuilder.commitish("main") + + def ghRelease = releaseBuilder.create() + ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar"), "application/java-archive"); + ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-sources.jar"), "application/java-archive"); + ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-javadoc.jar"), "application/java-archive"); + } +} + +// configure the maven publication +publishing { + publications { + gpr(MavenPublication) { + artifactId archivesBaseName + artifact(remapJar) { + builtBy remapJar + } + artifact(sourcesJar) { + builtBy remapSourcesJar + } + } + } + + // select the repositories you want to publish to + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/quiqueck/bclib") + credentials { + username = env.GITHUB_USER + password = env.GITHUB_TOKEN + } + } + } +} + +configurations { + dev { + canBeResolved = false + canBeConsumed = true + } +} + +artifacts { + dev jar +} diff --git a/build.gradle b/build.gradle index 3b9ff8a6..e4915857 100644 --- a/build.gradle +++ b/build.gradle @@ -1,164 +1,189 @@ -buildscript { - dependencies { - classpath 'org.kohsuke:github-api:1.114' - } -} - plugins { - id 'idea' - id 'eclipse' - id 'fabric-loom' version '0.7-SNAPSHOT' - id 'maven-publish' + id 'idea' + id 'eclipse' + id 'fabric-loom' version "${loom_version}" + id 'maven-publish' + id "com.modrinth.minotaur" version "2.+" + id "com.matthewprenger.cursegradle" version "1.4.0" } -sourceCompatibility = JavaVersion.VERSION_1_8 -targetCompatibility = JavaVersion.VERSION_1_8 +apply from: "bclib.gradle" -archivesBaseName = project.archives_base_name -version = project.mod_version -group = project.maven_group +//from https://lowcarbrob.medium.com/android-pro-tip-generating-your-apps-changelog-from-git-inside-build-gradle-19a07533eec4 +String generateChangelog() { + println "Assembeling Changelog ..." + def lastTag = "git describe --tags --abbrev=0".execute().text.trim() + def gitLogCmd = "git log $lastTag..HEAD --oneline --no-merges --pretty=format:\"%s\"".execute().text.trim() -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' } + def features = "" + def fixes = "" + def changes = "" + gitLogCmd.eachLine { gitLine -> + def line = gitLine.substring(1, gitLine.length() - 1) + if (line.trim().startsWith("[")) { + def sline = line.split("]", 2) + if (sline.length == 2) { + def type = sline[0].trim().toLowerCase().substring(1) + def comment = sline[1].trim() + + //filter issue links + if (comment.contains("(")) { + def cline = comment.split("\\(", 2) + if (cline.length == 2 && cline[1].contains("#")) { + comment = cline[0].trim() + } + } + + if (type == "fix" || type == "fixes" || type == "fixed") { + fixes += "- $comment \n" + } else if (type == "feature" || type == "features") { + features += "- $comment \n" + } else if (type == "change" || type == "changes" || type == "changed") { + changes += "- $comment \n" + } else { + println "Unknown Type: $type ($line)" + } + } + } + + } + def changelog = "" + if (!features.isEmpty()) { + changelog += "#### Features\n" + changelog += features.trim() + changelog += "\n\n" + } + if (!changes.isEmpty()) { + changelog += "#### Changes\n" + changelog += changes.trim() + changelog += "\n\n" + } + if (!fixes.isEmpty()) { + changelog += "#### Fixes\n" + changelog += fixes.trim() + changelog += "\n\n" + } + + println "Changelog since $lastTag:\n$changelog" + return changelog } -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}" +task changelog() { + doLast { + new File(projectDir, "CHANGES.md").text = generateChangelog() + } } -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" - } - } +modrinth { + def changes = new File(projectDir, "CHANGES.md") + if (changes.exists()) { + changes = changes.getText('UTF-8') + } else { + changes = "" + } + def modrinth_token = new File(projectDir, "../MODRINTH_TOKEN") + if (modrinth_token.exists()) { + modrinth_token = modrinth_token.text + } else { + modrinth_token = "" + } + def slurper = new groovy.json.JsonSlurper() + token = modrinth_token + projectId = project.archives_base_name + versionNumber = project.mod_version + versionType = project.release_channel + uploadFile = remapJar + gameVersions = slurper.parseText(project.modrinth_versions) + loaders = ["fabric"] + changelog = changes + dependencies { + required.project "fabric-api" + optional.project "modmenu" + } + debugMode = false } -def 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" - } - } +curseforge { + def slurper = new groovy.json.JsonSlurper() + apiKey = new File(projectDir, "../CURSEFORGE_TOKEN") + if (apiKey.exists()) { + apiKey = apiKey.text + } else { + apiKey = "" + } + + def changes = new File(projectDir, "CHANGES.md") + if (changes.exists()) { + changes = changes.getText('UTF-8') + } else { + changes = "" + } + + project { + id = '495191' + changelogType = 'markdown' + changelog = changes + releaseType = project.release_channel + def versions = slurper.parseText(project.modrinth_versions); + def latestVersion = '' + for (v in versions) { + addGameVersion v + latestVersion = "[$v]" + } + addGameVersion 'Fabric' + addGameVersion 'Java 17' + relations { + requiredDependency 'fabric-api' + optionalDependency 'modmenu' + } + mainArtifact(remapJar) { + displayName = "$project.archives_base_name-$project.version $latestVersion" + } + afterEvaluate { + mainArtifact(remapJar.outputs) + } + } + + options { + debug = false + forgeGradleIntegration = false + } } -processResources { - inputs.property "version", project.version - duplicatesStrategy = 'EXCLUDE' - from(sourceSets.main.resources.srcDirs) { - include "fabric.mod.json" - expand "version": project.version - } +task nextVersion() { + doLast { + def inputFile = new File('modrinth.json') + def gameVersions = java.net.URLEncoder.encode(project.modrinth_versions, "UTF-8") + new URL("https://api.modrinth.com/v2/project/${project.archives_base_name}/version?&game_versions=${gameVersions}").withInputStream { i -> inputFile.withOutputStream { it << i } } - from(sourceSets.main.resources.srcDirs) { - exclude "fabric.mod.json" - } -} + def json = new groovy.json.JsonSlurper().parseText(inputFile.text) + def version = json[0].version_number -// 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" -} + //increment patch version + def indexedVersionList = version.split(/\./).toList().withIndex() + indexedVersionList = indexedVersionList.collect { num, idx -> num.toInteger() } + indexedVersionList[2] = indexedVersionList[2].value + 1 + def updatedVersion = indexedVersionList.join(".") -javadoc { - options.tags = [ "reason" ] -} + println "\n\n" + println "------------- CURRENT VERSION -------------" + println "Last Published Version: " + version + println " Game Versions: " + json[0].game_versions + println " Status: " + json[0].status + println " Featured: " + json[0].featured + println " Downloaded: " + json[0].downloads + println "\n" + println "-------------- NEXT VERSION ---------------" + println "Next Version: " + updatedVersion + println "\n\n" -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} + def propertiesFile = new File("gradle.properties") + def newContents = propertiesFile.text.replaceFirst("mod_version=\\d+.\\d+.\\d+", "mod_version=${updatedVersion}") + propertiesFile.text = newContents -// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task -// if it is present. -// If you remove this task, sources will not be generated. -task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' - from sourceSets.main.allSource -} - -jar { - from "LICENSE" -} - -artifacts { - archives sourcesJar - archives javadocJar -} - -def env = System.getenv() - -import org.kohsuke.github.GHReleaseBuilder -import org.kohsuke.github.GitHub - -task release(dependsOn: [remapJar, sourcesJar, javadocJar]) { - onlyIf { - env.GITHUB_TOKEN - } - - doLast { - def github = GitHub.connectUsingOAuth(env.GITHUB_TOKEN as String) - def repository = github.getRepository("paulevsGitch/BCLib") - - def releaseBuilder = new GHReleaseBuilder(repository, version as String) - releaseBuilder.name("${archivesBaseName}-${version}") - releaseBuilder.body("A changelog can be found at https://github.com/paulevsGitch/BCLib/commits") - releaseBuilder.commitish("main") - - def ghRelease = releaseBuilder.create() - ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar"), "application/java-archive"); - ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-sources.jar"), "application/java-archive"); - ghRelease.uploadAsset(file("${project.buildDir}/libs/${archivesBaseName}-${version}-javadoc.jar"), "application/java-archive"); - } -} - -// configure the maven publication -publishing { - publications { - gpr(MavenPublication) { - artifactId archivesBaseName - artifact(remapJar) { - builtBy remapJar - } - artifact(sourcesJar) { - builtBy remapSourcesJar - } - } - } - - // select the repositories you want to publish to - repositories { - maven { - name = "GitHubPackages" - url = uri("https://maven.pkg.github.com/paulevsgitch/bclib") - credentials { - username = env.GITHUB_USER - password = env.GITHUB_TOKEN - } - } - } -} + def fabricFile = new File("src/main/resources/fabric.mod.json") + newContents = fabricFile.text.replaceFirst('"version": ".+"', "\"version\": \"${updatedVersion}\"") + fabricFile.text = newContents + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index d0cceeac..bfd2aadc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,21 @@ # Done to increase the memory available to gradle. -org.gradle.jvmargs=-Xmx2G - +org.gradle.jvmargs=-Xmx8G +#Modrinth +modrinth_versions=["1.20", "1.20.1"] +#`release`, `beta` or `alpha` +release_channel=release +#Loom +loom_version=1.0-SNAPSHOT # Fabric Properties -# check these on https://fabricmc.net/use -minecraft_version=1.16.5 -yarn_mappings=6 -loader_version=0.11.3 - +# check these on https://fabricmc.net/versions.html +minecraft_version=1.20.1 +loader_version=0.14.21 +fabric_version=0.86.1+1.20.1 # Mod Properties -mod_version = 0.1.45 -maven_group = ru.bclib -archives_base_name = bclib - +mod_version=3.0.14 +maven_group=org.betterx.bclib +archives_base_name=bclib # Dependencies -# 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.32.9+1.16 +modmenu_version=7.0.0 +emi_version=1.0.3+1.20 +wunderlib_version=1.1.5 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 490fda85..7454180f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f371643e..59bc51a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index 2fe81a7d..1b6c7873 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,78 +17,113 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# 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/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -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 +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 done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${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 - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | 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 @@ -97,7 +132,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 @@ -105,79 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -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 - -# 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" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # 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" ;; +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 fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# 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. -# 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" +# 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" ) + + 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 + done +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. + +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' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 9109989e..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -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 init +if "%ERRORLEVEL%" == "0" goto execute 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 init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,28 +64,14 @@ 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 %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/javadoc.css b/javadoc.css new file mode 100644 index 00000000..e393f777 --- /dev/null +++ b/javadoc.css @@ -0,0 +1,903 @@ +/* + * 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, \ + \ + \ + '); + 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, \ + \ + \ + '); +} + +/* + * 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; + } +} diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 00000000..e8af8cfd --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,6 @@ +# 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 \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index f91a4fe7..14eef3a4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,3 +7,40 @@ pluginManagement { gradlePluginPortal() } } + +// #### Custom Settings #### + +// Change the next line to disable local lib loading +def allowLocalLibUse = true + +//When true, the local lib is also used in commandline builds +def allowLocalLibInConsoleMode = false + +//The path were to look for the local BCLib +def WunderLibPath = '../WunderLib' + + +// #### Logic #### +def isIDE = properties.containsKey('android.injected.invoked.from.ide') + || (System.getenv("XPC_SERVICE_NAME") ?: "").contains("intellij") + || (System.getenv("XPC_SERVICE_NAME") ?: "").contains(".idea") + || System.getenv("IDEA_INITIAL_DIRECTORY") != null + +println "IntelliJ: ${isIDE}" + +def WunderLibFolder = new File(WunderLibPath) +if (allowLocalLibUse && (isIDE || allowLocalLibInConsoleMode) && WunderLibFolder.exists()) { + println "Using local WunderLib from '${WunderLibFolder}' in IntelliJ" + println "If you do not want to load the local version of WunderLib" + println "either rename the Folder containing WunderLib to something" + println "else, or set 'allowLocalLibUse' in settings.gradle" + println "to false." + println "" + println "If you receive version-errors when launching minecraft" + println "in IntelliJ, make sure you have set up gradle instead" + println "of IntelliJ to compile and run." + + include ':WunderLib' + project(":WunderLib").projectDir = WunderLibFolder + project(':WunderLib').buildFileName = './wunderlib-composit.gradle' +} diff --git a/src/main/generated/data/bclib/advancements/recipes/brewing/tag_cauldron.json b/src/main/generated/data/bclib/advancements/recipes/brewing/tag_cauldron.json new file mode 100644 index 00000000..fcc7b1c0 --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/brewing/tag_cauldron.json @@ -0,0 +1,33 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_cauldron" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_tag_c_iron_ingots", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_cauldron" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/combat/tag_shield.json b/src/main/generated/data/bclib/advancements/recipes/combat/tag_shield.json new file mode 100644 index 00000000..f8fe6b8b --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/combat/tag_shield.json @@ -0,0 +1,44 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_tag_minecraft_planks": { + "conditions": { + "items": [ + { + "tag": "minecraft:planks" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_shield" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_tag_c_iron_ingots", + "has_tag_minecraft_planks", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_shield" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/decorations/tag_shulker_box.json b/src/main/generated/data/bclib/advancements/recipes/decorations/tag_shulker_box.json new file mode 100644 index 00000000..bcb1c3ae --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/decorations/tag_shulker_box.json @@ -0,0 +1,46 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_shulker_shell": { + "conditions": { + "items": [ + { + "items": [ + "minecraft:shulker_shell" + ] + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_tag_c_chest": { + "conditions": { + "items": [ + { + "tag": "c:chest" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_shulker_box" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_tag_c_chest", + "has_shulker_shell", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_shulker_box" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/decorations/tag_smith_table.json b/src/main/generated/data/bclib/advancements/recipes/decorations/tag_smith_table.json new file mode 100644 index 00000000..b55b9497 --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/decorations/tag_smith_table.json @@ -0,0 +1,44 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_tag_minecraft_planks": { + "conditions": { + "items": [ + { + "tag": "minecraft:planks" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_smith_table" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_tag_c_iron_ingots", + "has_tag_minecraft_planks", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_smith_table" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/decorations/tag_stonecutter.json b/src/main/generated/data/bclib/advancements/recipes/decorations/tag_stonecutter.json new file mode 100644 index 00000000..40a82957 --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/decorations/tag_stonecutter.json @@ -0,0 +1,46 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_stone": { + "conditions": { + "items": [ + { + "items": [ + "minecraft:stone" + ] + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_stonecutter" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_stone", + "has_tag_c_iron_ingots", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_stonecutter" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/misc/tag_bucket.json b/src/main/generated/data/bclib/advancements/recipes/misc/tag_bucket.json new file mode 100644 index 00000000..c412a5d7 --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/misc/tag_bucket.json @@ -0,0 +1,33 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_bucket" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_tag_c_iron_ingots", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_bucket" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/redstone/tag_hopper.json b/src/main/generated/data/bclib/advancements/recipes/redstone/tag_hopper.json new file mode 100644 index 00000000..07513ffa --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/redstone/tag_hopper.json @@ -0,0 +1,44 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_tag_c_chest": { + "conditions": { + "items": [ + { + "tag": "c:chest" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_hopper" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_tag_c_chest", + "has_tag_c_iron_ingots", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_hopper" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/redstone/tag_piston.json b/src/main/generated/data/bclib/advancements/recipes/redstone/tag_piston.json new file mode 100644 index 00000000..9a4f6a78 --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/redstone/tag_piston.json @@ -0,0 +1,70 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_cobblestone": { + "conditions": { + "items": [ + { + "items": [ + "minecraft:cobblestone" + ] + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_redstone": { + "conditions": { + "items": [ + { + "items": [ + "minecraft:redstone" + ] + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_tag_minecraft_planks": { + "conditions": { + "items": [ + { + "tag": "minecraft:planks" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_piston" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_cobblestone", + "has_tag_c_iron_ingots", + "has_redstone", + "has_tag_minecraft_planks", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_piston" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/tools/tag_compass.json b/src/main/generated/data/bclib/advancements/recipes/tools/tag_compass.json new file mode 100644 index 00000000..00003230 --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/tools/tag_compass.json @@ -0,0 +1,46 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_redstone": { + "conditions": { + "items": [ + { + "items": [ + "minecraft:redstone" + ] + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_compass" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_tag_c_iron_ingots", + "has_redstone", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_compass" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/transportation/tag_minecart.json b/src/main/generated/data/bclib/advancements/recipes/transportation/tag_minecart.json new file mode 100644 index 00000000..bc6b2fe3 --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/transportation/tag_minecart.json @@ -0,0 +1,33 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_minecart" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_tag_c_iron_ingots", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_minecart" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/advancements/recipes/transportation/tag_rail.json b/src/main/generated/data/bclib/advancements/recipes/transportation/tag_rail.json new file mode 100644 index 00000000..129aab2e --- /dev/null +++ b/src/main/generated/data/bclib/advancements/recipes/transportation/tag_rail.json @@ -0,0 +1,46 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_stick": { + "conditions": { + "items": [ + { + "items": [ + "minecraft:stick" + ] + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_tag_c_iron_ingots": { + "conditions": { + "items": [ + { + "tag": "c:iron_ingots" + } + ] + }, + "trigger": "minecraft:inventory_changed" + }, + "has_the_recipe": { + "conditions": { + "recipe": "bclib:tag_rail" + }, + "trigger": "minecraft:recipe_unlocked" + } + }, + "requirements": [ + [ + "has_tag_c_iron_ingots", + "has_stick", + "has_the_recipe" + ] + ], + "rewards": { + "recipes": [ + "bclib:tag_rail" + ] + }, + "sends_telemetry_event": false +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_bucket.json b/src/main/generated/data/bclib/recipes/tag_bucket.json new file mode 100644 index 00000000..a6db4718 --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_bucket.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "I": { + "tag": "c:iron_ingots" + } + }, + "pattern": [ + "I I", + " I " + ], + "result": { + "item": "minecraft:bucket" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_cauldron.json b/src/main/generated/data/bclib/recipes/tag_cauldron.json new file mode 100644 index 00000000..51515c72 --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_cauldron.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "I": { + "tag": "c:iron_ingots" + } + }, + "pattern": [ + "I I", + "I I", + "III" + ], + "result": { + "item": "minecraft:cauldron" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_compass.json b/src/main/generated/data/bclib/recipes/tag_compass.json new file mode 100644 index 00000000..81374c62 --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_compass.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "equipment", + "key": { + "D": { + "item": "minecraft:redstone" + }, + "I": { + "tag": "c:iron_ingots" + } + }, + "pattern": [ + " I ", + "IDI", + " I " + ], + "result": { + "item": "minecraft:compass" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_hopper.json b/src/main/generated/data/bclib/recipes/tag_hopper.json new file mode 100644 index 00000000..8f180ea3 --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_hopper.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "redstone", + "key": { + "C": { + "tag": "c:chest" + }, + "I": { + "tag": "c:iron_ingots" + } + }, + "pattern": [ + "I I", + "ICI", + " I " + ], + "result": { + "item": "minecraft:hopper" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_minecart.json b/src/main/generated/data/bclib/recipes/tag_minecart.json new file mode 100644 index 00000000..fbc841db --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_minecart.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "I": { + "tag": "c:iron_ingots" + } + }, + "pattern": [ + "I I", + "III" + ], + "result": { + "item": "minecraft:minecart" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_piston.json b/src/main/generated/data/bclib/recipes/tag_piston.json new file mode 100644 index 00000000..1b0da91a --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_piston.json @@ -0,0 +1,27 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "redstone", + "key": { + "C": { + "item": "minecraft:cobblestone" + }, + "D": { + "item": "minecraft:redstone" + }, + "I": { + "tag": "c:iron_ingots" + }, + "W": { + "tag": "minecraft:planks" + } + }, + "pattern": [ + "WWW", + "CIC", + "CDC" + ], + "result": { + "item": "minecraft:piston" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_rail.json b/src/main/generated/data/bclib/recipes/tag_rail.json new file mode 100644 index 00000000..42ce70e2 --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_rail.json @@ -0,0 +1,22 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "I": { + "tag": "c:iron_ingots" + }, + "S": { + "item": "minecraft:stick" + } + }, + "pattern": [ + "I I", + "ISI", + "I I" + ], + "result": { + "count": 16, + "item": "minecraft:rail" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_shield.json b/src/main/generated/data/bclib/recipes/tag_shield.json new file mode 100644 index 00000000..de5987d1 --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_shield.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "equipment", + "key": { + "I": { + "tag": "c:iron_ingots" + }, + "W": { + "tag": "minecraft:planks" + } + }, + "pattern": [ + "WIW", + "WWW", + " W " + ], + "result": { + "item": "minecraft:shield" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_shulker_box.json b/src/main/generated/data/bclib/recipes/tag_shulker_box.json new file mode 100644 index 00000000..3bc78df7 --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_shulker_box.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "C": { + "tag": "c:chest" + }, + "S": { + "item": "minecraft:shulker_shell" + } + }, + "pattern": [ + "S", + "C", + "S" + ], + "result": { + "item": "minecraft:shulker_box" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_smith_table.json b/src/main/generated/data/bclib/recipes/tag_smith_table.json new file mode 100644 index 00000000..57885956 --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_smith_table.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "#": { + "tag": "minecraft:planks" + }, + "I": { + "tag": "c:iron_ingots" + } + }, + "pattern": [ + "II", + "##", + "##" + ], + "result": { + "item": "minecraft:smithing_table" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/bclib/recipes/tag_stonecutter.json b/src/main/generated/data/bclib/recipes/tag_stonecutter.json new file mode 100644 index 00000000..d77da183 --- /dev/null +++ b/src/main/generated/data/bclib/recipes/tag_stonecutter.json @@ -0,0 +1,20 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": { + "I": { + "tag": "c:iron_ingots" + }, + "S": { + "item": "minecraft:stone" + } + }, + "pattern": [ + " I ", + "SSS" + ], + "result": { + "item": "minecraft:stonecutter" + }, + "show_notification": true +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/bookshelves.json b/src/main/generated/data/c/tags/blocks/bookshelves.json new file mode 100644 index 00000000..6cdb0dda --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/bookshelves.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:bookshelf" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/chest.json b/src/main/generated/data/c/tags/blocks/chest.json new file mode 100644 index 00000000..67ef7257 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/chest.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:chest" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/dragon_immune.json b/src/main/generated/data/c/tags/blocks/dragon_immune.json new file mode 100644 index 00000000..4cda1417 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/dragon_immune.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#minecraft:dragon_immune" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/end_stones.json b/src/main/generated/data/c/tags/blocks/end_stones.json new file mode 100644 index 00000000..146dcd9a --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/end_stones.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:end_stone" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/grass_soil.json b/src/main/generated/data/c/tags/blocks/grass_soil.json new file mode 100644 index 00000000..bc6f9a69 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/grass_soil.json @@ -0,0 +1,9 @@ +{ + "replace": false, + "values": [ + "#c:terrain", + "#minecraft:dirt", + "#minecraft:logs", + "#minecraft:planks" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/is_obsidian.json b/src/main/generated/data/c/tags/blocks/is_obsidian.json new file mode 100644 index 00000000..0ec23bf6 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/is_obsidian.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "minecraft:crying_obsidian", + "minecraft:obsidian" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/mineable/hammer.json b/src/main/generated/data/c/tags/blocks/mineable/hammer.json new file mode 100644 index 00000000..2c8a729a --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/mineable/hammer.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#minecraft:mineable/pickaxe" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/mycelium.json b/src/main/generated/data/c/tags/blocks/mycelium.json new file mode 100644 index 00000000..ae89f16b --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/mycelium.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "minecraft:mycelium", + "#c:nether_mycelium" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/nether_ores.json b/src/main/generated/data/c/tags/blocks/nether_ores.json new file mode 100644 index 00000000..67ce55b5 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/nether_ores.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "minecraft:nether_gold_ore", + "minecraft:nether_quartz_ore" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/nether_stones.json b/src/main/generated/data/c/tags/blocks/nether_stones.json new file mode 100644 index 00000000..ec467d4c --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/nether_stones.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "#minecraft:base_stone_nether" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/nether_terrain.json b/src/main/generated/data/c/tags/blocks/nether_terrain.json new file mode 100644 index 00000000..79af3f13 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/nether_terrain.json @@ -0,0 +1,16 @@ +{ + "replace": false, + "values": [ + "minecraft:blackstone", + "minecraft:bone_block", + "minecraft:glowstone", + "minecraft:gravel", + "minecraft:magma_block", + "minecraft:red_sand", + "#c:nether_mycelium", + "#c:nether_ores", + "#c:netherrack", + "#c:soul_ground", + "#minecraft:nylium" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/netherrack.json b/src/main/generated/data/c/tags/blocks/netherrack.json new file mode 100644 index 00000000..9b865a29 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/netherrack.json @@ -0,0 +1,10 @@ +{ + "replace": false, + "values": [ + "minecraft:crimson_nylium", + "minecraft:nether_gold_ore", + "minecraft:nether_quartz_ore", + "minecraft:netherrack", + "minecraft:warped_nylium" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/plant.json b/src/main/generated/data/c/tags/blocks/plant.json new file mode 100644 index 00000000..f6442a55 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/plant.json @@ -0,0 +1,56 @@ +{ + "replace": false, + "values": [ + "minecraft:allium", + "minecraft:attached_melon_stem", + "minecraft:attached_pumpkin_stem", + "minecraft:azalea", + "minecraft:azure_bluet", + "minecraft:bamboo", + "minecraft:beetroots", + "minecraft:big_dripleaf", + "minecraft:big_dripleaf_stem", + "minecraft:blue_orchid", + "minecraft:cactus", + "minecraft:carrots", + "minecraft:cave_vines", + "minecraft:cave_vines_plant", + "minecraft:cocoa", + "minecraft:cornflower", + "minecraft:dandelion", + "minecraft:fern", + "minecraft:flowering_azalea", + "minecraft:grass", + "minecraft:large_fern", + "minecraft:lilac", + "minecraft:lily_of_the_valley", + "minecraft:lily_pad", + "minecraft:mangrove_leaves", + "minecraft:melon_stem", + "minecraft:orange_tulip", + "minecraft:oxeye_daisy", + "minecraft:peony", + "minecraft:pink_petals", + "minecraft:pink_tulip", + "minecraft:pitcher_crop", + "minecraft:pitcher_plant", + "minecraft:poppy", + "minecraft:potatoes", + "minecraft:pumpkin_stem", + "minecraft:red_tulip", + "minecraft:rose_bush", + "minecraft:small_dripleaf", + "minecraft:spore_blossom", + "minecraft:sugar_cane", + "minecraft:sunflower", + "minecraft:sweet_berry_bush", + "minecraft:tall_grass", + "minecraft:torchflower", + "minecraft:torchflower_crop", + "minecraft:vine", + "minecraft:wheat", + "minecraft:white_tulip", + "minecraft:wither_rose", + "#c:saplings" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/saplings.json b/src/main/generated/data/c/tags/blocks/saplings.json new file mode 100644 index 00000000..cada37e5 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/saplings.json @@ -0,0 +1,14 @@ +{ + "replace": false, + "values": [ + "minecraft:acacia_sapling", + "minecraft:bamboo_sapling", + "minecraft:birch_sapling", + "minecraft:cherry_sapling", + "minecraft:dark_oak_sapling", + "minecraft:jungle_sapling", + "minecraft:mangrove_propagule", + "minecraft:oak_sapling", + "minecraft:spruce_sapling" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/sculk_like.json b/src/main/generated/data/c/tags/blocks/sculk_like.json new file mode 100644 index 00000000..6459d8c9 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/sculk_like.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:sculk" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/soul_ground.json b/src/main/generated/data/c/tags/blocks/soul_ground.json new file mode 100644 index 00000000..18c9c11a --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/soul_ground.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "minecraft:soul_sand", + "minecraft:soul_soil" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/terrain.json b/src/main/generated/data/c/tags/blocks/terrain.json new file mode 100644 index 00000000..b93447c4 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/terrain.json @@ -0,0 +1,18 @@ +{ + "replace": false, + "values": [ + "minecraft:bone_block", + "minecraft:glowstone", + "minecraft:gravel", + "minecraft:magma_block", + "minecraft:red_sand", + "minecraft:sand", + "minecraft:sculk", + "#c:end_stones", + "#c:mycelium", + "#c:nether_terrain", + "#minecraft:base_stone_overworld", + "#minecraft:dripstone_replaceable_blocks", + "#minecraft:nylium" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/water_plant.json b/src/main/generated/data/c/tags/blocks/water_plant.json new file mode 100644 index 00000000..036f0507 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/water_plant.json @@ -0,0 +1,9 @@ +{ + "replace": false, + "values": [ + "minecraft:kelp", + "minecraft:kelp_plant", + "minecraft:seagrass", + "minecraft:tall_seagrass" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/workstation/farmer.json b/src/main/generated/data/c/tags/blocks/workstation/farmer.json new file mode 100644 index 00000000..3024e79d --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/workstation/farmer.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:composter" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/blocks/workstation/fisherman.json b/src/main/generated/data/c/tags/blocks/workstation/fisherman.json new file mode 100644 index 00000000..ce6563a2 --- /dev/null +++ b/src/main/generated/data/c/tags/blocks/workstation/fisherman.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "#c:barrel", + "#c:wooden_barrels" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/items/chest.json b/src/main/generated/data/c/tags/items/chest.json new file mode 100644 index 00000000..67ef7257 --- /dev/null +++ b/src/main/generated/data/c/tags/items/chest.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:chest" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/items/furnaces.json b/src/main/generated/data/c/tags/items/furnaces.json new file mode 100644 index 00000000..469e57c0 --- /dev/null +++ b/src/main/generated/data/c/tags/items/furnaces.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:furnace" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/items/iron_ingots.json b/src/main/generated/data/c/tags/items/iron_ingots.json new file mode 100644 index 00000000..549adc47 --- /dev/null +++ b/src/main/generated/data/c/tags/items/iron_ingots.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:iron_ingot" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/items/soul_ground.json b/src/main/generated/data/c/tags/items/soul_ground.json new file mode 100644 index 00000000..18c9c11a --- /dev/null +++ b/src/main/generated/data/c/tags/items/soul_ground.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "minecraft:soul_sand", + "minecraft:soul_soil" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/items/water_bottles.json b/src/main/generated/data/c/tags/items/water_bottles.json new file mode 100644 index 00000000..ab70c822 --- /dev/null +++ b/src/main/generated/data/c/tags/items/water_bottles.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:water_bucket" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/worldgen/biome/is_end_barrens.json b/src/main/generated/data/c/tags/worldgen/biome/is_end_barrens.json new file mode 100644 index 00000000..9c0ad26f --- /dev/null +++ b/src/main/generated/data/c/tags/worldgen/biome/is_end_barrens.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:end_barrens" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/worldgen/biome/is_end_center.json b/src/main/generated/data/c/tags/worldgen/biome/is_end_center.json new file mode 100644 index 00000000..6dce86ac --- /dev/null +++ b/src/main/generated/data/c/tags/worldgen/biome/is_end_center.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:the_end" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/worldgen/biome/is_end_highland.json b/src/main/generated/data/c/tags/worldgen/biome/is_end_highland.json new file mode 100644 index 00000000..311a0705 --- /dev/null +++ b/src/main/generated/data/c/tags/worldgen/biome/is_end_highland.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "minecraft:end_highlands", + "minecraft:end_midlands" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/worldgen/biome/is_end_midland.json b/src/main/generated/data/c/tags/worldgen/biome/is_end_midland.json new file mode 100644 index 00000000..b2f0e0c5 --- /dev/null +++ b/src/main/generated/data/c/tags/worldgen/biome/is_end_midland.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:end_midlands" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/c/tags/worldgen/biome/is_small_end_island.json b/src/main/generated/data/c/tags/worldgen/biome/is_small_end_island.json new file mode 100644 index 00000000..c00427aa --- /dev/null +++ b/src/main/generated/data/c/tags/worldgen/biome/is_small_end_island.json @@ -0,0 +1,10 @@ +{ + "replace": false, + "values": [ + "minecraft:small_end_islands", + { + "id": "nullscape:void_barrens", + "required": false + } + ] +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/tags/blocks/mineable/axe.json b/src/main/generated/data/minecraft/tags/blocks/mineable/axe.json new file mode 100644 index 00000000..f663d9dd --- /dev/null +++ b/src/main/generated/data/minecraft/tags/blocks/mineable/axe.json @@ -0,0 +1,9 @@ +{ + "replace": false, + "values": [ + "#c:wooden_barrels", + "#c:wooden_chests", + "#c:wooden_composter", + "#c:workbench" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/tags/blocks/nether_carver_replaceables.json b/src/main/generated/data/minecraft/tags/blocks/nether_carver_replaceables.json new file mode 100644 index 00000000..6d3c38cc --- /dev/null +++ b/src/main/generated/data/minecraft/tags/blocks/nether_carver_replaceables.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "#c:nether_stones", + "#c:netherrack" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/basalt_deltas.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/basalt_deltas.json new file mode 100644 index 00000000..956593b1 --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/basalt_deltas.json @@ -0,0 +1,10 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:basalt_deltas", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 1.0, + "intended_for": "OTHER_NETHER", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/crimson_forest.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/crimson_forest.json new file mode 100644 index 00000000..724df855 --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/crimson_forest.json @@ -0,0 +1,10 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:crimson_forest", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 1.0, + "intended_for": "OTHER_NETHER", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/end_barrens.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/end_barrens.json new file mode 100644 index 00000000..19c80d2f --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/end_barrens.json @@ -0,0 +1,10 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:end_barrens", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 1.0, + "intended_for": "OTHER_END_BARRENS", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/end_highlands.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/end_highlands.json new file mode 100644 index 00000000..5d82aa48 --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/end_highlands.json @@ -0,0 +1,11 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:end_highlands", + "edge": "minecraft:end_midlands", + "edgeSize": 8, + "fogDensity": 1.0, + "genChance": 0.5, + "intended_for": "OTHER_END_LAND", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/end_midlands.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/end_midlands.json new file mode 100644 index 00000000..eebd9660 --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/end_midlands.json @@ -0,0 +1,11 @@ +{ + "type": "bclib:biome", + "parent": "minecraft:end_highlands", + "biome": "minecraft:end_midlands", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 0.5, + "intended_for": "OTHER_END_LAND", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/nether_wastes.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/nether_wastes.json new file mode 100644 index 00000000..71aaebde --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/nether_wastes.json @@ -0,0 +1,10 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:nether_wastes", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 1.0, + "intended_for": "OTHER_NETHER", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/small_end_islands.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/small_end_islands.json new file mode 100644 index 00000000..23fc086d --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/small_end_islands.json @@ -0,0 +1,10 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:small_end_islands", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 1.0, + "intended_for": "OTHER_END_VOID", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/soul_sand_valley.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/soul_sand_valley.json new file mode 100644 index 00000000..6a341bdb --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/soul_sand_valley.json @@ -0,0 +1,10 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:soul_sand_valley", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 1.0, + "intended_for": "OTHER_NETHER", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/the_end.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/the_end.json new file mode 100644 index 00000000..35673a72 --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/the_end.json @@ -0,0 +1,10 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:the_end", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 1.0, + "intended_for": "OTHER_END_CENTER", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/the_void.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/the_void.json new file mode 100644 index 00000000..4c7fca75 --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/the_void.json @@ -0,0 +1,10 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:the_void", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 1.0, + "intended_for": "NONE", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/minecraft/worldgen/betterx/biome/warped_forest.json b/src/main/generated/data/minecraft/worldgen/betterx/biome/warped_forest.json new file mode 100644 index 00000000..7d9f1fe6 --- /dev/null +++ b/src/main/generated/data/minecraft/worldgen/betterx/biome/warped_forest.json @@ -0,0 +1,10 @@ +{ + "type": "bclib:biome", + "biome": "minecraft:warped_forest", + "edgeSize": 0, + "fogDensity": 1.0, + "genChance": 1.0, + "intended_for": "OTHER_NETHER", + "terrainHeight": 0.1, + "vertical": false +} \ No newline at end of file diff --git a/src/main/generated/data/worlds_together/tags/blocks/bonemeal/source/netherrack.json b/src/main/generated/data/worlds_together/tags/blocks/bonemeal/source/netherrack.json new file mode 100644 index 00000000..7ce3a5a2 --- /dev/null +++ b/src/main/generated/data/worlds_together/tags/blocks/bonemeal/source/netherrack.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "minecraft:crimson_nylium", + "minecraft:warped_nylium" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/worlds_together/tags/blocks/bonemeal/target/end_stone.json b/src/main/generated/data/worlds_together/tags/blocks/bonemeal/target/end_stone.json new file mode 100644 index 00000000..146dcd9a --- /dev/null +++ b/src/main/generated/data/worlds_together/tags/blocks/bonemeal/target/end_stone.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:end_stone" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/worlds_together/tags/blocks/bonemeal/target/netherrack.json b/src/main/generated/data/worlds_together/tags/blocks/bonemeal/target/netherrack.json new file mode 100644 index 00000000..d54c65bb --- /dev/null +++ b/src/main/generated/data/worlds_together/tags/blocks/bonemeal/target/netherrack.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:netherrack" + ] +} \ No newline at end of file diff --git a/src/main/generated/data/worlds_together/tags/blocks/bonemeal/target/obsidian.json b/src/main/generated/data/worlds_together/tags/blocks/bonemeal/target/obsidian.json new file mode 100644 index 00000000..03a3719e --- /dev/null +++ b/src/main/generated/data/worlds_together/tags/blocks/bonemeal/target/obsidian.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:obsidian" + ] +} \ No newline at end of file diff --git a/src/main/java/org/anti_ad/mc/ipn/api/IPNIgnore.java b/src/main/java/org/anti_ad/mc/ipn/api/IPNIgnore.java new file mode 100644 index 00000000..fdf56631 --- /dev/null +++ b/src/main/java/org/anti_ad/mc/ipn/api/IPNIgnore.java @@ -0,0 +1,12 @@ +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 { +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/BCLib.java b/src/main/java/org/betterx/bclib/BCLib.java new file mode 100644 index 00000000..f8471738 --- /dev/null +++ b/src/main/java/org/betterx/bclib/BCLib.java @@ -0,0 +1,189 @@ +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.levelgen.LevelGenEvents; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.api.v2.levelgen.structures.BCLStructurePoolElementTypes; +import org.betterx.bclib.api.v2.levelgen.structures.TemplatePiece; +import org.betterx.bclib.api.v2.levelgen.surface.rules.Conditions; +import org.betterx.bclib.api.v2.poi.PoiManager; +import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates; +import org.betterx.bclib.api.v3.levelgen.features.placement.PlacementModifiers; +import org.betterx.bclib.api.v3.tag.BCLBlockTags; +import org.betterx.bclib.blocks.signs.BaseHangingSignBlock; +import org.betterx.bclib.blocks.signs.BaseSignBlock; +import org.betterx.bclib.commands.CommandRegistry; +import org.betterx.bclib.commands.arguments.BCLibArguments; +import org.betterx.bclib.complexmaterials.BCLWoodTypeWrapper; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.config.PathConfig; +import org.betterx.bclib.networking.VersionChecker; +import org.betterx.bclib.recipes.AlloyingRecipe; +import org.betterx.bclib.recipes.AnvilRecipe; +import org.betterx.bclib.recipes.CraftingRecipes; +import org.betterx.bclib.registry.BaseBlockEntities; +import org.betterx.bclib.registry.BaseRegistry; +import org.betterx.bclib.registry.BlockRegistry; +import org.betterx.datagen.bclib.tests.TestStructure; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.util.Logger; +import org.betterx.worlds.together.world.WorldConfig; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.material.MapColor; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; +import net.fabricmc.loader.api.FabricLoader; + +import java.util.List; + +public class BCLib implements ModInitializer { + public static final String MOD_ID = "bclib"; + public static final Logger LOGGER = new Logger(MOD_ID); + + public static final boolean RUNS_NULLSCAPE = FabricLoader.getInstance() + .getModContainer("nullscape") + .isPresent(); + public static final boolean ADD_TEST_DATA = false; + + private void onDatagen() { + + } + + + @Override + public void onInitialize() { + WorldsTogether.onInitialize(); + BCLibArguments.register(); + LevelGenEvents.register(); + BlockPredicates.ensureStaticInitialization(); + BCLBiomeRegistry.register(); + BaseRegistry.register(); + BaseBlockEntities.register(); + CraftingRecipes.init(); + BCLStructurePoolElementTypes.ensureStaticallyLoaded(); + WorldConfig.registerModCache(MOD_ID); + DataExchangeAPI.registerMod(MOD_ID); + AnvilRecipe.register(); + AlloyingRecipe.register(); + Conditions.registerAll(); + CommandRegistry.register(); + BCLBlockTags.ensureStaticallyLoaded(); + PoiManager.registerAll(); + if (isDevEnvironment()) { + TestStructure.registerBase(); + } + + if (ADD_TEST_DATA) { + testObjects(); + } + + DataExchangeAPI.registerDescriptors(List.of( + HelloClient.DESCRIPTOR, + HelloServer.DESCRIPTOR, + RequestFiles.DESCRIPTOR, + SendFiles.DESCRIPTOR, + Chunker.DESCRIPTOR + ) + ); + + BCLibPatch.register(); + TemplatePiece.ensureStaticInitialization(); + PlacementModifiers.ensureStaticInitialization(); + Configs.save(); + + WorldsTogether.FORCE_SERVER_TO_BETTERX_PRESET = Configs.SERVER_CONFIG.forceBetterXPreset(); + VersionChecker.registerMod(MOD_ID); + + if (isDatagen()) { + onDatagen(); + } + } + + public static boolean isDevEnvironment() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + public static boolean isDatagen() { + return System.getProperty("fabric-api.datagen") != null; + } + + public static boolean isClient() { + return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT; + } + + public static ResourceLocation makeID(String path) { + return new ResourceLocation(MOD_ID, path); + } + + public static BCLWoodTypeWrapper TEST_WOOD; + public static BaseSignBlock TEST_SIGN = null; + public static BaseHangingSignBlock TEST_HANGING_SIGN = null; + + private static void testObjects() { + var bockReg = new BlockRegistry(new PathConfig(MOD_ID, "test")); + bockReg.register( + makeID("test_sign"), + TEST_SIGN + ); + bockReg.registerBlockOnly( + makeID("test_wall_sign"), + TEST_SIGN.getWallSignBlock() + ); + bockReg.register( + makeID("test_hanging_sign"), + TEST_HANGING_SIGN + ); + bockReg.registerBlockOnly( + makeID("test_wall_hanging_sign"), + TEST_HANGING_SIGN.getWallSignBlock() + ); + } + + static { + if (ADD_TEST_DATA) { + TEST_WOOD = BCLWoodTypeWrapper.create(makeID("test_wood")).setColor(MapColor.COLOR_MAGENTA).build(); + TEST_SIGN = new BaseSignBlock.Wood(TEST_WOOD); + TEST_HANGING_SIGN = new BaseHangingSignBlock.Wood(TEST_WOOD); + + + final ResourceKey TAB_TEST_KEY = ResourceKey.create( + Registries.CREATIVE_MODE_TAB, + makeID("test_tab") + ); + + CreativeModeTab.Builder builder = FabricItemGroup + .builder(); + builder.title(Component.translatable("itemGroup.bclib.test")); + builder.icon(() -> new ItemStack(Items.BARRIER)); + builder.displayItems((itemDisplayParameters, output) -> { + + var list = List.of(TEST_SIGN.asItem(), TEST_HANGING_SIGN.asItem()) + .stream().map(b -> new ItemStack(b, 1)).toList(); + + output.acceptAll(list); + }); + final CreativeModeTab TAB_TEST = builder.build(); + ; + + Registry.register( + BuiltInRegistries.CREATIVE_MODE_TAB, + TAB_TEST_KEY, + TAB_TEST + ); + + + } + } +} diff --git a/src/main/java/org/betterx/bclib/BCLibPatch.java b/src/main/java/org/betterx/bclib/BCLibPatch.java new file mode 100644 index 00000000..3f113dba --- /dev/null +++ b/src/main/java/org/betterx/bclib/BCLibPatch.java @@ -0,0 +1,27 @@ +package org.betterx.bclib; + +import org.betterx.bclib.api.v2.datafixer.DataFixerAPI; +import org.betterx.bclib.api.v2.datafixer.Patch; + +import java.util.Map; + +public final class BCLibPatch { + public static void register() { + DataFixerAPI.registerPatch(SignPatch::new); + } +} + +class SignPatch extends Patch { + public SignPatch() { + super(BCLib.MOD_ID, "3.0.11"); + } + + @Override + public Map getIDReplacements() { + return Map.ofEntries( + Map.entry("bclib:sign", "minecraft:sign") + ); + } +} + + diff --git a/src/main/java/org/betterx/bclib/api/v2/ComposterAPI.java b/src/main/java/org/betterx/bclib/api/v2/ComposterAPI.java new file mode 100644 index 00000000..96a4f5c0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/ComposterAPI.java @@ -0,0 +1,23 @@ +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; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/DiggerItemSpeed.java b/src/main/java/org/betterx/bclib/api/v2/DiggerItemSpeed.java new file mode 100644 index 00000000..c469d76c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/DiggerItemSpeed.java @@ -0,0 +1,35 @@ +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 modifiers = new LinkedList<>(); + + @FunctionalInterface + public interface SpeedModifier { + Optional calculateSpeed(ItemStack stack, BlockState state, float initialSpeed, float currentSpeed); + } + + public static void addModifier(SpeedModifier mod) { + modifiers.add(mod); + } + + public static Optional getModifiedSpeed(ItemStack stack, BlockState state, float initialSpeed) { + float currentSpeed = initialSpeed; + Optional speed = Optional.empty(); + for (SpeedModifier mod : modifiers) { + Optional res = mod.calculateSpeed(stack, state, initialSpeed, currentSpeed); + if (res.isPresent()) { + currentSpeed = res.get(); + speed = res; + } + } + + return speed; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/LifeCycleAPI.java b/src/main/java/org/betterx/bclib/api/v2/LifeCycleAPI.java new file mode 100644 index 00000000..5a32bdbd --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/LifeCycleAPI.java @@ -0,0 +1,146 @@ +package org.betterx.bclib.api.v2; + +import org.betterx.bclib.api.v2.datafixer.DataFixerAPI; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.level.CustomSpawner; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.ServerLevelData; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * provides some lifetime hooks for a Minecraft instance + */ +public class LifeCycleAPI { + private final static List onLoadLevelBiomes = new ArrayList<>(2); + private final static List onLoadLevel = new ArrayList<>(2); + private final static List 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 resourceKey, + ChunkProgressListener chunkProgressListener, + boolean bl, + long l, + List 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 biomeRegistry = world.registryAccess().registryOrThrow(Registries.BIOME); + onLoadLevelBiomes.forEach(c -> c.onLoad(world, seed, biomeRegistry)); + } + + /** + * A callback function that is used for each new ServerLevel instance + */ + public interface BeforeLevelLoadCall { + void beforeLoad(); + } + + /** + * A callback function that is used for each new ServerLevel instance + */ + public interface LevelLoadBiomesCall { + void onLoad(ServerLevel world, long seed, Registry 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 resourceKey, + ChunkProgressListener chunkProgressListener, + boolean bl, + long l, + List list, + boolean bl2 + ); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/ModIntegrationAPI.java b/src/main/java/org/betterx/bclib/api/v2/ModIntegrationAPI.java new file mode 100644 index 00000000..a4982abe --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/ModIntegrationAPI.java @@ -0,0 +1,49 @@ +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 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 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; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java b/src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java new file mode 100644 index 00000000..72e506da --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/PostInitAPI.java @@ -0,0 +1,267 @@ +package org.betterx.bclib.api.v2; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI; +import org.betterx.bclib.behaviours.interfaces.*; +import org.betterx.bclib.blocks.BaseBarrelBlock; +import org.betterx.bclib.blocks.BaseChestBlock; +import org.betterx.bclib.blocks.BaseFurnaceBlock; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.client.render.BaseChestBlockEntityRenderer; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.interfaces.Fuel; +import org.betterx.bclib.interfaces.PostInitable; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.bclib.interfaces.tools.*; +import org.betterx.bclib.items.tool.*; +import org.betterx.bclib.networking.VersionChecker; +import org.betterx.bclib.registry.BaseBlockEntities; +import org.betterx.worlds.together.tag.v3.*; + +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.dispenser.ShearsDispenseItemBehavior; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.DispenserBlock; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; +import net.fabricmc.fabric.api.registry.FuelRegistry; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.function.Consumer; + +public class PostInitAPI { + private static List> postInitFunctions = Lists.newArrayList(); + private static List> blockTags = Lists.newArrayList(); + private static List> 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 function) { + postInitFunctions.add(function); + } + + /** + * Called in proper BCLib entry points, for internal usage only. + * + * @param isClient {@code boolean}, {@code true} for client, {@code false} for server. + */ + public static void postInit(boolean isClient) { + BuiltInRegistries.BLOCK.forEach(block -> { + processBlockCommon(block); + if (isClient) { + processBlockClient(block); + } + }); + + + BuiltInRegistries.ITEM.forEach(item -> { + processItemCommon(item); + }); + + if (postInitFunctions != null) { + postInitFunctions.forEach(function -> function.accept(isClient)); + postInitFunctions = null; + } + blockTags = null; + itemTags = null; + InternalBiomeAPI.loadFabricAPIBiomes(); + Configs.BIOMES_CONFIG.saveChanges(); + + VersionChecker.startCheck(isClient); + } + + @Environment(EnvType.CLIENT) + private static void processBlockClient(Block block) { + if (block instanceof RenderLayerProvider) { + BCLRenderLayer layer = ((RenderLayerProvider) block).getRenderLayer(); + if (layer == BCLRenderLayer.CUTOUT) BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.cutout()); + else if (layer == BCLRenderLayer.TRANSLUCENT) + BlockRenderLayerMap.INSTANCE.putBlock(block, RenderType.translucent()); + } + if (block instanceof BaseChestBlock) { + BaseChestBlockEntityRenderer.registerRenderLayer(block); + } + } + + private static void processItemCommon(Item item) { + if (item instanceof TagProvider provider) { + try { + provider.addTags(null, itemTags); + } catch (NullPointerException ex) { + BCLib.LOGGER.error(item + " probably tried to access blockTags.", ex); + } + itemTags.forEach(tag -> TagManager.ITEMS.add(tag, item)); + itemTags.clear(); + } + + if (item instanceof BaseShovelItem) { + TagManager.ITEMS.add(item, ToolTags.FABRIC_SHOVELS, ItemTags.SHOVELS); + } else if (item instanceof BaseSwordItem) { + TagManager.ITEMS.add(item, ToolTags.FABRIC_SWORDS, ItemTags.SWORDS); + } else if (item instanceof BasePickaxeItem) { + TagManager.ITEMS.add(item, ToolTags.FABRIC_PICKAXES, ItemTags.PICKAXES); + } else if (item instanceof BaseAxeItem) { + TagManager.ITEMS.add(item, ToolTags.FABRIC_AXES, ItemTags.AXES); + } else if (item instanceof BaseHoeItem) { + TagManager.ITEMS.add(item, ToolTags.FABRIC_HOES, ItemTags.HOES); + } else if (item instanceof BaseShearsItem) { + TagManager.ITEMS.add(item, ToolTags.FABRIC_SHEARS, CommonItemTags.SHEARS); + DispenserBlock.registerBehavior(item.asItem(), new ShearsDispenseItemBehavior()); + } + } + + private static void processBlockCommon(Block block) { + //TODO: Some of this only needs to run on DataGen, add a special PostDataGenAPI for that + final Item item = block.asItem(); + if (block instanceof PostInitable) { + ((PostInitable) block).postInit(); + } + + if (block instanceof TagProvider) { + ((TagProvider) block).addTags(blockTags, itemTags); + blockTags.forEach(tag -> TagManager.BLOCKS.add(tag, block)); + if (item != null && item != Items.AIR) + itemTags.forEach(tag -> TagManager.ITEMS.add(tag, item)); + blockTags.clear(); + itemTags.clear(); + } + + if (block instanceof BaseChestBlock) { + BaseBlockEntities.CHEST.registerBlock(block); + } else if (block instanceof BaseBarrelBlock) { + BaseBlockEntities.BARREL.registerBlock(block); + } else if (block instanceof BaseFurnaceBlock) { + BaseBlockEntities.FURNACE.registerBlock(block); + } + if (!(block instanceof PreventMineableAdd)) { + if (block instanceof AddMineableShears) { + TagManager.BLOCKS.add(block, MineableTags.SHEARS); + } + if (block instanceof AddMineableAxe) { + if (!TagManager.BLOCKS.contains(BlockTags.WOODEN_DOORS, block) + && !TagManager.BLOCKS.contains(BlockTags.WOODEN_BUTTONS, block) + && !TagManager.BLOCKS.contains(BlockTags.WOODEN_SLABS, block) + && !TagManager.BLOCKS.contains(BlockTags.WOODEN_FENCES, block) + && !TagManager.BLOCKS.contains(BlockTags.WOODEN_STAIRS, block) + && !TagManager.BLOCKS.contains(BlockTags.WOODEN_PRESSURE_PLATES, block) + && !TagManager.BLOCKS.contains(BlockTags.WOODEN_TRAPDOORS, block) + && !TagManager.BLOCKS.contains(CommonBlockTags.WOODEN_BARREL, block) + && !TagManager.BLOCKS.contains(CommonBlockTags.WOODEN_CHEST, block) + && !TagManager.BLOCKS.contains(CommonBlockTags.WOODEN_COMPOSTER, block) + && !TagManager.BLOCKS.contains(CommonBlockTags.WORKBENCHES, block) + && !TagManager.BLOCKS.contains(BlockTags.SIGNS, block) + && !TagManager.BLOCKS.contains(BlockTags.PLANKS, block) + && !TagManager.BLOCKS.contains(BlockTags.LOGS, block) + && !TagManager.BLOCKS.contains(BlockTags.FENCE_GATES, block) + && !TagManager.BLOCKS.contains(BlockTags.ALL_HANGING_SIGNS, block) + && !TagManager.BLOCKS.contains(CommonBlockTags.WORKBENCHES, block) + && !TagManager.BLOCKS.contains(CommonBlockTags.BOOKSHELVES, block) + ) { + TagManager.BLOCKS.add(block, MineableTags.AXE); + } + } + if (block instanceof AddMineablePickaxe) { + TagManager.BLOCKS.add(block, MineableTags.PICKAXE); + } + if (block instanceof AddMineableShovel) { + TagManager.BLOCKS.add(block, MineableTags.SHOVEL); + } + if (block instanceof AddMineableHoe) { + TagManager.BLOCKS.add(block, MineableTags.HOE); + } + if (block instanceof AddMineableSword) { + TagManager.BLOCKS.add(block, MineableTags.SWORD); + } + if (block instanceof AddMineableHammer) { + TagManager.BLOCKS.add(block, MineableTags.HAMMER); + } + } + + if (block instanceof BehaviourCompostable c) { + if (item != null && item != Items.AIR) { + TagManager.ITEMS.add(block, CommonItemTags.COMPOSTABLE); + ComposterAPI.allowCompost(c.compostingChance(), item); + } else if (BCLib.isDatagen() && Configs.MAIN_CONFIG.verboseLogging()) { + BCLib.LOGGER.warning("Block " + block + " has compostable behaviour but no item!"); + } + } + + if (block instanceof BehaviourWaterPlantLike) { + TagManager.BLOCKS.add(block, CommonBlockTags.WATER_PLANT); + } + + if (block instanceof BehaviourPlant || block instanceof BehaviourShearablePlant) { + TagManager.BLOCKS.add(block, CommonBlockTags.PLANT); + } + + + if (block instanceof BehaviourSeedLike) { + TagManager.BLOCKS.add(block, CommonBlockTags.SEEDS); + if (item != null && item != Items.AIR) { + TagManager.ITEMS.add(block, CommonItemTags.SEEDS); + } + } + + if (block instanceof BehaviourSaplingLike) { + TagManager.BLOCKS.add(block, CommonBlockTags.SAPLINGS, BlockTags.SAPLINGS); + if (item != null && item != Items.AIR) { + TagManager.ITEMS.add(block, CommonItemTags.SAPLINGS, ItemTags.SAPLINGS); + } + } + + if (block instanceof BehaviourClimable c) { + TagManager.BLOCKS.add(block, BlockTags.CLIMBABLE); + } + + if (block instanceof BehaviourLeaves) { + TagManager.BLOCKS.add(block, BlockTags.LEAVES, CommonBlockTags.LEAVES); + if (item != null && item != Items.AIR) + TagManager.ITEMS.add(item, ItemTags.LEAVES, CommonItemTags.LEAVES); + } + + if (block instanceof BehaviourImmobile) { + TagManager.BLOCKS.add(block, CommonBlockTags.IMMOBILE); + } + + if (block instanceof BehaviourObsidian) { + TagManager.BLOCKS.add(block, CommonBlockTags.IS_OBSIDIAN); + } + + if (block instanceof BehaviourPortalFrame) { + TagManager.BLOCKS.add(block, CommonBlockTags.NETHER_PORTAL_FRAME); + } + + if (block instanceof BehaviourOre) { + TagManager.BLOCKS.add(block, CommonBlockTags.ORES); + } + + if (block instanceof Fuel fl) { + FuelRegistry.INSTANCE.add(block, fl.getFuelTime()); + } + + if (BCLib.isDatagen()) { + final ResourceLocation location = BuiltInRegistries.BLOCK.getKey(block); + if (!location.getNamespace().equals("minecraft")) { + if (!(block instanceof HasMinableBehaviour) && block.defaultBlockState() + .requiresCorrectToolForDrops()) { + BCLib.LOGGER.error("Block " + block + "(" + block.getClass() + ")" + " has no mineable behaviour!"); + } + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/ShovelAPI.java b/src/main/java/org/betterx/bclib/api/v2/ShovelAPI.java new file mode 100644 index 00000000..bdbc0351 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/ShovelAPI.java @@ -0,0 +1,22 @@ +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 map = ShovelItemAccessor.bclib_getFlattenables(); + map.put(target, convert); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementBuilderElements.java b/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementBuilderElements.java new file mode 100644 index 00000000..e692ad77 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementBuilderElements.java @@ -0,0 +1,43 @@ +package org.betterx.bclib.api.v2.advancement; + +import net.minecraft.advancements.DisplayInfo; +import net.minecraft.advancements.FrameType; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; + +import org.jetbrains.annotations.Nullable; + +class Display { + ItemStack icon; + Component title; + net.minecraft.network.chat.Component description; + @Nullable ResourceLocation background; + FrameType frame; + boolean showToast; + boolean announceChat; + boolean hidden; + + Display() { + } + + Display reset() { + this.icon = null; + this.title = null; + this.description = null; + frame = FrameType.TASK; + background = null; + showToast = true; + announceChat = true; + hidden = false; + return this; + } + + DisplayInfo build() { + return new DisplayInfo( + icon, title, description, + background, frame, showToast, announceChat, hidden + ); + } +} + diff --git a/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementManager.java b/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementManager.java new file mode 100644 index 00000000..44f09c76 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/advancement/AdvancementManager.java @@ -0,0 +1,509 @@ +package org.betterx.bclib.api.v2.advancement; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.structures.BCLStructure; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.set.wood.WoodSlots; +import org.betterx.bclib.items.complex.EquipmentSet; +import org.betterx.bclib.items.complex.EquipmentSlot; + +import net.minecraft.advancements.*; +import net.minecraft.advancements.critereon.*; +import net.minecraft.client.Minecraft; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.Container; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.structure.Structure; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class AdvancementManager { + static class OrderedBuilder extends Advancement.Builder { + OrderedBuilder() { + super(false); + } + } + + private static final Map ADVANCEMENTS = new LinkedHashMap<>(); + + public static void register(ResourceLocation id, Advancement.Builder builder) { + ADVANCEMENTS.put(id, builder); + } + + public static void registerAllDataGen(List namespaces, Consumer consumer) { + final Advancement ROOT_RECIPE = Advancement.Builder.advancement() + .addCriterion( + "impossible", + new ImpossibleTrigger.TriggerInstance() + ) + .build(RecipeBuilder.ROOT_RECIPE_ADVANCEMENT); + final Map BUILT = new HashMap<>(); + + for (var entry : ADVANCEMENTS.entrySet()) { + final ResourceLocation loc = entry.getKey(); + if (namespaces == null || namespaces.contains(loc.getNamespace())) { + final Advancement.Builder builder = entry.getValue(); + if (builder.canBuild(locToAdd -> { + if (locToAdd.equals(RecipeBuilder.ROOT_RECIPE_ADVANCEMENT)) return ROOT_RECIPE; + return BUILT.get(locToAdd); + })) { + final Advancement adv = builder.build(loc); + BUILT.put(loc, adv); + consumer.accept(adv); + } else { + BCLib.LOGGER.error("Unable to build Advancement " + loc); + } + } + } + } + + + public static class RewardsBuilder { + private final Builder calle; + private final AdvancementRewards.Builder builder = new AdvancementRewards.Builder(); + + private RewardsBuilder(Builder calle) { + this.calle = calle; + } + + public RewardsBuilder addExperience(int i) { + builder.addExperience(i); + return this; + } + + + public RewardsBuilder addLootTable(ResourceLocation resourceLocation) { + builder.addLootTable(resourceLocation); + return this; + } + + + public RewardsBuilder addRecipe(ResourceLocation resourceLocation) { + builder.addRecipe(resourceLocation); + return this; + } + + + public RewardsBuilder runs(ResourceLocation resourceLocation) { + builder.runs(resourceLocation); + return this; + } + + public Builder endReward() { + calle.rewards(builder.build()); + return calle; + } + } + + public enum AdvancementType { + REGULAR, + RECIPE_DECORATIONS, + RECIPE_TOOL + } + + public static class Builder { + private static final ThreadLocal DISPLAY_BUILDER = ThreadLocal.withInitial(DisplayBuilder::new); + private static final ResourceLocation RECIPES_ROOT = RecipeBuilder.ROOT_RECIPE_ADVANCEMENT; + + private final Advancement.Builder builder = new OrderedBuilder(); + private final ResourceLocation id; + private final AdvancementType type; + private boolean canBuild = true; + + private Builder(ResourceLocation id, AdvancementType type) { + ResourceLocation ID; + if (type == AdvancementType.RECIPE_DECORATIONS) { + ID = new ResourceLocation(id.getNamespace(), "recipes/decorations/" + id.getPath()); + builder.parent(RECIPES_ROOT); + } else if (type == AdvancementType.RECIPE_TOOL) { + ID = new ResourceLocation(id.getNamespace(), "recipes/tools/" + id.getPath()); + builder.parent(RECIPES_ROOT); + } else { + ID = id; + } + this.id = ID; + this.type = type; + } + + public static Builder createEmptyCopy(Builder builder) { + return new Builder(builder.id, builder.type); + } + + public static Builder create(ResourceLocation id) { + return new Builder(id, AdvancementType.REGULAR); + } + + public static Builder create(ResourceLocation id, AdvancementType type) { + return new Builder(id, type); + } + + public static Builder create(Item icon) { + return create(icon, AdvancementType.REGULAR); + } + + public static Builder create(ItemStack icon) { + return create(icon, AdvancementType.REGULAR); + } + + public static Builder create(ItemLike icon, AdvancementType type) { + return create(new ItemStack(icon), type); + } + + public static Builder create(ItemStack icon, AdvancementType type) { + return create(icon, type, (displayBuilder) -> { + }); + } + + public static Builder create(Item icon, AdvancementType type, Consumer displayAdapter) { + return create(new ItemStack(icon), type, displayAdapter); + } + + public static Builder create( + ItemStack icon, + AdvancementType type, + Consumer displayAdapter + ) { + var id = BuiltInRegistries.ITEM.getKey(icon.getItem()); + boolean canBuild = true; + if (id == null || icon.is(Items.AIR)) { + canBuild = false; + id = BuiltInRegistries.ITEM.getDefaultKey(); + } + + String baseName = "advancements." + id.getNamespace() + "." + id.getPath() + "."; + Builder b = new Builder(id, type); + var displayBuilder = b.startDisplay( + icon, + Component.translatable(baseName + "title"), + Component.translatable(baseName + "description") + ); + if (displayAdapter != null) displayAdapter.accept(displayBuilder); + b = displayBuilder.endDisplay(); + b.canBuild = canBuild; + return b; + } + + public static > Builder createRecipe( + T recipe, + AdvancementType type + ) { + Item item = recipe.getResultItem(Minecraft.getInstance().level.registryAccess()).getItem(); + return create(item, type, displayBuilder -> displayBuilder.hideToast().hideFromChat()) + //.awardRecipe(item) + .addRecipeUnlockCriterion("has_the_recipe", recipe) + .startReward() + .addRecipe(recipe.getId()) + .endReward() + .requirements(RequirementsStrategy.OR); + } + + public Builder parent(Advancement advancement) { + builder.parent(advancement); + return this; + } + + public Builder parent(ResourceLocation resourceLocation) { + builder.parent(resourceLocation); + return this; + } + + public DisplayBuilder startDisplay(ItemLike icon) { + String baseName = "advancements." + id.getNamespace() + "." + id.getPath() + "."; + return startDisplay( + icon, + Component.translatable(baseName + "title"), + Component.translatable(baseName + "description") + ); + } + + public DisplayBuilder startDisplay( + ItemLike icon, + Component title, + Component description + ) { + return startDisplay(new ItemStack(icon), title, description); + } + + public DisplayBuilder startDisplay( + ItemStack icon, + Component title, + Component description + ) { + if (icon == null) { + canBuild = false; + } else { + var id = BuiltInRegistries.ITEM.getKey(icon.getItem()); + if (id == null) { + canBuild = false; + } + } + DisplayBuilder dp = DISPLAY_BUILDER.get().reset(this); + return dp.icon(icon).title(title).description(description); + } + + Builder display(DisplayInfo displayInfo) { + builder.display(displayInfo); + return this; + } + + public Builder awardRecipe(ItemLike... items) { + RewardsBuilder rewardBuilder = startReward(); + for (ItemLike item : items) { + ResourceLocation id = BuiltInRegistries.ITEM.getKey(item.asItem()); + if (id == null) continue; + rewardBuilder.addRecipe(id); + } + return rewardBuilder.endReward(); + } + + public RewardsBuilder startReward() { + return new RewardsBuilder(this); + } + + public Builder rewards(AdvancementRewards advancementRewards) { + builder.rewards(advancementRewards); + return this; + } + + public Builder rewardXP(int xp) { + return rewards(AdvancementRewards.Builder.experience(500).build()); + } + + public Builder addCriterion(String string, CriterionTriggerInstance criterionTriggerInstance) { + builder.addCriterion(string, new Criterion(criterionTriggerInstance)); + return this; + } + + public Builder addCriterion(String string, Criterion criterion) { + builder.addCriterion(string, criterion); + return this; + } + + public Builder addAtStructureCriterion(String name, BCLStructure structure) { + return addAtStructureCriterion(name, structure.structureKey); + } + + public Builder addAtStructureCriterion(String name, ResourceKey structure) { + return addCriterion( + name, + PlayerTrigger + .TriggerInstance + .located( + LocationPredicate.inStructure(structure) + ) + ); + } + + public > Builder addRecipeUnlockCriterion(String name, T recipe) { + return addCriterion( + name, + RecipeUnlockedTrigger.unlocked(recipe.getId()) + ); + } + + public Builder addInventoryChangedCriterion(String name, ItemLike... items) { + return addCriterion( + name, + InventoryChangeTrigger.TriggerInstance.hasItems(items) + ); + } + + public Builder addInventoryChangedAnyCriterion(String name, ItemLike... items) { + InventoryChangeTrigger.TriggerInstance trigger = + InventoryChangeTrigger.TriggerInstance.hasItems(new ItemPredicate( + null, + Arrays.stream(items).map(i -> i.asItem()).collect(Collectors.toSet()), + MinMaxBounds.Ints.ANY, + MinMaxBounds.Ints.ANY, + EnchantmentPredicate.NONE, + EnchantmentPredicate.NONE, + null, + NbtPredicate.ANY + )); + return addCriterion(name, trigger); + } + + public Builder addInventoryChangedCriterion(String name, TagKey tag) { + return addCriterion( + name, + InventoryChangeTrigger.TriggerInstance.hasItems(new ItemPredicate( + tag, + null, + MinMaxBounds.Ints.ANY, + MinMaxBounds.Ints.ANY, + EnchantmentPredicate.NONE, + EnchantmentPredicate.NONE, + null, + NbtPredicate.ANY + )) + ); + } + + // + + public Builder addEquipmentSetSlotCriterion(EquipmentSet set, EquipmentSlot slot) { + return addInventoryChangedCriterion( + set.baseName + "_" + slot, + set.getSlot(slot) + ); + } + + public Builder addArmorSetCriterion(EquipmentSet set) { + return addEquipmentSetSlotCriterion(set, EquipmentSet.HELMET_SLOT) + .addEquipmentSetSlotCriterion(set, EquipmentSet.CHESTPLATE_SLOT) + .addEquipmentSetSlotCriterion(set, EquipmentSet.LEGGINGS_SLOT) + .addEquipmentSetSlotCriterion(set, EquipmentSet.BOOTS_SLOT); + } + + public Builder addToolSetCriterion(EquipmentSet set) { + return addEquipmentSetSlotCriterion(set, EquipmentSet.PICKAXE_SLOT) + .addEquipmentSetSlotCriterion(set, EquipmentSet.AXE_SLOT) + .addEquipmentSetSlotCriterion(set, EquipmentSet.SHOVEL_SLOT) + .addEquipmentSetSlotCriterion(set, EquipmentSet.SWORD_SLOT) + .addEquipmentSetSlotCriterion(set, EquipmentSet.HOE_SLOT); + } + + public Builder addWoodCriterion(WoodenComplexMaterial mat) { + return addInventoryChangedAnyCriterion( + "got_" + mat.getBaseName(), + mat.getBlock(WoodSlots.LOG), + mat.getBlock(WoodSlots.BARK), + mat.getBlock(WoodSlots.PLANKS) + ); + } + + public Builder addVisitBiomesCriterion(List> list) { + for (ResourceKey resourceKey : list) { + addCriterion( + resourceKey.location().toString(), + PlayerTrigger.TriggerInstance.located(LocationPredicate.inBiome(resourceKey)) + ); + } + return this; + } + + public Builder requirements(RequirementsStrategy requirementsStrategy) { + builder.requirements(requirementsStrategy); + return this; + } + + public Builder requirements(String[][] strings) { + builder.requirements(strings); + return this; + } + + public Builder printDebugJson() { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + BCLib.LOGGER.info(gson.toJson(builder.serializeToJson())); + return this; + } + + public ResourceLocation build() { + AdvancementManager.register(id, this.builder); + return this.id; + } + } + + public static class DisplayBuilder { + Builder base; + final Display display = new Display(); + + DisplayBuilder reset(Builder base) { + this.base = base; + this.display.reset(); + return this; + } + + public DisplayBuilder background(ResourceLocation value) { + display.background = value; + return this; + } + + public DisplayBuilder icon(ItemLike value) { + display.icon = new ItemStack(value); + return this; + } + + public DisplayBuilder icon(ItemStack value) { + display.icon = value; + return this; + } + + public DisplayBuilder title(Component value) { + display.title = value; + return this; + } + + public DisplayBuilder description(Component value) { + display.description = value; + return this; + } + + public DisplayBuilder showToast() { + display.showToast = true; + return this; + } + + public DisplayBuilder hideToast() { + display.showToast = false; + return this; + } + + public DisplayBuilder hidden() { + display.hidden = true; + return this; + } + + public DisplayBuilder visible() { + display.hidden = false; + return this; + } + + public DisplayBuilder announceToChat() { + display.announceChat = true; + return this; + } + + public DisplayBuilder hideFromChat() { + display.announceChat = false; + return this; + } + + public DisplayBuilder frame(FrameType type) { + display.frame = type; + return this; + } + + public DisplayBuilder challenge() { + return frame(FrameType.CHALLENGE); + } + + public DisplayBuilder task() { + return frame(FrameType.TASK); + } + + public DisplayBuilder goal() { + return frame(FrameType.GOAL); + } + + public Builder endDisplay() { + base.display(display.build()); + return base; + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/BaseDataHandler.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/BaseDataHandler.java new file mode 100644 index 00000000..ae35daea --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/BaseDataHandler.java @@ -0,0 +1,113 @@ +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); + } +} + diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/Connector.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/Connector.java new file mode 100644 index 00000000..d0d818d8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/Connector.java @@ -0,0 +1,19 @@ +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 getDescriptors() { + return api.getDescriptors(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/ConnectorClientside.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/ConnectorClientside.java new file mode 100644 index 00000000..3a3b728e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/ConnectorClientside.java @@ -0,0 +1,79 @@ +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); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/ConnectorServerside.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/ConnectorServerside.java new file mode 100644 index 00000000..e0bcd935 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/ConnectorServerside.java @@ -0,0 +1,88 @@ +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); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataExchangeAPI.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataExchangeAPI.java new file mode 100644 index 00000000..7c87481f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataExchangeAPI.java @@ -0,0 +1,219 @@ +package org.betterx.bclib.api.v2.dataexchange; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.dataexchange.handler.DataExchange; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSync; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSyncID; +import org.betterx.bclib.config.Config; +import org.betterx.bclib.config.Configs; +import org.betterx.worlds.together.util.ModUtil; + +import net.minecraft.network.FriendlyByteBuf; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Lists; + +import java.io.File; +import java.util.List; +import java.util.function.BiConsumer; + +public class DataExchangeAPI extends DataExchange { + private final static List MODS = Lists.newArrayList(); + + /** + * You should never need to create a custom instance of this Object. + */ + public DataExchangeAPI() { + super(); + } + + @Environment(EnvType.CLIENT) + protected ConnectorClientside clientSupplier(DataExchange api) { + return new ConnectorClientside(api); + } + + protected ConnectorServerside serverSupplier(DataExchange api) { + return new ConnectorServerside(api); + } + + /** + * Register a mod to participate in the DataExchange. + * + * @param modID - {@link String} modID. + */ + public static void registerMod(String modID) { + if (!MODS.contains(modID)) MODS.add(modID); + } + + /** + * Register a mod dependency to participate in the DataExchange. + * + * @param modID - {@link String} modID. + */ + public static void registerModDependency(String modID) { + if (ModUtil.getModInfo(modID, false) != null && !"0.0.0".equals(ModUtil.getModVersion(modID))) { + registerMod(modID); + } else { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info("Mod Dependency '" + modID + "' not found. This is probably OK."); + } + } + + /** + * Returns the IDs of all registered Mods. + * + * @return List of modIDs + */ + public static List 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 desc) { + DataExchange api = DataExchange.getInstance(); + api.getDescriptors() + .addAll(desc); + } + + /** + * Sends the Handler. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * The content of the file is requested for comparison. This will copy the + * entire file from the client to the server. + *

+ * 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. + *

+ * The content of the file is requested for comparison. This will copy the + * entire file from the client to the server. + *

+ * 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. + *

+ * 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 callback) { + AutoSync.addOnWriteCallback(callback); + } + + /** + * Returns the sync-folder for a given Mod. + *

+ * 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); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataHandler.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataHandler.java new file mode 100644 index 00000000..649956af --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataHandler.java @@ -0,0 +1,332 @@ +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 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!"); + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataHandlerDescriptor.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataHandlerDescriptor.java new file mode 100644 index 00000000..b572bc31 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/DataHandlerDescriptor.java @@ -0,0 +1,61 @@ +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 instancer) { + this(identifier, instancer, instancer, false, false); + } + + public DataHandlerDescriptor( + @NotNull ResourceLocation identifier, + @NotNull Supplier instancer, + boolean sendOnJoin, + boolean sendBeforeEnter + ) { + this(identifier, instancer, instancer, sendOnJoin, sendBeforeEnter); + } + + public DataHandlerDescriptor( + @NotNull ResourceLocation identifier, + @NotNull Supplier receiv_instancer, + @NotNull Supplier 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 INSTANCE; + @NotNull + public final Supplier 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); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/FileHash.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/FileHash.java new file mode 100644 index 00000000..dcbef098 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/FileHash.java @@ -0,0 +1,160 @@ +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(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/SyncFileHash.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/SyncFileHash.java new file mode 100644 index 00000000..da416911 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/SyncFileHash.java @@ -0,0 +1,113 @@ +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. + *

+ * A File-Hash contains the md5-sum of the File, as well as its size and byte-values from defined positions + *

+ * 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}. + *

+ * 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)); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/DataExchange.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/DataExchange.java new file mode 100644 index 00000000..255b994b --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/DataExchange.java @@ -0,0 +1,111 @@ +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 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 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. + *

+ * 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. + *

+ * 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. + *

+ * 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); + } + } + }); + } + + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/AutoFileSyncEntry.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/AutoFileSyncEntry.java new file mode 100644 index 00000000..17a3920f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/AutoFileSyncEntry.java @@ -0,0 +1,262 @@ +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 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 e = deserialize(buf); + AutoFileSyncEntry match = findMatching(e.first); + return new AutoSync.AutoSyncTriple(e.first, e.second, match); + } + + public static Pair 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); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/AutoSync.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/AutoSync.java new file mode 100644 index 00000000..64a3811d --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/AutoSync.java @@ -0,0 +1,207 @@ +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> 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. + *

+ * 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 callback) { + onWriteCallbacks.add(callback); + } + + private static final List autoSyncFiles = new ArrayList<>(4); + + public static List 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 syncFolderDescriptions = Arrays.asList(SYNC_FOLDER); + + private List syncFolderContent; + + protected List 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 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."); + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/AutoSyncID.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/AutoSyncID.java new file mode 100644 index 00000000..b8ace9bc --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/AutoSyncID.java @@ -0,0 +1,144 @@ +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. + *

+ * 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); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/Chunker.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/Chunker.java new file mode 100644 index 00000000..d9eab98e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/Chunker.java @@ -0,0 +1,280 @@ +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. + *

+ * {@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 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 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 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(); + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/ChunkerProgress.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/ChunkerProgress.java new file mode 100644 index 00000000..ae82fc38 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/ChunkerProgress.java @@ -0,0 +1,28 @@ +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; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/FileContentWrapper.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/FileContentWrapper.java new file mode 100644 index 00000000..669fc955 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/FileContentWrapper.java @@ -0,0 +1,75 @@ +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; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/HelloClient.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/HelloClient.java new file mode 100644 index 00000000..ead95a39 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/HelloClient.java @@ -0,0 +1,544 @@ +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. + *

+ * 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 { + } + + public static class ServerModMap extends HashMap 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 mods = DataExchangeAPI.registeredMods(); + final List inmods = mods; + if (Configs.SERVER_CONFIG.isOfferingAllMods() || Configs.SERVER_CONFIG.isOfferingInfosForMods()) { + mods = new ArrayList<>(inmods.size()); + mods.addAll(inmods); + mods.addAll(ModUtil + .getMods() + .entrySet() + .stream() + .filter(entry -> entry.getValue().metadata.getEnvironment() != ModEnvironment.SERVER && !inmods.contains( + entry.getKey())) + .map(entry -> entry.getKey()) + .collect(Collectors.toList()) + ); + } + + mods = mods + .stream() + .filter(entry -> !Configs.SERVER_CONFIG.get(ServerConfig.EXCLUDED_MODS).contains(entry)) + .collect(Collectors.toList()); + + //write Plugin Versions + buf.writeInt(mods.size()); + for (String modID : mods) { + final String ver = ModUtil.getModVersion(modID); + int size = 0; + + final ModInfo mi = ModUtil.getModInfo(modID); + if (mi != null) { + try { + size = (int) Files.size(mi.jarPath); + } catch (IOException e) { + BCLib.LOGGER.error("Unable to get File Size: " + e.getMessage()); + } + } + + + writeString(buf, modID); + buf.writeInt(ModUtil.convertModVersion(ver)); + buf.writeInt(size); + final boolean canDownload = size > 0 && Configs.SERVER_CONFIG.isOfferingMods() && (Configs.SERVER_CONFIG.isOfferingAllMods() || inmods.contains( + modID)); + buf.writeBoolean(canDownload); + + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" - Listing Mod " + modID + " v" + ver + " (size: " + PathUtil.humanReadableFileSize( + size) + ", download=" + canDownload + ")"); + } + } else { + BCLib.LOGGER.info("Server will not list Mods."); + buf.writeInt(0); + } + + if (Configs.SERVER_CONFIG.isOfferingFiles() || Configs.SERVER_CONFIG.isOfferingConfigs()) { + //do only include files that exist on the server + final List existingAutoSyncFiles = AutoSync.getAutoSyncFiles() + .stream() + .filter(e -> e.fileName.exists()) + .filter(e -> (e.isConfigFile() && Configs.SERVER_CONFIG.isOfferingConfigs()) || (e instanceof AutoFileSyncEntry.ForDirectFileRequest && Configs.SERVER_CONFIG.isOfferingFiles())) + .collect(Collectors.toList()); + + //send config Data + buf.writeInt(existingAutoSyncFiles.size()); + for (AutoFileSyncEntry entry : existingAutoSyncFiles) { + entry.serialize(buf); + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" - Offering " + (entry.isConfigFile() ? "Config " : "File ") + entry); + } + } else { + BCLib.LOGGER.info("Server will neither offer Files nor Configs."); + buf.writeInt(0); + } + + if (Configs.SERVER_CONFIG.isOfferingFiles()) { + buf.writeInt(AutoSync.syncFolderDescriptions.size()); + AutoSync.syncFolderDescriptions.forEach(desc -> { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" - Offering Folder " + desc.localFolder + " (allowDelete=" + desc.removeAdditionalFiles + ")"); + desc.serialize(buf); + }); + } else { + BCLib.LOGGER.info("Server will not offer Sync Folders."); + buf.writeInt(0); + } + + buf.writeBoolean(Configs.SERVER_CONFIG.isOfferingInfosForMods()); + } + + String bclibVersion = "0.0.0"; + + + IServerModMap modVersion = new ServerModMap(); + List autoSyncedFiles = null; + List 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 filesToRequest, + final List filesToRemove + ) { + if (!Configs.CLIENT_CONFIG.isAcceptingFiles()) { + return; + } + + if (autoSynFolders.size() > 0) { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info("Folders offered by Server:"); + } + + autoSynFolders.forEach(desc -> { + //desc contains the fileCache sent from the server, load the local version to get hold of the actual file cache on the client + SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(desc.folderID); + if (localDescriptor != null) { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" - " + desc.folderID + " (" + desc.localFolder + ", allowRemove=" + desc.removeAdditionalFiles + ")"); + localDescriptor.invalidateCache(); + + desc.relativeFilesStream() + .filter(desc::discardChildElements) + .forEach(subFile -> { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.warning(" * " + subFile.relPath + " (REJECTED)"); + }); + + + if (desc.removeAdditionalFiles) { + List additionalFiles = localDescriptor.relativeFilesStream() + .filter(subFile -> !desc.hasRelativeFile( + subFile)) + .map(desc::mapAbsolute) + .filter(desc::acceptChildElements) + .map(absPath -> new AutoSyncID.ForDirectFileRequest( + desc.folderID, + absPath.toFile() + )) + .collect(Collectors.toList()); + + if (Configs.MAIN_CONFIG.verboseLogging()) + additionalFiles.forEach(aid -> BCLib.LOGGER.info(" * " + desc.localFolder.relativize(aid.relFile.toPath()) + " (missing on server)")); + filesToRemove.addAll(additionalFiles); + } + + desc.relativeFilesStream() + .filter(desc::acceptChildElements) + .forEach(subFile -> { + SyncFolderDescriptor.SubFile localSubFile = localDescriptor.getLocalSubFile(subFile.relPath); + if (localSubFile != null) { + //the file exists locally, check if the hashes match + if (!localSubFile.hash.equals(subFile.hash)) { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" * " + subFile.relPath + " (changed)"); + filesToRequest.add(new AutoSyncID.ForDirectFileRequest( + desc.folderID, + new File(subFile.relPath) + )); + } else { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" * " + subFile.relPath); + } + } else { + //the file is missing locally + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" * " + subFile.relPath + " (missing on client)"); + filesToRequest.add(new AutoSyncID.ForDirectFileRequest( + desc.folderID, + new File(subFile.relPath) + )); + } + }); + + //free some memory + localDescriptor.invalidateCache(); + } else { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" - " + desc.folderID + " (Failed to find)"); + } + }); + } + + @Environment(EnvType.CLIENT) + private void processSingleFileSync(final List filesToRequest) { + final boolean debugHashes = Configs.CLIENT_CONFIG.shouldPrintDebugHashes(); + + if (autoSyncedFiles.size() > 0) { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info("Files offered by Server:"); + } + + //Handle single sync files + //Single files need to be registered for sync on both client and server + //There are no restrictions to the target folder, but the client decides the final + //location. + for (AutoSync.AutoSyncTriple e : autoSyncedFiles) { + String actionString = ""; + FileContentWrapper contentWrapper = new FileContentWrapper(e.serverContent); + if (e.localMatch == null) { + actionString = "(unknown source -> omitting)"; + //filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID)); + } else if (e.localMatch.needTransfer.test(e.localMatch.getFileHash(), e.serverHash, contentWrapper)) { + actionString = "(prepare update)"; + //we did not yet receive the new content + if (contentWrapper.getRawContent() == null) { + filesToRequest.add(new AutoSyncID(e.serverHash.modID, e.serverHash.uniqueID)); + } else { + filesToRequest.add(new AutoSyncID.WithContentOverride( + e.serverHash.modID, + e.serverHash.uniqueID, + contentWrapper, + e.localMatch.fileName + )); + } + } + if (Configs.MAIN_CONFIG.verboseLogging()) { + BCLib.LOGGER.info(" - " + e + ": " + actionString); + if (debugHashes) { + BCLib.LOGGER.info(" * " + e.serverHash + " (Server)"); + BCLib.LOGGER.info(" * " + e.localMatch.getFileHash() + " (Client)"); + BCLib.LOGGER.info(" * local Content " + (contentWrapper.getRawContent() == null)); + } + } + } + } + + + @Environment(EnvType.CLIENT) + private void processModFileSync(final List filesToRequest, final Set mismatchingMods) { + for (Entry e : modVersion.entrySet()) { + final String localVersion = ModUtil.convertModVersion(ModUtil.convertModVersion(ModUtil.getModVersion(e.getKey()))); + final OfferedModInfo serverInfo = e.getValue(); + + ModInfo nfo = ModUtil.getModInfo(e.getKey()); + final boolean clientOnly = nfo != null && nfo.metadata.getEnvironment() == ModEnvironment.CLIENT; + final boolean requestMod = !clientOnly && !serverInfo.version.equals(localVersion) && serverInfo.size > 0 && serverInfo.canDownload; + + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" - " + e.getKey() + " (client=" + localVersion + ", server=" + serverInfo.version + ", size=" + PathUtil.humanReadableFileSize( + serverInfo.size) + (requestMod ? ", requesting" : "") + (serverInfo.canDownload + ? "" + : ", not offered") + (clientOnly ? ", client only" : "") + ")"); + if (requestMod) { + filesToRequest.add(new AutoSyncID.ForModFileRequest(e.getKey(), serverInfo.version)); + } + if (!serverInfo.version.equals(localVersion)) { + mismatchingMods.add(e.getKey()); + } + } + + mismatchingMods.addAll(ModListScreen.localMissing(modVersion)); + mismatchingMods.addAll(ModListScreen.serverMissing(modVersion)); + } + + @Override + protected boolean isBlocking() { + return true; + } + + @Environment(EnvType.CLIENT) + @Override + protected void runOnClientGameThread(Minecraft client) { + if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) { + BCLib.LOGGER.info("Auto-Sync was disabled on the client."); + return; + } + final String localBclibVersion = getBCLibVersion(); + BCLib.LOGGER.info("Received Hello from Server. (client=" + localBclibVersion + ", server=" + bclibVersion + ")"); + + if (ModUtil.convertModVersion(localBclibVersion) != ModUtil.convertModVersion(bclibVersion)) { + showBCLibError(client); + return; + } + + final List filesToRequest = new ArrayList<>(2); + final List filesToRemove = new ArrayList<>(2); + final Set 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 files, + final List 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 localChanges = new ArrayList<>( + files.toArray().length); + List 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 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 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)); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/HelloServer.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/HelloServer.java new file mode 100644 index 00000000..df1994fa --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/HelloServer.java @@ -0,0 +1,119 @@ +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. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Description
ServerClient
Player enters World
<--{@link HelloServer}Sends the current BLib-Version installed on the Client
{@link HelloClient}-->Sends the current BClIb-Version, the Version of all Plugins and data for all AutpoSync-Files + * ({@link DataExchangeAPI#addAutoSyncFile(String, File)} on the Server
<--{@link RequestFiles}Request missing or out of sync Files from the Server
{@link SendFiles}-->Send Files from the Server to the Client
+ */ +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); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/RequestFiles.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/RequestFiles.java new file mode 100644 index 00000000..ec4da863 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/RequestFiles.java @@ -0,0 +1,111 @@ +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 files; + + private RequestFiles() { + this(null); + } + + public RequestFiles(List files) { + super(DESCRIPTOR.IDENTIFIER); + this.files = files; + } + + @Environment(EnvType.CLIENT) + @Override + protected boolean prepareDataOnClient() { + if (!Configs.CLIENT_CONFIG.isAllowingAutoSync()) { + BCLib.LOGGER.info("Auto-Sync was disabled on the client."); + return false; + } + return true; + } + + @Environment(EnvType.CLIENT) + @Override + protected void serializeDataOnClient(FriendlyByteBuf buf) { + newToken(); + writeString(buf, currentToken); + + buf.writeInt(files.size()); + + for (AutoSyncID a : files) { + a.serializeData(buf); + } + } + + String receivedToken = ""; + + @Override + protected void deserializeIncomingDataOnServer(FriendlyByteBuf buf, Player player, PacketSender responseSender) { + receivedToken = readString(buf); + int size = buf.readInt(); + files = new ArrayList<>(size); + + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info("Client requested " + size + " Files:"); + for (int i = 0; i < size; i++) { + AutoSyncID asid = AutoSyncID.deserializeData(buf); + files.add(asid); + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" - " + asid); + } + + + } + + @Override + protected void runOnServerGameThread(MinecraftServer server, Player player) { + if (!Configs.SERVER_CONFIG.isAllowingAutoSync()) { + BCLib.LOGGER.info("Auto-Sync was disabled on the server."); + return; + } + + List 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(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/SendFiles.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/SendFiles.java new file mode 100644 index 00000000..531a7cf2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/SendFiles.java @@ -0,0 +1,230 @@ +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 files; + private String token; + + public SendFiles() { + this(null, ""); + } + + public SendFiles(List 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 existingFiles = files.stream() + .filter(e -> e != null && e.fileName != null && e.fileName.exists()) + .collect(Collectors.toList()); + /* + //this will try to send a file that was not registered or requested by the client + existingFiles.add(new AutoFileSyncEntry("none", new File("D:\\MinecraftPlugins\\BetterNether\\run\\server.properties"),true,(a, b, content) -> { + System.out.println("Got Content:" + content.length); + return true; + }));*/ + + /*//this will try to send a folder-file that was not registered or requested by the client + existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("test.json"), DataExchange.SYNC_FOLDER.mapAbsolute("test.json").toFile()));*/ + + /*//this will try to send a folder-file that was not registered or requested by the client and is outside the base-folder + existingFiles.add(new AutoFileSyncEntry.ForDirectFileRequest(DataExchange.SYNC_FOLDER.folderID, new File("../breakout.json"), DataExchange.SYNC_FOLDER.mapAbsolute("../breakout.json").toFile()));*/ + + + writeString(buf, token); + buf.writeInt(existingFiles.size()); + + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info("Sending " + existingFiles.size() + " Files to Client:"); + for (AutoFileSyncEntry entry : existingFiles) { + int length = entry.serializeContent(buf); + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" - " + entry + " (" + PathUtil.humanReadableFileSize(length) + ")"); + } + } + + private List> receivedFiles; + + @Environment(EnvType.CLIENT) + @Override + protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) { + if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) { + token = readString(buf); + if (!token.equals(RequestFiles.currentToken)) { + RequestFiles.newToken(); + BCLib.LOGGER.error("Unrequested File Transfer!"); + receivedFiles = new ArrayList<>(0); + return; + } + RequestFiles.newToken(); + + int size = buf.readInt(); + receivedFiles = new ArrayList<>(size); + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info("Server sent " + size + " Files:"); + for (int i = 0; i < size; i++) { + Triple p = AutoFileSyncEntry.deserializeContent(buf); + if (p.first != null) { + final String type; + if (p.first.isConfigFile() && Configs.CLIENT_CONFIG.isAcceptingConfigs()) { + receivedFiles.add(p); + type = "Accepted Config "; + } else if (p.first instanceof AutoFileSyncEntry.ForModFileRequest && Configs.CLIENT_CONFIG.isAcceptingMods()) { + receivedFiles.add(p); + type = "Accepted Mod "; + } else if (Configs.CLIENT_CONFIG.isAcceptingFiles()) { + receivedFiles.add(p); + type = "Accepted File "; + } else { + type = "Ignoring "; + } + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info(" - " + type + p.first + " (" + PathUtil.humanReadableFileSize(p.second.length) + ")"); + } else { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.error(" - Failed to receive File " + p.third + ", possibly sent from a Mod that is not installed on the client."); + } + } + } + } + + @Environment(EnvType.CLIENT) + @Override + protected void runOnClientGameThread(Minecraft client) { + if (Configs.CLIENT_CONFIG.isAcceptingConfigs() || Configs.CLIENT_CONFIG.isAcceptingFiles() || Configs.CLIENT_CONFIG.isAcceptingMods()) { + BCLib.LOGGER.info("Writing Files:"); + + for (Pair 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(); + })); + + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/SyncFolderDescriptor.java b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/SyncFolderDescriptor.java new file mode 100644 index 00000000..bc4bd275 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/dataexchange/handler/autosync/SyncFolderDescriptor.java @@ -0,0 +1,220 @@ +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 fileCache; + + public SyncFolderDescriptor(String folderID, Path localFolder, boolean removeAdditionalFiles) { + this.removeAdditionalFiles = removeAdditionalFiles; + this.folderID = folderID; + this.localFolder = localFolder; + fileCache = null; + } + + @Override + public String toString() { + return "SyncFolderDescriptor{" + "folderID='" + folderID + '\'' + ", removeAdditionalFiles=" + removeAdditionalFiles + ", localFolder=" + localFolder + ", files=" + ( + fileCache == null + ? "?" + : fileCache.size()) + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof String) { + return folderID.equals(o); + } + if (o instanceof ForDirectFileRequest) { + return folderID.equals(((ForDirectFileRequest) o).uniqueID); + } + if (!(o instanceof SyncFolderDescriptor)) return false; + SyncFolderDescriptor that = (SyncFolderDescriptor) o; + return folderID.equals(that.folderID); + } + + @Override + public int hashCode() { + return folderID.hashCode(); + } + + public int fileCount() { + return fileCache == null ? 0 : fileCache.size(); + } + + public void invalidateCache() { + fileCache = null; + } + + public void loadCache() { + if (fileCache == null) { + fileCache = new ArrayList<>(8); + PathUtil.fileWalker(localFolder.toFile(), p -> fileCache.add(new SubFile( + localFolder.relativize(p) + .toString(), + FileHash.create(p.toFile()) + ))); + + /*//this tests if we can trick the system to load files that are not beneath the base-folder + if (!BCLib.isClient()) { + fileCache.add(new SubFile("../breakout.json", FileHash.create(mapAbsolute("../breakout.json").toFile()))); + }*/ + } + } + + public void serialize(FriendlyByteBuf buf) { + final boolean debugHashes = Configs.CLIENT_CONFIG.getBoolean(AutoSync.SYNC_CATEGORY, "debugHashes", false); + loadCache(); + + DataHandler.writeString(buf, folderID); + buf.writeBoolean(removeAdditionalFiles); + buf.writeInt(fileCache.size()); + fileCache.forEach(fl -> { + if (Configs.MAIN_CONFIG.verboseLogging()) { + BCLib.LOGGER.info(" - " + fl.relPath); + if (debugHashes) { + BCLib.LOGGER.info(" " + fl.hash); + } + } + fl.serialize(buf); + }); + } + + public static SyncFolderDescriptor deserialize(FriendlyByteBuf buf) { + final String folderID = DataHandler.readString(buf); + final boolean remAddFiles = buf.readBoolean(); + final int count = buf.readInt(); + SyncFolderDescriptor localDescriptor = AutoSync.getSyncFolderDescriptor(folderID); + + final SyncFolderDescriptor desc; + if (localDescriptor != null) { + desc = new SyncFolderDescriptor( + folderID, + localDescriptor.localFolder, + localDescriptor.removeAdditionalFiles && remAddFiles + ); + desc.fileCache = new ArrayList<>(count); + } else { + BCLib.LOGGER.warning(BCLib.isClient() + ? "Client" + : "Server" + " does not know Sync-Folder ID '" + folderID + "'"); + desc = null; + } + + for (int i = 0; i < count; i++) { + SubFile relPath = SubFile.deserialize(buf); + if (desc != null) desc.fileCache.add(relPath); + } + + return desc; + } + + //Note: make sure loadCache was called before using this + boolean hasRelativeFile(String relFile) { + return fileCache.stream() + .filter(sf -> sf.equals(relFile)) + .findFirst() + .isPresent(); + } + + //Note: make sure loadCache was called before using this + boolean hasRelativeFile(SubFile subFile) { + return hasRelativeFile(subFile.relPath); + } + + //Note: make sure loadCache was called before using this + SubFile getLocalSubFile(String relPath) { + return fileCache.stream() + .filter(sf -> sf.relPath.equals(relPath)) + .findFirst() + .orElse(null); + } + + Stream 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); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/datafixer/DataFixerAPI.java b/src/main/java/org/betterx/bclib/api/v2/datafixer/DataFixerAPI.java new file mode 100644 index 00000000..07a9cf20 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/datafixer/DataFixerAPI.java @@ -0,0 +1,598 @@ +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 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 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 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 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 onResume) { + MigrationProfile profile = loadProfileIfNeeded(dir); + + BiConsumer 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 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 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 players = getAllPlayers(dir); + List 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 getAllPlayers(File dir) { + List 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 getAllRegions(File dir, List 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.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); + } + } + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/datafixer/ForcedLevelPatch.java b/src/main/java/org/betterx/bclib/api/v2/datafixer/ForcedLevelPatch.java new file mode 100644 index 00000000..0d18a7e0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/datafixer/ForcedLevelPatch.java @@ -0,0 +1,57 @@ +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 getIDReplacements() { + return new HashMap(); + } + + @Override + public final PatchFunction getWorldDataPatcher() { + return null; + } + + @Override + public final PatchBiFunction getBlockStatePatcher() { + return null; + } + + @Override + public final List getWorldDataIDPaths() { + return null; + } + + @Override + public PatchFunction 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); +} + diff --git a/src/main/java/org/betterx/bclib/api/v2/datafixer/MigrationProfile.java b/src/main/java/org/betterx/bclib/api/v2/datafixer/MigrationProfile.java new file mode 100644 index 00000000..931773ca --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/datafixer/MigrationProfile.java @@ -0,0 +1,374 @@ +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 mods; + final Map idReplacements; + final List> levelPatchers; + final List> statePatchers; + final List worldDataPatchers; + final Map> 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 replacements = new HashMap(); + List> levelPatches = new LinkedList<>(); + List worldDataPatches = new LinkedList<>(); + List> statePatches = new LinkedList<>(); + HashMap> worldDataIDPaths = new HashMap<>(); + for (String modID : mods) { + + Patch.getALL() + .stream() + .filter(p -> p.modID.equals(modID)) + .forEach(patch -> { + List 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 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 getAllNbts(File dir, List 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 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> 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 f : statePatchers) { + changed |= f.apply(palette, states, this); + } + return changed; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/datafixer/Patch.java b/src/main/java/org/betterx/bclib/api/v2/datafixer/Patch.java new file mode 100644 index 00000000..cadf2ec3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/datafixer/Patch.java @@ -0,0 +1,237 @@ +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 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 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. + *

+ * 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}. + *

+ * 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 getIDReplacements() { + return new HashMap(); + } + + /** + * Return a {@link PatchFunction} that is called with the content of level.dat. + *

+ * 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} + *

+ * The default implementation of this method returns null. + * + * @return {@code true} if changes were applied and we need to save the data + */ + public PatchFunction 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} + *

+ * The default implementation of this method returns null. + * + * @return {@code true} if changes were applied and we need to save the data + */ + public PatchFunction 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. + *

+ * The first parameter is the palette and the second is the blockstate. + *

+ * 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} + *

+ * The default implementation of this method returns null. + * + * @return {@code true} if changes were applied and we need to save the data + */ + public PatchBiFunction 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. + *

+ * 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. + *

+ * {@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()} + *

+ * 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: + *

+     * 	 - global +
+     * 			  | - key (String)
+     * 			  | - items (List) +
+     * 							   | - { id (String) }
+     * 							   | - { id (String) }
+     * 
+ * The path global.items.id will fix all id-entries in the items-list, while the path + * global.key will only fix the key-entry. + *

+ * if the leaf-entry (= the last part of the path, which would be items in global.items) is a + * {@link CompoundTag}, the system will fix any id entry. If the {@link CompoundTag} contains an item + * or tag.BlockEntityTag entry, the system will recursivley continue with those. If an items + * or inventory-{@link net.minecraft.nbt.ListTag} was found, the system will continue recursivley with + * every item of that list. + *

+ * if the leaf-entry is a {@link net.minecraft.nbt.ListTag}, it is handle the same as a child items 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 getWorldDataIDPaths() { + return null; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/datafixer/PatchDidiFailException.java b/src/main/java/org/betterx/bclib/api/v2/datafixer/PatchDidiFailException.java new file mode 100644 index 00000000..053d29fe --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/datafixer/PatchDidiFailException.java @@ -0,0 +1,11 @@ +package org.betterx.bclib.api.v2.datafixer; + +public class PatchDidiFailException extends Exception { + public PatchDidiFailException() { + super(); + } + + public PatchDidiFailException(Exception e) { + super(e); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/LevelGenEvents.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/LevelGenEvents.java new file mode 100644 index 00000000..2b70401c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/LevelGenEvents.java @@ -0,0 +1,97 @@ +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.levelgen.biomes.InternalBiomeAPI; +import org.betterx.bclib.api.v2.poi.PoiManager; +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.world.WorldConfig; +import org.betterx.worlds.together.world.event.WorldEvents; + +import net.minecraft.core.Registry; +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.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelResource; +import net.minecraft.world.level.storage.LevelStorageSource; + +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +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::beforeWorldLoad); + + WorldEvents.ON_WORLD_LOAD.on(LevelGenEvents::onWorldLoad); + WorldEvents.WORLD_REGISTRY_READY.on(LevelGenEvents::worldRegistryReady); + WorldEvents.ON_FINALIZE_LEVEL_STEM.on(LevelGenEvents::finalizeStem); + WorldEvents.ON_FINALIZED_WORLD_LOAD.on(LevelGenEvents::finalizedWorldLoad); + + WorldEvents.PATCH_WORLD.on(LevelGenEvents::patchExistingWorld); + + WorldEvents.BEFORE_ADDING_TAGS.on(LevelGenEvents::applyBiomeTags); + } + + + private static void applyBiomeTags( + String directory, + Map> tagsMap + ) { + if (directory.equals(TagManager.BIOMES.directory)) { + InternalBiomeAPI._runBiomeTagAdders(); + } + } + + + private static boolean patchExistingWorld( + LevelStorageSource.LevelStorageAccess storageAccess, + Consumer allDone + ) { + final Path dataPath = storageAccess.getLevelPath(LevelResource.ROOT).resolve("data"); + WorldConfig.setDataDir(dataPath.toFile()); + return DataFixerAPI.fixData(storageAccess, allDone != null && BCLib.isClient(), allDone); + } + + private static void worldRegistryReady(RegistryAccess a) { + InternalBiomeAPI.initRegistry(a); + } + + private static void beforeWorldLoad( + LevelStorageSource.LevelStorageAccess storageAccess, + boolean isNewWorld, + boolean isServer + ) { + setupWorld(); + if (isNewWorld) { + WorldConfig.saveFile(BCLib.MOD_ID); + DataFixerAPI.initializePatchData(); + } + } + + private static void onWorldLoad() { + LifeCycleAPI._runBeforeLevelLoad(); + } + + private static void finalizeStem( + Registry dimensionRegistry, + ResourceKey dimension, + LevelStem levelStem + ) { + InternalBiomeAPI.applyModifications(levelStem.generator().getBiomeSource(), dimension); + } + + private static void finalizedWorldLoad(Registry dimensionRegistry) { + PoiManager.updateStates(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiome.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiome.java new file mode 100644 index 00000000..3c2ce01e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiome.java @@ -0,0 +1,506 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.util.WeightedList; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import com.mojang.datafixers.Products; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; +import net.minecraft.world.level.biome.Climate; + +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.Random; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + + +/** + * Stores additional Data for a {@link Biome}. Instances of {@link BCLBiome} are linked to + * Biomes using a {@link ResourceKey}. The data is registerd and Stored in + * {@link BCLBiomeRegistry#BCL_BIOMES_REGISTRY} registries, allowing them to be overriden by Datapacks. + *

+ * As such, if you extend BCLBiome with custom types, especially if those new type have a custom state, + * you need to provide a Codec for your class using + * {@link BCLBiomeRegistry#registerBiomeCodec(ResourceLocation, KeyDispatchDataCodec)}. + *

+ * You may use {@link BCLBiome#codecWithSettings(RecordCodecBuilder.Instance)} to create a Codec that includes + * all default settings for {@link BCLBiome} as well as additional Data for your specific subclass. + */ +public class BCLBiome implements BiomeData { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> codecWithSettings(instance).apply( + instance, + BCLBiome::new + )); + public static final KeyDispatchDataCodec KEY_CODEC = KeyDispatchDataCodec.of(CODEC); + + public KeyDispatchDataCodec codec() { + return KEY_CODEC; + } + + private static class CodecAttributes { + public RecordCodecBuilder t0 = Codec.FLOAT.fieldOf("terrainHeight") + .orElse(0.1f) + .forGetter((T o1) -> o1.settings.terrainHeight); + + public RecordCodecBuilder t1 = Codec.FLOAT.fieldOf("fogDensity") + .orElse(1.0f) + .forGetter((T o1) -> o1.settings.fogDensity); + public RecordCodecBuilder t2 = Codec.FLOAT.fieldOf("genChance") + .orElse(1.0f) + .forGetter((T o1) -> o1.settings.genChance); + public RecordCodecBuilder t3 = Codec.INT.fieldOf("edgeSize") + .orElse(0) + .forGetter((T o1) -> o1.settings.edgeSize); + public RecordCodecBuilder t4 = Codec.BOOL.fieldOf("vertical") + .orElse(false) + .forGetter((T o1) -> o1.settings.vertical); + public RecordCodecBuilder> t5 = + ResourceLocation.CODEC + .optionalFieldOf("edge") + .orElse(Optional.empty()) + .forGetter((T o1) -> ((BCLBiome) o1).edge == null + ? Optional.empty() + : Optional.of(((BCLBiome) o1).edge)); + public RecordCodecBuilder t6 = + ResourceLocation.CODEC.fieldOf("biome") + .forGetter((T o) -> ((BCLBiome) o).biomeID); + public RecordCodecBuilder>> t7 = + Climate.ParameterPoint.CODEC.listOf() + .optionalFieldOf("parameter_points") + .orElse(Optional.of(List.of())) + .forGetter((T o) -> + o.parameterPoints == null || o.parameterPoints.isEmpty() + ? Optional.empty() + : Optional.of(o.parameterPoints)); + + public RecordCodecBuilder> t8 = + ResourceLocation.CODEC.optionalFieldOf("parent") + .orElse(Optional.empty()) + .forGetter( + (T o1) -> + ((BCLBiome) o1).biomeParent == null + ? Optional.empty() + : Optional.of( + ((BCLBiome) o1).biomeParent)); + public RecordCodecBuilder> t10 = + Codec.STRING.optionalFieldOf("intended_for") + .orElse(Optional.of(BiomeAPI.BiomeType.NONE.getName())) + .forGetter((T o) -> + ((BCLBiome) o).intendedType == null + ? Optional.empty() + : Optional.of(((BCLBiome) o).intendedType.getName())); + } + + public static Products.P11, Float, Float, Float, Integer, Boolean, Optional, ResourceLocation, Optional>, Optional, Optional, P11> codecWithSettings( + RecordCodecBuilder.Instance instance, + final RecordCodecBuilder p11 + ) { + CodecAttributes a = new CodecAttributes<>(); + return instance.group(a.t0, a.t1, a.t2, a.t3, a.t4, a.t5, a.t6, a.t7, a.t8, a.t10, p11); + } + + public static Products.P12, Float, Float, Float, Integer, Boolean, Optional, ResourceLocation, Optional>, Optional, Optional, P11, P12> codecWithSettings( + RecordCodecBuilder.Instance instance, + final RecordCodecBuilder p11, + final RecordCodecBuilder p12 + ) { + CodecAttributes a = new CodecAttributes<>(); + return instance.group(a.t0, a.t1, a.t2, a.t3, a.t4, a.t5, a.t6, a.t7, a.t8, a.t10, p11, p12); + } + + public static Products.P14, Float, Float, Float, Integer, Boolean, Optional, ResourceLocation, Optional>, Optional, Optional, P11, P12, P13, P14> codecWithSettings( + RecordCodecBuilder.Instance instance, + final RecordCodecBuilder p11, + final RecordCodecBuilder p12, + final RecordCodecBuilder p13, + final RecordCodecBuilder p14 + ) { + CodecAttributes a = new CodecAttributes<>(); + return instance.group(a.t0, a.t1, a.t2, a.t3, a.t4, a.t5, a.t6, a.t7, a.t8, a.t10, p11, p12, p13, p14); + } + + public static Products.P13, Float, Float, Float, Integer, Boolean, Optional, ResourceLocation, Optional>, Optional, Optional, P11, P12, P13> codecWithSettings( + RecordCodecBuilder.Instance instance, + final RecordCodecBuilder p11, + final RecordCodecBuilder p12, + final RecordCodecBuilder p13 + ) { + CodecAttributes a = new CodecAttributes<>(); + return instance.group(a.t0, a.t1, a.t2, a.t3, a.t4, a.t5, a.t6, a.t7, a.t8, a.t10, p11, p12, p13); + } + + public static Products.P10, Float, Float, Float, Integer, Boolean, Optional, ResourceLocation, Optional>, Optional, Optional> codecWithSettings( + RecordCodecBuilder.Instance instance + ) { + CodecAttributes a = new CodecAttributes<>(); + return instance.group(a.t0, a.t1, a.t2, a.t3, a.t4, a.t5, a.t6, a.t7, a.t8, a.t10); + } + + public final BCLBiomeSettings settings; + private final Map customData = Maps.newHashMap(); + private final ResourceLocation biomeID; + private final ResourceKey biomeKey; + + protected final List parameterPoints = Lists.newArrayList(); + + private ResourceLocation biomeParent; + private ResourceLocation edge; + + private BiomeAPI.BiomeType intendedType = BiomeAPI.BiomeType.NONE; + + protected BCLBiome( + float terrainHeight, + float fogDensity, + float genChance, + int edgeSize, + boolean vertical, + Optional edge, + ResourceLocation biomeID, + Optional> parameterPoints, + Optional biomeParent, + Optional intendedType + ) { + this.settings = new BCLBiomeSettings( + terrainHeight, + fogDensity, + genChance, + edgeSize, + vertical + ); + this.edge = edge.orElse(null); + this.biomeID = biomeID; + this.biomeKey = ResourceKey.create(Registries.BIOME, biomeID); + this.biomeParent = biomeParent.orElse(null); + if (parameterPoints.isPresent()) this.parameterPoints.addAll(parameterPoints.get()); + this.setIntendedType(intendedType.map(t -> BiomeAPI.BiomeType.create(t)).orElse(BiomeAPI.BiomeType.NONE)); + } + + /** + * Create wrapper for existing biome using its {@link ResourceLocation} identifier. + * + * @param biomeKey {@link ResourceKey} for the {@link Biome}. + */ + protected BCLBiome(ResourceKey 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(Registries.BIOME, biomeID), null); + } + + /** + * Create wrapper for existing biome. + * + * @param biomeID Teh ResoureLocation for this Biome + */ + @ApiStatus.Internal + public BCLBiome(ResourceLocation biomeID, BiomeAPI.BiomeType type) { + this(ResourceKey.create(Registries.BIOME, biomeID), (BCLBiomeSettings) null); + setIntendedType(type); + } + + /** + * Create a new Biome + * + * @param biomeKey {@link ResourceKey} of the wrapped Biome + * @param defaults The Settings for this Biome or null if you want to apply the defaults + */ + protected BCLBiome(ResourceKey biomeKey, BCLBiomeSettings defaults) { + this.settings = defaults == null ? new BCLBiomeSettings() : defaults; + this.biomeID = biomeKey.location(); + this.biomeKey = biomeKey; + } + + /** + * Create a new Biome + * + * @param biomeID {@link ResourceLocation} of the wrapped Biome + * @param defaults The Settings for this Biome or null if you want to apply the defaults + */ + protected BCLBiome(ResourceLocation biomeID, BCLBiomeSettings defaults) { + this.settings = defaults == null ? new BCLBiomeSettings() : defaults; + this.biomeID = biomeID; + this.biomeKey = ResourceKey.create(Registries.BIOME, biomeID); + } + + /** + * Changes the intended Type for this Biome + * + * @param type the new type + * @return the same instance + */ + protected BCLBiome setIntendedType(BiomeAPI.BiomeType type) { + return _setIntendedType(type); + } + + BCLBiome _setIntendedType(BiomeAPI.BiomeType type) { + this.intendedType = type; + return this; + } + + public BiomeAPI.BiomeType getIntendedType() { + return this.intendedType; + } + + /** + * Get current biome edge. + * + * @return {@link BCLBiome} edge. + */ + @Nullable + public BCLBiome getEdge() { + return BiomeAPI.getBiome(edge); + } + + public boolean hasEdge() { + return !BCLBiomeRegistry.isEmptyBiome(edge); + } + + + BCLBiome _setEdge(BCLBiome edge) { + if (edge != null) { + this.edge = edge.biomeID; + edge.biomeParent = this.biomeID; + } else { + this.edge = null; + } + 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 newEdge The new edge + * @return same {@link BCLBiome}. + */ + public BCLBiome addEdge(BCLBiome newEdge) { + if (this.edge != null) { + newEdge.biomeParent = this.edge; + } else { + this._setEdge(newEdge); + } + 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.biomeID; + return this; + } + + private WeightedList getSubBiomes() { + RegistryAccess acc = WorldBootstrap.getLastRegistryAccess(); + WeightedList subbiomes = new WeightedList<>(); + subbiomes.add(this, 1.0f); + if (acc == null) return subbiomes; + + Registry reg = acc.registry(BCLBiomeRegistry.BCL_BIOMES_REGISTRY).orElse(null); + if (reg == null) reg = BCLBiomeRegistry.BUILTIN_BCL_BIOMES; + + for (Map.Entry, BCLBiome> entry : reg.entrySet()) { + BCLBiome b = entry.getValue(); + if ( + this.biomeID.equals(entry.getValue().biomeParent) + && !entry.getValue().isEdgeBiome() + ) { + subbiomes.add(b, b.settings.genChance); + } + } + + return subbiomes; + } + + public void forEachSubBiome(BiConsumer consumer) { + final WeightedList subbiomes = getSubBiomes(); + 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 BiomeAPI.getBiome(this.biomeParent); + } + + public boolean hasParentBiome() { + return !BCLBiomeRegistry.isEmptyBiome(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.equals(this.biomeID)); + } + + /** + * Getter for biome identifier. + * + * @return {@link ResourceLocation} + */ + public ResourceLocation getID() { + return biomeID; + } + + + /** + * Getter for biomeKey + * + * @return {@link ResourceKey}. + */ + public ResourceKey getBiomeKey() { + return biomeKey; + } + + public ResourceKey getBCLBiomeKey() { + return (ResourceKey) (Object) ResourceKey.create(BCLBiomeRegistry.BCL_BIOMES_REGISTRY, biomeID); + } + + /** + * For internal use from BiomeAPI only + */ + void afterRegistration() { + + } + + public boolean is(ResourceKey key) { + return biomeID.equals(key.location()); + } + + public boolean is(ResourceLocation loc) { + return biomeID.equals(loc); + } + + public boolean is(BCLBiome biome) { + if (biome == null) return false; + return biomeID.equals(biome.biomeID); + } + + public boolean equals(ResourceKey key) { + return is(key); + } + + public boolean equals(ResourceLocation loc) { + return is(loc); + } + + public boolean equals(BCLBiome biome) { + return is(biome); + } + + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof BCLBiome biome) { + return is(biome); + } + if (obj instanceof ResourceKey key) { + return is(key); + } + if (obj instanceof ResourceLocation loc) { + return is(loc); + } + return super.equals(obj); + } + + @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 params) { + this.parameterPoints.addAll(params); + } + + public void forEachClimateParameter(Consumer consumer) { + this.parameterPoints.forEach(consumer); + } + + /** + * Returns the group used in the config Files for this biome + *

+ * 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() { + final BCLBiome parent = getParentBiome(); + if (parent == null) return false; + return this.biomeID.equals(parent.edge); + } + + boolean allowFabricRegistration() { + return !isEdgeBiome(); + } + + @ApiStatus.Internal + private Biome biomeToRegister; + + @ApiStatus.Internal + void _setBiomeToRegister(Biome b) { + this.biomeToRegister = b; + } + + @ApiStatus.Internal + Biome _getBiomeToRegister() { + return this.biomeToRegister; + } + + @ApiStatus.Internal + boolean _hasBiomeToRegister() { + return this.biomeToRegister != null; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeBuilder.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeBuilder.java new file mode 100644 index 00000000..bb3385f5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeBuilder.java @@ -0,0 +1,994 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.api.v2.levelgen.structures.BCLStructure; +import org.betterx.bclib.api.v2.levelgen.surface.SurfaceRuleBuilder; +import org.betterx.bclib.api.v3.levelgen.features.BCLFeature; +import org.betterx.bclib.entity.BCLEntityWrapper; +import org.betterx.bclib.mixin.common.BiomeGenerationSettingsAccessor; +import org.betterx.bclib.util.CollectionsUtil; +import org.betterx.bclib.util.Pair; +import org.betterx.ui.ColorUtil; +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.core.registries.Registries; +import net.minecraft.data.worldgen.BiomeDefaultFeatures; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.data.worldgen.biome.OverworldBiomes; +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.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +public class BCLBiomeBuilder { + static final ConcurrentLinkedQueue> UNBOUND_BIOMES = new ConcurrentLinkedQueue<>(); + + public static int calculateSkyColor(float temperature) { + return OverworldBiomes.calculateSkyColor(temperature); + } + + public static int DEFAULT_NETHER_WATER_COLOR = 0x3F76E4; + public static int DEFAULT_END_WATER_COLOR = DEFAULT_NETHER_WATER_COLOR; + public static int DEFAULT_NETHER_WATER_FOG_COLOR = 0x050533; + public static int DEFAULT_END_WATER_FOG_COLOR = DEFAULT_NETHER_WATER_FOG_COLOR; + public static int DEFAULT_END_FOG_COLOR = 0xA080A0; + public static int DEFAULT_END_SKY_COLOR = 0x000000; + public static float DEFAULT_NETHER_TEMPERATURE = 2.0f; + public static float DEFAULT_END_TEMPERATURE = 0.5f; + public static float DEFAULT_NETHER_WETNESS = 0.0f; + public static float DEFAULT_END_WETNESS = 0.5f; + + + @FunctionalInterface + public interface BiomeSupplier extends BiFunction, BCLBiomeSettings, T> { + } + + @FunctionalInterface + private interface FeatureSupplier extends Consumer { + } + + @FunctionalInterface + interface BuildCompletion extends Function, Biome> { + } + + private static final SurfaceRules.ConditionSource SURFACE_NOISE = SurfaceRules.noiseCondition( + Noises.SOUL_SAND_LAYER, + -0.012 + ); + + private final List featureSupliers = new LinkedList<>(); + private final List>>> carvers = new ArrayList<>( + 1); + private BiomeGenerationSettings.Builder generationSettings; + private BiomeSpecialEffects.Builder effectsBuilder; + private MobSpawnSettings.Builder spawnSettings; + private SurfaceRules.RuleSource surfaceRule; + private boolean hasPrecipitation; + final private ResourceLocation biomeID; + + + private final Set> tags = Sets.newHashSet(); + + private final List 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 BCLBiome parent; + private boolean vertical; + + private BiomeAPI.BiomeType biomeType; + + + BCLBiomeBuilder(ResourceLocation biomeID) { + this.biomeID = biomeID; + this.hasPrecipitation = false; + this.generationSettings = null; + this.effectsBuilder = null; + this.spawnSettings = null; + this.temperature = 1.0F; + this.fogDensity = 1.0F; + this.edgeSize = 0; + this.downfall = 1.0F; + this.genChance = 1.0F; + this.height = 0.1F; + this.vertical = false; + this.edge = null; + this.parent = null; + this.biomeType = null; + } + + /** + * Starts new biome building process. + * + * @param biomeID {@link ResourceLocation} biome identifier. + * @return prepared {@link BCLBiomeBuilder} instance. + */ + public static BCLBiomeBuilder start( + ResourceLocation biomeID + ) { + return new BCLBiomeBuilder(biomeID); + } + + public BCLBiomeBuilder addNetherClimateParamater(float temperature, float humidity, float offset) { + parameters.add(Climate.parameters(temperature, humidity, 0, 0, 0, 0, offset)); + return this; + } + + public BCLBiomeBuilder addNetherClimateParamater(float temperature, float humidity) { + return addNetherClimateParamater(temperature, humidity, 0); + } + + public BCLBiomeBuilder parentBiome(BCLBiome parent) { + this.parent = parent; + 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. + * @deprecated Use hasPrecipitation() instead + */ + @Deprecated(forRemoval = true) + public BCLBiomeBuilder precipitation(Precipitation precipitation) { + return hasPrecipitation(precipitation != Precipitation.NONE); + } + + /** + * Set biome {@link Precipitation}. Affect biome visual effects (rain, snow, none). + * + * @param precipitation true, if this biome can have presipitation. Rain/Snow is determined by temperature + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder hasPrecipitation(boolean precipitation) { + this.hasPrecipitation = 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 BCLBiomeBuilder spawn( + EntityType 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 BCLBiomeBuilder spawn( + BCLEntityWrapper 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(Holder 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(Holder 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( + Holder 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(Holder 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(Holder 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(Holder 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 feature) { + featureSupliers.add(gen -> gen.addFeature(decoration, feature)); + return this; + } + + /** + * 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, ResourceKey feature) { + featureSupliers.add(gen -> gen.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 featureAdd) { + featureSupliers.add(gen -> featureAdd.accept(gen)); + 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.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 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> carver) { + final Optional>> oKey = carver.unwrapKey(); + if (oKey.isPresent()) { + return carver(step, oKey.get()); + } + + return this; + } + + public BCLBiomeBuilder carver( + GenerationStep.Carving step, + ResourceKey> carverKey + ) { + BiomeModifications.addCarver( + ctx -> ctx.getBiomeKey().location().equals(biomeID), + step, + carverKey + ); + 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; + } + + /** + * Changes the type for the Biome. The intended Type defines in which Dimension a + * Biome is allowed to spawn. Currently each Biome can only spawn in one dimension + * + * @param type The intended type + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder intendedType(BiomeAPI.BiomeType type) { + this.biomeType = type; + return this; + } + + + /** + * Changes the intended type for the Biome to an EndLand Biome + * + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder endLandBiome() { + return intendedType(BiomeAPI.BiomeType.BCL_END_LAND); + } + + /** + * Changes the intended type for the Biome to an EndVoid (aka small islands) Biome + * + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder endVoidBiome() { + return intendedType(BiomeAPI.BiomeType.BCL_END_VOID); + } + + /** + * Changes the intended type for the Biome to an Endbarrens Biome + * + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder endBarrensBiome() { + return intendedType(BiomeAPI.BiomeType.BCL_END_BARRENS); + } + + /** + * Changes the intended type for the Biome to an End Center Island Biome + * + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder endCenterBiome() { + return intendedType(BiomeAPI.BiomeType.BCL_END_CENTER); + } + + /** + * Changes the intended type for the Biome to a Nether Biome + * + * @return same {@link BCLBiomeBuilder} instance. + */ + public BCLBiomeBuilder netherBiome() { + return intendedType(BiomeAPI.BiomeType.BCL_NETHER); + } + + public BCLBiomeBuilder tag(TagKey... tag) { + for (TagKey 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 = true; + 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>> 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(BootstapContext bootstrapContext) { + + if (generationSettings == null) { + generationSettings = new BiomeGenerationSettings.Builder( + bootstrapContext.lookup(Registries.PLACED_FEATURE), + bootstrapContext.lookup(Registries.CONFIGURED_CARVER) + ); + } + return generationSettings; + } + + /** + * Finalize biome creation. + * + * @return created {@link BCLBiome} instance. + */ + public BCLBiomeContainer build() { + return build(BCLBiome::new); + } + + + /** + * Finalize biome creation. + * + * @param biomeConstructor {@link BiomeSupplier} biome constructor. + * @return created {@link BCLBiome} instance. + */ + public BCLBiomeContainer build(BiomeSupplier biomeConstructor) { + BCLBiomeSettings settings = BCLBiomeSettings.createBCL() + .setTerrainHeight(height) + .setFogDensity(fogDensity) + .setGenChance(genChance) + .setEdgeSize(edgeSize) + .setVertical(vertical) + .build(); + final T bclBiome = biomeConstructor.apply(ResourceKey.create(Registries.BIOME, biomeID), settings); + tags.forEach(tagKey -> TagManager.BIOMES.add(tagKey, bclBiome.getBiomeKey())); + + bclBiome.addClimateParameters(parameters); + if (biomeType != null) + bclBiome._setIntendedType(biomeType); + + + BiomeBuilder builder = new BiomeBuilder() + .hasPrecipitation(hasPrecipitation) + .temperature(temperature) + .downfall(downfall); + + builder.mobSpawnSettings(getSpawns().build()); + builder.specialEffects(getEffects().build()); + + if (edge != null) { + bclBiome._setEdge(edge); + } + + //res.addBiomeTags(tags); + //res.setSurface(surfaceRule); + + //carvers.forEach(cfg -> BiomeAPI.addBiomeCarver(biome, cfg.second, cfg.first)); + final UnboundBCLBiome unbound = new UnboundBCLBiome<>( + bclBiome, + parent, + ctx -> { + BiomeGenerationSettings.Builder genBuilder = getGeneration(ctx); + featureSupliers.forEach(s -> s.accept(genBuilder)); + return builder.generationSettings(fixGenerationSettings(genBuilder.build())).build(); + } + ); + + UNBOUND_BIOMES.add(unbound); + return unbound; + } + + public static void registerUnbound(BootstapContext context) { + UNBOUND_BIOMES.forEach(u -> u.register(context)); + UNBOUND_BIOMES.clear(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeContainer.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeContainer.java new file mode 100644 index 00000000..97d73737 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeContainer.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.world.level.biome.Biome; + +public class BCLBiomeContainer { + protected final T biome; + + BCLBiomeContainer(T biome) { + this.biome = biome; + } + + public T biome() { + return biome; + } + + public BCLBiomeContainer register(BootstapContext bootstrapContext) { + return register(bootstrapContext, biome.getIntendedType()); + } + + public BCLBiomeContainer register(BootstapContext bootstrapContext, BiomeAPI.BiomeType dim) { + return this; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeRegistry.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeRegistry.java new file mode 100644 index 00000000..446f2aff --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeRegistry.java @@ -0,0 +1,268 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.BCLib; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import com.mojang.serialization.Codec; +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.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; + +import net.fabricmc.fabric.api.biome.v1.NetherBiomes; +import net.fabricmc.fabric.api.biome.v1.TheEndBiomes; +import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback; +import net.fabricmc.fabric.api.event.registry.FabricRegistryBuilder; +import net.fabricmc.fabric.api.event.registry.RegistryAttribute; +import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class BCLBiomeRegistry { + public static final ResourceKey> BCL_BIOMES_REGISTRY = + createRegistryKey(WorldsTogether.makeID("worldgen/betterx/biome")); + + public static final ResourceKey>> BCL_BIOME_CODEC_REGISTRY = + createRegistryKey(WorldsTogether.makeID("worldgen/betterx/biome_codec")); + + public static Registry> BIOME_CODECS = FabricRegistryBuilder + .from(new MappedRegistry<>(BCL_BIOME_CODEC_REGISTRY, Lifecycle.stable())) + .attribute(RegistryAttribute.MODDED) + .buildAndRegister(); + public static MappedRegistry BUILTIN_BCL_BIOMES = new MappedRegistry<>( + BCL_BIOMES_REGISTRY, + Lifecycle.stable() + ); + + /** + * 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 boolean didCreate = false; + + public static boolean isEmptyBiome(ResourceLocation l) { + return l == null || Biomes.THE_VOID.location().equals(l); + } + + public static boolean isEmptyBiome(BCLBiome b) { + return b == null || b == EMPTY_BIOME; + } + + /** + * Register a codec for a custom subclass of {@link BCLBiome}. Each subclass needs to provide + * a codec, otherwise the instance will get rebuild as a regular BCLib biome loosing the Type + * of the class as well as all member values + * + * @param location A {@link ResourceLocation} identifying this class + * @param codec The matching Codec + * @return The codec that will get used + */ + public static Codec registerBiomeCodec( + ResourceLocation location, + KeyDispatchDataCodec codec + ) { + Registry.register(BIOME_CODECS, location, codec.codec()); + return codec.codec(); + } + + /** + * Register new Biome Data + * + * @param biome The Biome Data to register + * @return The resource-key for the registry + */ + @ApiStatus.Internal + public static ResourceKey registerForDatagen(BCLBiome biome) { + if (BUILTIN_BCL_BIOMES == null) return biome.getBCLBiomeKey(); + + Registry.register( + BUILTIN_BCL_BIOMES, + biome.getBCLBiomeKey(), + biome + ); + + return biome.getBCLBiomeKey(); + } + + public static void register(BCLBiome biome) { + registerForDatagen(biome); + } + + public static BCLBiome registerIfUnknown(Holder biomeHolder, @NotNull BiomeAPI.BiomeType intendedType) { + if (biomeHolder == null) return null; + return registerIfUnknown(biomeHolder.unwrapKey().orElse(null), intendedType); + } + + public static BCLBiome registerIfUnknown(ResourceKey biomeKey, @NotNull BiomeAPI.BiomeType intendedType) { + if (biomeKey != null) { + if (!hasBiome(biomeKey, BCLBiomeRegistry.registryOrNull())) { + final var bclBiome = new BCLBiome( + biomeKey.location(), + intendedType + ); + BCLBiomeRegistry.register(bclBiome); + return bclBiome; + } + } + return null; + } + + public static boolean hasBiome(ResourceKey key, Registry bclBiomes) { + return hasBiome(key.location(), bclBiomes); + } + + public static boolean hasBiome(ResourceLocation loc, Registry bclBiomes) { + if (loc == null) return false; + if (bclBiomes != null && bclBiomes.containsKey(loc)) { + return true; + } + + return BUILTIN_BCL_BIOMES.containsKey(loc); + } + + public static BCLBiome getBiome(ResourceKey key, Registry bclBiomes) { + return getBiome(key.location(), bclBiomes); + } + + public static BCLBiome getBiome(ResourceLocation loc, Registry bclBiomes) { + if (bclBiomes != null && bclBiomes.containsKey(loc)) { + return bclBiomes.get(loc); + } + + return BUILTIN_BCL_BIOMES.get(loc); + } + + public static BCLBiome getBiomeOrNull(ResourceLocation loc, Registry bclBiomes) { + if (!hasBiome(loc, bclBiomes)) return null; + return getBiome(loc, bclBiomes); + } + + public static BCLBiome getBiomeOrNull(ResourceKey key, Registry bclBiomes) { + return getBiomeOrNull(key.location(), bclBiomes); + } + + public static BCLBiome getBiomeOrEmpty(ResourceLocation loc, Registry bclBiomes) { + if (!hasBiome(loc, bclBiomes)) return EMPTY_BIOME; + return getBiome(loc, bclBiomes); + } + + public static BCLBiome getBiomeOrEmpty(ResourceKey key, Registry bclBiomes) { + return getBiomeOrEmpty(key.location(), bclBiomes); + } + + private static ResourceKey> createRegistryKey(ResourceLocation location) { + return ResourceKey.createRegistryKey(location); + } + + + private static Codec bootstrapCodecs(Registry> registry) { + return Registry.register(registry, BCLib.makeID("biome"), BCLBiome.KEY_CODEC.codec()); + } + + public static Registry registryOrNull() { + if (WorldBootstrap.getLastRegistryAccess() == null) return null; + return WorldBootstrap.getLastRegistryAccess().registry(BCL_BIOMES_REGISTRY).orElse(null); + } + + + public static Stream> getAll(BiomeAPI.BiomeType dim) { + Set> result = new HashSet<>(); + final Registry reg = registryOrNull(); + if (reg != null) { + reg.entrySet() + .stream() + .filter(e -> e.getValue().getIntendedType().is(dim)) + .map(e -> e.getKey()) + .forEach(k -> result.add(k)); + } + + if (BUILTIN_BCL_BIOMES != null) { + BUILTIN_BCL_BIOMES + .entrySet() + .stream() + .filter(e -> e.getValue().getIntendedType().is(dim)) + .map(e -> e.getKey()) + .filter(k -> !result.contains(k)) + .forEach(k -> result.add(k)); + } + return result.stream(); + } + + private static Registry getBclBiomesRegistry(@Nullable RegistryAccess access) { + if (access != null) { + return access + .registry(BCLBiomeRegistry.BCL_BIOMES_REGISTRY) + .orElse(BUILTIN_BCL_BIOMES); + } else { + return BUILTIN_BCL_BIOMES; + } + } + + @ApiStatus.Internal + public static void register() { + bootstrapCodecs(BIOME_CODECS); + } + + @ApiStatus.Internal + public static void bootstrap(BootstapContext ctx) { + //copy from builtin, disabled as we do not bootstrap any biomes, all are loaded from the default datapack + //vanilla biomes are provided by bclib +// for (Map.Entry, BCLBiome> e : BUILTIN_BCL_BIOMES.entrySet()) { +// ctx.register(e.getKey(), e.getValue()); +// } + } + + private static void onBiomeLoad(Registry registry, int rawID, ResourceLocation id, BCLBiome biome) { + //this ensures that all BCL Manage Biomes get added to the fabric Biome-API on load + if (!"minecraft".equals(id.getNamespace())) { + if (biome.getIntendedType().is(BiomeAPI.BiomeType.BCL_NETHER)) { + for (var params : biome.parameterPoints) { + NetherBiomes.addNetherBiome(biome.getBiomeKey(), params); + } + } else if (biome.getIntendedType().is(BiomeAPI.BiomeType.BCL_END_CENTER)) { + TheEndBiomes.addMainIslandBiome(biome.getBiomeKey(), 1.0); + } else if (biome.getIntendedType().is(BiomeAPI.BiomeType.BCL_END_BARRENS)) { + TheEndBiomes.addBarrensBiome(biome.getParentBiome().getBiomeKey(), biome.getBiomeKey(), 1.0); + } else if (biome.getIntendedType().is(BiomeAPI.BiomeType.BCL_END_LAND)) { + TheEndBiomes.addHighlandsBiome(biome.getBiomeKey(), 1.0); + TheEndBiomes.addMidlandsBiome(biome.getBiomeKey(), biome.getBiomeKey(), 1.0); + } else if (biome.getIntendedType().is(BiomeAPI.BiomeType.BCL_END_VOID)) { + TheEndBiomes.addSmallIslandsBiome(biome.getBiomeKey(), 1.0); + } else { + BCLib.LOGGER.info("Did not manage biome " + biome); + } + + //System.out.println("Loaded " + biome); + } + } + + static { + DynamicRegistrySetupCallback.EVENT.register(registryManager -> { + Optional> oBCLBiomeRegistry = registryManager.asDynamicRegistryManager() + .registry(BCLBiomeRegistry.BCL_BIOMES_REGISTRY); + if (oBCLBiomeRegistry.isPresent()) { + final Registry registry = oBCLBiomeRegistry.orElseThrow(); + RegistryEntryAddedCallback + .event(oBCLBiomeRegistry.get()) + .register((rawId, loc, biome) -> BCLBiomeRegistry.onBiomeLoad(registry, rawId, loc, biome)); + } else { + BCLib.LOGGER.warning("No valid BCLBiome Registry available!"); + } + }); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeSettings.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeSettings.java new file mode 100644 index 00000000..f9762069 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BCLBiomeSettings.java @@ -0,0 +1,158 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +public class BCLBiomeSettings { + public static Builder createBCL() { + return new Builder(); + } + + public static class Builder extends CommonBuilder { + public Builder() { + super(new BCLBiomeSettings()); + } + } + + public static class 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; + } + + /** + * Sets fog density for this biome. + * + * @param fogDensity + * @return same {@link Builder}. + */ + public R setFogDensity(float fogDensity) { + storage.fogDensity = fogDensity; + return (R) this; + } + } + + BCLBiomeSettings( + float terrainHeight, + float fogDensity, + float genChance, + int edgeSize, + boolean vertical + ) { + this.terrainHeight = terrainHeight; + this.fogDensity = fogDensity; + this.genChance = genChance; + this.edgeSize = edgeSize; + this.vertical = vertical; + } + + protected BCLBiomeSettings() { + this.terrainHeight = 0.1F; + this.fogDensity = 1.0F; + this.genChance = 1.0F; + this.edgeSize = 0; + this.vertical = false; + } + + float terrainHeight; + float fogDensity; + float genChance; + int edgeSize; + boolean vertical; + + + 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; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BiomeAPI.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BiomeAPI.java new file mode 100644 index 00000000..591102a2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BiomeAPI.java @@ -0,0 +1,875 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.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 org.betterx.worlds.together.world.event.WorldBootstrap; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.*; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.BootstapContext; +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.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 java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class BiomeAPI { + + public static final BCLBiome THE_END = InternalBiomeAPI.wrapBiome( + Biomes.THE_END, + InternalBiomeAPI.OTHER_END_CENTER + ); + public static final BCLBiome NETHER_WASTES_BIOME = InternalBiomeAPI.wrapBiome( + Biomes.NETHER_WASTES, + InternalBiomeAPI.OTHER_NETHER + ); + public static final BCLBiome CRIMSON_FOREST_BIOME = InternalBiomeAPI.wrapBiome( + Biomes.CRIMSON_FOREST, + InternalBiomeAPI.OTHER_NETHER + ); + public static final BCLBiome WARPED_FOREST_BIOME = InternalBiomeAPI.wrapBiome( + Biomes.WARPED_FOREST, + InternalBiomeAPI.OTHER_NETHER + ); + public static final BCLBiome SOUL_SAND_VALLEY_BIOME = InternalBiomeAPI.wrapBiome( + Biomes.SOUL_SAND_VALLEY, + InternalBiomeAPI.OTHER_NETHER + ); + public static final BCLBiome BASALT_DELTAS_BIOME = InternalBiomeAPI.wrapBiome( + Biomes.BASALT_DELTAS, + InternalBiomeAPI.OTHER_NETHER + ); + public static final BCLBiome END_MIDLANDS = InternalBiomeAPI.wrapBiome( + Biomes.END_MIDLANDS, + 0.5F, + InternalBiomeAPI.OTHER_END_LAND + ); + public static final BCLBiome END_HIGHLANDS = InternalBiomeAPI.wrapBiome( + Biomes.END_HIGHLANDS, + END_MIDLANDS, + 8, + 0.5F, + InternalBiomeAPI.OTHER_END_LAND + ); + public static final BCLBiome END_BARRENS = InternalBiomeAPI.wrapBiome( + Biomes.END_BARRENS, + InternalBiomeAPI.OTHER_END_BARRENS + ); + public static final BCLBiome SMALL_END_ISLANDS = InternalBiomeAPI.wrapBiome( + 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} + */ + static BCLBiome registerBuiltinBiomeAndOverrideIntendedDimension( + BootstapContext bootstrapContext, + BCLBiome bclbiome, + BiomeType dim + ) { + + bclbiome._setIntendedType(dim); + return registerBiome(bootstrapContext, bclbiome); + } + + public static class BiomeType { + public static final Codec DIRECT_CODEC = RecordCodecBuilder.create(instance -> instance + .group( + Codec.STRING.fieldOf("name") + .orElse("undefined") + .forGetter(o -> o.name) + + ).apply(instance, BiomeType::create)); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + Codec.STRING.fieldOf("name") + .orElse("undefined") + .forGetter(o -> o.name), + Codec.STRING.fieldOf("parent") + .orElse("none") + .forGetter(o -> o.parentOrNull == null ? "none" : o.parentOrNull.name) + + ).apply(instance, BiomeType::create)); + + private static final Map KNOWN_TYPES = new HashMap<>(); + + 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, (biome, ignored) -> { + ResourceKey key = biome.getBiomeKey(); + if (!biome.isEdgeBiome()) { + biome.forEachClimateParameter(p -> NetherBiomes.addNetherBiome(key, p)); + } + }); + 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, (biome, ignored) -> { + float weight = biome.settings.getGenChance(); + ResourceKey key = biome.getBiomeKey(); + + if (biome.isEdgeBiome()) { + ResourceKey parentKey = biome.getParentBiome().getBiomeKey(); + TheEndBiomes.addMidlandsBiome(parentKey, key, weight); + } else { + TheEndBiomes.addHighlandsBiome(key, weight); + } + }); + public static final BiomeType BCL_END_VOID = new BiomeType("BCL_END_VOID", END_VOID, (biome, ignored) -> { + float weight = biome.settings.getGenChance(); + ResourceKey key = biome.getBiomeKey(); + if (!biome.isEdgeBiome()) { + TheEndBiomes.addSmallIslandsBiome(key, weight); + } + }); + public static final BiomeType BCL_END_CENTER = new BiomeType("BCL_END_CENTER", END_CENTER, (biome, ignored) -> { + float weight = biome.settings.getGenChance(); + ResourceKey key = biome.getBiomeKey(); + if (!biome.isEdgeBiome()) { + TheEndBiomes.addMainIslandBiome(key, weight); + } + }); + + public static final BiomeType BCL_END_BARRENS = new BiomeType( + "BCL_END_BARRENS", + END_BARRENS, + (biome, highlandBiome) -> { + float weight = biome.settings.getGenChance(); + ResourceKey key = biome.getBiomeKey(); + if (!biome.isEdgeBiome()) { + ResourceKey parentKey = highlandBiome.getBiomeKey(); + TheEndBiomes.addBarrensBiome(parentKey, key, weight); + } + } + ); + + public final BiomeType parentOrNull; + private final String name; + + public static BiomeType getMainBiomeTypeForDimension(ResourceKey key) { + if (key.equals(LevelStem.END)) return END_LAND; + else if (key.equals(LevelStem.NETHER)) return NETHER; + else if (key.equals(LevelStem.OVERWORLD)) return OVERWORLD; + return null; + } + + @FunctionalInterface + interface ExtraRegisterTaks { + void register(@NotNull BCLBiome biome, @Nullable BCLBiome parent); + } + + final ExtraRegisterTaks extraRegisterTask; + + private static BiomeType create(String name, String parentOrNull) { + BiomeType known = KNOWN_TYPES.get(name); + BiomeType parent = parentOrNull == null || "none".equals(parentOrNull) + ? null + : KNOWN_TYPES.get(parentOrNull); + if (known != null) { + if (known.parentOrNull != parent) { + BCLib.LOGGER.warning("BiomeType " + name + " was deserialized with parent " + parent + " but already has " + known.parentOrNull); + } + return known; + } + return new BiomeType(name, parent); + } + + static BiomeType create(String name) { + BiomeType known = KNOWN_TYPES.get(name); + if (known != null) { + return known; + } + return NONE; + } + + public BiomeType(String name) { + this(name, null); + } + + public BiomeType(String name, BiomeType parentOrNull) { + this(name, parentOrNull, (b, a) -> { + }); + } + + public BiomeType(String name, BiomeType parentOrNull, ExtraRegisterTaks extraRegisterTask) { + this.parentOrNull = parentOrNull; + this.name = name; + this.extraRegisterTask = extraRegisterTask; + KNOWN_TYPES.put(name, this); + } + + 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; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BiomeType biomeType = (BiomeType) o; + return name.equals(biomeType.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + } + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * + * @param bclbiome {@link BCLBiome} + * @return {@link BCLBiome} + */ + static BCLBiome registerBiome(BootstapContext bootstrapContext, BCLBiome bclbiome) { + HolderGetter registryOrNull = bootstrapContext.lookup(Registries.BIOME); + if (registryOrNull != null + && bclbiome._hasBiomeToRegister() + && registryOrNull.get(bclbiome.getBiomeKey()).map(v -> v.isBound()).orElse(false) == false) { + bootstrapContext.register(bclbiome.getBiomeKey(), bclbiome._getBiomeToRegister()); + + BCLBiomeRegistry.registerForDatagen(bclbiome); + } + + return finishBiomeRegistration(bclbiome); + } + + static BCLBiome finishBiomeRegistration(BCLBiome bclbiome) { + BiomeType dim = bclbiome.getIntendedType(); + if (dim != null && dim.is(BiomeType.NETHER)) { + TagManager.BIOMES.add(BiomeTags.IS_NETHER, bclbiome.getBiomeKey()); + } else if (dim != null && dim.is(BiomeType.END)) { + TagManager.BIOMES.add(BiomeTags.IS_END, bclbiome.getBiomeKey()); + + if (dim.is(BiomeType.END_VOID)) { + TagManager.BIOMES.add(CommonBiomeTags.IS_SMALL_END_ISLAND, bclbiome.getBiomeKey()); + } else if (dim.is(BiomeType.END_BARRENS)) { + TagManager.BIOMES.add(CommonBiomeTags.IS_END_BARRENS, bclbiome.getBiomeKey()); + } else if (dim.is(BiomeType.END_LAND)) { + TagManager.BIOMES.add(CommonBiomeTags.IS_END_HIGHLAND, bclbiome.getBiomeKey()); + } else if (dim.is(BiomeType.END_CENTER)) { + TagManager.BIOMES.add(CommonBiomeTags.IS_END_CENTER, bclbiome.getBiomeKey()); + } + } + + + bclbiome.afterRegistration(); + + return bclbiome; + } + + static BCLBiome registerSubBiome( + BootstapContext bootstrapContext, + BCLBiome parent, + BCLBiome subBiome + ) { + return registerSubBiome( + bootstrapContext, + parent, + subBiome, + parent.getIntendedType() + ); + } + + static BCLBiome registerSubBiome( + BootstapContext bootstrapContext, + BCLBiome parent, + BCLBiome subBiome, + BiomeType dim + ) { + registerBuiltinBiomeAndOverrideIntendedDimension(bootstrapContext, subBiome, dim); + parent.addSubBiome(subBiome); + + return subBiome; + } + + /** + * 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} + */ + static BCLBiome registerEndLandBiome(BootstapContext bootstrapContext, BCLBiome biome) { + registerBuiltinBiomeAndOverrideIntendedDimension(bootstrapContext, biome, BiomeType.BCL_END_LAND); + + float weight = biome.settings.getGenChance(); + ResourceKey key = biome.getBiomeKey(); + + if (biome.isEdgeBiome()) { + ResourceKey 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} + */ + static BCLBiome registerEndVoidBiome(BootstapContext bootstrapContext, BCLBiome biome) { + registerBuiltinBiomeAndOverrideIntendedDimension(bootstrapContext, biome, BiomeType.BCL_END_VOID); + + float weight = biome.settings.getGenChance(); + ResourceKey key = biome.getBiomeKey(); + if (!biome.isEdgeBiome()) { + 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} + */ + static BCLBiome registerEndCenterBiome(BootstapContext bootstrapContext, BCLBiome biome) { + registerBuiltinBiomeAndOverrideIntendedDimension(bootstrapContext, biome, BiomeType.BCL_END_CENTER); + + float weight = biome.settings.getGenChance(); + ResourceKey key = biome.getBiomeKey(); + if (!biome.isEdgeBiome()) { + 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} + */ + static BCLBiome registerEndBarrensBiome( + BootstapContext bootstrapContext, + BCLBiome highlandBiome, + BCLBiome biome + ) { + registerBuiltinBiomeAndOverrideIntendedDimension(bootstrapContext, biome, BiomeType.BCL_END_BARRENS); + + float weight = biome.settings.getGenChance(); + ResourceKey key = biome.getBiomeKey(); + if (!biome.isEdgeBiome()) { + ResourceKey parentKey = highlandBiome.getBiomeKey(); + TheEndBiomes.addBarrensBiome(parentKey, key, weight); + } + return biome; + } + + + /** + * 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} + */ + static BCLBiome registerNetherBiome(BootstapContext bootstrapContext, BCLBiome bclBiome) { + registerBuiltinBiomeAndOverrideIntendedDimension(bootstrapContext, bclBiome, BiomeType.BCL_NETHER); + + ResourceKey key = bclBiome.getBiomeKey(); + if (!bclBiome.isEdgeBiome()) { + bclBiome.forEachClimateParameter(p -> NetherBiomes.addNetherBiome(key, p)); + } + return bclBiome; + } + + /** + * 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}. + */ + public static BCLBiome getRenderBiome(Biome biome) { + BCLBiome endBiome = InternalBiomeAPI.CLIENT.get(biome); + if (endBiome == null) { + var acc = WorldBootstrap.getLastRegistryAccessOrElseBuiltin(); + if (acc != null) { + final Registry reg = BCLBiomeRegistry.registryOrNull(); + ResourceLocation id = acc + .registryOrThrow(Registries.BIOME) + .getKey(biome); + endBiome = BCLBiomeRegistry.getBiomeOrEmpty(id, reg); + 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> key = InternalBiomeAPI.biomeRegistry.getResourceKey(biome); + if (key.isPresent()) return key.get(); + } + + return 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) { + final ResourceKey key = getBiomeKey(biome); + if (key != null) + id = key.location(); + } + + if (id == null) { + BCLib.LOGGER.error("Unable to get ID for " + biome + ". Falling back to empty Biome..."); + id = BCLBiomeRegistry.EMPTY_BIOME.getID(); + } + + return id; + } + + /** + * Get biome {@link ResourceLocation} from given {@link Biome}. + * + * @param biome - {@link Holder} from server world. + * @return biome {@link ResourceLocation}. + */ + public static ResourceLocation getBiomeID(Holder biome) { + return biome + .unwrapKey() + .map(h -> h.location()) + .orElse(null); + } + + + /** + * Get {@link BCLBiome} from given {@link ResourceLocation}. + * + * @param biomeID - biome {@link ResourceLocation}. + * @return {@link BCLBiome} or {@code null}. + */ + public static @Nullable BCLBiome getBiome(ResourceLocation biomeID) { + return BCLBiomeRegistry.getBiomeOrNull(biomeID, BCLBiomeRegistry.registryOrNull()); + } + + /** + * Get {@link BCLBiome} from given {@link Biome}. + * + * @param biome - biome {@link Biome}. + * @return {@link BCLBiome} or {@code null}. + */ + public static @Nullable 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 null}. + */ + public static @Nullable BCLBiome getBiome(Holder biome) { + return getBiome(BiomeAPI.getBiomeID(biome)); + } + + public static Holder getFromRegistry(ResourceLocation biomeID) { + if (InternalBiomeAPI.biomeRegistry != null) { + var holder = InternalBiomeAPI.biomeRegistry.getHolder(ResourceKey.create(Registries.BIOME, biomeID)); + if (holder.isPresent()) return holder.get(); + } + + if (WorldBootstrap.getLastRegistryAccess() != null) { + var reg = WorldBootstrap.getLastRegistryAccess().registryOrThrow(Registries.BIOME); + if (reg.containsKey(biomeID)) { + return reg.getHolderOrThrow(ResourceKey.create(Registries.BIOME, biomeID)); + } + } + return null; + } + + public static boolean wasRegisteredAs(ResourceLocation biomeID, BiomeType dim) { + if (BCLBiomeRegistry.isEmptyBiome(biomeID)) return false; + final BCLBiome res = BCLBiomeRegistry.getBiomeOrEmpty(biomeID, BCLBiomeRegistry.registryOrNull()); + if (res == null) return false; + return res.getIntendedType().is(dim); + } + + 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 dimensionID, + BiConsumer> modification + ) { + List>> 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> 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> 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> 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> modification + ) { + List>> 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> 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> modification) { + onFinishingBiomeTags(Level.END, modification); + } + + + /** + * 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, + 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, Decoration step, Holder... 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, + Decoration step, + List> additionalFeatures + ) { + BiomeGenerationSettingsAccessor accessor = (BiomeGenerationSettingsAccessor) biome.value() + .getGenerationSettings(); + List> allFeatures = CollectionsUtil.getMutable(accessor.bclib_getFeatures()); + List> features = getFeaturesListCopy(allFeatures, step); + + for (var feature : additionalFeatures) { + if (!features.contains(feature)) + features.add(feature); + } + + allFeatures.set(step.ordinal(), HolderSet.direct(features)); + final Supplier>> 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> 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 void addBiomeMobSpawn( + Holder biome, + EntityType entityType, + int weight, + int minGroupCount, + int maxGroupCount + ) { + final MobCategory category = entityType.getCategory(); + MobSpawnSettingsAccessor accessor = (MobSpawnSettingsAccessor) biome.value().getMobSettings(); + Map> spawners = CollectionsUtil.getMutable(accessor.bcl_getSpawners()); + List 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 findTopMaterial(WorldGenLevel world, BlockPos pos) { + return findTopMaterial(getBiome(world.getBiome(pos))); + } + + public static Optional findTopMaterial(Holder biome) { + return findTopMaterial(getBiome(biome.value())); + } + + public static Optional findTopMaterial(Biome biome) { + return findTopMaterial(getBiome(biome)); + } + + public static Optional findTopMaterial(BCLBiome biome) { + if (biome instanceof SurfaceMaterialProvider smp) { + return Optional.of(smp.getTopMaterial()); + } + return Optional.empty(); + } + + public static Optional findUnderMaterial(Holder biome) { + return findUnderMaterial(getBiome(biome.value())); + } + + public static Optional 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} instance. Should be biome from world. + */ + public static void setBiome(ChunkAccess chunk, BlockPos pos, Holder biome) { + int sectionY = (pos.getY() - chunk.getMinBuildHeight()) >> 4; + PalettedContainerRO> biomes = chunk.getSection(sectionY).getBiomes(); + if (biomes instanceof PalettedContainer> 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} instance. Should be biome from world. + */ + public static void setBiome(LevelAccessor level, BlockPos pos, Holder biome) { + ChunkAccess chunk = level.getChunk(pos); + setBiome(chunk, pos, biome); + } + + + private static List> getFeaturesListCopy( + List> features, + Decoration step + ) { + return getFeaturesListCopy(features, step.ordinal()); + } + + private static List> getFeaturesListCopy(List> features, int index) { + while (features.size() <= index) { + features.add(HolderSet.direct(Lists.newArrayList())); + } + return features.get(index).stream().collect(Collectors.toList()); + } + + public static List getAllBiomes(BiomeType type) { + List res = new ArrayList<>(); + var access = WorldBootstrap.getLastRegistryAccess(); + Registry reg; + if (access == null) reg = BCLBiomeRegistry.BUILTIN_BCL_BIOMES; + else reg = access.registryOrThrow(BCLBiomeRegistry.BCL_BIOMES_REGISTRY); + + for (var e : reg.entrySet()) { + if (e.getValue().getIntendedType().is(type)) { + res.add(e.getValue()); + } + } + + return res; + } + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BiomeData.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BiomeData.java new file mode 100644 index 00000000..2997ef38 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/BiomeData.java @@ -0,0 +1,15 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import com.mojang.serialization.Codec; +import net.minecraft.util.KeyDispatchDataCodec; + +import java.util.function.Function; + +public interface BiomeData { + Codec CODEC = BCLBiomeRegistry + .BIOME_CODECS + .byNameCodec() + .dispatch(b -> b.codec().codec(), Function.identity()); + + KeyDispatchDataCodec codec(); +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/InternalBiomeAPI.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/InternalBiomeAPI.java new file mode 100644 index 00000000..ab24fcb5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/InternalBiomeAPI.java @@ -0,0 +1,328 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.Configs; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderGetter; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +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.dimension.LevelStem; +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.concurrent.atomic.AtomicInteger; +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 CLIENT = Maps.newHashMap(); + static final Map, Integer> FEATURE_ORDER = Maps.newHashMap(); + + static final Map, AtomicInteger> BIOME_ADDITIONS = Maps.newHashMap(); + static final MutableInt FEATURE_ORDER_ID = new MutableInt(0); + static final Map, List>>> MODIFICATIONS = Maps.newHashMap(); + static final Map>>> TAG_ADDERS = Maps.newHashMap(); + static Registry biomeRegistry; + static RegistryAccess registryAccess; + + 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 biomeRegistry = access.registry(Registries.BIOME).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. + *

+ * 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> 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> 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> 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 s = null; + if (mod.getKey() == Level.NETHER) + s = BCLBiomeRegistry.getAll(BiomeAPI.BiomeType.NETHER).map(k -> k.location()); + else if (mod.getKey() == Level.END) + s = BCLBiomeRegistry.getAll(BiomeAPI.BiomeType.END).map(k -> k.location()); + if (s != null) { + s.forEach(id -> { + Holder 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); + } + }); + } + } + } + + public static void applyModifications(BiomeSource source, ResourceKey dimension) { + if (Configs.MAIN_CONFIG.verboseLogging()) + BCLib.LOGGER.info("\nApply Modifications for " + dimension.location() + source.toString() + .replace("\n", "\n ")); + + final Set> biomes = source.possibleBiomes(); + List>> modifications = MODIFICATIONS.get(dimension); + for (Holder biomeHolder : biomes) { + if (biomeHolder.isBound()) { + applyModificationsAndUpdateFeatures(modifications, biomeHolder); + } + } + } + + private static void applyModificationsAndUpdateFeatures( + List>> modifications, + Holder biome + ) { + ResourceLocation biomeID = BiomeAPI.getBiomeID(biome); + if (modifications != null) { + modifications.forEach(consumer -> { + consumer.accept(biomeID, biome); + }); + } + } + + private static final Set 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 wrapBiome(ResourceKey biomeKey, BiomeAPI.BiomeType type) { + return wrapBiome(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 <0 the default genChance is used + * @return {@link BCLBiome} + */ + public static BCLBiome wrapBiome(ResourceKey biomeKey, float genChance, BiomeAPI.BiomeType type) { + return wrapBiome( + biomeKey, + genChance < 0 ? null : VanillaBiomeSettings.createVanilla().setGenChance(genChance).build(), + type + ); + } + + public static BCLBiome wrapBiome( + ResourceKey biomeKey, + BCLBiome edgeBiome, + int edgeBiomeSize, + float genChance, + BiomeAPI.BiomeType type + ) { + VanillaBiomeSettings.Builder settings = VanillaBiomeSettings.createVanilla(); + if (genChance >= 0) settings.setGenChance(genChance); + settings.setEdgeSize(edgeBiomeSize); + + final BCLBiome b = wrapBiome(biomeKey, settings.build(), type); + b._setEdge(edgeBiome); + return b; + } + + /** + * Create a wrapper for a vanilla {@link Biome}. + * + * @param biomeKey The source biome to wrap + * @param setings the {@link VanillaBiomeSettings} to use + * @return {@link BCLBiome} + */ + private static BCLBiome wrapBiome( + ResourceKey biomeKey, + VanillaBiomeSettings setings, + BiomeAPI.BiomeType type + ) { + final Registry reg = BCLBiomeRegistry.registryOrNull(); + if (BCLBiomeRegistry.hasBiome(biomeKey, reg)) { + return BCLBiomeRegistry.getBiome(biomeKey, reg); + } + + BCLBiome bclBiome = new BCLBiome(biomeKey, setings); + bclBiome._setIntendedType(type); + + registerBuiltinBiome(bclBiome); + 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 biomeKey The source biome to wrap + * @return {@link BCLBiome} + */ + public static BCLBiome wrapNativeBiome(ResourceKey biomeKey, BiomeAPI.BiomeType type) { + final Registry reg = BCLBiomeRegistry.registryOrNull(); + if (!BCLBiomeRegistry.hasBiome(biomeKey, reg)) { + BCLBiome bclBiome = wrapBiome(biomeKey, type); + BCLBiomeRegistry.register(bclBiome); + registerBuiltinBiome(bclBiome); + return bclBiome; + } else { + return BCLBiomeRegistry.getBiome(biomeKey, reg); + } + + + } + + static { + DynamicRegistrySetupCallback.EVENT.register(registryManager -> { + Optional> oBiomeRegistry = registryManager.asDynamicRegistryManager() + .registry(Registries.BIOME); + if (oBiomeRegistry.isPresent()) { + RegistryEntryAddedCallback + .event(oBiomeRegistry.get()) + .register((rawId, id, biome) -> { + BCLBiome b = BiomeAPI.getBiome(id); + if (!"minecraft".equals(id.getNamespace()) && BCLBiomeRegistry.isEmptyBiome(b)) { + //BCLib.LOGGER.info(" #### " + rawId + ", " + biome + ", " + id); + //BIOMES_TO_SORT.add(id); +// BIOME_ADDITIONS.computeIfAbsent(oBiomeRegistry.get(), reg -> new AtomicInteger(0)) +// .incrementAndGet(); + } + }); + } else { + BCLib.LOGGER.warning("No valid Biome Registry available!"); + } + }); + + } + + /** + * The BCLBiomeSource keeps track of Modifications that happen after the BiomeSource was initialized. + * This appears to happen especially for new Worlds where the Biome Source is deserialized + * when the WolrdPreset registry is built for the CreateScreen. However Farbic Biomes are not yet + * added to the biomeRegistry at this stage. + * The counter is incremented in the DynamicRegistrySetupCallback.EVENT for the Biome Registry + * + * @param registry The registry you want to check + * @return The current number of additions since the world creation was started + */ + public static int getBiomeRegistryModificationCount(HolderGetter registry) { + if (registry == null) return 0; + return BIOME_ADDITIONS.computeIfAbsent(registry, reg -> new AtomicInteger(0)).get(); + } + + /** + * Register {@link BCLBiome} instance and its {@link Biome} if necessary. + * + * @param bclbiome {@link BCLBiome} + * @return {@link BCLBiome} + */ + + public static BCLBiome registerBuiltinBiome(BCLBiome bclbiome) { + return BiomeAPI.finishBiomeRegistration(bclbiome); + } + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/UnboundBCLBiome.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/UnboundBCLBiome.java new file mode 100644 index 00000000..1cc5acb3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/UnboundBCLBiome.java @@ -0,0 +1,78 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.world.level.biome.Biome; + +import java.util.Objects; + +class UnboundBCLBiome extends BCLBiomeContainer { + private final BCLBiome parentBiome; + private final BCLBiomeBuilder.BuildCompletion supplier; + private BCLBiomeContainer registered; + + UnboundBCLBiome(T biome, BCLBiome parentBiome, BCLBiomeBuilder.BuildCompletion supplier) { + super(biome); + this.parentBiome = parentBiome; + this.supplier = supplier; + } + + + @Override + public BCLBiomeContainer register(BootstapContext bootstrapContext, BiomeAPI.BiomeType dim) { + if (registered != null) return registered; + if (dim == null) dim = BiomeAPI.BiomeType.NONE; + + biome._setBiomeToRegister(this.supplier.apply(bootstrapContext)); + + if (hasParent()) { + BiomeAPI.registerSubBiome(bootstrapContext, parentBiome, biome, dim); + } else if (dim.is(BiomeAPI.BiomeType.END_LAND)) { + BiomeAPI.registerEndLandBiome(bootstrapContext, biome); + } else if (dim.is(BiomeAPI.BiomeType.END_VOID)) { + BiomeAPI.registerEndVoidBiome(bootstrapContext, biome); + } else if (dim.is(BiomeAPI.BiomeType.END_BARRENS)) { + BiomeAPI.registerEndBarrensBiome(bootstrapContext, parentBiome, biome); + } else if (dim.is(BiomeAPI.BiomeType.END_CENTER)) { + BiomeAPI.registerEndCenterBiome(bootstrapContext, biome); + } else if (dim.is(BiomeAPI.BiomeType.NETHER)) { + BiomeAPI.registerNetherBiome(bootstrapContext, biome); + } else { + BiomeAPI.registerBuiltinBiomeAndOverrideIntendedDimension(bootstrapContext, biome, dim); + } + + BCLBiomeBuilder.UNBOUND_BIOMES.remove(this); + registered = new BCLBiomeContainer<>(this.biome); + return registered; + } + + @Override + public T biome() { + return biome; + } + + public boolean hasParent() { + return parentBiome != null; + } + + public BCLBiome parentBiome() { + return parentBiome; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + UnboundBCLBiome that = (UnboundBCLBiome) obj; + return Objects.equals(this.biome, that.biome); + } + + @Override + public int hashCode() { + return Objects.hash(biome); + } + + @Override + public String toString() { + return "UnregisteredBiome[" + "biome=" + biome + ']'; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/VanillaBiomeSettings.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/VanillaBiomeSettings.java new file mode 100644 index 00000000..33db568e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/biomes/VanillaBiomeSettings.java @@ -0,0 +1,14 @@ +package org.betterx.bclib.api.v2.levelgen.biomes; + + +public class VanillaBiomeSettings extends BCLBiomeSettings { + public static class Builder extends BCLBiomeSettings.CommonBuilder { + public Builder() { + super(new VanillaBiomeSettings()); + } + } + + public static Builder createVanilla() { + return new Builder(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/ScatterFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/ScatterFeatureConfig.java new file mode 100644 index 00000000..ef88a4ec --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/config/ScatterFeatureConfig.java @@ -0,0 +1,548 @@ +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 extends Function15, Optional, Optional, 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 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 tipBlock, + Optional bottomBlock, + Optional 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 Codec buildCodec(Instancer 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 { + private BlockStateProvider clusterBlock; + private BlockStateProvider tipBlock; + private BlockStateProvider bottomBlock; + private Optional 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 instancer; + + public Builder(Instancer instancer) { + this.instancer = instancer; + } + + public static Builder start(Instancer instancer) { + return new Builder<>(instancer); + } + + public Builder block(Block b) { + return block(b.defaultBlockState()); + } + + public Builder singleBlock(Block b) { + return block(b.defaultBlockState()).heightRange(1, 1).spread(0, 0, ConstantInt.of(0)); + } + + public Builder 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 tipBlock(BlockState s) { + tipBlock = BlockStateProvider.simple(s); + return this; + } + + public Builder bottomBlock(BlockState s) { + bottomBlock = BlockStateProvider.simple(s); + return this; + } + + public Builder tripleShape(Block s) { + return tripleShape(s.defaultBlockState()); + } + + public Builder 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 tripleShapeCeil(Block s) { + return tripleShapeCeil(s.defaultBlockState()); + } + + public Builder 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 block(BlockStateProvider s) { + this.clusterBlock = s; + if (tipBlock == null) tipBlock = s; + if (bottomBlock == null) bottomBlock = s; + return this; + } + + public Builder tipBlock(BlockStateProvider s) { + tipBlock = s; + return this; + } + + public Builder bottomBlock(BlockStateProvider s) { + bottomBlock = s; + return this; + } + + public Builder heightRange(int min, int max) { + minHeight = min; + maxHeight = max; + return this; + } + + public Builder growWhileFree() { + growWhileFree = true; + return this; + } + + public Builder minHeight(int h) { + minHeight = h; + return this; + } + + public Builder maxHeight(int h) { + maxHeight = h; + return this; + } + + public Builder generateBaseBlock(BlockState baseState) { + return generateBaseBlock(baseState, 1, 0, 0, 0); + } + + public Builder generateBaseBlock(BlockState baseState, float baseReplaceChance) { + return generateBaseBlock(baseState, baseReplaceChance, 0, 0, 0); + } + + + public Builder generateBaseBlock( + BlockState baseState, + float chanceOfDirectionalSpread, + float chanceOfSpreadRadius2, + float chanceOfSpreadRadius3 + ) { + return generateBaseBlock( + baseState, + 1, + chanceOfDirectionalSpread, + chanceOfSpreadRadius2, + chanceOfSpreadRadius3 + ); + } + + public Builder 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 noSpread() { + return spread(0, 0, ConstantInt.of(0)); + } + + + public Builder spread(float maxSpread, float sizeVariation) { + return spread(maxSpread, sizeVariation, ConstantInt.of((int) Math.min(16, 4 * maxSpread * maxSpread))); + } + + public Builder spread(float maxSpread, float sizeVariation, IntProvider spreadCount) { + this.spreadCount = spreadCount; // + this.maxSpread = maxSpread; + this.sizeVariation = sizeVariation; + return this; + } + + public Builder floorChance(float chance) { + this.floorChance = chance; + return this; + } + + public Builder onFloor() { + this.floorChance = 1; + return this; + } + + public Builder 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 startOnSolid() { + return Builder.start(OnSolid::new); + } + + public static class OnSolid extends ScatterFeatureConfig { + public static final Codec CODEC = buildCodec(OnSolid::new); + + protected OnSolid( + BlockStateProvider clusterBlock, + Optional tipBlock, + Optional bottomBlock, + Optional 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 startExtendTop() { + return Builder.start(ExtendTop::new); + } + + public static class ExtendTop extends ScatterFeatureConfig { + public static final Codec CODEC = buildCodec(ExtendTop::new); + + protected ExtendTop( + BlockStateProvider clusterBlock, + Optional tipBlock, + Optional bottomBlock, + Optional 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 startExtendBottom() { + return Builder.start(ExtendBottom::new); + } + + public static class ExtendBottom extends ScatterFeatureConfig { + public static final Codec CODEC = buildCodec(ExtendBottom::new); + + protected ExtendBottom( + BlockStateProvider clusterBlock, + Optional tipBlock, + Optional bottomBlock, + Optional 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); + } + } + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/DefaultFeature.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/DefaultFeature.java new file mode 100644 index 00000000..89cec529 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/features/features/DefaultFeature.java @@ -0,0 +1,45 @@ +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 { + 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); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLBaseStructureBuilder.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLBaseStructureBuilder.java new file mode 100644 index 00000000..71422dc1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLBaseStructureBuilder.java @@ -0,0 +1,119 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import org.betterx.worlds.together.tag.v3.TagManager; + +import com.mojang.serialization.Codec; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.TerrainAdjustment; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; +import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType; +import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; + +import java.util.concurrent.ConcurrentLinkedQueue; + +abstract class BCLBaseStructureBuilder> { + static final ConcurrentLinkedQueue> UNBOUND_STRUCTURES = new ConcurrentLinkedQueue<>(); + static final ConcurrentLinkedQueue> UNBOUND_STRUCTURE_SETS = new ConcurrentLinkedQueue<>(); + + protected final ResourceLocation structureID; + protected BCLStructure.StructureBuilder structureBuilder; + + private GenerationStep.Decoration step; + + private StructurePlacement placement; + + private TagKey biomeTag; + + private TerrainAdjustment terrainAdjustment; + + protected BCLBaseStructureBuilder( + ResourceLocation structureID, + BCLStructure.StructureBuilder structureBuilder + ) { + this.structureID = structureID; + this.structureBuilder = structureBuilder; + + this.step = GenerationStep.Decoration.SURFACE_STRUCTURES; + this.terrainAdjustment = TerrainAdjustment.NONE; + this.placement = null; + this.biomeTag = null; + } + + public T adjustment(TerrainAdjustment value) { + this.terrainAdjustment = value; + return (T) this; + } + + public T step(GenerationStep.Decoration value) { + this.step = value; + return (T) this; + } + + public T placement(StructurePlacement value) { + this.placement = value; + return (T) this; + } + + public T randomPlacement(int spacing, int separation) { + this.placement = new RandomSpreadStructurePlacement( + spacing, + separation, + RandomSpreadType.LINEAR, + 13323129 + spacing + separation + structureID.toString().hashCode() % 10000 + ); + return (T) this; + } + + public T biomeTag(String modID, String path) { + this.biomeTag = TagManager.BIOMES.makeStructureTag(modID, path); + return (T) this; + } + + public T biomeTag(TagKey tag) { + this.biomeTag = tag; + return (T) this; + } + + protected abstract Codec getCodec(); + + public BCLStructure build() { + if (placement == null) { + throw new IllegalStateException("Placement needs to be defined for " + this.structureID); + } + + if (structureBuilder == null) { + throw new IllegalStateException("A structure builder needs to be defined for " + this.structureID); + } + + if (biomeTag == null) biomeTag(structureID.getNamespace(), structureID.getPath()); + + var res = new BCLStructure.Unbound<>( + structureID, + step, + placement, + getCodec(), + biomeTag, + structureBuilder, + terrainAdjustment + ); + UNBOUND_STRUCTURES.add(res); + UNBOUND_STRUCTURE_SETS.add(res); + return res; + } + + static void registerUnbound(BootstapContext context) { + UNBOUND_STRUCTURES.forEach(s -> s.register(context)); + UNBOUND_STRUCTURES.clear(); + } + + static void registerUnboundSets(BootstapContext context) { + UNBOUND_STRUCTURE_SETS.forEach(s -> s.registerSet(context)); + UNBOUND_STRUCTURE_SETS.clear(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLJigsawStructureBuilder.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLJigsawStructureBuilder.java new file mode 100644 index 00000000..c7b8d073 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLJigsawStructureBuilder.java @@ -0,0 +1,101 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import com.mojang.serialization.Codec; +import net.minecraft.core.HolderGetter; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.VerticalAnchor; +import net.minecraft.world.level.levelgen.heightproviders.ConstantHeight; +import net.minecraft.world.level.levelgen.heightproviders.HeightProvider; +import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; +import net.minecraft.world.level.levelgen.structure.structures.JigsawStructure; + +import java.util.Optional; + +public class BCLJigsawStructureBuilder extends BCLBaseStructureBuilder { + private ResourceKey startPool; + private Optional startJigsawName; + private int maxDepth; + private HeightProvider startHeight; + private boolean useExpansionHack; + private Optional projectStartToHeightmap; + private int maxDistanceFromCenter; + + public BCLJigsawStructureBuilder( + ResourceLocation structureID + ) { + super(structureID, null); + this.maxDepth = 6; + this.startHeight = ConstantHeight.of(VerticalAnchor.absolute(0)); + this.maxDistanceFromCenter = 80; + this.useExpansionHack = false; + this.startJigsawName = Optional.empty(); + this.projectStartToHeightmap = Optional.empty(); + } + + public BCLJigsawStructureBuilder projectStartToHeightmap(Heightmap.Types value) { + this.projectStartToHeightmap = Optional.of(value); + return this; + } + + public BCLJigsawStructureBuilder maxDistanceFromCenter(int value) { + this.maxDistanceFromCenter = value; + return this; + } + + public BCLJigsawStructureBuilder startJigsawName(ResourceLocation value) { + this.startJigsawName = Optional.of(value); + return this; + } + + public BCLJigsawStructureBuilder useExpansionHack(boolean value) { + this.useExpansionHack = value; + return this; + } + + public BCLJigsawStructureBuilder maxDepth(int value) { + this.maxDepth = value; + return this; + } + + public BCLJigsawStructureBuilder startHeight(HeightProvider value) { + this.startHeight = value; + return this; + } + + public BCLJigsawStructureBuilder startPool(ResourceKey pool) { + this.startPool = pool; + return this; + } + + @Override + protected Codec getCodec() { + return JigsawStructure.CODEC; + } + + @Override + public BCLStructure build() { + if (startPool == null) { + throw new IllegalStateException("Start pool must be set for " + this.structureID); + } + + this.structureBuilder = (BCLStructure.StructureBuilderWithContext) (structureSettings, ctx) -> { + HolderGetter templateGetter = ctx.lookup(Registries.TEMPLATE_POOL); + + return new JigsawStructure( + structureSettings, + templateGetter.getOrThrow(startPool), + startJigsawName, + maxDepth, + startHeight, + useExpansionHack, + projectStartToHeightmap, + maxDistanceFromCenter + ); + }; + + return super.build(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructure.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructure.java new file mode 100644 index 00000000..c127d4d7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructure.java @@ -0,0 +1,287 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeBuilder; + +import com.mojang.serialization.Codec; +import net.minecraft.core.*; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.structure.*; +import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.NotNull; + +public abstract class BCLStructure { + public interface StructureBuilder { + S apply(Structure.StructureSettings structureSettings); + } + + public interface StructureCodecProvider { + Codec getCodec(); + } + + public interface StructureBuilderWithContext extends StructureBuilder { + default S apply(Structure.StructureSettings structureSettings) { + return apply(structureSettings, null); + } + + S apply(Structure.StructureSettings structureSettings, BootstapContext ctx); + } + + public static class Unbound extends BCLStructure { + private final StructureBuilder structureBuilder; + private final TerrainAdjustment terrainAdjustment; + + private Bound registered; + + protected Unbound( + @NotNull ResourceLocation id, + @NotNull GenerationStep.Decoration step, + @NotNull StructurePlacement placement, + @NotNull Codec codec, + @NotNull TagKey biomeTag, + @NotNull StructureBuilder structureBuilder, + @NotNull TerrainAdjustment terrainAdjustment + ) { + super( + id, + ResourceKey.create(Registries.STRUCTURE, id), + ResourceKey.create(Registries.STRUCTURE_SET, id), + step, + placement, + //codec, + biomeTag, + BCLStructure.registerStructureType(id, codec) + ); + + registered = null; + this.structureBuilder = structureBuilder; + this.terrainAdjustment = terrainAdjustment; + } + + + public Bound register(BootstapContext bootstrapContext) { + if (registered != null) return registered; + final Structure.StructureSettings settings = structure( + bootstrapContext, + this.biomeTag, + this.featureStep, + terrainAdjustment + ); + S baseStructure; + if (structureBuilder instanceof StructureBuilderWithContext sctx) { + baseStructure = sctx.apply(settings, bootstrapContext); + } else { + baseStructure = structureBuilder.apply(settings); + } + + Holder.Reference structure = bootstrapContext.register(structureKey, baseStructure); + BCLStructureBuilder.UNBOUND_STRUCTURES.remove(this); + registered = new Bound<>( + this.id, + this.structureKey, + this.structureSetKey, + this.featureStep, + this.spreadConfig, + //this.STRUCTURE_CODEC, + this.biomeTag, + this.structureType, + baseStructure, + structure + ); + return registered; + } + } + + public static class Bound extends BCLStructure { + public final S baseStructure; + public final Holder structure; + + private Bound( + @NotNull ResourceLocation id, + @NotNull ResourceKey structureKey, + @NotNull ResourceKey structureSetKey, + @NotNull GenerationStep.Decoration featureStep, + @NotNull StructurePlacement placement, + //@NotNull Codec codec, + @NotNull TagKey biomeTag, + @NotNull StructureType structureType, + @NotNull S baseStructure, + @NotNull Holder structure + ) { + super(id, structureKey, structureSetKey, featureStep, placement, /**codec,**/biomeTag, structureType); + + this.baseStructure = baseStructure; + this.structure = structure; + } + + public Holder getStructure() { + return structure; + } + + public Bound register(BootstapContext bootstrapContext) { + return this; + } + } + + + protected final GenerationStep.Decoration featureStep; + protected final List biomes = Lists.newArrayList(); + protected final ResourceLocation id; + public final TagKey biomeTag; + public final ResourceKey structureKey; + + public final ResourceKey structureSetKey; + public final StructurePlacement spreadConfig; + + public final StructureType structureType; + + //public final Codec STRUCTURE_CODEC; + + private static HolderSet biomes(BootstapContext bootstrapContext, TagKey tagKey) { + return bootstrapContext.lookup(Registries.BIOME).getOrThrow(tagKey); + } + + private static Structure.StructureSettings structure( + BootstapContext bootstrapContext, + TagKey tagKey, + Map map, + GenerationStep.Decoration decoration, + TerrainAdjustment terrainAdjustment + ) { + return new Structure.StructureSettings(biomes(bootstrapContext, tagKey), map, decoration, terrainAdjustment); + } + + private static Structure.StructureSettings structure( + BootstapContext bootstrapContext, + TagKey tagKey, + GenerationStep.Decoration decoration, + TerrainAdjustment terrainAdjustment + ) { + return structure(bootstrapContext, tagKey, Map.of(), decoration, terrainAdjustment); + } + + private static StructureType registerStructureType( + ResourceLocation id, + Codec codec + ) { + final ResourceKey> key = ResourceKey.create(Registries.STRUCTURE_TYPE, id); + return (StructureType) Registry.register( + BuiltInRegistries.STRUCTURE_TYPE, + key, + () -> (Codec) codec + ); + } + + protected BCLStructure( + @NotNull ResourceLocation id, + @NotNull ResourceKey structureKey, + @NotNull ResourceKey structureSetKey, + @NotNull GenerationStep.Decoration step, + @NotNull StructurePlacement placement, + //@NotNull Codec codec, + @NotNull TagKey biomeTag, + @NotNull StructureType structureType + ) { + this.id = id; + this.featureStep = step; + //this.STRUCTURE_CODEC = codec; + this.spreadConfig = placement; + this.structureKey = structureKey; + this.structureSetKey = structureSetKey; + this.biomeTag = biomeTag; + + this.structureType = structureType; + } + + /** + * runs the {@code PieceGeneratorSupplier.Context::validBiome} from the given context at + * height=5 in the middle of the chunk. + * + * @param context The context to test with. + * @return true, if this feature can spawn in the current biome + */ + public static boolean isValidBiome(Structure.GenerationContext context) { + return isValidBiome(context, 5); + } + + /** + * runs the {@code PieceGeneratorSupplier.Context::validBiome} from the given context at the + * given height in the middle of the chunk. + * + * @param context The context to test with. + * @param yPos The Height to test for + * @return true, if this feature can spawn in the current biome + */ + public static boolean isValidBiome(Structure.GenerationContext context, int yPos) { + BlockPos blockPos = context.chunkPos().getMiddleBlockPosition(yPos); + return context.validBiome().test( + context + .chunkGenerator() + .getBiomeSource() + .getNoiseBiome( + QuartPos.fromBlock(blockPos.getX()), + QuartPos.fromBlock(blockPos.getY()), + QuartPos.fromBlock(blockPos.getZ()), + context.randomState().sampler() + ) + ); + } + + public GenerationStep.Decoration getFeatureStep() { + return featureStep; + } + + /** + * Get the structure ID; + * + * @return {@link ResourceLocation} id. + */ + public ResourceLocation getID() { + return id; + } + + /** + * Adds biome into internal biome list, used in {@link BCLBiomeBuilder}. + * + * @param biome {@link ResourceLocation} biome ID. + */ + public void addInternalBiome(ResourceLocation biome) { + biomes.add(biome); + } + + /** + * Get biome list where this structure feature can generate. Only represents biomes made with {@link BCLBiomeBuilder} and only + * if structure was added during building process. Modification of this list will not affect structure generation. + * + * @return {@link List} of biome {@link ResourceLocation}. + */ + public List getBiomes() { + return biomes; + } + + private boolean registeredSet = false; + + public void registerSet(BootstapContext bootstrapContext) { + if (registeredSet) return; + registeredSet = true; + bootstrapContext.register(structureSetKey, new StructureSet( + bootstrapContext.lookup(Registries.STRUCTURE).getOrThrow(structureKey), + spreadConfig + )); + BCLStructureBuilder.UNBOUND_STRUCTURE_SETS.remove(this); + } + + public abstract Bound register(BootstapContext bootstrapContext); +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructureBuilder.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructureBuilder.java new file mode 100644 index 00000000..403a7c0b --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructureBuilder.java @@ -0,0 +1,63 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import com.mojang.serialization.Codec; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureSet; + +public class BCLStructureBuilder extends BCLBaseStructureBuilder> { + private Codec codec; + + private BCLStructureBuilder( + ResourceLocation structureID, + BCLStructure.StructureBuilder structureBuilder + ) { + super(structureID, structureBuilder); + + if (structureBuilder instanceof BCLStructure.StructureCodecProvider sctx) { + this.codec = sctx.getCodec(); + } else { + this.codec = Structure.simpleCodec((settings) -> structureBuilder.apply(settings)); + } + } + + public static BCLJigsawStructureBuilder jigsaw( + ResourceLocation structureID + ) { + return new BCLJigsawStructureBuilder(structureID); + } + + public static BCLStructureBuilder start( + ResourceLocation structureID, + BCLStructure.StructureBuilderWithContext structureBuilder + ) { + return new BCLStructureBuilder<>(structureID, structureBuilder); + } + + public static BCLStructureBuilder start( + ResourceLocation structureID, + BCLStructure.StructureBuilder structureBuilder + ) { + return new BCLStructureBuilder<>(structureID, structureBuilder); + } + + public BCLStructureBuilder codec(Codec value) { + this.codec = value; + return this; + } + + @Override + protected Codec getCodec() { + return codec; + } + + + public static void registerUnbound(BootstapContext context) { + BCLBaseStructureBuilder.registerUnbound(context); + } + + public static void registerUnboundSets(BootstapContext context) { + BCLBaseStructureBuilder.registerUnboundSets(context); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructurePoolElementTypes.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructurePoolElementTypes.java new file mode 100644 index 00000000..858e6e75 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/BCLStructurePoolElementTypes.java @@ -0,0 +1,27 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import org.betterx.bclib.BCLib; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement; +import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElementType; + +public class BCLStructurePoolElementTypes { + public static final StructurePoolElementType END = register( + BCLib.makeID("single_end_pool_element"), SingleEndPoolElement.CODEC); + + + public static

StructurePoolElementType

register( + ResourceLocation id, + Codec

codec + ) { + return Registry.register(BuiltInRegistries.STRUCTURE_POOL_ELEMENT, id, () -> codec); + } + + public static void ensureStaticallyLoaded() { + // NO-OP + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/SingleEndPoolElement.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/SingleEndPoolElement.java new file mode 100644 index 00000000..1e31573a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/SingleEndPoolElement.java @@ -0,0 +1,92 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement; +import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElementType; +import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; + +import java.util.function.Function; + +public class SingleEndPoolElement extends SinglePoolElement { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + SingleEndPoolElement.templateCodec(), + SingleEndPoolElement.processorsCodec(), + SingleEndPoolElement.projectionCodec() + ).apply(instance, SingleEndPoolElement::new)); + + public SingleEndPoolElement( + Either either, + Holder holder, + StructureTemplatePool.Projection projection + ) { + super(either, holder, projection); + } + + public static Function end( + ResourceLocation id, + Holder holder + ) { + return projection -> new SingleEndPoolElement( + Either.left(id), + holder, + projection + ); + } + + @Override + public boolean place( + StructureTemplateManager structureTemplateManager, + WorldGenLevel worldGenLevel, + StructureManager structureManager, + ChunkGenerator chunkGenerator, + BlockPos blockPos, + BlockPos blockPos2, + Rotation rotation, + BoundingBox boundingBox, + RandomSource randomSource, + boolean bl + ) { + //in the end, we don't want to generate anything below y=5 + if (blockPos.getY() < 5) return false; + boolean hasEmptySpace = worldGenLevel.isEmptyBlock(blockPos.above(2)) || worldGenLevel.isEmptyBlock(blockPos); + if (!hasEmptySpace) + return false; + + return super.place( + structureTemplateManager, + worldGenLevel, + structureManager, + chunkGenerator, + blockPos, + blockPos2, + rotation, + boundingBox, + randomSource, + bl + ); + } + + @Override + public StructurePoolElementType getType() { + return BCLStructurePoolElementTypes.END; + } + + @Override + public String toString() { + return "SingleEnd[" + this.template + "]"; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureNBT.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureNBT.java new file mode 100644 index 00000000..454be563 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureNBT.java @@ -0,0 +1,241 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.util.BlocksHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; + +import com.google.common.collect.Maps; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.*; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.Nullable; + +public class StructureNBT { + public final ResourceLocation location; + protected StructureTemplate structure; + + + protected StructureNBT(ResourceLocation location) { + this.location = location; + this.structure = readStructureFromJar(location); + } + + protected StructureNBT(ResourceLocation location, StructureTemplate structure) { + this.location = location; + this.structure = structure; + } + + public static Rotation getRandomRotation(RandomSource random) { + return Rotation.getRandom(random); + } + + public static Mirror getRandomMirror(RandomSource random) { + return Mirror.values()[random.nextInt(3)]; + } + + private static final Map STRUCTURE_CACHE = Maps.newHashMap(); + + public static StructureNBT create(ResourceLocation location) { + return STRUCTURE_CACHE.computeIfAbsent(location, r -> new StructureNBT(r)); + } + + public boolean generateCentered(ServerLevelAccessor world, BlockPos pos, Rotation rotation, Mirror mirror) { + BlockPos newPos = getCenteredPos(pos, rotation, mirror); + if (newPos == null) return false; + StructurePlaceSettings data = new StructurePlaceSettings().setRotation(rotation).setMirror(mirror); + structure.placeInWorld( + world, + newPos, + newPos, + data, + world.getRandom(), + BlocksHelper.SET_SILENT + ); + return true; + } + + public boolean generateAt(ServerLevelAccessor world, BlockPos pos, Rotation rotation, Mirror mirror) { + StructurePlaceSettings data = new StructurePlaceSettings().setRotation(rotation).setMirror(mirror); + structure.placeInWorld( + world, + pos, + pos, + data, + world.getRandom(), + BlocksHelper.SET_SILENT + ); + return true; + } + + @Nullable + private BlockPos getCenteredPos(BlockPos pos, Rotation rotation, Mirror mirror) { + if (structure == null) { + BCLib.LOGGER.error("No structure: " + location.toString()); + return null; + } + + MutableBlockPos blockpos2 = new MutableBlockPos().set(structure.getSize()); + if (mirror == Mirror.FRONT_BACK) + blockpos2.setX(-blockpos2.getX()); + if (mirror == Mirror.LEFT_RIGHT) + blockpos2.setZ(-blockpos2.getZ()); + blockpos2.set(blockpos2.rotate(rotation)); + return pos.offset(-blockpos2.getX() >> 1, 0, -blockpos2.getZ() >> 1); + } + + private static final Map READER_CACHE = Maps.newHashMap(); + + private static StructureTemplate readStructureFromJar(ResourceLocation resource) { + return READER_CACHE.computeIfAbsent(resource, r -> _readStructureFromJar(r)); + } + + private static String getStructurePath(ResourceLocation resource) { + return "data/" + resource.getNamespace() + "/structures/" + resource.getPath(); + } + + private static StructureTemplate _readStructureFromJar(ResourceLocation resource) { + try { + InputStream inputstream = MinecraftServer.class.getResourceAsStream("/" + getStructurePath(resource) + ".nbt"); + return readStructureFromStream(inputstream); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } + + /** + * Returns a list of all structures found at the given resource location. + * + * @param resource The resource location to search. + * @param recursionDepth The maximum recursion depth or 0 to indicate no limitation + * @return A list of all structures found at the given resource location. + */ + public static List createResourcesFrom(ResourceLocation resource, int recursionDepth) { + String ns = resource.getNamespace(); + String nm = resource.getPath(); + + final String resourceFolder = getStructurePath(resource); + final URL url = MinecraftServer.class.getClassLoader().getResource(resourceFolder); + if (url != null) { + final URI uri; + try { + uri = url.toURI(); + } catch (URISyntaxException e) { + BCLib.LOGGER.error("Unable to load Resources: ", e); + return null; + } + Path myPath; + if (uri.getScheme().equals("jar")) { + FileSystem fileSystem = null; + try { + fileSystem = FileSystems.getFileSystem(uri); + } catch (FileSystemNotFoundException notLoaded) { + try { + fileSystem = FileSystems.newFileSystem(uri, new HashMap<>()); + } catch (IOException e) { + BCLib.LOGGER.error("Unable to load Filesystem: ", e); + return null; + } + } + + myPath = fileSystem.getPath(resourceFolder); + } else { + myPath = Paths.get(uri); + } + if (Files.isDirectory(myPath)) { + try { + // /bclib place nbt minecraft:village/plains 0 southOf 0 -60 -0 controller + return Files.walk(myPath, recursionDepth <= 0 ? Integer.MAX_VALUE : recursionDepth) + .filter(p -> Files.isRegularFile(p)) + .map(p -> { + if (p.isAbsolute()) + return Path.of(uri).relativize(p).toString(); + else { + return p.toString().replace(resourceFolder, "").replaceAll("^/+", ""); + } + }) + .filter(s -> s.endsWith(".nbt")) + .map(s -> new ResourceLocation( + ns, + (nm.isEmpty() ? "" : (nm + "/")) + s.substring(0, s.length() - 4) + )) + .sorted(Comparator.comparing(ResourceLocation::toString)) + .map(r -> { + BCLib.LOGGER.info("Loading Structure: " + r); + try { + return StructureNBT.create(r); + } catch (Exception e) { + BCLib.LOGGER.error("Unable to load Structure " + r, e); + } + return null; + }) + .toList(); + } catch (IOException e) { + BCLib.LOGGER.error("Unable to load Resources: ", e); + return null; + } + } + } + return null; + } + + private static StructureTemplate readStructureFromStream(InputStream stream) throws IOException { + CompoundTag nbttagcompound = NbtIo.readCompressed(stream); + + StructureTemplate template = new StructureTemplate(); + + template.load(BuiltInRegistries.BLOCK.asLookup(), nbttagcompound); + + return template; + } + + public BlockPos getSize(Rotation rotation) { + if (rotation == Rotation.NONE || rotation == Rotation.CLOCKWISE_180) + return new BlockPos(structure.getSize()); + else { + Vec3i size = structure.getSize(); + int x = size.getX(); + int z = size.getZ(); + return new BlockPos(z, size.getY(), x); + } + } + + public String getName() { + return location.getPath(); + } + + public BoundingBox getBoundingBox(BlockPos pos, Rotation rotation, Mirror mirror) { + return structure.getBoundingBox(new StructurePlaceSettings().setRotation(rotation).setMirror(mirror), pos); + } + + public BoundingBox getCenteredBoundingBox(BlockPos pos, Rotation rotation, Mirror mirror) { + return structure.getBoundingBox( + new StructurePlaceSettings().setRotation(rotation).setMirror(mirror), + getCenteredPos(pos, rotation, mirror) + ); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructurePlacementType.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructurePlacementType.java new file mode 100644 index 00000000..bcace34e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructurePlacementType.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import com.mojang.serialization.Codec; +import net.minecraft.util.StringRepresentable; + +public enum StructurePlacementType implements StringRepresentable { + FLOOR, WALL, CEIL, LAVA, UNDER; + + public static final Codec CODEC = StringRepresentable.fromEnum(StructurePlacementType::values); + + public String getName() { + return this.getSerializedName(); + } + + @Override + public String getSerializedName() { + return this.name().toLowerCase(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructurePools.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructurePools.java new file mode 100644 index 00000000..6f39b55c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructurePools.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import com.mojang.datafixers.util.Either; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.structure.pools.LegacySinglePoolElement; +import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement; +import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList; + +import java.util.function.Function; + +public class StructurePools { + public static ResourceKey createKey(ResourceLocation id) { + return ResourceKey.create(Registries.TEMPLATE_POOL, id); + } + + public static Function single( + ResourceLocation id, + Holder holder + ) { + return (projection) -> new SinglePoolElement(Either.left(id), holder, projection); + } + + public static Function legacy( + ResourceLocation id, + Holder holder + ) { + return (projection) -> new LegacySinglePoolElement(Either.left(id), holder, projection); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureWorld.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureWorld.java new file mode 100644 index 00000000..35e188d8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureWorld.java @@ -0,0 +1,176 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.levelgen.structure.BoundingBox; + +import com.google.common.collect.Maps; + +import java.util.Map; + +public class StructureWorld { + private final Map parts = Maps.newHashMap(); + private ChunkPos lastPos; + private Part lastPart; + private int minX = Integer.MAX_VALUE; + private int minY = Integer.MAX_VALUE; + private int minZ = Integer.MAX_VALUE; + private int maxX = Integer.MIN_VALUE; + private int maxY = Integer.MIN_VALUE; + private int maxZ = Integer.MIN_VALUE; + + public StructureWorld() { + } + + public StructureWorld(CompoundTag tag) { + minX = tag.getInt("minX"); + maxX = tag.getInt("maxX"); + minY = tag.getInt("minY"); + maxY = tag.getInt("maxY"); + minZ = tag.getInt("minZ"); + maxZ = tag.getInt("maxZ"); + + ListTag map = tag.getList("parts", 10); + map.forEach((element) -> { + CompoundTag compound = (CompoundTag) element; + Part part = new Part(compound); + int x = compound.getInt("x"); + int z = compound.getInt("z"); + parts.put(new ChunkPos(x, z), part); + }); + } + + public void setBlock(BlockPos pos, BlockState state) { + ChunkPos cPos = new ChunkPos(pos); + + if (cPos.equals(lastPos)) { + lastPart.addBlock(pos, state); + return; + } + + Part part = parts.get(cPos); + if (part == null) { + part = new Part(); + parts.put(cPos, part); + + if (cPos.x < minX) minX = cPos.x; + if (cPos.x > maxX) maxX = cPos.x; + if (cPos.z < minZ) minZ = cPos.z; + if (cPos.z > maxZ) maxZ = cPos.z; + } + if (pos.getY() < minY) minY = pos.getY(); + if (pos.getY() > maxY) maxY = pos.getY(); + part.addBlock(pos, state); + + lastPos = cPos; + lastPart = part; + } + + public boolean placeChunk(WorldGenLevel world, ChunkPos chunkPos) { + Part part = parts.get(chunkPos); + if (part != null) { + ChunkAccess chunk = world.getChunk(chunkPos.x, chunkPos.z); + part.placeChunk(chunk); + return true; + } + return false; + } + + public CompoundTag toBNT() { + CompoundTag tag = new CompoundTag(); + tag.putInt("minX", minX); + tag.putInt("maxX", maxX); + tag.putInt("minY", minY); + tag.putInt("maxY", maxY); + tag.putInt("minZ", minZ); + tag.putInt("maxZ", maxZ); + ListTag map = new ListTag(); + tag.put("parts", map); + parts.forEach((pos, part) -> { + map.add(part.toNBT(pos.x, pos.z)); + }); + return tag; + } + + public BoundingBox getBounds() { + if (minX == Integer.MAX_VALUE || maxX == Integer.MIN_VALUE || minZ == Integer.MAX_VALUE || maxZ == Integer.MIN_VALUE) { + return BoundingBox.infinite(); + } + return new BoundingBox(minX << 4, minY, minZ << 4, (maxX << 4) | 15, maxY, (maxZ << 4) | 15); + } + + private static final class Part { + Map blocks = Maps.newHashMap(); + + public Part() { + } + + public Part(CompoundTag tag) { + ListTag map = tag.getList("blocks", 10); + ListTag map2 = tag.getList("states", 10); + BlockState[] states = new BlockState[map2.size()]; + for (int i = 0; i < states.length; i++) { + states[i] = NbtUtils.readBlockState( + BuiltInRegistries.BLOCK.asLookup(), + (CompoundTag) map2.get(i) + ); + } + + map.forEach((element) -> { + CompoundTag block = (CompoundTag) element; + BlockPos pos = NbtUtils.readBlockPos(block.getCompound("pos")); + int stateID = block.getInt("state"); + BlockState state = stateID < states.length ? states[stateID] : Block.stateById(stateID); + blocks.put(pos, state); + }); + } + + void addBlock(BlockPos pos, BlockState state) { + BlockPos inner = new BlockPos(pos.getX() & 15, pos.getY(), pos.getZ() & 15); + blocks.put(inner, state); + } + + void placeChunk(ChunkAccess chunk) { + blocks.forEach((pos, state) -> { + chunk.setBlockState(pos, state, false); + }); + } + + CompoundTag toNBT(int x, int z) { + CompoundTag tag = new CompoundTag(); + tag.putInt("x", x); + tag.putInt("z", z); + ListTag map = new ListTag(); + tag.put("blocks", map); + ListTag stateMap = new ListTag(); + tag.put("states", stateMap); + + int[] id = new int[1]; + Map states = Maps.newHashMap(); + + blocks.forEach((pos, state) -> { + int stateID = states.getOrDefault(states, -1); + if (stateID < 0) { + stateID = id[0]++; + states.put(state, stateID); + stateMap.add(NbtUtils.writeBlockState(state)); + } + + CompoundTag block = new CompoundTag(); + block.put("pos", NbtUtils.writeBlockPos(pos)); + block.putInt("state", stateID); + map.add(block); + }); + + return tag; + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureWorldNBT.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureWorldNBT.java new file mode 100644 index 00000000..adaed8b3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/StructureWorldNBT.java @@ -0,0 +1,279 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import org.betterx.bclib.util.BlocksHelper; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.levelgen.structure.BoundingBox; + +import com.google.common.collect.Maps; + +import java.util.Map; + +public class StructureWorldNBT extends StructureNBT { + public static final Codec CODEC = + RecordCodecBuilder.create((instance) -> + instance.group( + ResourceLocation.CODEC + .fieldOf("location") + .forGetter((cfg) -> cfg.location), + + Codec + .INT + .fieldOf("offset_y") + .orElse(0) + .forGetter((cfg) -> cfg.offsetY), + + StructurePlacementType.CODEC + .fieldOf("placement") + .orElse(StructurePlacementType.FLOOR) + .forGetter((cfg) -> cfg.type), + Codec + .FLOAT + .fieldOf("chance") + .orElse(1.0f) + .forGetter((cfg) -> cfg.chance) + ) + .apply(instance, StructureWorldNBT::new) + ); + + public final StructurePlacementType type; + public final int offsetY; + public final float chance; + + protected StructureWorldNBT(ResourceLocation location, int offsetY, StructurePlacementType type, float chance) { + super(location); + this.offsetY = offsetY; + this.type = type; + this.chance = chance; + } + + private static final Map READER_CACHE = Maps.newHashMap(); + + public static StructureWorldNBT create(ResourceLocation location, int offsetY, StructurePlacementType type) { + return create(location, offsetY, type, 1.0f); + } + + public static StructureWorldNBT create( + ResourceLocation location, + int offsetY, + StructurePlacementType type, + float chance + ) { + String key = location.toString() + "::" + offsetY + "::" + type.getSerializedName(); + return READER_CACHE.computeIfAbsent(key, r -> new StructureWorldNBT(location, offsetY, type, chance)); + } + + public boolean generateIfPlaceable( + ServerLevelAccessor level, + BlockPos pos, + RandomSource random + ) { + return generateIfPlaceable( + level, + pos, + getRandomRotation(random), + getRandomMirror(random) + ); + } + + public boolean generateIfPlaceable( + ServerLevelAccessor level, + BlockPos pos, + Rotation r, + Mirror m + ) { + if (canGenerate(level, pos, r)) { + return generate(level, pos, r, m); + } + return false; + } + + public boolean generate(ServerLevelAccessor level, BlockPos pos, Rotation r, Mirror m) { + return generateCentered(level, pos.above(offsetY), r, m); + } + + protected boolean canGenerate(LevelAccessor level, BlockPos pos, Rotation rotation) { + if (type == StructurePlacementType.FLOOR) + return canGenerateFloor(level, pos, rotation); + else if (type == StructurePlacementType.LAVA) + return canGenerateLava(level, pos, rotation); + else if (type == StructurePlacementType.UNDER) + return canGenerateUnder(level, pos, rotation); + else if (type == StructurePlacementType.CEIL) + return canGenerateCeil(level, pos, rotation); + else + return false; + } + + private boolean containsBedrock(LevelAccessor level, BlockPos startPos) { + for (int i = 0; i < this.structure.getSize().getY(); i += 2) { + if (level.getBlockState(startPos.above(i)).is(Blocks.BEDROCK)) { + return true; + } + } + return false; + } + + protected boolean canGenerateFloorFreeAbove(LevelAccessor world, BlockPos pos, Rotation rotation) { + if (containsBedrock(world, pos)) return false; + + return getAirFractionFoundation(world, pos, rotation) < 0.5 + && world.getBlockState(pos.above(2)).is(Blocks.AIR) + && world.getBlockState(pos.above(4)).is(Blocks.AIR); + } + + protected boolean canGenerateFloor(LevelAccessor world, BlockPos pos, Rotation rotation) { + if (containsBedrock(world, pos)) return false; + + return getAirFraction(world, pos, rotation) > 0.6 && getAirFractionFoundation(world, pos, rotation) < 0.5; + } + + protected boolean canGenerateLava(LevelAccessor world, BlockPos pos, Rotation rotation) { + if (containsBedrock(world, pos)) return false; + + return getLavaFractionFoundation(world, pos, rotation) > 0.9 && getAirFraction(world, pos, rotation) > 0.9; + } + + protected boolean canGenerateUnder(LevelAccessor world, BlockPos pos, Rotation rotation) { + if (containsBedrock(world, pos)) return false; + + return getAirFraction(world, pos, rotation) < 0.2; + } + + protected boolean canGenerateCeil(LevelAccessor world, BlockPos pos, Rotation rotation) { + if (containsBedrock(world, pos)) return false; + + return getAirFractionBottom(world, pos, rotation) > 0.8 && getAirFraction(world, pos, rotation) < 0.6; + } + + public BoundingBox boundingBox(Rotation r, BlockPos p) { + return getBoundingBox(p, r, Mirror.NONE); + } + + protected float getAirFraction(LevelAccessor world, BlockPos pos, Rotation rotation) { + final MutableBlockPos POS = new MutableBlockPos(); + int airCount = 0; + + MutableBlockPos size = new MutableBlockPos().set(new BlockPos(structure.getSize()).rotate(rotation)); + size.setX(Math.abs(size.getX()) >> 1); + size.setZ(Math.abs(size.getZ()) >> 1); + + BlockPos start = pos.offset(-size.getX(), 0, -size.getZ()); + BlockPos end = pos.offset(size.getX(), size.getY() + offsetY, size.getZ()); + int count = 0; + + for (int x = start.getX(); x <= end.getX(); x++) { + POS.setX(x); + for (int y = start.getY(); y <= end.getY(); y++) { + POS.setY(y); + for (int z = start.getZ(); z <= end.getZ(); z++) { + POS.setZ(z); + if (world.isEmptyBlock(POS)) + airCount++; + count++; + } + } + } + + return (float) airCount / count; + } + + private float getLavaFractionFoundation(LevelAccessor world, BlockPos pos, Rotation rotation) { + final MutableBlockPos POS = new MutableBlockPos(); + int lavaCount = 0; + + MutableBlockPos size = new MutableBlockPos().set(new BlockPos(structure.getSize()).rotate(rotation)); + size.setX(Math.abs(size.getX()) >> 1); + size.setZ(Math.abs(size.getZ()) >> 1); + + BlockPos start = pos.offset(-(size.getX()), 0, -(size.getZ())); + BlockPos end = pos.offset(size.getX(), 0, size.getZ()); + int count = 0; + + POS.setY(pos.getY() - 1); + for (int x = start.getX(); x <= end.getX(); x++) { + POS.setX(x); + for (int z = start.getZ(); z <= end.getZ(); z++) { + POS.setZ(z); + + if (BlocksHelper.isLava(world.getBlockState(POS))) + lavaCount++; + count++; + } + } + + return (float) lavaCount / count; + } + + private float getAirFractionFoundation(LevelAccessor world, BlockPos pos, Rotation rotation) { + final MutableBlockPos POS = new MutableBlockPos(); + int airCount = 0; + + MutableBlockPos size = new MutableBlockPos().set(new BlockPos(structure.getSize()).rotate(rotation)); + size.setX(Math.abs(size.getX()) >> 1); + size.setZ(Math.abs(size.getZ()) >> 1); + + BlockPos start = pos.offset(-(size.getX()), -1, -(size.getZ())); + BlockPos end = pos.offset(size.getX(), 0, size.getZ()); + int count = 0; + + for (int x = start.getX(); x <= end.getX(); x++) { + POS.setX(x); + for (int y = start.getY(); y <= end.getY(); y++) { + POS.setY(y); + for (int z = start.getZ(); z <= end.getZ(); z++) { + POS.setZ(z); + if (world.getBlockState(POS).canBeReplaced()) + airCount++; + count++; + } + } + } + + return (float) airCount / count; + } + + private float getAirFractionBottom(LevelAccessor world, BlockPos pos, Rotation rotation) { + final MutableBlockPos POS = new MutableBlockPos(); + int airCount = 0; + + MutableBlockPos size = new MutableBlockPos().set(new BlockPos(structure.getSize()).rotate(rotation)); + size.setX(Math.abs(size.getX())); + size.setZ(Math.abs(size.getZ())); + + float y1 = Math.min(offsetY, 0); + float y2 = Math.max(offsetY, 0); + BlockPos start = pos.offset(-(size.getX() >> 1), (int) y1, -(size.getZ() >> 1)); + BlockPos end = pos.offset(size.getX() >> 1, (int) y2, size.getZ() >> 1); + int count = 0; + + for (int x = start.getX(); x <= end.getX(); x++) { + POS.setX(x); + for (int y = start.getY(); y <= end.getY(); y++) { + POS.setY(y); + for (int z = start.getZ(); z <= end.getZ(); z++) { + POS.setZ(z); + if (world.getBlockState(POS).canBeReplaced()) + airCount++; + count++; + } + } + } + + return (float) airCount / count; + } + + public boolean loaded() { + return structure != null; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/TemplatePiece.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/TemplatePiece.java new file mode 100644 index 00000000..10f605dc --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/TemplatePiece.java @@ -0,0 +1,211 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.MHelper; +import org.betterx.bclib.util.StructureErode; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Registry; +import net.minecraft.core.Vec3i; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.TemplateStructurePiece; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceType; +import net.minecraft.world.level.levelgen.structure.templatesystem.BlockIgnoreProcessor; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; + +public class TemplatePiece extends TemplateStructurePiece { + private final int erosion; + private final boolean cover; + public static final StructurePieceType INSTANCE = setTemplatePieceId( + TemplatePiece::new, + "template_piece" + ); + + + private static StructurePieceType setFullContextPieceId(StructurePieceType structurePieceType, String id) { + return Registry.register(BuiltInRegistries.STRUCTURE_PIECE, BCLib.makeID(id), structurePieceType); + } + + private static StructurePieceType setTemplatePieceId( + StructurePieceType.StructureTemplateType structureTemplateType, + String string + ) { + return setFullContextPieceId(structureTemplateType, string); + } + + + public static void ensureStaticInitialization() { + } + + public TemplatePiece( + StructureTemplateManager structureTemplateManager, + ResourceLocation resourceLocation, + BlockPos centerPos, + Rotation rotation, + Mirror mirror, + BlockPos halfSize + ) { + this(structureTemplateManager, resourceLocation, centerPos, rotation, mirror, halfSize, 0, false); + } + + public TemplatePiece( + StructureTemplateManager structureTemplateManager, + ResourceLocation resourceLocation, + BlockPos centerPos, + Rotation rotation, + Mirror mirror, + BlockPos halfSize, + int erosion, + boolean cover + ) { + super( + INSTANCE, + 0, + structureTemplateManager, + resourceLocation, + resourceLocation.toString(), + makeSettings(rotation, mirror, halfSize), + shiftPos(rotation, mirror, halfSize, centerPos) + ); + this.erosion = erosion; + this.cover = cover; + } + + public TemplatePiece(StructureTemplateManager structureTemplateManager, CompoundTag compoundTag) { + super( + INSTANCE, + compoundTag, + structureTemplateManager, + (ResourceLocation resourceLocation) -> makeSettings(compoundTag) + ); + if (compoundTag.contains("E")) + this.erosion = compoundTag.getInt("E"); + else + this.erosion = 0; + + if (compoundTag.contains("C")) + this.cover = compoundTag.getBoolean("C"); + else + this.cover = true; + } + + private static BlockPos shiftPos( + Rotation rotation, + Mirror mirror, + BlockPos halfSize, + BlockPos pos + ) { + halfSize = StructureTemplate.transform(halfSize, mirror, rotation, halfSize); + return pos.offset(-halfSize.getX(), 0, -halfSize.getZ()); + } + + private static StructurePlaceSettings makeSettings(CompoundTag compoundTag) { + return makeSettings( + Rotation.valueOf(compoundTag.getString("R")), + Mirror.valueOf(compoundTag.getString("M")), + new BlockPos(compoundTag.getInt("RX"), compoundTag.getInt("RY"), compoundTag.getInt("RZ")) + ); + + } + + private static StructurePlaceSettings makeSettings(Rotation rotation, Mirror mirror, BlockPos halfSize) { + return new StructurePlaceSettings().setRotation(rotation) + .setMirror(mirror) + .setRotationPivot(halfSize) + .addProcessor(BlockIgnoreProcessor.STRUCTURE_BLOCK); + } + + @Override + protected void addAdditionalSaveData( + StructurePieceSerializationContext structurePieceSerializationContext, + CompoundTag tag + ) { + super.addAdditionalSaveData(structurePieceSerializationContext, tag); + tag.putString("R", this.placeSettings.getRotation().name()); + tag.putString("M", this.placeSettings.getMirror().name()); + tag.putInt("RX", this.placeSettings.getRotationPivot().getX()); + tag.putInt("RY", this.placeSettings.getRotationPivot().getY()); + tag.putInt("RZ", this.placeSettings.getRotationPivot().getZ()); + tag.putInt("E", this.erosion); + tag.putBoolean("C", this.cover); + } + + @Override + protected void handleDataMarker( + String string, + BlockPos blockPos, + ServerLevelAccessor serverLevelAccessor, + RandomSource randomSource, + BoundingBox boundingBox + ) { + + } + + @Override + public void postProcess( + WorldGenLevel world, + StructureManager structureManager, + ChunkGenerator chunkGenerator, + RandomSource random, + BoundingBox boundingBox, + ChunkPos chunkPos, + BlockPos blockPos + ) { + BlockState coverState = null; + if (cover) { + BlockPos.MutableBlockPos mPos = new BlockPos( + this.boundingBox.minX() - 1, + blockPos.getY(), + this.boundingBox.minZ() - 1 + ).mutable(); + if (BlocksHelper.findOnSurroundingSurface( + world, + mPos, + Direction.DOWN, + 8, + s -> s.is(CommonBlockTags.TERRAIN) + )) { + mPos.move(Direction.DOWN); + coverState = world.getBlockState(mPos); + } + } + super.postProcess(world, structureManager, chunkGenerator, random, boundingBox, chunkPos, blockPos); + BoundingBox bounds = BoundingBox.fromCorners(new Vec3i( + boundingBox.minX(), + this.boundingBox.minY(), + boundingBox.minZ() + ), new Vec3i(boundingBox.maxX(), this.boundingBox.maxY(), boundingBox.maxZ())); + + if (erosion > 0) { + int x1 = MHelper.min(bounds.maxX(), this.boundingBox.maxX()); + int x0 = MHelper.max(bounds.minX(), this.boundingBox.minX()); + int z1 = MHelper.min(bounds.maxZ(), this.boundingBox.maxZ()); + int z0 = MHelper.max(bounds.minZ(), this.boundingBox.minZ()); + bounds = BoundingBox.fromCorners(new Vec3i(x0, bounds.minY(), z0), new Vec3i(x1, bounds.maxY(), z1)); + StructureErode.erode(world, bounds, erosion, random); + } + + if (cover) { + //System.out.println("CoverState:" + coverState + ", " + blockPos + " " + boundingBox.getCenter()); + StructureErode.cover(world, bounds, random, coverState); + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/TemplateStructure.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/TemplateStructure.java new file mode 100644 index 00000000..6e390efd --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/TemplateStructure.java @@ -0,0 +1,304 @@ +package org.betterx.bclib.api.v2.levelgen.structures; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.QuartPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.NoiseColumn; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.WorldGenerationContext; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; + +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; + +public abstract class TemplateStructure extends Structure { + protected final List configs; + + public static Codec simpleTemplateCodec(BiFunction, T> instancer) { + return RecordCodecBuilder.create((instance) -> instance + .group( + Structure.settingsCodec(instance), + ExtraCodecs.nonEmptyList(Config.CODEC.listOf()) + .fieldOf("configs") + .forGetter((T ruinedPortalStructure) -> ruinedPortalStructure.configs) + ) + .apply(instance, instancer) + ); + } + + + protected TemplateStructure( + StructureSettings structureSettings, + ResourceLocation location, + int offsetY, + StructurePlacementType type, + float chance + ) { + this(structureSettings, List.of(new Config(location, offsetY, type, chance))); + } + + protected TemplateStructure( + StructureSettings structureSettings, + List configs + ) { + super(structureSettings); + this.configs = configs; + } + + protected Config randomConfig(RandomSource random) { + if (this.configs.size() > 1) { + final float chanceSum = configs.parallelStream().map(c -> c.chance()).reduce(0.0f, (p, c) -> p + c); + float rnd = random.nextFloat() * chanceSum; + + for (Config c : configs) { + rnd -= c.chance(); + if (rnd <= 0) return c; + } + } else { + return this.configs.get(0); + } + + return null; + } + + protected boolean isLavaPlaceable(BlockState state, BlockState before) { + return (state == null || state.is(Blocks.AIR)) && before.is(Blocks.LAVA); + } + + protected boolean isFloorPlaceable(BlockState state, BlockState before) { + return (state == null || state.is(Blocks.AIR)) && before.isSolid(); + } + + protected int erosion(RandomSource rnd) { + return 0; + } + + protected boolean cover(RandomSource rnd) { + return false; + } + + @Override + public Optional findGenerationPoint(GenerationContext ctx) { + final Config config = randomConfig(ctx.random()); + if (config == null) return Optional.empty(); + + ChunkPos chunkPos = ctx.chunkPos(); + final int x = chunkPos.getMinBlockX(); + final int z = chunkPos.getMinBlockZ(); + if (!hasValidBiomeAtRandomHeight(ctx, x, z)) + return Optional.empty(); + + WorldGenerationContext worldGenerationContext = new WorldGenerationContext( + ctx.chunkGenerator(), + ctx.heightAccessor() + ); + + StructureTemplate structureTemplate = ctx.structureTemplateManager().getOrCreate(config.location); + + final BiPredicate isCorrectBase; + final int searchStep; + final int minBaseCount; + final float minAirRatio = 0.6f; + if (config.type == StructurePlacementType.LAVA) { + isCorrectBase = this::isLavaPlaceable; + minBaseCount = 5; + searchStep = 1; + } else if (config.type == StructurePlacementType.CEIL) { + isCorrectBase = this::isFloorPlaceable; + minBaseCount = 3; + searchStep = -1; + } else { + isCorrectBase = this::isFloorPlaceable; + minBaseCount = 3; + searchStep = 1; + } + + + final int seaLevel = + ctx.chunkGenerator().getSeaLevel() + + (searchStep > 0 ? 0 : (structureTemplate.getSize(Rotation.NONE).getY() + config.offsetY)); + final int maxHeight = + worldGenerationContext.getGenDepth() + - 4 + - (searchStep > 0 ? (structureTemplate.getSize(Rotation.NONE).getY() + config.offsetY) : 0); + + BlockPos halfSize = new BlockPos( + structureTemplate.getSize().getX() / 2, + 0, + structureTemplate.getSize().getZ() / 2 + ); + Rotation rotation = StructureNBT.getRandomRotation(ctx.random()); + Mirror mirror = StructureNBT.getRandomMirror(ctx.random()); + BlockPos.MutableBlockPos centerPos = new BlockPos.MutableBlockPos( + x, + 0, + z + ); + BoundingBox boundingBox = structureTemplate.getBoundingBox(centerPos, rotation, halfSize, mirror); + + var noiseColumns = ImmutableList + .of( + new BlockPos(boundingBox.getCenter().getX(), 0, boundingBox.getCenter().getZ()), + new BlockPos(boundingBox.minX(), 0, boundingBox.minZ()), + new BlockPos(boundingBox.maxX(), 0, boundingBox.minZ()), + new BlockPos(boundingBox.minX(), 0, boundingBox.maxZ()), + new BlockPos(boundingBox.maxX(), 0, boundingBox.maxZ()) + ) + .stream() + .map(blockPos -> ctx.chunkGenerator().getBaseColumn( + blockPos.getX(), + blockPos.getZ(), + ctx.heightAccessor(), + ctx.randomState() + )).toList(); + + int y = noiseColumns + .stream() + .map(column -> findY(column, isCorrectBase, searchStep, seaLevel, maxHeight)) + .reduce( + searchStep > 0 ? Integer.MAX_VALUE : Integer.MIN_VALUE, + (p, c) -> searchStep > 0 ? Math.min(p, c) : Math.max(p, c) + ); + + if (y >= maxHeight || y < seaLevel) return Optional.empty(); + if (!BCLStructure.isValidBiome(ctx, y)) return Optional.empty(); + + int baseCount = noiseColumns + .stream() + .map(column -> isCorrectBase.test(null, column.getBlock(y - searchStep))) + .filter(b -> b) + .map(b -> 1) + .reduce(0, (p, c) -> p + c); + + if (baseCount < minBaseCount) return Optional.empty(); + + float airRatio = noiseColumns + .stream() + .map(column -> airRatio(column, y, boundingBox.getYSpan(), searchStep)) + .reduce(0.0f, (p, c) -> p + c) / noiseColumns.size(); + + if (airRatio < minAirRatio) return Optional.empty(); + + centerPos.setY(y - (searchStep == 1 ? 0 : (structureTemplate.getSize(Rotation.NONE).getY()))); + + int erosion = erosion(ctx.random()); + boolean cover = cover(ctx.random()); + // if (!structure.canGenerate(ctx.chunkGenerator()., centerPos)) + return Optional.of(new GenerationStub( + centerPos, + structurePiecesBuilder -> + structurePiecesBuilder.addPiece( + new TemplatePiece( + ctx.structureTemplateManager(), + config.location, + centerPos.offset( + 0, + config.offsetY, + 0 + ), + rotation, + mirror, + halfSize, + erosion, + cover + )) + )); + + } + + private boolean hasValidBiomeAtRandomHeight(GenerationContext ctx, int x, int z) { + final int randomY = ctx.random() + .nextIntBetweenInclusive( + ctx.heightAccessor().getMinBuildHeight(), + ctx.heightAccessor().getMaxBuildHeight() + ); + + Holder holder = ctx.chunkGenerator() + .getBiomeSource() + .getNoiseBiome( + QuartPos.fromBlock(x), + QuartPos.fromBlock(randomY), + QuartPos.fromBlock(z), + ctx.randomState().sampler() + ); + if (!ctx.validBiome().test(holder)) { + return false; + } + return true; + } + + private float airRatio(NoiseColumn column, int y, int height, int searchStep) { + int airCount = 0; + for (int i = y; i < y + height && i > y - height; i += searchStep) { + BlockState state = column.getBlock(i); + if (state.isAir() || state.canBeReplaced()) { + airCount++; + } + } + return airCount / (float) height; + } + + + private int findY( + NoiseColumn column, + BiPredicate isCorrectBase, + int searchStep, + int seaLevel, + int maxHeight + ) { + int y = searchStep > 0 ? seaLevel : maxHeight - 1; + BlockState state = column.getBlock(y - searchStep); + + for (; y < maxHeight && y >= seaLevel; y += searchStep) { + BlockState before = state; + state = column.getBlock(y); + if (isCorrectBase.test(state, before)) break; + } + return y; + } + + + public record Config(ResourceLocation location, int offsetY, StructurePlacementType type, float chance) { + public static final Codec CODEC = + RecordCodecBuilder.create((instance) -> + instance.group( + ResourceLocation.CODEC + .fieldOf("location") + .forGetter((cfg) -> cfg.location), + + Codec + .INT + .fieldOf("offset_y") + .orElse(0) + .forGetter((cfg) -> cfg.offsetY), + + StructurePlacementType.CODEC + .fieldOf("placement") + .orElse(StructurePlacementType.FLOOR) + .forGetter((cfg) -> cfg.type), + Codec + .FLOAT + .fieldOf("chance") + .orElse(1.0f) + .forGetter((cfg) -> cfg.chance) + ) + .apply(instance, Config::new) + ); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/templatesystem/DestructionStructureProcessor.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/templatesystem/DestructionStructureProcessor.java new file mode 100644 index 00000000..0c0c228e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/templatesystem/DestructionStructureProcessor.java @@ -0,0 +1,43 @@ +package org.betterx.bclib.api.v2.levelgen.structures.templatesystem; + +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.MHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorType; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo; + +public class DestructionStructureProcessor extends StructureProcessor { + private int chance = 4; + + public void setChance(int chance) { + this.chance = chance; + } + + @Override + public StructureBlockInfo processBlock( + LevelReader worldView, + BlockPos pos, + BlockPos blockPos, + StructureBlockInfo structureBlockInfo, + StructureBlockInfo structureBlockInfo2, + StructurePlaceSettings structurePlacementData + ) { + if (!BlocksHelper.isInvulnerable( + structureBlockInfo2.state(), + worldView, + structureBlockInfo2.pos() + ) && MHelper.RANDOM.nextInt(chance) == 0) { + return null; + } + return structureBlockInfo2; + } + + @Override + protected StructureProcessorType getType() { + return StructureProcessorType.RULE; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/templatesystem/TerrainStructureProcessor.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/templatesystem/TerrainStructureProcessor.java new file mode 100644 index 00000000..0b779c69 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/structures/templatesystem/TerrainStructureProcessor.java @@ -0,0 +1,43 @@ +package org.betterx.bclib.api.v2.levelgen.structures.templatesystem; + +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorType; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo; + +public class TerrainStructureProcessor extends StructureProcessor { + private final Block defaultBlock; + + public TerrainStructureProcessor(Block defaultBlock) { + this.defaultBlock = defaultBlock; + } + + @Override + public StructureBlockInfo processBlock( + LevelReader worldView, + BlockPos pos, + BlockPos blockPos, + StructureBlockInfo structureBlockInfo, + StructureBlockInfo structureBlockInfo2, + StructurePlaceSettings structurePlacementData + ) { + BlockPos bpos = structureBlockInfo2.pos(); + if (structureBlockInfo2.state().is(defaultBlock) && worldView.isEmptyBlock(bpos.above())) { + final BlockState top = BiomeAPI.findTopMaterial(worldView.getBiome(pos)) + .orElse(defaultBlock.defaultBlockState()); + return new StructureBlockInfo(bpos, top, structureBlockInfo2.nbt()); + } + return structureBlockInfo2; + } + + @Override + protected StructureProcessorType getType() { + return StructureProcessorType.RULE; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/SurfaceRuleBuilder.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/SurfaceRuleBuilder.java new file mode 100644 index 00000000..92c04e97 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/SurfaceRuleBuilder.java @@ -0,0 +1,340 @@ +package org.betterx.bclib.api.v2.levelgen.surface; + +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.bclib.api.v2.levelgen.surface.rules.Conditions; +import org.betterx.bclib.api.v2.levelgen.surface.rules.DoubleBlockSurfaceNoiseCondition; +import org.betterx.bclib.api.v2.levelgen.surface.rules.NoiseCondition; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.SurfaceRules.RuleSource; +import net.minecraft.world.level.levelgen.placement.CaveSurface; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +public class SurfaceRuleBuilder { + private static final Map RULES_CACHE = Maps.newHashMap(); + private static final SurfaceRuleBuilder INSTANCE = new SurfaceRuleBuilder(); + private final List rules = Lists.newArrayList(); + private SurfaceRuleEntry entryInstance; + private ResourceKey biomeKey; + + private SurfaceRuleBuilder() { + } + + public static SurfaceRuleBuilder start() { + INSTANCE.biomeKey = null; + INSTANCE.rules.clear(); + return INSTANCE; + } + + /** + * Restricts surface to only one biome. + * + * @param biomeKey {@link ResourceKey} for the {@link Biome}. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder biome(ResourceKey biomeKey) { + this.biomeKey = biomeKey; + return this; + } + + /** + * Restricts surface to only one biome. + * + * @param biome {@link Biome}. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder biome(Biome biome) { + return biome(BiomeAPI.getBiomeKey(biome)); + } + + /** + * Set biome surface with specified {@link BlockState}. Example - block of grass in the Overworld biomes + * + * @param state {@link BlockState} for the ground cover. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder surface(BlockState state) { + entryInstance = getFromCache("surface_" + state.toString(), () -> { + RuleSource rule = SurfaceRules.state(state); + rule = SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, rule); + return new SurfaceRuleEntry(2, rule); + }); + rules.add(entryInstance); + return this; + } + + /** + * Set biome subsurface with specified {@link BlockState}. Example - dirt in the Overworld biomes. + * + * @param state {@link BlockState} for the subterrain layer. + * @param depth block layer depth. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder subsurface(BlockState state, int depth) { + entryInstance = getFromCache("subsurface_" + depth + "_" + state.toString(), () -> { + RuleSource rule = SurfaceRules.state(state); + rule = SurfaceRules.ifTrue(SurfaceRules.stoneDepthCheck(depth, false, CaveSurface.FLOOR), rule); + return new SurfaceRuleEntry(3, rule); + }); + rules.add(entryInstance); + return this; + } + + /** + * Set biome filler with specified {@link BlockState}. Example - stone in the Overworld biomes. The rule is added with priority 10. + * + * @param state {@link BlockState} for filling. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder filler(BlockState state) { + entryInstance = getFromCache( + "fill_" + state.toString(), + () -> new SurfaceRuleEntry(10, SurfaceRules.state(state)) + ); + rules.add(entryInstance); + return this; + } + + /** + * Set biome floor with specified {@link BlockState}. Example - underside of a gravel floor. The rule is added with priority 3. + * + * @param state {@link BlockState} for the ground cover. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder floor(BlockState state) { + entryInstance = getFromCache("floor_" + state.toString(), () -> { + RuleSource rule = SurfaceRules.state(state); + return new SurfaceRuleEntry(3, SurfaceRules.ifTrue(SurfaceRules.ON_FLOOR, rule)); + }); + rules.add(entryInstance); + return this; + } + + /** + * Set biome floor material with specified {@link BlockState} and height. The rule is added with priority 3. + * + * @param state {@link BlockState} for the subterrain layer. + * @param height block layer height. + * @param noise The noise object that is applied + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder belowFloor(BlockState state, int height, NoiseCondition noise) { + entryInstance = getFromCache( + "below_floor_" + height + "_" + state.toString() + "_" + noise.getClass() + .getSimpleName(), + () -> { + RuleSource rule = SurfaceRules.state(state); + rule = SurfaceRules.ifTrue( + SurfaceRules.stoneDepthCheck( + height, + false, + CaveSurface.FLOOR + ), + SurfaceRules.ifTrue(noise, rule) + ); + return new SurfaceRuleEntry(3, rule); + } + ); + rules.add(entryInstance); + return this; + } + + /** + * Set biome floor material with specified {@link BlockState} and height. The rule is added with priority 3. + * + * @param state {@link BlockState} for the subterrain layer. + * @param height block layer height. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder belowFloor(BlockState state, int height) { + entryInstance = getFromCache("below_floor_" + height + "_" + state.toString(), () -> { + RuleSource rule = SurfaceRules.state(state); + rule = SurfaceRules.ifTrue(SurfaceRules.stoneDepthCheck(height, false, CaveSurface.FLOOR), rule); + return new SurfaceRuleEntry(3, rule); + }); + rules.add(entryInstance); + return this; + } + + /** + * Set biome ceiling with specified {@link BlockState}. Example - block of sandstone in the Overworld desert in air pockets. The rule is added with priority 3. + * + * @param state {@link BlockState} for the ground cover. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder ceil(BlockState state) { + entryInstance = getFromCache("ceil_" + state.toString(), () -> { + RuleSource rule = SurfaceRules.state(state); + return new SurfaceRuleEntry(3, SurfaceRules.ifTrue(SurfaceRules.ON_CEILING, rule)); + }); + rules.add(entryInstance); + return this; + } + + /** + * Set biome ceiling material with specified {@link BlockState} and height. Example - sandstone in the Overworld deserts. The rule is added with priority 3. + * + * @param state {@link BlockState} for the subterrain layer. + * @param height block layer height. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder aboveCeil(BlockState state, int height) { + entryInstance = getFromCache("above_ceil_" + height + "_" + state.toString(), () -> { + RuleSource rule = SurfaceRules.state(state); + rule = SurfaceRules.ifTrue(SurfaceRules.stoneDepthCheck(height, false, CaveSurface.CEILING), rule); + return new SurfaceRuleEntry(3, rule); + }); + rules.add(entryInstance); + return this; + } + + /** + * Will cover steep areas (with large terrain angle). Example - Overworld mountains. + * + * @param state {@link BlockState} for the steep layer. + * @param depth layer depth + * @return + */ + public SurfaceRuleBuilder steep(BlockState state, int depth) { + entryInstance = getFromCache("steep_" + depth + "_" + state.toString(), () -> { + RuleSource rule = SurfaceRules.state(state); + rule = SurfaceRules.ifTrue(SurfaceRules.stoneDepthCheck(depth, false, CaveSurface.FLOOR), rule); + rule = SurfaceRules.ifTrue(SurfaceRules.steep(), rule); + int priority = depth < 1 ? 0 : 1; + return new SurfaceRuleEntry(priority, rule); + }); + rules.add(entryInstance); + return this; + } + + /** + * Allows to add custom rule. + * + * @param priority rule priority, lower values = higher priority (rule will be applied before others). + * @param rule custom {@link SurfaceRules.RuleSource}. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder rule(int priority, SurfaceRules.RuleSource rule) { + rules.add(new SurfaceRuleEntry(priority, rule)); + return this; + } + + /** + * Allows to add custom rule. + * + * @param rule custom {@link SurfaceRules.RuleSource}. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder rule(SurfaceRules.RuleSource rule) { + return rule(7, rule); + } + + /** + * Set biome floor with specified {@link BlockState} and the {@link DoubleBlockSurfaceNoiseCondition}. The rule is added with priority 3. + * + * @param surfaceBlockA {@link BlockState} for the ground cover. + * @param surfaceBlockB {@link BlockState} for the alternative ground cover. + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder chancedFloor(BlockState surfaceBlockA, BlockState surfaceBlockB) { + return chancedFloor(surfaceBlockA, surfaceBlockB, Conditions.DOUBLE_BLOCK_SURFACE_NOISE); + } + + /** + * Set biome floor with specified {@link BlockState} and the given Noise Function. The rule is added with priority 3. + * + * @param surfaceBlockA {@link BlockState} for the ground cover. + * @param surfaceBlockB {@link BlockState} for the alternative ground cover. + * @param noise The {@link NoiseCondition} + * @return same {@link SurfaceRuleBuilder} instance. + */ + public SurfaceRuleBuilder chancedFloor(BlockState surfaceBlockA, BlockState surfaceBlockB, NoiseCondition noise) { + entryInstance = getFromCache( + "chancedFloor_" + surfaceBlockA + "_" + surfaceBlockB + "_" + noise.getClass() + .getSimpleName(), + () -> { + RuleSource rule = + SurfaceRules.ifTrue( + SurfaceRules.ON_FLOOR, + SurfaceRules.sequence( + SurfaceRules.ifTrue( + noise, + SurfaceRules.state( + surfaceBlockA) + ), + SurfaceRules.state(surfaceBlockB) + ) + ); + return new SurfaceRuleEntry(4, rule); + } + ); + rules.add(entryInstance); + return this; + } + + public SurfaceRuleBuilder chancedFloor(BlockState surfaceBlockA, RuleSource surfaceBlockB, NoiseCondition noise) { + entryInstance = getFromCache( + "chancedFloor_" + surfaceBlockA + "_" + surfaceBlockB + "_" + noise.getClass() + .getSimpleName(), + () -> { + RuleSource rule = + SurfaceRules.ifTrue( + SurfaceRules.ON_FLOOR, + SurfaceRules.sequence( + SurfaceRules.ifTrue( + noise, + SurfaceRules.state( + surfaceBlockA) + ), + surfaceBlockB + ) + ); + return new SurfaceRuleEntry(4, rule); + } + ); + rules.add(entryInstance); + return this; + } + + /** + * Finalise rule building process. + * + * @return {@link SurfaceRules.RuleSource}. + */ + public SurfaceRules.RuleSource build() { + Collections.sort(rules); + List ruleList = rules.stream().map(entry -> entry.getRule()).toList(); + SurfaceRules.RuleSource[] ruleArray = ruleList.toArray(new SurfaceRules.RuleSource[ruleList.size()]); + SurfaceRules.RuleSource rule = SurfaceRules.sequence(ruleArray); + if (biomeKey != null) { + rule = SurfaceRules.ifTrue(SurfaceRules.isBiome(biomeKey), rule); + } + return rule; + } + + /** + * Internal function, will take entry from cache or create it if necessary. + * + * @param name {@link String} entry internal name. + * @param supplier {@link Supplier} for {@link SurfaceRuleEntry}. + * @return new or existing {@link SurfaceRuleEntry}. + */ + private static SurfaceRuleEntry getFromCache(String name, Supplier supplier) { + SurfaceRuleEntry entry = RULES_CACHE.get(name); + if (entry == null) { + entry = supplier.get(); + RULES_CACHE.put(name, entry); + } + return entry; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/SurfaceRuleEntry.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/SurfaceRuleEntry.java new file mode 100644 index 00000000..323beb3f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/SurfaceRuleEntry.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.api.v2.levelgen.surface; + +import net.minecraft.world.entity.Mob; +import net.minecraft.world.level.levelgen.SurfaceRules; + +import org.jetbrains.annotations.NotNull; + +public class SurfaceRuleEntry implements Comparable { + private final SurfaceRules.RuleSource rule; + private final byte priority; + + public SurfaceRuleEntry(int priority, SurfaceRules.RuleSource rule) { + this.priority = (byte) priority; + this.rule = rule; + } + + protected SurfaceRules.RuleSource getRule() { + return rule; + } + + @Override + public int compareTo(@NotNull SurfaceRuleEntry entry) { + return Integer.compare(priority, entry.priority); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/Conditions.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/Conditions.java new file mode 100644 index 00000000..d306b362 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/Conditions.java @@ -0,0 +1,91 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.interfaces.NumericProvider; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.valueproviders.UniformFloat; +import net.minecraft.world.level.levelgen.SurfaceRules; + +public class Conditions { + public static final ThresholdCondition DOUBLE_BLOCK_SURFACE_NOISE = new ThresholdCondition( + 4141, + 0, + UniformFloat.of(-0.4f, 0.4f), + 0.1, + 0.1 + ); + + public static final ThresholdCondition FORREST_FLOOR_SURFACE_NOISE_A = new ThresholdCondition( + 614, + 0, + UniformFloat.of(-0.2f, 0f), + 0.1, + 0.1 + ); + + public static final ThresholdCondition FORREST_FLOOR_SURFACE_NOISE_B = new ThresholdCondition( + 614, + 0, + UniformFloat.of(-0.7f, -0.5f), + 0.1, + 0.1 + ); + + public static final ThresholdCondition NETHER_SURFACE_NOISE = new ThresholdCondition( + 245, + 0, + UniformFloat.of(-0.7f, -0.5f), + 0.05, + 0.05 + ); + + public static final ThresholdCondition NETHER_SURFACE_NOISE_LARGE = new ThresholdCondition( + 523, + 0, + UniformFloat.of(-0.4f, -0.3f), + 0.5, + 0.5 + ); + + public static final VolumeThresholdCondition NETHER_VOLUME_NOISE = new VolumeThresholdCondition( + 245, + 0, + UniformFloat.of(-0.1f, 0.2f), + 0.1, + 0.2, + 0.1 + ); + + public static final VolumeThresholdCondition NETHER_VOLUME_NOISE_LARGE = new VolumeThresholdCondition( + 523, + 0, + UniformFloat.of(-0.1f, 0.4f), + 0.2, + 0.2, + 0.2 + ); + + public static final NumericProvider NETHER_NOISE = new NetherNoiseCondition(); + + public static void register(ResourceLocation location, Codec codec) { + Registry.register(BuiltInRegistries.MATERIAL_CONDITION, location, codec); + } + + public static void registerNumeric(ResourceLocation location, Codec codec) { + Registry.register(NumericProvider.NUMERIC_PROVIDER, location, codec); + } + + public static void registerAll() { + registerNumeric(BCLib.makeID("rnd_int"), RandomIntProvider.CODEC); + registerNumeric(BCLib.makeID("nether_noise"), NetherNoiseCondition.CODEC); + register(BCLib.makeID("threshold_condition"), ThresholdCondition.CODEC); + register(BCLib.makeID("volume_threshold_condition"), VolumeThresholdCondition.CODEC); + register(BCLib.makeID("rough_noise_condition"), RoughNoiseCondition.CODEC); + + Registry.register(BuiltInRegistries.MATERIAL_RULE, "bclib_switch_rule", SwitchRuleSource.CODEC); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/DoubleBlockSurfaceNoiseCondition.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/DoubleBlockSurfaceNoiseCondition.java new file mode 100644 index 00000000..6aa0ec7c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/DoubleBlockSurfaceNoiseCondition.java @@ -0,0 +1,5 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +public class DoubleBlockSurfaceNoiseCondition { + public static final ThresholdCondition CONDITION = Conditions.DOUBLE_BLOCK_SURFACE_NOISE; +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/NetherNoiseCondition.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/NetherNoiseCondition.java new file mode 100644 index 00000000..6df8b05a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/NetherNoiseCondition.java @@ -0,0 +1,48 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +import org.betterx.bclib.interfaces.NumericProvider; +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; +import org.betterx.bclib.util.MHelper; + +import com.mojang.serialization.Codec; + +public class NetherNoiseCondition implements NumericProvider { + public static final Codec CODEC = Codec.BYTE.fieldOf("nether_noise") + .xmap( + (obj) -> (NetherNoiseCondition) Conditions.NETHER_NOISE, + obj -> (byte) 0 + ) + .codec(); + + + NetherNoiseCondition() { + } + + + @Override + public Codec pcodec() { + return CODEC; + } + + @Override + public int getNumber(SurfaceRulesContextAccessor context) { + final int x = context.getBlockX(); + final int y = context.getBlockY(); + final int z = context.getBlockZ(); + double value = Conditions.NETHER_VOLUME_NOISE.noiseContext.noise.eval( + x * Conditions.NETHER_VOLUME_NOISE.scaleX, + y * Conditions.NETHER_VOLUME_NOISE.scaleY, + z * Conditions.NETHER_VOLUME_NOISE.scaleZ + ); + + int offset = Conditions.NETHER_VOLUME_NOISE.noiseContext.random.nextInt(20) == 0 ? 3 : 0; + + float cmp = MHelper.randRange(0.4F, 0.5F, Conditions.NETHER_VOLUME_NOISE.noiseContext.random); + if (value > cmp || value < -cmp) return 2 + offset; + + if (value > Conditions.NETHER_VOLUME_NOISE.range.sample(Conditions.NETHER_VOLUME_NOISE.noiseContext.random)) + return 0 + offset; + + return 1 + offset; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/NoiseCondition.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/NoiseCondition.java new file mode 100644 index 00000000..87405225 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/NoiseCondition.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; + +import net.minecraft.world.level.levelgen.SurfaceRules; + +public interface NoiseCondition extends SurfaceRules.ConditionSource { + boolean test(SurfaceRulesContextAccessor context); +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/RandomIntProvider.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/RandomIntProvider.java new file mode 100644 index 00000000..c97f5fb6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/RandomIntProvider.java @@ -0,0 +1,27 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +import org.betterx.bclib.interfaces.NumericProvider; +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; +import org.betterx.bclib.util.MHelper; + +import com.mojang.serialization.Codec; + +public record RandomIntProvider(int range) implements NumericProvider { + public static final Codec CODEC = Codec.INT.fieldOf("range") + .xmap(RandomIntProvider::new, obj -> obj.range) + .codec(); + + @Override + public int getNumber(SurfaceRulesContextAccessor context) { + return MHelper.RANDOM.nextInt(range); + } + + @Override + public Codec pcodec() { + return CODEC; + } + + static { + + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/RoughNoiseCondition.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/RoughNoiseCondition.java new file mode 100644 index 00000000..fff560e7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/RoughNoiseCondition.java @@ -0,0 +1,116 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + + +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; +import org.betterx.bclib.noise.Noises; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.util.RandomSource; +import net.minecraft.util.valueproviders.FloatProvider; +import net.minecraft.util.valueproviders.UniformFloat; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +public class RoughNoiseCondition implements SurfaceRules.ConditionSource { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + ResourceKey.codec(Registries.NOISE).fieldOf("noise").forGetter(o -> o.noise), + Codec.DOUBLE.fieldOf("min_threshold").forGetter(o -> o.minThreshold), + Codec.DOUBLE.fieldOf("max_threshold").forGetter(o -> o.maxThreshold), + FloatProvider.CODEC.fieldOf("roughness").forGetter(o -> o.roughness) + ) + .apply( + instance, + (noise1, minThreshold1, maxThreshold1, roughness1) -> new RoughNoiseCondition( + noise1, + roughness1, + minThreshold1, + maxThreshold1 + ) + )); + + public static final KeyDispatchDataCodec KEY_CODEC = KeyDispatchDataCodec.of(CODEC); + + private final ResourceKey noise; + private final double minThreshold; + private final double maxThreshold; + private final FloatProvider roughness; + + public RoughNoiseCondition( + ResourceKey noise, + FloatProvider roughness, + double minThreshold, + double maxThreshold + ) { + this.noise = noise; + this.minThreshold = minThreshold; + + this.maxThreshold = maxThreshold; + this.roughness = roughness; + } + + public RoughNoiseCondition( + ResourceKey noise, + FloatProvider roughness, + double minThreshold + ) { + this(noise, roughness, minThreshold, Double.MAX_VALUE); + } + + public RoughNoiseCondition( + ResourceKey noise, + double minThreshold + ) { + this(noise, UniformFloat.of(-0.2f, 0.4f), minThreshold, Double.MAX_VALUE); + } + + public RoughNoiseCondition( + ResourceKey noise, + double minThreshold, + double maxThreshold + ) { + this(noise, UniformFloat.of(-0.1f, 0.4f), minThreshold, maxThreshold); + } + + @Override + public KeyDispatchDataCodec codec() { + return KEY_CODEC; + } + + @Override + public SurfaceRules.Condition apply(final SurfaceRules.Context context2) { + final SurfaceRulesContextAccessor ctx = SurfaceRulesContextAccessor.class.cast(context2); + final NormalNoise normalNoise = ctx.getRandomState().getOrCreateNoise(this.noise); + final RandomSource roughnessSource = ctx.getRandomState() + .getOrCreateRandomFactory(Noises.ROUGHNESS_NOISE.location()) + .fromHashOf(Noises.ROUGHNESS_NOISE.location()); + + class NoiseThresholdCondition extends SurfaceRules.LazyCondition { + NoiseThresholdCondition() { + super(context2); + } + + @Override + protected long getContextLastUpdate() { + final SurfaceRulesContextAccessor ctx = SurfaceRulesContextAccessor.class.cast(this.context); + return ctx.getLastUpdateY() + ctx.getLastUpdateXZ(); + } + + protected boolean compute() { + double d = normalNoise + .getValue( + ctx.getBlockX(), + ctx.getBlockZ(), + ctx.getBlockZ() + ) + roughness.sample(roughnessSource); + return d >= minThreshold && d <= maxThreshold; + } + } + + return new NoiseThresholdCondition(); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/SurfaceNoiseCondition.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/SurfaceNoiseCondition.java new file mode 100644 index 00000000..80d7d4a5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/SurfaceNoiseCondition.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; + +import net.minecraft.world.level.levelgen.SurfaceRules.Condition; +import net.minecraft.world.level.levelgen.SurfaceRules.Context; +import net.minecraft.world.level.levelgen.SurfaceRules.LazyXZCondition; + +public abstract class SurfaceNoiseCondition implements NoiseCondition { + @Override + public final Condition apply(Context context2) { + final SurfaceNoiseCondition self = this; + + class Generator extends LazyXZCondition { + Generator() { + super(context2); + } + + @Override + protected boolean compute() { + final SurfaceRulesContextAccessor context = SurfaceRulesContextAccessor.class.cast(this.context); + if (context == null) return false; + return self.test(context); + } + } + + return new Generator(); + } + + public abstract boolean test(SurfaceRulesContextAccessor context); + + +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/SwitchRuleSource.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/SwitchRuleSource.java new file mode 100644 index 00000000..b34d4dcf --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/SwitchRuleSource.java @@ -0,0 +1,53 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +import org.betterx.bclib.interfaces.NumericProvider; +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.SurfaceRules.Context; +import net.minecraft.world.level.levelgen.SurfaceRules.RuleSource; +import net.minecraft.world.level.levelgen.SurfaceRules.SurfaceRule; + +import java.util.List; +import org.jetbrains.annotations.Nullable; + +// +public record SwitchRuleSource(NumericProvider selector, List collection) implements RuleSource { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + NumericProvider.CODEC.fieldOf("selector").forGetter(SwitchRuleSource::selector), + RuleSource.CODEC.listOf().fieldOf("collection").forGetter(SwitchRuleSource::collection) + ) + .apply( + instance, + SwitchRuleSource::new + )); + + private static final KeyDispatchDataCodec KEY_CODEC = KeyDispatchDataCodec.of(SwitchRuleSource.CODEC); + + @Override + public KeyDispatchDataCodec codec() { + return KEY_CODEC; + } + + @Override + public SurfaceRule apply(Context context) { + + return new SurfaceRule() { + @Nullable + @Override + public BlockState tryApply(int x, int y, int z) { + final SurfaceRulesContextAccessor ctx = SurfaceRulesContextAccessor.class.cast(context); + int nr = Math.max(0, selector.getNumber(ctx)) % collection.size(); + + return collection.get(nr).apply(context).tryApply(x, y, z); + } + }; + } + + static { + + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/ThresholdCondition.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/ThresholdCondition.java new file mode 100644 index 00000000..f6a40db7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/ThresholdCondition.java @@ -0,0 +1,80 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; +import org.betterx.bclib.noise.OpenSimplexNoise; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.util.RandomSource; +import net.minecraft.util.valueproviders.ConstantFloat; +import net.minecraft.util.valueproviders.FloatProvider; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.ThreadSafeLegacyRandomSource; + +import com.google.common.collect.Maps; + +import java.util.Map; + +public class ThresholdCondition extends SurfaceNoiseCondition { + private static final Map NOISES = Maps.newHashMap(); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + Codec.LONG.fieldOf("seed").forGetter(p -> p.noiseContext.seed), + Codec.DOUBLE.fieldOf("threshold").orElse(0.0).forGetter(p -> p.threshold), + FloatProvider.CODEC.fieldOf("threshold_offset").orElse(ConstantFloat.of(0)).forGetter(p -> p.range), + Codec.DOUBLE.fieldOf("scale_x").orElse(0.1).forGetter(p -> p.scaleX), + Codec.DOUBLE.fieldOf("scale_z").orElse(0.1).forGetter(p -> p.scaleZ) + ) + .apply(instance, ThresholdCondition::new)); + public static final KeyDispatchDataCodec KEY_CODEC = KeyDispatchDataCodec.of(CODEC); + private final Context noiseContext; + private final double threshold; + private final FloatProvider range; + private final double scaleX; + private final double scaleZ; + + public ThresholdCondition(long noiseSeed, double threshold, FloatProvider range, double scaleX, double scaleZ) { + this.threshold = threshold; + this.range = range; + this.scaleX = scaleX; + this.scaleZ = scaleZ; + + noiseContext = NOISES.computeIfAbsent(noiseSeed, seed -> new Context(seed)); + } + + @Override + public boolean test(SurfaceRulesContextAccessor context) { + final double x = context.getBlockX() * scaleX; + final double z = context.getBlockZ() * scaleZ; + if (noiseContext.lastX == x && noiseContext.lastZ == z) + return noiseContext.lastValue + range.sample(noiseContext.random) > threshold; + double value = noiseContext.noise.eval(x, z); + + noiseContext.lastX = x; + noiseContext.lastZ = z; + noiseContext.lastValue = value; + return value + range.sample(noiseContext.random) > threshold; + } + + @Override + public KeyDispatchDataCodec codec() { + return KEY_CODEC; + } + + static class Context { + public final OpenSimplexNoise noise; + public final RandomSource random; + public final long seed; + + public double lastX = Integer.MIN_VALUE; + public double lastZ = Integer.MIN_VALUE; + public double lastValue = 0; + + Context(long seed) { + this.seed = seed; + this.noise = new OpenSimplexNoise(seed); + this.random = new ThreadSafeLegacyRandomSource(seed * 2); + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/VolumeNoiseCondition.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/VolumeNoiseCondition.java new file mode 100644 index 00000000..54dae221 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/VolumeNoiseCondition.java @@ -0,0 +1,41 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; + +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.level.levelgen.SurfaceRules.Condition; +import net.minecraft.world.level.levelgen.SurfaceRules.ConditionSource; +import net.minecraft.world.level.levelgen.SurfaceRules.Context; +import net.minecraft.world.level.levelgen.SurfaceRules.LazyCondition; + +public abstract class VolumeNoiseCondition implements NoiseCondition { + public abstract KeyDispatchDataCodec codec(); + + @Override + public final Condition apply(Context context2) { + final VolumeNoiseCondition self = this; + + class Generator extends LazyCondition { + Generator() { + super(context2); + } + + @Override + protected long getContextLastUpdate() { + final SurfaceRulesContextAccessor ctx = SurfaceRulesContextAccessor.class.cast(this.context); + return ctx.getLastUpdateY() + ctx.getLastUpdateXZ(); + } + + @Override + protected boolean compute() { + final SurfaceRulesContextAccessor context = SurfaceRulesContextAccessor.class.cast(this.context); + if (context == null) return false; + return self.test(context); + } + } + + return new Generator(); + } + + public abstract boolean test(SurfaceRulesContextAccessor context); +} diff --git a/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/VolumeThresholdCondition.java b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/VolumeThresholdCondition.java new file mode 100644 index 00000000..f26af967 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/levelgen/surface/rules/VolumeThresholdCondition.java @@ -0,0 +1,106 @@ +package org.betterx.bclib.api.v2.levelgen.surface.rules; + +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; +import org.betterx.bclib.noise.OpenSimplexNoise; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.util.RandomSource; +import net.minecraft.util.valueproviders.ConstantFloat; +import net.minecraft.util.valueproviders.FloatProvider; +import net.minecraft.world.level.levelgen.SurfaceRules; +import net.minecraft.world.level.levelgen.ThreadSafeLegacyRandomSource; + +import com.google.common.collect.Maps; + +import java.util.Map; + +public class VolumeThresholdCondition extends VolumeNoiseCondition { + private static final Map NOISES = Maps.newHashMap(); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + Codec.LONG.fieldOf("seed").forGetter(p -> p.noiseContext.seed), + Codec.DOUBLE.fieldOf("threshold").orElse(0.0).forGetter(p -> p.threshold), + FloatProvider.CODEC.fieldOf("threshold_offset").orElse(ConstantFloat.of(0)).forGetter(p -> p.range), + Codec.DOUBLE.fieldOf("scale_x").orElse(0.1).forGetter(p -> p.scaleX), + Codec.DOUBLE.fieldOf("scale_y").orElse(0.1).forGetter(p -> p.scaleY), + Codec.DOUBLE.fieldOf("scale_z").orElse(0.1).forGetter(p -> p.scaleZ) + ) + .apply(instance, VolumeThresholdCondition::new)); + public static final KeyDispatchDataCodec KEY_CODEC = KeyDispatchDataCodec.of(CODEC); + public final VolumeThresholdCondition.Context noiseContext; + public final double threshold; + public final FloatProvider range; + public final double scaleX; + public final double scaleY; + public final double scaleZ; + + public VolumeThresholdCondition( + long noiseSeed, + double threshold, + FloatProvider range, + double scaleX, + double scaleY, + double scaleZ + ) { + this.threshold = threshold; + this.range = range; + this.scaleX = scaleX; + this.scaleY = scaleY; + this.scaleZ = scaleZ; + + noiseContext = NOISES.computeIfAbsent(noiseSeed, seed -> new Context(seed)); + } + + public double getValue(SurfaceRulesContextAccessor context) { + return getValue(context.getBlockX(), context.getBlockY(), context.getBlockZ()); + } + + public double getValue(int xx, int yy, int zz) { + final double x = xx * scaleX; + final double y = yy * scaleY; + final double z = zz * scaleZ; + + if (noiseContext.lastX == x + && noiseContext.lastY == y + && noiseContext.lastZ == z) + return noiseContext.lastValue + range.sample(noiseContext.random); + + double value = noiseContext.noise.eval(x, y, z); + + noiseContext.lastX = x; + noiseContext.lastZ = z; + noiseContext.lastY = y; + noiseContext.lastValue = value; + + return value + range.sample(noiseContext.random); + } + + @Override + public boolean test(SurfaceRulesContextAccessor context) { + return getValue(context) > threshold; + } + + @Override + public KeyDispatchDataCodec codec() { + return KEY_CODEC; + } + + public static class Context { + public final OpenSimplexNoise noise; + public final RandomSource random; + public final long seed; + + double lastX = Integer.MIN_VALUE; + double lastY = Integer.MIN_VALUE; + double lastZ = Integer.MIN_VALUE; + double lastValue = 0; + + Context(long seed) { + this.seed = seed; + this.noise = new OpenSimplexNoise(seed); + this.random = new ThreadSafeLegacyRandomSource(seed * 3 + 1); + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/poi/BCLPoiType.java b/src/main/java/org/betterx/bclib/api/v2/poi/BCLPoiType.java new file mode 100644 index 00000000..3d064836 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/poi/BCLPoiType.java @@ -0,0 +1,113 @@ +package org.betterx.bclib.api.v2.poi; + +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.ai.village.poi.PoiManager; +import net.minecraft.world.entity.ai.village.poi.PoiRecord; +import net.minecraft.world.entity.ai.village.poi.PoiType; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.border.WorldBorder; + +import com.google.common.collect.ImmutableSet; + +import java.util.Comparator; +import java.util.Optional; +import java.util.Set; + +public class BCLPoiType { + public final ResourceKey key; + public final PoiType type; + public final Set matchingStates; + public final int maxTickets; + public final int validRange; + + public BCLPoiType( + ResourceKey key, + PoiType type, + Set matchingStates, + int maxTickets, + int validRange + ) { + this.key = key; + this.type = type; + this.matchingStates = matchingStates; + this.maxTickets = maxTickets; + this.validRange = validRange; + } + + public static Set getBlockStates(Block block) { + return ImmutableSet.copyOf(block.getStateDefinition().getPossibleStates()); + } + + public void setTag(TagKey tag) { + org.betterx.bclib.api.v2.poi.PoiManager.setTag(key, tag); + } + + public Optional findPoiAround( + ServerLevel level, + BlockPos center, + boolean wideSearch, + WorldBorder worldBorder + ) { + return findPoiAround(key, level, center, wideSearch, worldBorder); + } + + public Optional findClosest( + ServerLevel level, + BlockPos center, + int radius + ) { + return level.getPoiManager().findClosest( + holder -> holder.is(this.key), + (pos) -> true, + center, + radius, + PoiManager.Occupancy.ANY + ); + } + + public Optional findPoiAround( + ServerLevel level, + BlockPos center, + int radius, + WorldBorder worldBorder + ) { + return findPoiAround(key, level, center, radius, worldBorder); + } + + public static Optional findPoiAround( + ResourceKey key, + ServerLevel level, + BlockPos center, + boolean wideSearch, + WorldBorder worldBorder + ) { + return findPoiAround(key, level, center, wideSearch ? 16 : 128, worldBorder); + } + + public static Optional findPoiAround( + ResourceKey key, + ServerLevel level, + BlockPos center, + int radius, + WorldBorder worldBorder + ) { + PoiManager poiManager = level.getPoiManager(); + + poiManager.ensureLoadedAndValid(level, center, radius); + Optional record = poiManager + .getInSquare(holder -> holder.is(key), center, radius, PoiManager.Occupancy.ANY) + .filter(poiRecord -> worldBorder.isWithinBounds(poiRecord.getPos())) + .sorted(Comparator.comparingDouble(poiRecord -> poiRecord.getPos().distSqr(center)) + .thenComparingInt(poiRecord -> poiRecord.getPos().getY())) + .filter(poiRecord -> level.getBlockState(poiRecord.getPos()) + .hasProperty(BlockStateProperties.HORIZONTAL_AXIS)) + .findFirst(); + + return record.map(poiRecord -> poiRecord.getPos()); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/poi/PoiManager.java b/src/main/java/org/betterx/bclib/api/v2/poi/PoiManager.java new file mode 100644 index 00000000..e6241c07 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/poi/PoiManager.java @@ -0,0 +1,93 @@ +package org.betterx.bclib.api.v2.poi; + +import org.betterx.bclib.api.v2.levelgen.biomes.InternalBiomeAPI; +import org.betterx.worlds.together.tag.v3.CommonPoiTags; + +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.ai.village.poi.PoiType; +import net.minecraft.world.entity.ai.village.poi.PoiTypes; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.*; +import org.jetbrains.annotations.ApiStatus; + +public class PoiManager { + public static BCLPoiType register( + ResourceLocation location, + Set matchingStates, + int maxTickets, + int validRanges + ) { + ResourceKey key = ResourceKey.create(Registries.POINT_OF_INTEREST_TYPE, location); + PoiType type = PoiTypes.register( + BuiltInRegistries.POINT_OF_INTEREST_TYPE, + key, + matchingStates, + maxTickets, + validRanges + ); + return new BCLPoiType(key, type, matchingStates, maxTickets, validRanges); + } + + public static void setTag(ResourceKey type, TagKey tag) { + var oHolder = BuiltInRegistries.POINT_OF_INTEREST_TYPE.getHolder(type); + if (oHolder.isPresent()) { + setTag(oHolder.get().value(), tag); + didAddTagFor(oHolder.get(), tag); + } + } + + private static void setTag(PoiType type, TagKey tag) { + if ((Object) type instanceof PoiTypeExtension ext) { + ext.bcl_setTag(tag); + } + } + + @ApiStatus.Internal + public static void registerAll() { + PoiManager.setTag(PoiTypes.FISHERMAN, CommonPoiTags.FISHERMAN_WORKSTATION); + PoiManager.setTag(PoiTypes.FARMER, CommonPoiTags.FARMER_WORKSTATION); + } + + + private static final List> TYPES_WITH_TAGS = new ArrayList<>(4); + private static Map> ORIGINAL_BLOCK_STATES = null; + + private static void didAddTagFor(Holder type, TagKey tag) { + TYPES_WITH_TAGS.remove(type); + if (tag != null) TYPES_WITH_TAGS.add(type); + } + + + @ApiStatus.Internal + public static void updateStates() { + if (ORIGINAL_BLOCK_STATES == null) { + //We have not yet tainted the original states, so we will create a copy now + ORIGINAL_BLOCK_STATES = new HashMap<>(PoiTypes.TYPE_BY_STATE); + } else { + //restore unaltered state + PoiTypes.TYPE_BY_STATE.clear(); + PoiTypes.TYPE_BY_STATE.putAll(ORIGINAL_BLOCK_STATES); + } + + for (Holder type : TYPES_WITH_TAGS) { + if ((Object) type.value() instanceof PoiTypeExtension ex) { + TagKey tag = ex.bcl_getTag(); + if (tag != null) { + var registry = InternalBiomeAPI.worldRegistryAccess().registryOrThrow(tag.registry()); + for (var block : registry.getTagOrEmpty(tag)) { + for (var state : block.value().getStateDefinition().getPossibleStates()) { + PoiTypes.TYPE_BY_STATE.put(state, type); + } + } + } + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/poi/PoiTypeExtension.java b/src/main/java/org/betterx/bclib/api/v2/poi/PoiTypeExtension.java new file mode 100644 index 00000000..96063ee1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/poi/PoiTypeExtension.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.api.v2.poi; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Block; + +public interface PoiTypeExtension { + void bcl_setTag(TagKey tag); + TagKey bcl_getTag(); +} diff --git a/src/main/java/org/betterx/bclib/api/v2/spawning/SpawnRuleBuilder.java b/src/main/java/org/betterx/bclib/api/v2/spawning/SpawnRuleBuilder.java new file mode 100644 index 00000000..512c2878 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/spawning/SpawnRuleBuilder.java @@ -0,0 +1,397 @@ +package org.betterx.bclib.api.v2.spawning; + +import org.betterx.bclib.entity.BCLEntityWrapper; +import org.betterx.bclib.interfaces.SpawnRule; +import org.betterx.bclib.util.BlocksHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.Difficulty; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.SpawnPlacements.SpawnPredicate; +import net.minecraft.world.entity.SpawnPlacements.Type; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.levelgen.Heightmap.Types; +import net.minecraft.world.phys.AABB; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.*; +import java.util.function.Supplier; + +public class SpawnRuleBuilder { + private static final Map RULES_CACHE = Maps.newHashMap(); + private static final SpawnRuleBuilder INSTANCE = new SpawnRuleBuilder(); + private final List rules = Lists.newArrayList(); + private SpawnRuleEntry entryInstance; + private EntityType entityType; + + private SpawnRuleBuilder() { + } + + /** + * Starts new rule building process. + * + * @param entityType The entity you want to build a rule for + * @return prepared {@link SpawnRuleBuilder} instance. + */ + public static SpawnRuleBuilder start(EntityType entityType) { + INSTANCE.entityType = entityType; + INSTANCE.rules.clear(); + return INSTANCE; + } + + /** + * Starts new rule building process. + * + * @param wrapper The entity you want to build a rule for + * @return prepared {@link SpawnRuleBuilder} instance. + */ + public static SpawnRuleBuilder start(BCLEntityWrapper wrapper) { + SpawnRuleBuilder builder = start(wrapper.type()); + if (!wrapper.canSpawn()) { + builder.preventSpawn(); + } + return builder; + } + + /** + * Stop entity spawn entierly + * + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder preventSpawn() { + entryInstance = getFromCache("prevent", () -> { + return new SpawnRuleEntry(-1, (type, world, spawnReason, pos, random) -> false, "Prevent Spawn"); + }); + rules.add(entryInstance); + return this; + } + + /** + * Stop entity spawn on peaceful {@link Difficulty} + * + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder notPeaceful() { + entryInstance = getFromCache("not_peaceful", () -> { + return new SpawnRuleEntry( + 0, + (type, world, spawnReason, pos, random) -> world.getDifficulty() != Difficulty.PEACEFUL, + "Not Peaceful" + ); + }); + rules.add(entryInstance); + return this; + } + + /** + * Restricts entity spawn above world surface (flying mobs). + * + * @param minHeight minimal spawn height. + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder aboveGround(int minHeight) { + entryInstance = getFromCache("above_ground", () -> { + return new SpawnRuleEntry(0, (type, world, spawnReason, pos, random) -> { + if (pos.getY() < world.getMinBuildHeight() + 2) { + return false; + } + return BlocksHelper.findSurfaceBelow( + world, + pos, + pos.getY() - minHeight, + (bs) -> !BlocksHelper.isFree(bs) + ) + .isEmpty(); + //return pos.getY() > world.getHeight(Types.WORLD_SURFACE, pos.getX(), pos.getZ()) + minHeight; + }, + "Above Ground" + ); + }); + rules.add(entryInstance); + return this; + } + + /** + * Restricts entity spawn below world logical height (useful for Nether mobs). + * + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder belowMaxHeight() { + entryInstance = getFromCache("below_max_height", () -> { + return new SpawnRuleEntry( + 0, + (type, world, spawnReason, pos, random) -> pos.getY() < world.dimensionType() + .logicalHeight(), + "Below Max Height" + ); + }); + rules.add(entryInstance); + return this; + } + + /** + * Restricts spawning only to vanilla valid blocks. + * + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder onlyOnValidBlocks() { + entryInstance = getFromCache("only_on_valid_blocks", () -> { + return new SpawnRuleEntry(0, (type, world, spawnReason, pos, random) -> { + BlockPos below = pos.below(); + return world.getBlockState(below).isValidSpawn(world, below, type); + }, "Only On Valid Blocks"); + }); + rules.add(entryInstance); + return this; + } + + /** + * Restricts spawning only to specified blocks. + * + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder onlyOnBlocks(Block... blocks) { + final Block[] floorBlocks = blocks; + Arrays.sort(floorBlocks, Comparator.comparing(Block::getDescriptionId)); + + StringBuilder builder = new StringBuilder("only_on_blocks"); + for (Block block : floorBlocks) { + builder.append('_'); + builder.append(block.getDescriptionId()); + } + + entryInstance = getFromCache(builder.toString(), () -> { + return new SpawnRuleEntry(0, (type, world, spawnReason, pos, random) -> { + Block below = world.getBlockState(pos.below()).getBlock(); + for (Block floor : floorBlocks) { + if (floor == below) { + return true; + } + } + return false; + }, "Only On Blocks"); + }); + + rules.add(entryInstance); + return this; + } + + /** + * Will spawn entity with 1 / chance probability (randomly). + * + * @param chance probability limit. + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder withChance(int chance) { + entryInstance = getFromCache("with_chance_" + chance, () -> { + return new SpawnRuleEntry( + 1, + (type, world, spawnReason, pos, random) -> random.nextInt(chance) == 0, + "With Chance" + ); + }); + rules.add(entryInstance); + return this; + } + + /** + * Will spawn entity only below specified brightness value. + * + * @param lightLevel light level upper limit. + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder belowBrightness(int lightLevel) { + entryInstance = getFromCache("below_brightness_" + lightLevel, () -> { + return new SpawnRuleEntry( + 2, + (type, world, spawnReason, pos, random) -> world.getMaxLocalRawBrightness(pos) <= lightLevel, + "Below Brightness" + ); + }); + rules.add(entryInstance); + return this; + } + + /** + * Will spawn entity only above specified brightness value. + * + * @param lightLevel light level lower limit. + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder aboveBrightness(int lightLevel) { + entryInstance = getFromCache("above_brightness_" + lightLevel, () -> { + return new SpawnRuleEntry( + 2, + (type, world, spawnReason, pos, random) -> world.getMaxLocalRawBrightness(pos) >= lightLevel, + "Above Brightness" + ); + }); + rules.add(entryInstance); + return this; + } + + /** + * Entity spawn will follow common vanilla spawn rules - spawn in darkness and not on peaceful level. + * + * @param lightLevel light level upper limit. + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder hostile(int lightLevel) { + return notPeaceful().belowBrightness(lightLevel); + } + + /** + * Entity spawn will follow common vanilla spawn rules - spawn in darkness (below light level 7) and not on peaceful level. + * + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder vanillaHostile() { + return hostile(7); + } + + /** + * Will spawn entity only if count of nearby entities will be lower than specified. + * + * @param selectorType selector {@link EntityType} to search. + * @param count max entity count. + * @param side side of box to search in. + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder maxNearby(EntityType selectorType, int count, int side) { + final Class baseClass = selectorType.getBaseClass(); + entryInstance = getFromCache("max_nearby_" + selectorType.getDescriptionId() + "_" + count + "_" + side, () -> { + return new SpawnRuleEntry(3, (type, world, spawnReason, pos, random) -> { + try { + final AABB box = new AABB(pos).inflate(side, world.getHeight(), side); + final List list = world.getEntitiesOfClass(baseClass, box, (entity) -> true); + return list.size() < count; + } catch (Exception e) { + return true; + } + }, "Max Nearby " + count + "/" + side); + }); + rules.add(entryInstance); + return this; + } + + /** + * Will spawn entity only if count of nearby entities with same type will be lower than specified. + * + * @param count max entity count. + * @param side side of box to search in. + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder maxNearby(int count, int side) { + return maxNearby(entityType, count, side); + } + + /** + * Will spawn entity only if count of nearby entities with same type will be lower than specified. + * + * @param count max entity count. + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder maxNearby(int count) { + return maxNearby(entityType, count, 256); + } + + /** + * Allows to add custom spawning rule for specific entities. + * + * @param rule {@link SpawnRule} rule, can be a lambda expression. + * @return same {@link SpawnRuleBuilder} instance. + */ + public SpawnRuleBuilder customRule(SpawnRule rule) { + rules.add(new SpawnRuleEntry(7, rule, "Custom Rule")); + return this; + } + + /** + * Finalize spawning rule creation. + * + * @param spawnType {@link Type} of spawn. + * @param heightmapType {@link Types} heightmap type. + */ + public void build(Type spawnType, Types heightmapType) { + final List rulesCopy = Lists.newArrayList(this.rules); + Collections.sort(rulesCopy); + + SpawnPredicate predicate = (entityType, serverLevelAccessor, mobSpawnType, blockPos, random) -> { + for (SpawnRuleEntry rule : rulesCopy) { + if (!rule.canSpawn(entityType, serverLevelAccessor, mobSpawnType, blockPos, random)) { +// BCLib.LOGGER.info("Rejected Spawn of " +// + entityType.getDescriptionId() +// + " at " + blockPos.getX() + " " + blockPos.getY() + " " + blockPos.getZ() +// + " because " + rule.debugName +// + " at " + serverLevelAccessor.getBlockState(blockPos) +// + " above " + serverLevelAccessor.getBlockState(blockPos.below()) +// ); + + return false; + } + } +// BCLib.LOGGER.info("Spawning " + entityType.getDescriptionId() + " at " + blockPos.getX() + " " + blockPos.getY() + " " + blockPos.getZ() + ""); + return true; + }; + + SpawnPlacements.register(entityType, spawnType, heightmapType, predicate); + } + + /** + * Finalize spawning rule creation with No Restrictions spawn type, useful for flying entities. + * + * @param heightmapType {@link Types} heightmap type. + */ + public void buildNoRestrictions(Types heightmapType) { + build(Type.NO_RESTRICTIONS, heightmapType); + } + + /** + * Finalize spawning rule creation with On Ground spawn type, useful for common entities. + * + * @param heightmapType {@link Types} heightmap type. + */ + public void buildOnGround(Types heightmapType) { + build(Type.ON_GROUND, heightmapType); + } + + /** + * Finalize spawning rule creation with In Water spawn type, useful for water entities. + * + * @param heightmapType {@link Types} heightmap type. + */ + public void buildInWater(Types heightmapType) { + build(Type.IN_WATER, heightmapType); + } + + /** + * Finalize spawning rule creation with In Lava spawn type, useful for lava entities. + * + * @param heightmapType {@link Types} heightmap type. + */ + public void buildInLava(Types heightmapType) { + build(Type.IN_LAVA, heightmapType); + } + + /** + * Internal function, will take entry from cache or create it if necessary. + * + * @param name {@link String} entry internal name. + * @param supplier {@link Supplier} for {@link SpawnRuleEntry}. + * @return new or existing {@link SpawnRuleEntry}. + */ + private static SpawnRuleEntry getFromCache(String name, Supplier supplier) { + return supplier.get(); +// SpawnRuleEntry entry = RULES_CACHE.get(name); +// if (entry == null) { +// entry = supplier.get(); +// RULES_CACHE.put(name, entry); +// } +// return entry; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v2/spawning/SpawnRuleEntry.java b/src/main/java/org/betterx/bclib/api/v2/spawning/SpawnRuleEntry.java new file mode 100644 index 00000000..817ae3e6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v2/spawning/SpawnRuleEntry.java @@ -0,0 +1,39 @@ +package org.betterx.bclib.api.v2.spawning; + +import org.betterx.bclib.interfaces.SpawnRule; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.level.LevelAccessor; + +import org.jetbrains.annotations.NotNull; + +public class SpawnRuleEntry implements Comparable { + private final SpawnRule rule; + private final byte priority; + //public final String debugName; + + public SpawnRuleEntry(int priority, SpawnRule rule, String debugName) { + this.priority = (byte) priority; + this.rule = rule; + //this.debugName = debugName; + } + + protected boolean canSpawn( + EntityType type, + LevelAccessor world, + MobSpawnType spawnReason, + BlockPos pos, + RandomSource random + ) { + return rule.canSpawn(type, world, spawnReason, pos, random); + } + + @Override + public int compareTo(@NotNull SpawnRuleEntry entry) { + return Integer.compare(priority, entry.priority); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/BlockSpreader.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BlockSpreader.java new file mode 100644 index 00000000..ed03243c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BlockSpreader.java @@ -0,0 +1,76 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.util.random.SimpleWeightedRandomList; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.stateproviders.WeightedStateProvider; + +import java.util.HashMap; +import java.util.Map; + +abstract class BlockSpreader implements BonemealBlockSpreader { + protected abstract boolean isValidSource(BlockState state); + protected abstract boolean hasCustomBehaviour(BlockState state); + + public boolean isValidBonemealSpreadTarget( + BlockGetter blockGetter, + BlockPos blockPos, + BlockState blockState, + boolean bl + ) { + if (!canSpreadAt(blockGetter, blockPos)) { + return false; + } else { + for (BlockPos testPos : BlockPos.betweenClosed( + blockPos.offset(-1, -1, -1), + blockPos.offset(1, 1, 1) + )) { + BlockState state = blockGetter.getBlockState(testPos); + if (isValidSource(state)) + if (hasCustomBehaviour(state)) + return true; + } + return false; + } + } + + public boolean performBonemealSpread( + ServerLevel serverLevel, + RandomSource randomSource, + BlockPos blockPos, + BlockState blockState + ) { + final Map sourceBlocks = new HashMap<>(); + + for (BlockPos testPos : BlockPos.betweenClosed( + blockPos.offset(-1, -1, -1), + blockPos.offset(1, 1, 1) + )) { + BlockState state = serverLevel.getBlockState(testPos); + if (isValidSource(state)) { + sourceBlocks.compute(state, (k, v) -> { + if (v == null) return 1; + return v + 1; + }); + } + } + + SimpleWeightedRandomList.Builder builder = new SimpleWeightedRandomList.Builder<>(); + for (Map.Entry e : sourceBlocks.entrySet()) { + builder.add(e.getKey(), e.getValue()); + } + WeightedStateProvider provider = new WeightedStateProvider(builder.build()); + + BlockState bl = provider.getState(randomSource, blockPos); + if (bl != null) { + serverLevel.setBlock(blockPos, bl, 3); + return true; + } + + + return false; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealAPI.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealAPI.java new file mode 100644 index 00000000..1a6e7425 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealAPI.java @@ -0,0 +1,150 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import org.betterx.bclib.api.v3.levelgen.features.BCLConfigureFeature; +import org.betterx.bclib.api.v3.tag.BCLBlockTags; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; + +import java.util.HashMap; +import java.util.Map; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +public class BonemealAPI { + public static BonemealAPI INSTANCE = new BonemealAPI(); + private final Map, BonemealBlockSpreader> taggedSpreaders; + private final Map featureSpreaders; + + private BonemealAPI() { + taggedSpreaders = new HashMap<>(); + featureSpreaders = new HashMap<>(); + + addSpreadableBlocks(BCLBlockTags.BONEMEAL_TARGET_NETHERRACK, NetherrackSpreader.INSTANCE); + addSpreadableBlocks(BCLBlockTags.BONEMEAL_TARGET_END_STONE, EndStoneSpreader.INSTANCE); + addSpreadableBlocks(BCLBlockTags.BONEMEAL_TARGET_OBSIDIAN, BCLBlockTags.BONEMEAL_SOURCE_OBSIDIAN); + } + + /** + * Bonemeal can be used to spread vegetation to neighbouring blocks. + *

+ * This method allows you to register a block (the type that was clicked with bonemeal) with + * a {@link BCLConfigureFeature} that will be placed on the bonemeald block + *

+ * You can achieve the same behaviour by implementing {@link BonemealNyliumLike} on your custom + * BlockClass. This is mainly intended for vanilla Blocks where you need to add bonemeal + * behaviour + * + * @param target The block-type + * @param spreadableFeature the feature to place + */ + public void addSpreadableFeatures( + Block target, + @NotNull BCLConfigureFeature, ?> spreadableFeature + ) { + featureSpreaders.put(target, new FeatureSpreader(target, spreadableFeature)); + } + + /** + * Bonemeal can be used to spread for example Nylium to Netherrack. + *

+ * In this example, Netherrack is the target block which will get replaced by one of the source blocks (like + * Warped or Crimson Nylium. You can register Tag-Combinations to easily add your own behaviour for custom + * blocks. + *

+ * When a Block with the Target-Tag + * + * @param targetTag If you click a Block with the given Tag using Bonemeal, you will replace it with + * a block from the sourceTag + * @param sourceTag Blocks with this Tag can replace the Target block if they are in a 3x3 Neighborhood + * centered around the target Block. + */ + public void addSpreadableBlocks(@NotNull TagKey targetTag, @NotNull TagKey sourceTag) { + taggedSpreaders.put(targetTag, new TaggedBonemealBlockSpreader(sourceTag)); + } + + /** + * See {@link #addSpreadableBlocks(TagKey, TagKey)} for Details. + * + * @param targetTag If you click a Block with the given Tag using Bonemeal, you will replace it with + * * a block from the sourceTag + * @param spreader The {@link BonemealBlockSpreader}-Object that is called when a corresponding target-Block + * is clicked with Bone-Meal + */ + public void addSpreadableBlocks(@NotNull TagKey targetTag, @NotNull BonemealBlockSpreader spreader) { + taggedSpreaders.put(targetTag, spreader); + } + + /** + * When a block is clicked with Bonemeal, this method will be called with the state of the given Block. + *

+ * If the Method returs a valid {@link BonemealBlockSpreader}-Instance, it will override the default behaviour + * for the BoneMeal, otherwise the vanilla action will be performed. + * + * @param state The {@link BlockState} you need to test + * @return A valid spreader instance, or {@code null} + */ + @ApiStatus.Internal + public BonemealBlockSpreader blockSpreaderForState( + BlockGetter blockGetter, + BlockPos pos, + @NotNull BlockState state + ) { + for (var e : taggedSpreaders.entrySet()) { + if (state.is(e.getKey()) && e.getValue().canSpreadAt(blockGetter, pos)) { + return e.getValue(); + } + } + + return null; + } + + @ApiStatus.Internal + public FeatureSpreader featureSpreaderForState(@NotNull BlockState state) { + return featureSpreaders.get(state.getBlock()); + } + + @ApiStatus.Internal + public boolean runSpreaders(ItemStack itemStack, Level level, BlockPos blockPos, boolean forceBonemeal) { + BlockState blockState = level.getBlockState(blockPos); + BonemealBlockSpreader spreader = org.betterx.bclib.api.v3.bonemeal.BonemealAPI + .INSTANCE + .blockSpreaderForState(level, blockPos, blockState); + + if (spreader != null) { + if (spreader.isValidBonemealSpreadTarget(level, blockPos, blockState, level.isClientSide)) { + if (level instanceof ServerLevel) { + if (spreader.performBonemealSpread((ServerLevel) level, level.random, blockPos, blockState)) { + itemStack.shrink(1); + } + } + return true; + } + } + + FeatureSpreader fSpreader = org.betterx.bclib.api.v3.bonemeal.BonemealAPI + .INSTANCE + .featureSpreaderForState(blockState); + + if (fSpreader != null) { + if (fSpreader.isValidBonemealTarget(level, blockPos, blockState, level.isClientSide)) { + if (level instanceof ServerLevel) { + if (forceBonemeal || fSpreader.isBonemealSuccess(level, level.random, blockPos, blockState)) { + fSpreader.performBonemeal((ServerLevel) level, level.random, blockPos, blockState); + } + itemStack.shrink(1); + } + return true; + } + } + + return false; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealBlockSpreader.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealBlockSpreader.java new file mode 100644 index 00000000..9343879a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealBlockSpreader.java @@ -0,0 +1,20 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; + +public interface BonemealBlockSpreader { + boolean isValidBonemealSpreadTarget(BlockGetter blockGetter, BlockPos blockPos, BlockState blockState, boolean bl); + boolean canSpreadAt(BlockGetter blockGetter, BlockPos blockPos); + + boolean performBonemealSpread( + ServerLevel serverLevel, + RandomSource randomSource, + BlockPos blockPos, + BlockState blockState + ); + +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealGrassLike.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealGrassLike.java new file mode 100644 index 00000000..55d1321f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealGrassLike.java @@ -0,0 +1,103 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.BonemealableBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.configurations.RandomPatchConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import java.util.List; + +public interface BonemealGrassLike extends BonemealableBlock { + BlockState getGrowableCoverState(); //Blocks.GRASS.defaultBlockState(); + Block getHostBlock(); //this + + Holder getCoverFeature(); //VegetationPlacements.GRASS_BONEMEAL + List> getFlowerFeatures(); /*serverLevel.getBiome(currentPos) + .value() + .getGenerationSettings() + .getFlowerFeatures();*/ + + default boolean canGrowFlower(RandomSource random) { + return random.nextInt(8) == 0; + } + default boolean canGrowCover(RandomSource random) { + return random.nextInt(10) == 0; + } + + default boolean isValidBonemealTarget( + BlockGetter blockGetter, + BlockPos blockPos, + BlockState blockState, + boolean bl + ) { + return blockGetter.getBlockState(blockPos.above()).isAir(); + } + + default boolean isBonemealSuccess( + Level level, + RandomSource randomSource, + BlockPos blockPos, + BlockState blockState + ) { + return true; + } + + default void performBonemeal(ServerLevel serverLevel, RandomSource random, BlockPos pos, BlockState state) { + final BlockPos above = pos.above(); + final BlockState growableState = getGrowableCoverState(); + + outerLoop: + for (int bonemealAttempt = 0; bonemealAttempt < 128; ++bonemealAttempt) { + BlockPos currentPos = above; + + for (int j = 0; j < bonemealAttempt / 16; ++j) { + currentPos = currentPos.offset( + random.nextInt(3) - 1, + (random.nextInt(3) - 1) * random.nextInt(3) / 2, + random.nextInt(3) - 1 + ); + if (!serverLevel.getBlockState(currentPos.below()).is(getHostBlock()) + || serverLevel.getBlockState(currentPos) + .isCollisionShapeFullBlock(serverLevel, currentPos)) { + continue outerLoop; + } + } + + BlockState currentState = serverLevel.getBlockState(currentPos); + if (currentState.is(growableState.getBlock()) && canGrowCover(random)) { + ((BonemealableBlock) growableState.getBlock()).performBonemeal( + serverLevel, + random, + currentPos, + currentState + ); + } + + if (currentState.isAir()) { + Holder boneFeature; + if (canGrowFlower(random)) { + List> list = getFlowerFeatures(); + if (list.isEmpty()) { + continue; + } + + boneFeature = ((RandomPatchConfiguration) list.get(0).config()).feature(); + } else { + boneFeature = getCoverFeature(); + } + + boneFeature.value() + .place(serverLevel, serverLevel.getChunkSource().getGenerator(), random, currentPos); + } + } + + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealNyliumLike.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealNyliumLike.java new file mode 100644 index 00000000..7359e6ba --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/BonemealNyliumLike.java @@ -0,0 +1,52 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import org.betterx.bclib.api.v3.levelgen.features.BCLConfigureFeature; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.BonemealableBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; + +//adapted from NyliumBlock +public interface BonemealNyliumLike extends BonemealableBlock { + Block getHostBlock(); //this + BCLConfigureFeature, ?> getCoverFeature(); + + default boolean isValidBonemealTarget( + LevelReader blockGetter, + BlockPos blockPos, + BlockState blockState, + boolean bl + ) { + return blockGetter.getBlockState(blockPos.above()).isAir(); + } + + default boolean isBonemealSuccess( + Level level, + RandomSource randomSource, + BlockPos blockPos, + BlockState blockState + ) { + return true; + } + + default void performBonemeal( + ServerLevel serverLevel, + RandomSource randomSource, + BlockPos blockPos, + BlockState blockState + ) { + final BlockState currentState = serverLevel.getBlockState(blockPos); + if (currentState.is(getHostBlock())) { + BCLConfigureFeature, ?> feature = getCoverFeature(); + if (feature != null) { + feature.placeInWorld(serverLevel, blockPos.above(), randomSource, true); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/EndStoneSpreader.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/EndStoneSpreader.java new file mode 100644 index 00000000..6d9ad02c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/EndStoneSpreader.java @@ -0,0 +1,12 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import org.betterx.bclib.api.v3.tag.BCLBlockTags; + +public class EndStoneSpreader extends TaggedBonemealBlockSpreader { + static final EndStoneSpreader INSTANCE = new EndStoneSpreader(); + + protected EndStoneSpreader() { + super(BCLBlockTags.BONEMEAL_SOURCE_END_STONE); + } + +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/FeatureSpreader.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/FeatureSpreader.java new file mode 100644 index 00000000..cd4a9561 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/FeatureSpreader.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import org.betterx.bclib.api.v3.levelgen.features.BCLConfigureFeature; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; + +public class FeatureSpreader implements BonemealNyliumLike { + public final BCLConfigureFeature, ?> spreadableFeature; + public final Block hostBlock; + + public FeatureSpreader(Block hostBlock, BCLConfigureFeature, ?> spreadableFeature) { + this.spreadableFeature = spreadableFeature; + this.hostBlock = hostBlock; + } + + @Override + public boolean isValidBonemealTarget( + LevelReader blockGetter, + BlockPos blockPos, + BlockState blockState, + boolean bl + ) { + return spreadableFeature != null + && BonemealNyliumLike.super.isValidBonemealTarget(blockGetter, blockPos, blockState, bl); + } + + @Override + public Block getHostBlock() { + return hostBlock; + } + + @Override + public BCLConfigureFeature, ?> getCoverFeature() { + return spreadableFeature; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/NetherrackSpreader.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/NetherrackSpreader.java new file mode 100644 index 00000000..fd73bafa --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/NetherrackSpreader.java @@ -0,0 +1,18 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import org.betterx.bclib.api.v3.tag.BCLBlockTags; + +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +public class NetherrackSpreader extends TaggedBonemealBlockSpreader { + public static final NetherrackSpreader INSTANCE = new NetherrackSpreader(); + + protected NetherrackSpreader() { + super(BCLBlockTags.BONEMEAL_SOURCE_NETHERRACK); + } + + protected boolean hasCustomBehaviour(BlockState state) { + return !state.is(Blocks.WARPED_NYLIUM) && !state.is(Blocks.CRIMSON_NYLIUM); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/TaggedBonemealBlockSpreader.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/TaggedBonemealBlockSpreader.java new file mode 100644 index 00000000..11419ba9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/TaggedBonemealBlockSpreader.java @@ -0,0 +1,31 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import net.minecraft.core.BlockPos; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +class TaggedBonemealBlockSpreader extends BlockSpreader { + public final TagKey blockTag; + + public TaggedBonemealBlockSpreader(TagKey blockTag) { + this.blockTag = blockTag; + } + + @Override + public boolean canSpreadAt(BlockGetter blockGetter, BlockPos blockPos) { + final BlockState aboveState = blockGetter.getBlockState(blockPos.above()); + return aboveState.isAir() && aboveState.propagatesSkylightDown(blockGetter, blockPos); + } + + @Override + protected boolean isValidSource(BlockState state) { + return state.is(blockTag); + } + + @Override + protected boolean hasCustomBehaviour(BlockState state) { + return true; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/bonemeal/WaterGrassSpreader.java b/src/main/java/org/betterx/bclib/api/v3/bonemeal/WaterGrassSpreader.java new file mode 100644 index 00000000..b80823ab --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/bonemeal/WaterGrassSpreader.java @@ -0,0 +1,81 @@ +package org.betterx.bclib.api.v3.bonemeal; + +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.WeightedList; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.TagKey; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +public class WaterGrassSpreader extends TaggedBonemealBlockSpreader { + public WaterGrassSpreader(TagKey sourceBlockTag) { + super(sourceBlockTag); + } + + @Override + public boolean canSpreadAt(BlockGetter blockGetter, BlockPos blockPos) { + final BlockState stateAbove = blockGetter.getBlockState(blockPos.above()); + return !stateAbove.getFluidState().isEmpty() && stateAbove.is(Blocks.WATER); + } + + @Override + public boolean isValidBonemealSpreadTarget( + BlockGetter blockGetter, + BlockPos blockPos, + BlockState blockState, + boolean bl + ) { + return canSpreadAt(blockGetter, blockPos); + } + + @Override + public boolean performBonemealSpread( + ServerLevel level, + RandomSource randomSource, + BlockPos pos, + BlockState blockState + ) { + final BlockPos.MutableBlockPos currentPos = new BlockPos.MutableBlockPos(); + final WeightedList> sourceSet = new WeightedList<>(); + BuiltInRegistries.BLOCK.getTagOrEmpty(blockTag).forEach(c -> sourceSet.add(c, 1)); + + int y1 = pos.getY() + 3; + int y2 = pos.getY() - 3; + boolean result = false; + for (byte i = 0; i < 64; i++) { + int x = (int) (pos.getX() + level.random.nextGaussian() * 2); + int z = (int) (pos.getZ() + level.random.nextGaussian() * 2); + currentPos.setX(x); + currentPos.setZ(z); + for (int y = y1; y >= y2; y--) { + currentPos.setY(y); + BlockPos down = currentPos.below(); + if (BlocksHelper.isFluid(level.getBlockState(currentPos)) + && !BlocksHelper.isFluid(level.getBlockState(down))) { + Holder grass = sourceSet.get(randomSource); + if (grass.isBound()) { + if (grass.value().canSurvive(grass.value().defaultBlockState(), level, currentPos)) { + level.setBlock(currentPos, grass.value().defaultBlockState(), BlocksHelper.SET_SILENT); + result = true; + } + } +// BiConsumer grass = getWaterGrassState(level, down); +// if (grass != null) { +// grass.accept(level, currentPos); +// result = true; +// } + + break; + } + } + } + return result; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/AdvancementDataProvider.java b/src/main/java/org/betterx/bclib/api/v3/datagen/AdvancementDataProvider.java new file mode 100644 index 00000000..c6853af7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/AdvancementDataProvider.java @@ -0,0 +1,28 @@ +package org.betterx.bclib.api.v3.datagen; + +import org.betterx.bclib.api.v2.advancement.AdvancementManager; + +import net.minecraft.advancements.Advancement; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricAdvancementProvider; + +import java.util.List; +import java.util.function.Consumer; + +public abstract class AdvancementDataProvider extends FabricAdvancementProvider { + protected final List modIDs; + + protected AdvancementDataProvider(List modIDs, FabricDataOutput output) { + super(output); + this.modIDs = modIDs; + } + + protected abstract void bootstrap(); + + @Override + public void generateAdvancement(Consumer consumer) { + bootstrap(); + AdvancementManager.registerAllDataGen(modIDs, consumer); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/BlockLootTableProvider.java b/src/main/java/org/betterx/bclib/api/v3/datagen/BlockLootTableProvider.java new file mode 100644 index 00000000..ceb8774a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/BlockLootTableProvider.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.api.v3.datagen; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.provider.SimpleFabricLootTableProvider; + +import java.util.List; +import java.util.function.BiConsumer; + +public class BlockLootTableProvider extends SimpleFabricLootTableProvider { + protected final List modIDs; + + public BlockLootTableProvider( + FabricDataOutput output, + List modIDs + ) { + super(output, LootContextParamSets.BLOCK); + this.modIDs = modIDs; + } + + + @Override + public void generate(BiConsumer biConsumer) { + for (Block block : BuiltInRegistries.BLOCK) { + if (block instanceof LootDropProvider dropper) { + ResourceLocation id = BuiltInRegistries.BLOCK.getKey(block); + if (id != null && modIDs.contains(id.getNamespace())) { + LootTable.Builder builder = LootTable.lootTable(); + dropper.getDroppedItemsBCL(builder); + biConsumer.accept(id.withPrefix("blocks/"), builder); + } + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/DatapackRecipeBuilder.java b/src/main/java/org/betterx/bclib/api/v3/datagen/DatapackRecipeBuilder.java new file mode 100644 index 00000000..d162d665 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/DatapackRecipeBuilder.java @@ -0,0 +1,15 @@ +package org.betterx.bclib.api.v3.datagen; + +import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; + +public interface DatapackRecipeBuilder { + ResourceLocation getId(); + + default String getNamespace() { + return this.getId().getNamespace(); + } + void build(Consumer cc); +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/DropSelfLootProvider.java b/src/main/java/org/betterx/bclib/api/v3/datagen/DropSelfLootProvider.java new file mode 100644 index 00000000..182828dd --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/DropSelfLootProvider.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.api.v3.datagen; + +import org.betterx.bclib.behaviours.interfaces.BehaviourExplosionResistant; + +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.LootItem; +import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition; +import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; + +public interface DropSelfLootProvider extends LootDropProvider { + @Override + default void getDroppedItemsBCL(LootTable.Builder builder) { + var pool = LootPool.lootPool() + .setRolls(ConstantValue.exactly(1.0f)) + .add(LootItem.lootTableItem((B) this)); + + if (this instanceof BehaviourExplosionResistant) { + pool = pool.when(ExplosionCondition.survivesExplosion()); + } + + builder.withPool(pool); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/FlatLevelPresetHelper.java b/src/main/java/org/betterx/bclib/api/v3/datagen/FlatLevelPresetHelper.java new file mode 100644 index 00000000..0b714d54 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/FlatLevelPresetHelper.java @@ -0,0 +1,64 @@ +package org.betterx.bclib.api.v3.datagen; + +import net.minecraft.core.HolderGetter; +import net.minecraft.core.HolderSet; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.flat.FlatLayerInfo; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorPreset; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.structure.StructureSet; + +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +public class FlatLevelPresetHelper { + public static void register( + BootstapContext ctx, + ResourceKey presetKey, + ItemLike icon, + ResourceKey biomeKey, + Set> allowedStructureSets, + boolean addDecorations, + boolean addLakes, + FlatLayerInfo... flatLayerInfos + ) { + final HolderGetter structureSets = ctx.lookup(Registries.STRUCTURE_SET); + Objects.requireNonNull(structureSets); + + final HolderGetter placedFeatures = ctx.lookup(Registries.PLACED_FEATURE); + final HolderGetter biomes = ctx.lookup(Registries.BIOME); + + HolderSet.Direct structures = HolderSet.direct( + allowedStructureSets.stream() + .map(structureSets::getOrThrow) + .toList()); + + FlatLevelGeneratorSettings flatLevelGeneratorSettings = new FlatLevelGeneratorSettings( + Optional.of(structures), + biomes.getOrThrow(biomeKey), + FlatLevelGeneratorSettings.createLakesList(placedFeatures) + ); + if (addDecorations) { + flatLevelGeneratorSettings.setDecoration(); + } + + if (addLakes) { + flatLevelGeneratorSettings.setAddLakes(); + } + + for (int i = flatLayerInfos.length - 1; i >= 0; --i) { + flatLevelGeneratorSettings.getLayersInfo().add(flatLayerInfos[i]); + } + + ctx.register( + presetKey, + new FlatLevelGeneratorPreset(icon.asItem().builtInRegistryHolder(), flatLevelGeneratorSettings) + ); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/LootDropProvider.java b/src/main/java/org/betterx/bclib/api/v3/datagen/LootDropProvider.java new file mode 100644 index 00000000..98129444 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/LootDropProvider.java @@ -0,0 +1,7 @@ +package org.betterx.bclib.api.v3.datagen; + +import net.minecraft.world.level.storage.loot.LootTable; + +public interface LootDropProvider { + void getDroppedItemsBCL(LootTable.Builder builder); +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/ProcessorHelper.java b/src/main/java/org/betterx/bclib/api/v3/datagen/ProcessorHelper.java new file mode 100644 index 00000000..21164eee --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/ProcessorHelper.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.api.v3.datagen; + +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList; + +import java.util.List; + +public class ProcessorHelper { + public static ResourceKey createKey(ResourceLocation id) { + return ResourceKey.create(Registries.PROCESSOR_LIST, id); + } + + public static void register( + BootstapContext bootstapContext, + ResourceKey resourceKey, + List list + ) { + bootstapContext.register(resourceKey, new StructureProcessorList(list)); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/RecipeDataProvider.java b/src/main/java/org/betterx/bclib/api/v3/datagen/RecipeDataProvider.java new file mode 100644 index 00000000..d952dd1a --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/RecipeDataProvider.java @@ -0,0 +1,47 @@ +package org.betterx.bclib.api.v3.datagen; + +import org.betterx.bclib.BCLib; + +import net.minecraft.data.recipes.FinishedRecipe; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public class RecipeDataProvider extends FabricRecipeProvider { + private static List RECIPES; + + @Nullable + protected final List modIDs; + + public RecipeDataProvider(@Nullable List modIDs, FabricDataOutput output) { + super(output); + this.modIDs = modIDs; + } + + @Override + public void buildRecipes(Consumer exporter) { + if (RECIPES == null) return; + + for (var r : RECIPES) { + if (modIDs.size() == 0 || modIDs.indexOf(r.getNamespace()) >= 0) { + r.build(exporter); + } + } + } + + @ApiStatus.Internal + public static void register(DatapackRecipeBuilder builder) { + //thi is only used withe the Data Generator, so we do not keep this list on a regular run + if (!BCLib.isDatagen()) { + return; + } + if (RECIPES == null) RECIPES = new ArrayList<>(); + RECIPES.add(builder); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/RegistriesDataProvider.java b/src/main/java/org/betterx/bclib/api/v3/datagen/RegistriesDataProvider.java new file mode 100644 index 00000000..a3d4215e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/RegistriesDataProvider.java @@ -0,0 +1,148 @@ +package org.betterx.bclib.api.v3.datagen; + +import org.betterx.worlds.together.util.Logger; + +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.Encoder; +import com.mojang.serialization.JsonOps; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.RegistrySetBuilder; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataProvider; +import net.minecraft.data.PackOutput; +import net.minecraft.resources.RegistryOps; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; + +import com.google.gson.JsonElement; + +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public abstract class RegistriesDataProvider implements DataProvider { + protected final RegistrySupplier registries; + protected final PackOutput output; + protected final Logger LOGGER; + protected final CompletableFuture registriesFuture; + + protected RegistriesDataProvider( + Logger logger, + RegistrySupplier registries, + FabricDataOutput generator, + CompletableFuture registriesFuture + ) { + this.LOGGER = logger; + this.output = generator; + this.registriesFuture = registriesFuture; + this.registries = registries; + } + + @Override + public CompletableFuture run(CachedOutput cachedOutput) { + LOGGER.info("Serialize Registries " + registries.defaultModIDs); + + return registriesFuture.thenCompose(registriesProvider -> CompletableFuture + .supplyAsync(() -> registries) + .thenCompose(entries -> { + registries.acquireLock(); + final RegistryOps dynamicOps = RegistryOps.create( + JsonOps.INSTANCE, + registriesProvider + ); + + CompletableFuture[] futures = entries + .allRegistries + .stream() + .map(registryData -> serializeRegistry( + cachedOutput, + registriesProvider, + dynamicOps, + registryData, + registries + )) + .toArray(CompletableFuture[]::new); + + registries.releaseLock(); + return CompletableFuture.allOf(futures); + } + ) + ); + } + + + private CompletableFuture serializeRegistry( + CachedOutput cachedOutput, + HolderLookup.Provider registryAccess, + DynamicOps dynamicOps, + RegistrySupplier.RegistryInfo registryData, + RegistrySupplier registries + ) { + try { + registries.MAIN_LOCK.acquire(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + final List> elements = registryData.allElements(registryAccess); + final HolderLookup.RegistryLookup registry = registryAccess.lookupOrThrow(registryData.key()); + LOGGER.info("Serializing " + + elements.size() + + "/" + + registry.listElements().count() + + " elements from " + + registryData.data.key() + ); + registries.MAIN_LOCK.release(); + + if (!elements.isEmpty()) { + PackOutput.PathProvider pathProvider = this.output.createPathProvider( + PackOutput.Target.DATA_PACK, + registryData.key().location().getPath() + ); + + return CompletableFuture.allOf(elements.stream().map(entry -> serializeElements( + pathProvider.json(entry.unwrapKey().orElseThrow().location()), + cachedOutput, + dynamicOps, + registryData.elementCodec(), + entry.value() + )).toArray(CompletableFuture[]::new)); + } + return CompletableFuture.completedFuture(null); + } + + private CompletableFuture serializeElements( + Path path, + CachedOutput cachedOutput, + DynamicOps dynamicOps, + Encoder encoder, + E object + ) { + Optional optional = encoder.encodeStart(dynamicOps, object) + .resultOrPartial(string -> this.LOGGER.error( + "Couldn't serialize element {}: {}", + path, + string + )); + if (optional.isPresent()) { + return DataProvider.saveStable(cachedOutput, optional.get(), path); + } + return CompletableFuture.completedFuture(null); + } + + public void buildRegistry(RegistrySetBuilder registryBuilder) { + registries.allRegistries + .stream() + .filter(nfo -> nfo.registryBootstrap != null) + .forEach(nfo -> { + + }); + } + + @Override + public String getName() { + return "Registries"; + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/RegistrySupplier.java b/src/main/java/org/betterx/bclib/api/v3/datagen/RegistrySupplier.java new file mode 100644 index 00000000..049467f4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/RegistrySupplier.java @@ -0,0 +1,255 @@ +package org.betterx.bclib.api.v3.datagen; + +import org.betterx.bclib.BCLib; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistrySetBuilder; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.RegistryDataLoader; +import net.minecraft.resources.ResourceKey; + +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Semaphore; +import org.jetbrains.annotations.Nullable; + +public abstract class RegistrySupplier { + private static final int MAX_PERMITS = 2000; + private final Semaphore BOOTSTRAP_LOCK = new Semaphore(MAX_PERMITS); + public final Semaphore MAIN_LOCK = new Semaphore(1); + + final List> allRegistries; + @Nullable List defaultModIDs; + + protected RegistrySupplier( + @Nullable List defaultModIDs + ) { + this.defaultModIDs = defaultModIDs; + this.allRegistries = initializeRegistryList(defaultModIDs); + try { + BOOTSTRAP_LOCK.acquire(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + protected abstract List> initializeRegistryList(@Nullable List modIDs); + + public void bootstrapRegistries(RegistrySetBuilder registryBuilder) { + for (RegistrySupplier.RegistryInfo nfo : allRegistries) { + nfo.add(registryBuilder, BOOTSTRAP_LOCK); + } + BOOTSTRAP_LOCK.release(); + } + + void acquireLock() { + try { + BOOTSTRAP_LOCK.acquire(MAX_PERMITS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + void releaseLock() { + BOOTSTRAP_LOCK.release(MAX_PERMITS); + } + + public class InfoList extends LinkedList> { + public void add(ResourceKey> key, Codec elementCodec) { + this.add(new RegistrySupplier.RegistryInfo(key, elementCodec)); + } + + public void add(ResourceKey> key, Codec elementCodec, String... modIDs) { + this.add(new RegistrySupplier.RegistryInfo(key, elementCodec, modIDs)); + } + + public void add(ResourceKey> key, Codec elementCodec, List modIDs) { + this.add(new RegistrySupplier.RegistryInfo(key, elementCodec, modIDs)); + } + + public void addUnfiltered(ResourceKey> key, Codec elementCodec) { + this.add(new RegistrySupplier.RegistryInfo( + key, + elementCodec, + RegistrySupplier.RegistryInfo.UNFILTERED + )); + } + + public void add( + ResourceKey> key, + Codec elementCodec, + RegistrySetBuilder.RegistryBootstrap registryBootstrap + ) { + this.add(new RegistrySupplier.RegistryInfo(key, elementCodec, registryBootstrap)); + } + + public void add( + ResourceKey> key, + Codec elementCodec, + RegistrySetBuilder.RegistryBootstrap registryBootstrap, + String... modIDs + ) { + this.add(new RegistrySupplier.RegistryInfo(key, elementCodec, registryBootstrap, modIDs)); + } + + public void add( + ResourceKey> key, + Codec elementCodec, + RegistrySetBuilder.RegistryBootstrap registryBootstrap, + List modIDs + ) { + this.add(new RegistrySupplier.RegistryInfo(key, elementCodec, registryBootstrap, modIDs)); + } + + public void addUnfiltered( + ResourceKey> key, + Codec elementCodec, + RegistrySetBuilder.RegistryBootstrap registryBootstrap + ) { + this.add(new RegistrySupplier.RegistryInfo( + key, + elementCodec, + registryBootstrap, + RegistryInfo.UNFILTERED + )); + } + + public void addBootstrapOnly( + ResourceKey> key, + Codec elementCodec, + RegistrySetBuilder.RegistryBootstrap registryBootstrap + ) { + this.add(new RegistrySupplier.RegistryInfo(key, elementCodec, registryBootstrap, List.of())); + } + } + + public final class RegistryInfo { + public static final List UNFILTERED = null; + public final RegistryDataLoader.RegistryData data; + public final List modIDs; + public final RegistrySetBuilder.RegistryBootstrap registryBootstrap; + + public RegistryInfo( + RegistryDataLoader.RegistryData data, + List modIDs, + RegistrySetBuilder.RegistryBootstrap registryBootstrap + ) { + this.data = data; + this.modIDs = modIDs; + this.registryBootstrap = registryBootstrap; + } + + public RegistryInfo( + ResourceKey> key, + Codec elementCodec, + RegistrySetBuilder.RegistryBootstrap registryBootstrap + ) { + this( + new RegistryDataLoader.RegistryData<>(key, elementCodec), + RegistrySupplier.this.defaultModIDs, + registryBootstrap + ); + } + + public RegistryInfo( + ResourceKey> key, + Codec elementCodec, + RegistrySetBuilder.RegistryBootstrap registryBootstrap, + String... modIDs + ) { + this(new RegistryDataLoader.RegistryData<>(key, elementCodec), List.of(modIDs), registryBootstrap); + } + + public RegistryInfo( + ResourceKey> key, + Codec elementCodec, + RegistrySetBuilder.RegistryBootstrap registryBootstrap, + List modIDs + ) { + this(new RegistryDataLoader.RegistryData<>(key, elementCodec), modIDs, registryBootstrap); + } + + public RegistryInfo(ResourceKey> key, Codec elementCodec) { + this( + new RegistryDataLoader.RegistryData<>(key, elementCodec), + RegistrySupplier.this.defaultModIDs, + null + ); + } + + public RegistryInfo(ResourceKey> key, Codec elementCodec, String... modIDs) { + this(new RegistryDataLoader.RegistryData<>(key, elementCodec), List.of(modIDs), null); + } + + public RegistryInfo(ResourceKey> key, Codec elementCodec, List modIDs) { + this(new RegistryDataLoader.RegistryData<>(key, elementCodec), modIDs, null); + } + + public ResourceKey> key() { + return data.key(); + } + + public Codec elementCodec() { + return data.elementCodec(); + } + + List> allElements(HolderLookup.Provider registryAccess) { + final HolderLookup.RegistryLookup registry = registryAccess.lookupOrThrow(key()); + return registry + .listElementIds() + .filter(k -> modIDs == null || modIDs.contains(k.location().getNamespace())) + .map(k -> (Holder) registry.get(k).orElseThrow()) + .toList(); + } + + + private void add(RegistrySetBuilder registryBuilder, final Semaphore LOCK_BOOSTRAP) { + try { + LOCK_BOOSTRAP.acquire(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + BCLib.LOGGER.info("Adding:" + key()); + registryBuilder.add(key(), (BootstapContext ctx) -> { + if (registryBootstrap != null) { + registryBootstrap.run(ctx); + } + LOCK_BOOSTRAP.release(); + }); + } + + public RegistryDataLoader.RegistryData data() { + return data; + } + + public List modIDs() { + return modIDs; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (RegistryInfo) obj; + return Objects.equals(this.data, that.data) && + Objects.equals(this.modIDs, that.modIDs); + } + + @Override + public int hashCode() { + return Objects.hash(data, modIDs); + } + + @Override + public String toString() { + return "RegistryInfo[" + + "data=" + data + ", " + + "modIDs=" + modIDs + ']'; + } + + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/datagen/TagDataProvider.java b/src/main/java/org/betterx/bclib/api/v3/datagen/TagDataProvider.java new file mode 100644 index 00000000..73e23456 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/datagen/TagDataProvider.java @@ -0,0 +1,103 @@ +package org.betterx.bclib.api.v3.datagen; + +import org.betterx.worlds.together.tag.v3.TagRegistry; + +import net.minecraft.core.HolderLookup; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagEntry; +import net.minecraft.tags.TagKey; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider; + +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import org.jetbrains.annotations.Nullable; + +public class TagDataProvider extends FabricTagProvider { + @Nullable + protected final List modIDs; + + protected final TagRegistry tagRegistry; + + private final Set> forceWrite; + + /** + * Constructs a new {@link FabricTagProvider} with the default computed path. + * + *

Common implementations of this class are provided. + * + * @param tagRegistry + * @param modIDs List of ModIDs that are allowed to inlcude data. All Resources in the namespace of the + * mod will be written to the tag. If null all elements get written, and empty list will + * write nothing + * @param output the {@link FabricDataOutput} instance + * @param registriesFuture the backing registry for the tag type + */ + public TagDataProvider( + TagRegistry tagRegistry, + @Nullable List modIDs, + FabricDataOutput output, + CompletableFuture registriesFuture + ) { + this(tagRegistry, modIDs, Set.of(), output, registriesFuture); + } + + /** + * Constructs a new {@link FabricTagProvider} with the default computed path. + * + *

Common implementations of this class are provided. + * + * @param tagRegistry + * @param modIDs List of ModIDs that are allowed to inlcude data. All Resources in the namespace of the + * mod will be written to the tag. If null all elements get written, and empty list will + * write nothing + * @param forceWriteKeys the keys that should allways get written + * @param output the {@link FabricDataOutput} instance + * @param registriesFuture the backing registry for the tag type + */ + public TagDataProvider( + TagRegistry tagRegistry, + @Nullable List modIDs, + Set> forceWriteKeys, + FabricDataOutput output, + CompletableFuture registriesFuture + ) { + super(output, tagRegistry.registryKey, registriesFuture); + this.tagRegistry = tagRegistry; + this.modIDs = modIDs; + this.forceWrite = forceWriteKeys; + } + + protected boolean shouldAdd(ResourceLocation loc) { + return modIDs == null || modIDs.contains(loc.getNamespace()); + } + + protected boolean isOptional(TagEntry e) { + return (e.verifyIfPresent(id -> false, id -> false)); + } + + @Override + protected void addTags(HolderLookup.Provider arg) { + tagRegistry.forEachEntry((tag, locs, tags) -> { + if (!forceWrite.contains(tag) && locs.isEmpty() && tags.isEmpty()) return; + + final FabricTagProvider.FabricTagBuilder builder = getOrCreateTagBuilder(tag); + + locs.sort(Comparator.comparing(a -> a.first.toString())); + tags.sort(Comparator.comparing(a -> a.first.location().toString())); + + locs.forEach(pair -> { + if (isOptional(pair.second)) builder.addOptional(pair.first); + else builder.add(pair.first); + }); + + tags.forEach(pair -> { + if (isOptional(pair.second)) builder.addOptionalTag(pair.first); + else builder.forceAddTag(pair.first); + }); + }, (tag, loc) -> forceWrite.contains(tag) || shouldAdd(tag.location()) || this.shouldAdd(loc)); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLConfigureFeature.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLConfigureFeature.java new file mode 100644 index 00000000..7817c54f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLConfigureFeature.java @@ -0,0 +1,142 @@ +package org.betterx.bclib.api.v3.levelgen.features; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +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.RandomPatchConfiguration; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class BCLConfigureFeature, FC extends FeatureConfiguration> { + public static class Unregistered, FC extends FeatureConfiguration> extends BCLConfigureFeature { + private BCLConfigureFeature registered; + + Unregistered(ResourceLocation id, Holder> configuredFeature) { + super(id, configuredFeature, false); + registered = null; + } + + @Override + public BCLConfigureFeature register(BootstapContext> bootstrapContext) { + if (registered != null) return registered; + + Holder> holder = BCLFeatureBuilder.register( + bootstrapContext, + id, + configuredFeature.value() + ); + BCLFeatureBuilder.UNBOUND_FEATURES.remove(this); + registered = new BCLConfigureFeature<>(id, holder, true); + return registered; + } + } + + private static final Map>, BCLConfigureFeature> KNOWN = new HashMap<>(); + + public final ResourceLocation id; + public final Holder> configuredFeature; + public final boolean registered; + + BCLConfigureFeature(ResourceLocation id, Holder> configuredFeature, boolean registered) { + this.id = id; + this.configuredFeature = configuredFeature; + this.registered = registered; + } + + public F getFeature() { + return configuredFeature.value().feature(); + } + + public FC getConfiguration() { + return configuredFeature.value().config(); + } + + + public BCLPlacedFeatureBuilder place() { + return place(this.id); + } + + public BCLPlacedFeatureBuilder place(ResourceLocation id) { + return BCLPlacedFeatureBuilder.place(id, this); + } + + static , FC extends FeatureConfiguration> BCLConfigureFeature create(Holder> registeredFeature) { + return (BCLConfigureFeature) KNOWN.computeIfAbsent( + (Holder>) (Object) registeredFeature, + holder -> new BCLConfigureFeature<>(holder.unwrapKey().orElseThrow() + .location(), registeredFeature, false) + ); + } + + public boolean placeInWorld(ServerLevel level, BlockPos pos, RandomSource random) { + return placeInWorld(level, pos, random, false); + } + + public boolean placeInWorld(ServerLevel level, BlockPos pos, RandomSource random, boolean unchanged) { + return placeUnboundInWorld(getFeature(), getConfiguration(), level, pos, random, unchanged); + } + + private static boolean placeUnboundInWorld( + Feature feature, + FeatureConfiguration config, + ServerLevel level, + BlockPos pos, + RandomSource random, + boolean asIs + ) { + if (!asIs) { + 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 placeInWorld( + Feature feature, + ServerLevel level, + BlockPos pos, + RandomSource random + ) { + return placeUnboundInWorld(feature, FeatureConfiguration.NONE, level, pos, random, true); + } + + public static boolean placeInWorld( + Feature feature, + FC config, + ServerLevel level, + BlockPos pos, + RandomSource random + ) { + return placeUnboundInWorld(feature, config, level, pos, random, true); + } + + public BCLConfigureFeature register(BootstapContext> bootstrapContext) { + return this; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLFeature.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLFeature.java new file mode 100644 index 00000000..8f439f77 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLFeature.java @@ -0,0 +1,135 @@ +package org.betterx.bclib.api.v3.levelgen.features; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.levelgen.features.config.*; +import org.betterx.bclib.api.v3.levelgen.features.features.*; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import org.jetbrains.annotations.ApiStatus; + +public class BCLFeature, FC extends FeatureConfiguration> { + public static class Unregistered, FC extends FeatureConfiguration> extends BCLFeature { + private BCLFeature registered; + + Unregistered( + BCLConfigureFeature configuredFeature, + Holder placed, + GenerationStep.Decoration decoration + ) { + super(configuredFeature, placed, decoration); + registered = null; + } + + @Override + public BCLFeature register(BootstapContext bootstrapContext) { + if (registered != null) return registered; + Holder holder = BCLPlacedFeatureBuilder.register( + bootstrapContext, + getPlacedFeature() + ); + BCLPlacedFeatureBuilder.UNBOUND_FEATURES.remove(this); + registered = new BCLFeature<>(configuredFeature, holder, decoration); + return registered; + } + } + + public static final Feature PLACE_BLOCK = register( + BCLib.makeID("place_block"), + new PlaceBlockFeature<>(PlaceFacingBlockConfig.CODEC) + ); + + + public static final Feature TEMPLATE = register( + BCLib.makeID("template"), + new TemplateFeature( + TemplateFeatureConfig.CODEC) + ); + + public static final Feature MARK_POSTPROCESSING = register( + BCLib.makeID( + "mark_postprocessing"), + new MarkPostProcessingFeature() + ); + + public static final Feature SEQUENCE = register( + BCLib.makeID("sequence"), + new SequenceFeature() + ); + + public static final Feature CONDITION = register( + BCLib.makeID("condition"), + new ConditionFeature() + ); + + public static final Feature PILLAR = register( + BCLib.makeID("pillar"), + new PillarFeature() + ); + public final BCLConfigureFeature configuredFeature; + public final Holder placedFeature; + public final GenerationStep.Decoration decoration; + + @ApiStatus.Internal + BCLFeature( + BCLConfigureFeature configuredFeature, + Holder placed, + GenerationStep.Decoration decoration + ) { + this.configuredFeature = configuredFeature; + this.placedFeature = placed; + this.decoration = decoration; + } + + /** + * Get raw feature. + * + * @return {@link Feature}. + */ + public F getFeature() { + return configuredFeature.getFeature(); + } + + /** + * Get configured feature. + * + * @return {@link PlacedFeature}. + */ + public Holder getPlacedFeature() { + return placedFeature; + } + + /** + * Get feature decoration step. + * + * @return {@link GenerationStep.Decoration}. + */ + public GenerationStep.Decoration getDecoration() { + return decoration; + } + + public FC getConfiguration() { + return configuredFeature.getConfiguration(); + } + + + public static > F register( + ResourceLocation location, + F feature + ) { + return Registry.register(BuiltInRegistries.FEATURE, location, feature); + } + + public BCLFeature register(BootstapContext bootstrapContext) { + return this; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLFeatureBuilder.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLFeatureBuilder.java new file mode 100644 index 00000000..fe43baca --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLFeatureBuilder.java @@ -0,0 +1,1125 @@ +package org.betterx.bclib.api.v3.levelgen.features; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.structures.StructurePlacementType; +import org.betterx.bclib.api.v2.levelgen.structures.StructureWorldNBT; +import org.betterx.bclib.api.v2.poi.BCLPoiType; +import org.betterx.bclib.api.v3.levelgen.features.config.PillarFeatureConfig; +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 org.betterx.bclib.api.v3.levelgen.features.features.PillarFeature; +import org.betterx.bclib.api.v3.levelgen.features.features.PlaceBlockFeature; +import org.betterx.bclib.api.v3.levelgen.features.features.SequenceFeature; +import org.betterx.bclib.api.v3.levelgen.features.features.TemplateFeature; +import org.betterx.bclib.blocks.BlockProperties; +import org.betterx.bclib.util.FullReferenceHolder; +import org.betterx.bclib.util.Triple; + +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.random.SimpleWeightedRandomList; +import net.minecraft.util.valueproviders.ConstantInt; +import net.minecraft.util.valueproviders.IntProvider; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.feature.*; +import net.minecraft.world.level.levelgen.feature.configurations.*; +import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider; +import net.minecraft.world.level.levelgen.feature.stateproviders.SimpleStateProvider; +import net.minecraft.world.level.levelgen.feature.stateproviders.WeightedStateProvider; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.structure.templatesystem.BlockMatchTest; +import net.minecraft.world.level.levelgen.structure.templatesystem.RuleTest; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("unused") +public abstract class BCLFeatureBuilder, FC extends FeatureConfiguration> { + @FunctionalInterface + public interface HolderBuilder, FC extends FeatureConfiguration> { + Holder> apply( + ResourceLocation id, + ConfiguredFeature feature + ); + } + + @FunctionalInterface + public interface FeatureBuilder, FC extends FeatureConfiguration, B extends BCLConfigureFeature> { + B create(ResourceLocation id, Holder> configuredFeature); + } + + static ConcurrentLinkedQueue> UNBOUND_FEATURES = new ConcurrentLinkedQueue<>(); + + /** + * Starts a new {@link BCLFeature} builder. + * + * @param featureID {@link ResourceLocation} feature identifier. + * @param feature {@link Feature} to construct. + * @return {@link BCLFeatureBuilder} instance. + */ + public static , FC extends FeatureConfiguration> WithConfiguration start( + ResourceLocation featureID, + F feature + ) { + return new WithConfiguration<>(featureID, feature); + } + + public static ForSimpleBlock start( + ResourceLocation featureID, + Block block + ) { + return start(featureID, BlockStateProvider.simple(block)); + } + + public static ForSimpleBlock start( + ResourceLocation featureID, + BlockState state + ) { + return start(featureID, BlockStateProvider.simple(state)); + } + + public static ForSimpleBlock start( + ResourceLocation featureID, + BlockStateProvider provider + ) { + return new ForSimpleBlock( + featureID, + (SimpleBlockFeature) Feature.SIMPLE_BLOCK, + provider + ); + } + + public static WeightedBlock startWeighted(ResourceLocation featureID) { + return new WeightedBlock( + featureID, + (SimpleBlockFeature) Feature.SIMPLE_BLOCK + ); + } + + public static WeightedBlockPatch startWeightedRandomPatch(ResourceLocation featureID) { + return new WeightedBlockPatch( + featureID, + (RandomPatchFeature) Feature.RANDOM_PATCH + ); + } + + public static WeightedBlockPatch startBonemealPatch( + ResourceLocation featureID + ) { + return startWeightedRandomPatch(featureID).likeDefaultBonemeal(); + } + + public static RandomPatch startRandomPatch( + ResourceLocation featureID, + Holder featureToPlace + ) { + return new RandomPatch( + featureID, + (RandomPatchFeature) Feature.RANDOM_PATCH, + featureToPlace + ); + } + + public static AsRandomSelect startRandomSelect( + ResourceLocation featureID + ) { + return new AsRandomSelect( + featureID, + (RandomSelectorFeature) Feature.RANDOM_SELECTOR + ); + } + + public static AsMultiPlaceRandomSelect startRandomSelect( + ResourceLocation featureID, + AsMultiPlaceRandomSelect.Placer placementModFunction + ) { + return new AsMultiPlaceRandomSelect( + featureID, + (RandomSelectorFeature) Feature.RANDOM_SELECTOR, + placementModFunction + ); + } + + public static NetherForrestVegetation startNetherVegetation( + ResourceLocation featureID + ) { + return new NetherForrestVegetation( + featureID, + (NetherForestVegetationFeature) Feature.NETHER_FOREST_VEGETATION + ); + } + + public static NetherForrestVegetation startBonemealNetherVegetation(ResourceLocation featureID) { + return new NetherForrestVegetation( + featureID, + (NetherForestVegetationFeature) Feature.NETHER_FOREST_VEGETATION + ).spreadHeight(1).spreadWidth(3); + } + + public static WithTemplates startWithTemplates(ResourceLocation featureID) { + return new WithTemplates( + featureID, + (TemplateFeature) BCLFeature.TEMPLATE + ); + } + + public static AsBlockColumn startColumn(ResourceLocation featureID) { + return new AsBlockColumn<>( + featureID, + (BlockColumnFeature) Feature.BLOCK_COLUMN + ); + } + + public static AsPillar startPillar( + ResourceLocation featureID, + PillarFeatureConfig.KnownTransformers transformer + ) { + return new AsPillar( + featureID, + (PillarFeature) BCLFeature.PILLAR, + transformer + ); + } + + public static AsSequence startSequence(ResourceLocation featureID) { + return new AsSequence( + featureID, + (SequenceFeature) BCLFeature.SEQUENCE + ); + } + + public static AsOre startOre(ResourceLocation featureID) { + return new AsOre( + featureID, + (OreFeature) Feature.ORE + ); + } + + public static FacingBlock startFacing(ResourceLocation featureID) { + return new FacingBlock( + featureID, + (PlaceBlockFeature) BCLFeature.PLACE_BLOCK + ); + } + + + protected final ResourceLocation featureID; + private final F feature; + + private BCLFeatureBuilder(ResourceLocation featureID, F feature) { + this.featureID = featureID; + this.feature = feature; + } + + + /** + * Internally used by the builder. Normally you should not have to call this method directly as it is + * handled by {@link #buildAndRegister(BootstapContext)} + * + * @param id The ID to register this feature with + * @param cFeature The configured Feature + * @param The Feature Class + * @param The FeatureConfiguration Class + * @return The Holder for the new Feature + */ + public static , FC extends FeatureConfiguration> Holder> register( + BootstapContext> ctx, + ResourceLocation id, + ConfiguredFeature cFeature + ) { + ResourceKey> key = ResourceKey.create(Registries.CONFIGURED_FEATURE, id); + return (Holder>) (Object) ctx.register(key, cFeature); + } + + public abstract FC createConfiguration(); + + protected BCLConfigureFeature buildAndCreateHolder(HolderBuilder holderBuilder) { + return buildAndCreateHolder( + (featureID, holder) -> new BCLConfigureFeature<>(featureID, holder, true), + holderBuilder + ); + } + + protected > B buildAndCreateHolder( + FeatureBuilder featureBuilder, + HolderBuilder holderBuilder + ) { + FC config = createConfiguration(); + if (config == null) { + throw new IllegalStateException("Feature configuration for " + featureID + " can not be null!"); + } + ConfiguredFeature cFeature = new ConfiguredFeature<>(feature, config); + Holder> holder = holderBuilder.apply(featureID, cFeature); + return featureBuilder.create(featureID, holder); + } + + public BCLConfigureFeature buildAndRegister(BootstapContext> bootstrapCtx) { + return buildAndCreateHolder((featureID, cFeature) -> register(bootstrapCtx, featureID, cFeature)); + } + + public BCLConfigureFeature buildInline() { + return buildAndCreateHolder( + (featureID, cFeature) -> Holder.direct(cFeature) + ); + } + + public BCLConfigureFeature.Unregistered build() { + final var res = buildAndCreateHolder( + (featureID, holder) -> new BCLConfigureFeature.Unregistered<>(featureID, holder), + (featureID, cFeature) -> (FullReferenceHolder>) (Object) FullReferenceHolder.create( + Registries.CONFIGURED_FEATURE, + featureID, + cFeature + ) + ); + UNBOUND_FEATURES.add(res); + return res; + } + + public static void registerUnbound(BootstapContext> bootstapContext) { + UNBOUND_FEATURES.forEach(u -> u.register(bootstapContext)); + UNBOUND_FEATURES.clear(); + } + + public BCLInlinePlacedBuilder inlinePlace() { + BCLConfigureFeature f = buildInline(); + return BCLInlinePlacedBuilder.place(f); + } + + public Holder inlinePlace(BCLInlinePlacedBuilder placer) { + BCLConfigureFeature f = buildInline(); + return placer.build(f).getPlacedFeature(); + } + + public static class AsOre extends BCLFeatureBuilder { + private final List targetStates = new LinkedList<>(); + private int size = 6; + private float discardChanceOnAirExposure = 0; + + private AsOre(ResourceLocation featureID, OreFeature feature) { + super(featureID, feature); + } + + public AsOre add(Block containedIn, Block ore) { + return this.add(containedIn, ore.defaultBlockState()); + } + + public AsOre add(Block containedIn, BlockState ore) { + return this.add(new BlockMatchTest(containedIn), ore); + } + + public AsOre add(RuleTest containedIn, Block ore) { + return this.add(containedIn, ore.defaultBlockState()); + } + + public AsOre add(RuleTest containedIn, BlockState ore) { + targetStates.add(OreConfiguration.target( + containedIn, + ore + )); + return this; + } + + public AsOre veinSize(int size) { + this.size = size; + return this; + } + + public AsOre discardChanceOnAirExposure(float chance) { + this.discardChanceOnAirExposure = chance; + return this; + } + + @Override + public OreConfiguration createConfiguration() { + return new OreConfiguration(targetStates, size, discardChanceOnAirExposure); + } + } + + public static class AsPillar extends BCLFeatureBuilder { + private IntProvider maxHeight; + private IntProvider minHeight; + private BlockStateProvider stateProvider; + + private final PillarFeatureConfig.KnownTransformers transformer; + private Direction direction = Direction.UP; + private BlockPredicate allowedPlacement = BlockPredicate.ONLY_IN_AIR_PREDICATE; + + private AsPillar( + @NotNull ResourceLocation featureID, + @NotNull PillarFeature feature, + @NotNull PillarFeatureConfig.KnownTransformers transformer + ) { + super(featureID, feature); + this.transformer = transformer; + } + + public AsPillar allowedPlacement(BlockPredicate predicate) { + this.allowedPlacement = predicate; + return this; + } + + public AsPillar direction(Direction v) { + this.direction = v; + return this; + } + + public AsPillar blockState(Block v) { + return blockState(BlockStateProvider.simple(v.defaultBlockState())); + } + + public AsPillar blockState(BlockState v) { + return blockState(BlockStateProvider.simple(v)); + } + + public AsPillar blockState(BlockStateProvider v) { + this.stateProvider = v; + return this; + } + + public AsPillar maxHeight(int v) { + this.maxHeight = ConstantInt.of(v); + return this; + } + + public AsPillar maxHeight(IntProvider v) { + this.maxHeight = v; + return this; + } + + public AsPillar minHeight(int v) { + this.minHeight = ConstantInt.of(v); + return this; + } + + public AsPillar minHeight(IntProvider v) { + this.minHeight = v; + return this; + } + + + @Override + public PillarFeatureConfig createConfiguration() { + if (stateProvider == null) { + throw new IllegalStateException("A Pillar Features need a stateProvider"); + } + if (maxHeight == null) { + throw new IllegalStateException("A Pillar Features need a height"); + } + if (minHeight == null) minHeight = ConstantInt.of(0); + return new PillarFeatureConfig( + minHeight, + maxHeight, + direction, + allowedPlacement, + stateProvider, + transformer + ); + } + } + + public static class AsSequence extends BCLFeatureBuilder { + private final List> features = new LinkedList<>(); + + private AsSequence( + @NotNull ResourceLocation featureID, + @NotNull SequenceFeature feature + ) { + super(featureID, feature); + } + + + public AsSequence add(org.betterx.bclib.api.v3.levelgen.features.BCLFeature p) { + return add(p.placedFeature); + } + + public AsSequence add(Holder p) { + features.add(p); + return this; + } + + @Override + public SequenceFeatureConfig createConfiguration() { + return new SequenceFeatureConfig(features); + } + } + + public static class AsBlockColumn> extends BCLFeatureBuilder { + private final List layers = new LinkedList<>(); + private Direction direction = Direction.UP; + private BlockPredicate allowedPlacement = BlockPredicate.ONLY_IN_AIR_PREDICATE; + private boolean prioritizeTip = false; + + private AsBlockColumn( + @NotNull ResourceLocation featureID, + @NotNull FF feature + ) { + super(featureID, feature); + } + + public AsBlockColumn add(int height, Block block) { + return add(ConstantInt.of(height), BlockStateProvider.simple(block)); + } + + public AsBlockColumn add(int height, BlockState state) { + return add(ConstantInt.of(height), BlockStateProvider.simple(state)); + } + + public AsBlockColumn add(int height, BlockStateProvider state) { + return add(ConstantInt.of(height), state); + } + + protected static SimpleWeightedRandomList buildWeightedList(BlockState state) { + return SimpleWeightedRandomList + .builder() + .add(state, 1) + .build(); + } + + public final AsBlockColumn addRandom(int height, BlockState... states) { + return this.addRandom(ConstantInt.of(height), states); + } + + public final AsBlockColumn addRandom(IntProvider height, BlockState... states) { + var builder = SimpleWeightedRandomList.builder(); + for (BlockState state : states) builder.add(state, 1); + return add(height, new WeightedStateProvider(builder.build())); + } + + public AsBlockColumn add(IntProvider height, Block block) { + return add(height, BlockStateProvider.simple(block)); + } + + public AsBlockColumn add(IntProvider height, BlockState state) { + return add(height, BlockStateProvider.simple(state)); + } + + public AsBlockColumn add(IntProvider height, BlockStateProvider state) { + layers.add(new BlockColumnConfiguration.Layer(height, state)); + return this; + } + + public AsBlockColumn addTripleShape(BlockState state, IntProvider midHeight) { + return this + .add(1, state.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.BOTTOM)) + .add(midHeight, state.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.MIDDLE)) + .add(1, state.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.TOP)); + } + + public AsBlockColumn addTripleShapeUpsideDown(BlockState state, IntProvider midHeight) { + return this + .add(1, state.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.TOP)) + .add(midHeight, state.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.MIDDLE)) + .add(1, state.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.BOTTOM)); + } + + public AsBlockColumn addBottomShapeUpsideDown(BlockState state, IntProvider midHeight) { + return this + .add(midHeight, state.setValue(BlockProperties.BOTTOM, false)) + .add(1, state.setValue(BlockProperties.BOTTOM, true)); + } + + public AsBlockColumn addBottomShape(BlockState state, IntProvider midHeight) { + return this + .add(1, state.setValue(BlockProperties.BOTTOM, true)) + .add(midHeight, state.setValue(BlockProperties.BOTTOM, false)); + } + + public AsBlockColumn addTopShapeUpsideDown(BlockState state, IntProvider midHeight) { + return this + .add(1, state.setValue(BlockProperties.TOP, true)) + .add(midHeight, state.setValue(BlockProperties.TOP, false)); + } + + public AsBlockColumn addTopShape(BlockState state, IntProvider midHeight) { + return this + .add(midHeight, state.setValue(BlockProperties.TOP, false)) + .add(1, state.setValue(BlockProperties.TOP, true)); + } + + public AsBlockColumn direction(Direction dir) { + direction = dir; + return this; + } + + public AsBlockColumn prioritizeTip() { + return this.prioritizeTip(true); + } + + public AsBlockColumn prioritizeTip(boolean v) { + prioritizeTip = v; + return this; + } + + public AsBlockColumn allowedPlacement(BlockPredicate v) { + allowedPlacement = v; + return this; + } + + @Override + public BlockColumnConfiguration createConfiguration() { + return new BlockColumnConfiguration(layers, direction, allowedPlacement, prioritizeTip); + } + } + + public static class WithTemplates extends BCLFeatureBuilder, TemplateFeatureConfig> { + private final List templates = new LinkedList<>(); + + private WithTemplates( + @NotNull ResourceLocation featureID, + @NotNull TemplateFeature feature + ) { + super(featureID, feature); + } + + public WithTemplates add( + ResourceLocation location, + int offsetY, + StructurePlacementType type, + float chance + ) { + templates.add(TemplateFeatureConfig.cfg(location, offsetY, type, chance)); + return this; + } + + @Override + public TemplateFeatureConfig createConfiguration() { + return new TemplateFeatureConfig(templates); + } + } + + public static class NetherForrestVegetation extends BCLFeatureBuilder { + private SimpleWeightedRandomList.Builder blocks; + private WeightedStateProvider stateProvider; + private int spreadWidth = 8; + private int spreadHeight = 4; + + private NetherForrestVegetation( + @NotNull ResourceLocation featureID, + @NotNull NetherForestVegetationFeature feature + ) { + super(featureID, feature); + } + + public NetherForrestVegetation spreadWidth(int v) { + spreadWidth = v; + return this; + } + + public NetherForrestVegetation spreadHeight(int v) { + spreadHeight = v; + return this; + } + + public NetherForrestVegetation addAllStates(Block block, int weight) { + Set states = BCLPoiType.getBlockStates(block); + states.forEach(s -> add(block.defaultBlockState(), Math.max(1, weight / states.size()))); + return this; + } + + public NetherForrestVegetation addAllStatesFor(IntegerProperty prop, Block block, int weight) { + Collection values = prop.getPossibleValues(); + values.forEach(s -> add(block.defaultBlockState().setValue(prop, s), Math.max(1, weight / values.size()))); + return this; + } + + public NetherForrestVegetation add(Block block, int weight) { + return add(block.defaultBlockState(), weight); + } + + public NetherForrestVegetation add(BlockState state, int weight) { + if (stateProvider != null) { + throw new IllegalStateException("You can not add new state once a WeightedStateProvider was built. (" + state + ", " + weight + ")"); + } + if (blocks == null) { + blocks = SimpleWeightedRandomList.builder(); + } + blocks.add(state, weight); + return this; + } + + public NetherForrestVegetation provider(WeightedStateProvider provider) { + if (blocks != null) { + throw new IllegalStateException( + "You can not set a WeightedStateProvider after states were added manually."); + } + stateProvider = provider; + return this; + } + + @Override + public NetherForestVegetationConfig createConfiguration() { + if (stateProvider == null && blocks == null) { + throw new IllegalStateException("NetherForestVegetationConfig needs at least one BlockState"); + } + if (stateProvider == null) stateProvider = new WeightedStateProvider(blocks.build()); + return new NetherForestVegetationConfig(stateProvider, spreadWidth, spreadHeight); + } + } + + public static class RandomPatch extends BCLFeatureBuilder { + private final Holder featureToPlace; + private int tries = 96; + private int xzSpread = 7; + private int ySpread = 3; + + private RandomPatch( + @NotNull ResourceLocation featureID, + @NotNull RandomPatchFeature feature, + @NotNull Holder featureToPlace + ) { + super(featureID, feature); + this.featureToPlace = featureToPlace; + } + + public RandomPatch likeDefaultNetherVegetation() { + return likeDefaultNetherVegetation(8, 4); + } + + public RandomPatch likeDefaultNetherVegetation(int xzSpread, int ySpread) { + this.xzSpread = xzSpread; + this.ySpread = ySpread; + tries = xzSpread * xzSpread; + return this; + } + + public RandomPatch tries(int v) { + tries = v; + return this; + } + + public RandomPatch spreadXZ(int v) { + xzSpread = v; + return this; + } + + public RandomPatch spreadY(int v) { + ySpread = v; + return this; + } + + + @Override + public RandomPatchConfiguration createConfiguration() { + return new RandomPatchConfiguration(tries, xzSpread, ySpread, featureToPlace); + } + } + + public static class WithConfiguration, FC extends FeatureConfiguration> extends BCLFeatureBuilder { + private FC configuration; + + private WithConfiguration( + @NotNull ResourceLocation featureID, + @NotNull F feature + ) { + super(featureID, feature); + } + + public WithConfiguration configuration(FC config) { + this.configuration = config; + return this; + } + + + @Override + public FC createConfiguration() { + if (configuration == null) { + //Moonlight Lib seems to trigger a load of our data before + //NoneFeatureConfiguration.NONE is initialized. This Code + // is meant to prevent that... + if (NoneFeatureConfiguration.NONE != null) + return (FC) NoneFeatureConfiguration.NONE; + + return (FC) NoneFeatureConfiguration.INSTANCE; + } + return configuration; + } + } + + public static class FacingBlock extends BCLFeatureBuilder, PlaceFacingBlockConfig> { + private final SimpleWeightedRandomList.Builder stateBuilder = SimpleWeightedRandomList.builder(); + BlockState firstState; + private int count = 0; + private List directions = PlaceFacingBlockConfig.HORIZONTAL; + + private FacingBlock( + @NotNull ResourceLocation featureID, + @NotNull PlaceBlockFeature feature + ) { + super(featureID, feature); + } + + + public FacingBlock allHorizontal() { + directions = PlaceFacingBlockConfig.HORIZONTAL; + return this; + } + + public FacingBlock allVertical() { + directions = PlaceFacingBlockConfig.VERTICAL; + return this; + } + + public FacingBlock allDirections() { + directions = PlaceFacingBlockConfig.ALL; + return this; + } + + public FacingBlock add(Block block) { + return add(block, 1); + } + + public FacingBlock add(BlockState state) { + return this.add(state, 1); + } + + public FacingBlock add(Block block, int weight) { + return add(block.defaultBlockState(), weight); + } + + public FacingBlock add(BlockState state, int weight) { + if (firstState == null) firstState = state; + count++; + stateBuilder.add(state, weight); + return this; + } + + public FacingBlock addAllStates(Block block, int weight) { + Set states = BCLPoiType.getBlockStates(block); + states.forEach(s -> add(block.defaultBlockState(), Math.max(1, weight / states.size()))); + return this; + } + + public FacingBlock addAllStatesFor(IntegerProperty prop, Block block, int weight) { + Collection values = prop.getPossibleValues(); + values.forEach(s -> add(block.defaultBlockState().setValue(prop, s), Math.max(1, weight / values.size()))); + return this; + } + + + @Override + public PlaceFacingBlockConfig createConfiguration() { + BlockStateProvider provider = null; + if (count == 1) { + provider = SimpleStateProvider.simple(firstState); + } else { + SimpleWeightedRandomList list = stateBuilder.build(); + if (!list.isEmpty()) { + provider = new WeightedStateProvider(list); + } + } + + if (provider == null) { + throw new IllegalStateException("Facing Blocks need a State Provider."); + } + return new PlaceFacingBlockConfig(provider, directions); + } + } + + public static class ForSimpleBlock extends BCLFeatureBuilder { + private final BlockStateProvider provider; + + private ForSimpleBlock( + @NotNull ResourceLocation featureID, + @NotNull SimpleBlockFeature feature, + @NotNull BlockStateProvider provider + ) { + super(featureID, feature); + this.provider = provider; + } + + @Override + public SimpleBlockConfiguration createConfiguration() { + return new SimpleBlockConfiguration(provider); + } + } + + public static class WeightedBlockPatch extends WeightedBaseBlock { + + private BlockPredicate groundType = null; + private boolean isEmpty = true; + private int tries = 96; + private int xzSpread = 7; + private int ySpread = 3; + + protected WeightedBlockPatch( + @NotNull ResourceLocation featureID, + @NotNull RandomPatchFeature feature + ) { + super(featureID, feature); + } + + public WeightedBlockPatch isEmpty() { + return this.isEmpty(true); + } + + public WeightedBlockPatch isEmpty(boolean value) { + this.isEmpty = value; + return this; + } + + public WeightedBlockPatch isOn(BlockPredicate predicate) { + this.groundType = predicate; + return this; + } + + public WeightedBlockPatch isEmptyAndOn(BlockPredicate predicate) { + return this.isEmpty().isOn(predicate); + } + + public WeightedBlockPatch likeDefaultNetherVegetation() { + return likeDefaultNetherVegetation(8, 4); + } + + public WeightedBlockPatch likeDefaultNetherVegetation(int xzSpread, int ySpread) { + this.xzSpread = xzSpread; + this.ySpread = ySpread; + tries = xzSpread * xzSpread; + return this; + } + + public WeightedBlockPatch likeDefaultBonemeal() { + return this.tries(9) + .spreadXZ(3) + .spreadY(1); + } + + public WeightedBlockPatch tries(int v) { + tries = v; + return this; + } + + public WeightedBlockPatch spreadXZ(int v) { + xzSpread = v; + return this; + } + + public WeightedBlockPatch spreadY(int v) { + ySpread = v; + return this; + } + + @Override + public RandomPatchConfiguration createConfiguration() { + BCLInlinePlacedBuilder, SimpleBlockConfiguration> blockFeature = BCLFeatureBuilder + .start( + new ResourceLocation(featureID.getNamespace(), "tmp_" + featureID.getPath()), + Feature.SIMPLE_BLOCK + ) + .configuration(new SimpleBlockConfiguration(new WeightedStateProvider(stateBuilder.build()))) + .inlinePlace(); + + if (isEmpty) blockFeature.isEmpty(); + if (groundType != null) blockFeature.isOn(groundType); + + return new RandomPatchConfiguration(tries, xzSpread, ySpread, blockFeature.build().getPlacedFeature()); + } + } + + public static class WeightedBlock extends WeightedBaseBlock { + private WeightedBlock( + @NotNull ResourceLocation featureID, + @NotNull SimpleBlockFeature feature + ) { + super(featureID, feature); + } + + @Override + public SimpleBlockConfiguration createConfiguration() { + return new SimpleBlockConfiguration(new WeightedStateProvider(stateBuilder.build())); + } + } + + + private abstract static class WeightedBaseBlock, FC extends FeatureConfiguration, W extends WeightedBaseBlock> extends BCLFeatureBuilder { + SimpleWeightedRandomList.Builder stateBuilder = SimpleWeightedRandomList.builder(); + + protected WeightedBaseBlock( + @NotNull ResourceLocation featureID, + @NotNull F feature + ) { + super(featureID, feature); + } + + public W add(Block block, int weight) { + return add(block.defaultBlockState(), weight); + } + + public W add(BlockState state, int weight) { + stateBuilder.add(state, weight); + return (W) this; + } + + public W addAllStates(Block block, int weight) { + Set states = BCLPoiType.getBlockStates(block); + states.forEach(s -> add(block.defaultBlockState(), Math.max(1, weight / states.size()))); + return (W) this; + } + + public W addAllStatesFor(IntegerProperty prop, Block block, int weight) { + Collection values = prop.getPossibleValues(); + values.forEach(s -> add(block.defaultBlockState().setValue(prop, s), Math.max(1, weight / values.size()))); + return (W) this; + } + } + + public static class AsRandomSelect extends BCLFeatureBuilder { + private final List features = new LinkedList<>(); + private Holder defaultFeature; + + private AsRandomSelect( + @NotNull ResourceLocation featureID, + @NotNull RandomSelectorFeature feature + ) { + super(featureID, feature); + } + + + public AsRandomSelect add(Holder feature, float weight) { + features.add(new WeightedPlacedFeature(feature, weight)); + return this; + } + + public AsRandomSelect defaultFeature(Holder feature) { + defaultFeature = feature; + return this; + } + + @Override + public RandomFeatureConfiguration createConfiguration() { + return new RandomFeatureConfiguration(features, defaultFeature); + } + } + + public static class AsMultiPlaceRandomSelect extends BCLFeatureBuilder { + public interface Placer { + Holder place( + BCLInlinePlacedBuilder placer, + int id + ); + } + + private final List> features = new LinkedList<>(); + + private final Placer modFunction; + + private AsMultiPlaceRandomSelect( + @NotNull ResourceLocation featureID, + @NotNull RandomSelectorFeature feature, + @NotNull Placer mod + ) { + super(featureID, feature); + this.modFunction = mod; + } + + private static int featureCounter = 0; + private static int lastID = 0; + + public AsMultiPlaceRandomSelect addAllStates(Block block, int weight) { + return addAllStates(block, weight, lastID + 1); + } + + public AsMultiPlaceRandomSelect addAll(int weight, Block... blocks) { + return addAll(weight, lastID + 1, blocks); + } + + public AsMultiPlaceRandomSelect addAllStatesFor(IntegerProperty prop, Block block, int weight) { + return addAllStatesFor(prop, block, weight, lastID + 1); + } + + public AsMultiPlaceRandomSelect add(Block block, float weight) { + return add(BlockStateProvider.simple(block), weight); + } + + public AsMultiPlaceRandomSelect add(BlockState state, float weight) { + return add(BlockStateProvider.simple(state), weight); + } + + public AsMultiPlaceRandomSelect add(BlockStateProvider provider, float weight) { + return add(provider, weight, lastID + 1); + } + + + public AsMultiPlaceRandomSelect addAllStates(Block block, int weight, int id) { + Set states = BCLPoiType.getBlockStates(block); + SimpleWeightedRandomList.Builder builder = SimpleWeightedRandomList.builder(); + states.forEach(s -> builder.add(block.defaultBlockState(), 1)); + + this.add(new WeightedStateProvider(builder.build()), weight, id); + return this; + } + + public AsMultiPlaceRandomSelect addAll(int weight, int id, Block... blocks) { + SimpleWeightedRandomList.Builder builder = SimpleWeightedRandomList.builder(); + for (Block block : blocks) { + builder.add(block.defaultBlockState(), 1); + } + + this.add(new WeightedStateProvider(builder.build()), weight, id); + return this; + } + + public AsMultiPlaceRandomSelect addAllStatesFor(IntegerProperty prop, Block block, int weight, int id) { + Collection values = prop.getPossibleValues(); + SimpleWeightedRandomList.Builder builder = SimpleWeightedRandomList.builder(); + values.forEach(s -> builder.add(block.defaultBlockState().setValue(prop, s), 1)); + this.add(new WeightedStateProvider(builder.build()), weight, id); + return this; + } + + public AsMultiPlaceRandomSelect add(Block block, float weight, int id) { + return add(BlockStateProvider.simple(block), weight, id); + } + + public AsMultiPlaceRandomSelect add(BlockState state, float weight, int id) { + return add(BlockStateProvider.simple(state), weight, id); + } + + public AsMultiPlaceRandomSelect add(BlockStateProvider provider, float weight, int id) { + features.add(new Triple<>(provider, weight, id)); + lastID = Math.max(lastID, id); + return this; + } + + private Holder place(BlockStateProvider p, int id) { + var builder = BCLFeatureBuilder + .start(BCLib.makeID("temp_select_feature" + (featureCounter++)), p) + .inlinePlace(); + return modFunction.place(builder, id); + } + + @Override + public RandomFeatureConfiguration createConfiguration() { + if (modFunction == null) { + throw new IllegalStateException("AsMultiPlaceRandomSelect needs a placement.modification Function"); + } + float sum = this.features.stream().map(p -> p.second).reduce(0.0f, Float::sum); + List features = this.features.stream() + .map(p -> new WeightedPlacedFeature( + this.place(p.first, p.third), + p.second / sum + )) + .toList(); + + + return new RandomFeatureConfiguration( + features.subList(0, features.size() - 1), + features.get(features.size() - 1).feature + ); + } + } +} + + diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLInlinePlacedBuilder.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLInlinePlacedBuilder.java new file mode 100644 index 00000000..012e581c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLInlinePlacedBuilder.java @@ -0,0 +1,81 @@ +package org.betterx.bclib.api.v3.levelgen.features; + +import net.minecraft.core.Holder; +import net.minecraft.data.worldgen.placement.PlacementUtils; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; + +public class BCLInlinePlacedBuilder, FC extends FeatureConfiguration> extends CommonPlacedFeatureBuilder> { + private final BCLConfigureFeature cFeature; + + private BCLInlinePlacedBuilder(BCLConfigureFeature cFeature) { + this.cFeature = cFeature; + } + + /** + * Starts a new {@link BCLFeature} builder. + * + * @param holder {@link Feature} the configured Feature to start from. + * @return {@link CommonPlacedFeatureBuilder} instance. + */ + public static , FC extends FeatureConfiguration> BCLInlinePlacedBuilder place( + ResourceLocation featureID, + Holder> holder + ) { + return place(BCLConfigureFeature.create(holder)); + } + + + /** + * Starts a new {@link BCLFeature} builder. + * + * @param cFeature {@link Feature} the configured Feature to start from. + * @return {@link CommonPlacedFeatureBuilder} instance. + */ + static , FC extends FeatureConfiguration> BCLInlinePlacedBuilder place( + BCLConfigureFeature cFeature + ) { + return new BCLInlinePlacedBuilder(cFeature); + } + + /** + * Builds a new inline (not registered) {@link PlacedFeature}. + * + * @return created {@link PlacedFeature} instance. + */ + @Override + public BCLFeature.Unregistered build() { + return build(cFeature); + } + + /** + * Builds a new inline (not registered) {@link PlacedFeature}. + * + * @return created {@link PlacedFeature} instance. + */ + public BCLFeature.Unregistered build(BCLConfigureFeature feature) { + PlacementModifier[] modifiers = modifications.toArray(new PlacementModifier[modifications.size()]); + Holder holder = PlacementUtils.inlinePlaced( + feature.configuredFeature, + modifiers + ); + return new BCLFeature.Unregistered<>(feature, holder, GenerationStep.Decoration.VEGETAL_DECORATION); + } + + /** + * Builds a new inline (not registered) {@link PlacedFeature}. + * + * @return created {@link PlacedFeature} instance. + */ + public Holder build(F feature, FC configuration) { + PlacementModifier[] modifiers = modifications.toArray(new PlacementModifier[modifications.size()]); + return PlacementUtils.inlinePlaced(feature, configuration, modifiers); + } + + +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLPlacedFeatureBuilder.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLPlacedFeatureBuilder.java new file mode 100644 index 00000000..a0b5d713 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/BCLPlacedFeatureBuilder.java @@ -0,0 +1,118 @@ +package org.betterx.bclib.api.v3.levelgen.features; + +import org.betterx.bclib.util.FullReferenceHolder; + +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class BCLPlacedFeatureBuilder, FC extends FeatureConfiguration> extends CommonPlacedFeatureBuilder> { + private final ResourceLocation featureID; + private GenerationStep.Decoration decoration = GenerationStep.Decoration.VEGETAL_DECORATION; + private final BCLConfigureFeature cFeature; + + static final ConcurrentLinkedQueue UNBOUND_FEATURES = new ConcurrentLinkedQueue<>(); + + private BCLPlacedFeatureBuilder( + ResourceLocation featureID, + BCLConfigureFeature cFeature + ) { + this.featureID = featureID; + this.cFeature = cFeature; + } + + + /** + * Set generation step for the feature. Default is {@code VEGETAL_DECORATION}. + * + * @param decoration {@link GenerationStep.Decoration} step. + * @return same {@link CommonPlacedFeatureBuilder} instance. + */ + public BCLPlacedFeatureBuilder decoration(GenerationStep.Decoration decoration) { + this.decoration = decoration; + return this; + } + + /** + * Starts a new {@link BCLFeature} builder. + * + * @param featureID {@link ResourceLocation} feature identifier. + * @param holder {@link Feature} the configured Feature to start from. + * @return {@link CommonPlacedFeatureBuilder} instance. + */ + public static , FC extends FeatureConfiguration> BCLPlacedFeatureBuilder place( + ResourceLocation featureID, + Holder> holder + ) { + return place(featureID, BCLConfigureFeature.create(holder)); + } + + + /** + * Starts a new {@link BCLFeature} builder. + * + * @param featureID {@link ResourceLocation} feature identifier. + * @param cFeature {@link Feature} the configured Feature to start from. + * @return {@link CommonPlacedFeatureBuilder} instance. + */ + static , FC extends FeatureConfiguration> BCLPlacedFeatureBuilder place( + ResourceLocation featureID, + BCLConfigureFeature cFeature + ) { + return new BCLPlacedFeatureBuilder(featureID, cFeature); + } + + /** + * Builds a new {@link BCLFeature} instance. + * + * @return created {@link BCLFeature} instance. + */ + public BCLFeature.Unregistered build() { + final ResourceKey key = ResourceKey.create(Registries.PLACED_FEATURE, featureID); + PlacedFeature pFeature = new PlacedFeature( + (Holder>) (Object) cFeature.configuredFeature, + List.copyOf(modifications) + ); + FullReferenceHolder holder = FullReferenceHolder.create( + Registries.PLACED_FEATURE, + featureID, + pFeature + ); + final BCLFeature.Unregistered res = new BCLFeature.Unregistered<>(cFeature, holder, decoration); + UNBOUND_FEATURES.add(res); + return res; + } + + public static void registerUnbound(BootstapContext bootstrapContext) { + UNBOUND_FEATURES.forEach(u -> u.register(bootstrapContext)); + UNBOUND_FEATURES.clear(); + } + + public static Holder register( + BootstapContext bootstrapContext, + Holder holder + ) { + return bootstrapContext.register(holder.unwrapKey().orElseThrow(), holder.value()); + } + + + /** + * Builds a new {@link BCLFeature} instance. + * Features will be registered during this process. + * + * @return created {@link BCLFeature} instance. + */ + public BCLFeature buildAndRegister(BootstapContext bootstapContext) { + return build().register(bootstapContext); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/CommonPlacedFeatureBuilder.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/CommonPlacedFeatureBuilder.java new file mode 100644 index 00000000..dc5b1279 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/CommonPlacedFeatureBuilder.java @@ -0,0 +1,452 @@ +package org.betterx.bclib.api.v3.levelgen.features; + +import org.betterx.bclib.api.v3.levelgen.features.blockpredicates.BlockPredicates; +import org.betterx.bclib.api.v3.levelgen.features.config.PlaceFacingBlockConfig; +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.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.Noises; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.placement.*; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +abstract class CommonPlacedFeatureBuilder, FC extends FeatureConfiguration, T extends CommonPlacedFeatureBuilder> { + + protected final List modifications = new LinkedList<>(); + + /** + * Add feature placement modifier. Used as a condition for feature how to generate. + * + * @param modifiers {@link PlacementModifier}s to add. + * @return same {@link CommonPlacedFeatureBuilder} instance. + */ + public T modifier(PlacementModifier... modifiers) { + for (var m : modifiers) + modifications.add(m); + return (T) this; + } + + /** + * Add feature placement modifier. Used as a condition for feature how to generate. + * + * @param modifiers {@link PlacementModifier}s to add. + * @return same {@link CommonPlacedFeatureBuilder} instance. + */ + public T modifier(List modifiers) { + modifications.addAll(modifiers); + return (T) this; + } + + /** + * Generate feature in certain iterations (per chunk). + * + * @param count how many times feature will be generated in chunk. + * @return same {@link CommonPlacedFeatureBuilder} instance. + */ + public T 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 CommonPlacedFeatureBuilder} instance. + */ + public T countMax(int count) { + return modifier(CountPlacement.of(UniformInt.of(0, count))); + } + + public T countRange(int min, int max) { + return modifier(CountPlacement.of(UniformInt.of(min, max))); + } + + /** + * Generate points for every xz-Coordinate in a chunk. Be carefuller, this is quite expensive! + * + * @return same {@link CommonPlacedFeatureBuilder} instance. + */ + public T all() { + return modifier(All.simple()); + } + + public T stencil() { + return modifier(Stencil.all()); + } + + public T stencilOneIn4() { + return modifier(Stencil.oneIn4()); + } + + + /** + * 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 CommonPlacedFeatureBuilder} instance. + */ + @SuppressWarnings("deprecation") + public T onEveryLayer(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 CommonPlacedFeatureBuilder} instance. + */ + @SuppressWarnings("deprecation") + public T onEveryLayerMax(int count) { + return modifier(CountOnEveryLayerPlacement.of(UniformInt.of(0, count))); + } + + public T onEveryLayer() { + return modifier(OnEveryLayer.simple()); + } + + public T onEveryLayerMin4() { + return modifier(OnEveryLayer.min4()); + } + + public T underEveryLayer() { + return modifier(UnderEveryLayer.simple()); + } + + public T underEveryLayerMin4() { + return modifier(UnderEveryLayer.min4()); + } + + /** + * Will place feature once every n-th attempts (in average). + * + * @param n amount of attempts. + * @return same {@link CommonPlacedFeatureBuilder} instance. + */ + public T onceEvery(int n) { + return modifier(RarityFilter.onAverageOnceEvery(n)); + } + + /** + * Restricts feature generation only to biome where feature was added. + * + * @return same {@link CommonPlacedFeatureBuilder} instance. + */ + public T onlyInBiome() { + return modifier(BiomeFilter.biome()); + } + + public T noiseIn(double min, double max, float scaleXZ, float scaleY) { + return modifier(new NoiseFilter(Noises.GRAVEL, min, max, scaleXZ, scaleY)); + } + + public T noiseAbove(double value, float scaleXZ, float scaleY) { + return modifier(new NoiseFilter(Noises.GRAVEL, value, Double.MAX_VALUE, scaleXZ, scaleY)); + } + + public T noiseBelow(double value, float scaleXZ, float scaleY) { + return modifier(new NoiseFilter(Noises.GRAVEL, -Double.MAX_VALUE, value, scaleXZ, scaleY)); + } + + /** + * Randomize the xz-Coordinates + * + * @return same {@link CommonPlacedFeatureBuilder} instance. + */ + public T squarePlacement() { + return modifier(InSquarePlacement.spread()); + } + + public T onHeightmap(Heightmap.Types types) { + return modifier(HeightmapPlacement.onHeightmap(types)); + } + + + /** + * Select random height that is 10 above min Build height and 10 below max generation height + * + * @return The instance it was called on + */ + public T 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 T 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 T 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 T randomHeight() { + return modifier(PlacementUtils.FULL_RANGE); + } + + public T spreadHorizontal(IntProvider p) { + return modifier(RandomOffsetPlacement.horizontal(p)); + } + + public T spreadVertical(IntProvider p) { + return modifier(RandomOffsetPlacement.horizontal(p)); + } + + public T spread(IntProvider horizontal, IntProvider vertical) { + return modifier(RandomOffsetPlacement.of(horizontal, vertical)); + } + + public T offset(Direction dir) { + return modifier(Offset.inDirection(dir)); + } + + public T 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 T findSolidFloor(int distance) { + return modifier(FindSolidInDirection.down(distance)); + } + + public T noiseBasedCount(float noiseLevel, int belowNoiseCount, int aboveNoiseCount) { + return modifier(NoiseThresholdCountPlacement.of(noiseLevel, belowNoiseCount, aboveNoiseCount)); + } + + public T extendDown(int min, int max) { + return modifier(new Extend(Direction.DOWN, UniformInt.of(min, max))); + } + + public T inBasinOf(BlockPredicate... predicates) { + return modifier(new IsBasin(BlockPredicate.anyOf(predicates))); + } + + public T inOpenBasinOf(BlockPredicate... predicates) { + return modifier(IsBasin.openTop(BlockPredicate.anyOf(predicates))); + } + + public T is(BlockPredicate... predicates) { + return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.empty())); + } + + public T isAbove(BlockPredicate... predicates) { + return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.of(Direction.DOWN.getNormal()))); + } + + public T isUnder(BlockPredicate... predicates) { + return modifier(new Is(BlockPredicate.anyOf(predicates), Optional.of(Direction.UP.getNormal()))); + } + + public T findSolidCeil(int distance) { + return modifier(FindSolidInDirection.up(distance)); + } + + /** + * Cast a ray with max {@code distance} length to find the next solid Block. The ray will travel through replaceable + * Blocks 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 T findSolidSurface(Direction dir, int distance) { + return modifier(new FindSolidInDirection(dir, distance, 0)); + } + + public T findSolidSurface(List dir, int distance, boolean randomSelect) { + return modifier(new FindSolidInDirection(dir, distance, randomSelect, 0)); + } + + public T onWalls(int distance, int depth) { + return modifier(new FindSolidInDirection(PlaceFacingBlockConfig.HORIZONTAL, distance, false, depth)); + } + + public T heightmap() { + return modifier(PlacementUtils.HEIGHTMAP); + } + + public T heightmapTopSolid() { + return modifier(PlacementUtils.HEIGHTMAP_TOP_SOLID); + } + + public T heightmapWorldSurface() { + return modifier(PlacementUtils.HEIGHTMAP_WORLD_SURFACE); + } + + public T extendXZ(int xzSpread) { + IntProvider xz = UniformInt.of(0, xzSpread); + return (T) modifier( + new ForAll(List.of( + new Extend(Direction.NORTH, xz), + new Extend(Direction.SOUTH, xz), + new Extend(Direction.EAST, xz), + new Extend(Direction.WEST, xz) + )), + new ForAll(List.of( + new Extend(Direction.EAST, xz), + new Extend(Direction.WEST, xz), + new Extend(Direction.NORTH, xz), + new Extend(Direction.SOUTH, xz) + )) + ); + } + + public T extendXYZ(int xzSpread, int ySpread) { + IntProvider xz = UniformInt.of(0, xzSpread); + return (T) extendXZ(xzSpread).extendDown(1, ySpread); + } + + public T isEmpty() { + return modifier(BlockPredicateFilter.forPredicate(BlockPredicate.ONLY_IN_AIR_PREDICATE)); + } + + + public T is(BlockPredicate predicate) { + return modifier(BlockPredicateFilter.forPredicate(predicate)); + } + + public T isNextTo(BlockPredicate predicate) { + return modifier(new IsNextTo(predicate)); + } + + public T belowIsNextTo(BlockPredicate predicate) { + return modifier(new IsNextTo(predicate, Direction.DOWN.getNormal())); + } + + public T isNextTo(BlockPredicate predicate, Vec3i offset) { + return modifier(new IsNextTo(predicate, offset)); + } + + public T isOn(BlockPredicate predicate) { + return modifier(Is.below(predicate)); + } + + + public T inBiomes(ResourceLocation... biomeID) { + return modifier(InBiome.matchingID(biomeID)); + } + + public T notInBiomes(ResourceLocation... biomeID) { + return modifier(InBiome.notMatchingID(biomeID)); + } + + public T isEmptyAndOn(BlockPredicate predicate) { + return (T) this.isEmpty().isOn(predicate); + } + + public T isEmptyAndOnNylium() { + return isEmptyAndOn(BlockPredicates.ONLY_NYLIUM); + } + + public T isEmptyAndOnNetherGround() { + return isEmptyAndOn(BlockPredicates.ONLY_NETHER_GROUND); + } + + public T isUnder(BlockPredicate predicate) { + return modifier(Is.above(predicate)); + } + + public T isEmptyAndUnder(BlockPredicate predicate) { + return (T) this.isEmpty().isUnder(predicate); + } + + public T isEmptyAndUnderNylium() { + return isEmptyAndUnder(BlockPredicates.ONLY_NYLIUM); + } + + public T isEmptyAndUnderNetherGround() { + return isEmptyAndUnder(BlockPredicates.ONLY_NETHER_GROUND); + } + + public T vanillaNetherGround(int countPerLayer) { + return (T) this.randomHeight4FromFloorCeil().onlyInBiome().onEveryLayer(countPerLayer).onlyInBiome(); + } + + public T betterNetherGround(int countPerLayer) { + return (T) this.randomHeight4FromFloorCeil() + .count(countPerLayer) + .squarePlacement() + .onlyInBiome() + .onEveryLayerMin4() + .onlyInBiome(); + } + + public T betterNetherCeiling(int countPerLayer) { + return (T) this.randomHeight4FromFloorCeil() + .count(countPerLayer) + .squarePlacement() + .onlyInBiome() + .underEveryLayerMin4() + .onlyInBiome(); + } + + public T betterNetherOnWall(int countPerLayer) { + return (T) this.count(countPerLayer) + .squarePlacement() + .randomHeight4FromFloorCeil() + .onlyInBiome() + .onWalls(16, 0); + } + + public T betterNetherInWall(int countPerLayer) { + return (T) this.count(countPerLayer) + .squarePlacement() + .randomHeight4FromFloorCeil() + .onlyInBiome() + .onWalls(16, 1); + } + + /** + * Builds a new inline (not registered) {@link PlacedFeature}. + * + * @return created {@link PlacedFeature} instance. + */ + abstract BCLFeature.Unregistered build(); + + public BCLFeatureBuilder.RandomPatch inRandomPatch(ResourceLocation id) { + return BCLFeatureBuilder.startRandomPatch(id, build().getPlacedFeature()); + } + + public BCLFeatureBuilder.RandomPatch randomBonemealDistribution(ResourceLocation id) { + return inRandomPatch(id) + .tries(9) + .spreadXZ(3) + .spreadY(1); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/UserGrowableFeature.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/UserGrowableFeature.java new file mode 100644 index 00000000..09335bff --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/UserGrowableFeature.java @@ -0,0 +1,15 @@ +package org.betterx.bclib.api.v3.levelgen.features; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; + +public interface UserGrowableFeature { + boolean grow( + ServerLevelAccessor level, + BlockPos pos, + RandomSource random, + FC configuration + ); +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/blockpredicates/BlockPredicates.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/blockpredicates/BlockPredicates.java new file mode 100644 index 00000000..9eb0501e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/blockpredicates/BlockPredicates.java @@ -0,0 +1,49 @@ +package org.betterx.bclib.api.v3.levelgen.features.blockpredicates; + +import org.betterx.bclib.BCLib; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicateType; +import net.minecraft.world.level.material.Fluids; + +public class BlockPredicates { + public static final BlockPredicate ONLY_NYLIUM = BlockPredicate.matchesTag(BlockTags.NYLIUM); + public static final BlockPredicate ONLY_MYCELIUM = BlockPredicate.matchesTag(CommonBlockTags.MYCELIUM); + public static final BlockPredicate ONLY_GRAVEL_OR_SAND = BlockPredicate.matchesBlocks( + Blocks.GRAVEL, + Blocks.SAND, + Blocks.RED_SAND + ); + public static final BlockPredicate ONLY_SOUL_GROUND = BlockPredicate.matchesTag(CommonBlockTags.SOUL_GROUND); + public static final BlockPredicate ONLY_NETHER_GROUND = BlockPredicate.matchesTag(CommonBlockTags.NETHER_TERRAIN); + public static final BlockPredicate ONLY_NETHER_GROUND_AND_BASALT = BlockPredicate.anyOf( + ONLY_NETHER_GROUND, + BlockPredicate.matchesBlocks(Blocks.BASALT) + ); + public static final BlockPredicate ONLY_GROUND = BlockPredicate.matchesTag(CommonBlockTags.TERRAIN); + + public static final BlockPredicate ONLY_LAVA = BlockPredicate.matchesFluids(Fluids.LAVA); + public static final BlockPredicate ONLY_GROUND_OR_LAVA = BlockPredicate.anyOf( + BlockPredicate.matchesTag(CommonBlockTags.TERRAIN), + BlockPredicate.matchesFluids(Fluids.LAVA) + ); + public static final BlockPredicateType FULL_SHAPE = register( + BCLib.makeID("full_shape"), + IsFullShape.CODEC + ); + + public static

BlockPredicateType

register(ResourceLocation location, Codec

codec) { + return Registry.register(BuiltInRegistries.BLOCK_PREDICATE_TYPE, location, () -> codec); + } + + public static void ensureStaticInitialization() { + + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/blockpredicates/IsFullShape.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/blockpredicates/IsFullShape.java new file mode 100644 index 00000000..10dc7e21 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/blockpredicates/IsFullShape.java @@ -0,0 +1,41 @@ +package org.betterx.bclib.api.v3.levelgen.features.blockpredicates; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicateType; + +public class IsFullShape implements BlockPredicate { + public static final IsFullShape HERE = new IsFullShape(); + public static final Codec CODEC = RecordCodecBuilder.create( + instance -> instance + .group( + Vec3i.offsetCodec(16).optionalFieldOf("offset", Vec3i.ZERO).forGetter((p) -> p.offset) + ).apply(instance, IsFullShape::new)); + + protected final Vec3i offset; + + private IsFullShape() { + this(Vec3i.ZERO); + } + + public IsFullShape(Vec3i offset) { + super(); + this.offset = offset; + } + + + public BlockPredicateType type() { + return BlockPredicates.FULL_SHAPE; + } + + @Override + public boolean test(WorldGenLevel worldGenLevel, BlockPos blockPos) { + BlockState state = worldGenLevel.getBlockState(blockPos.offset(this.offset)); + return state.isCollisionShapeFullBlock(worldGenLevel, blockPos); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/ConditionFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/ConditionFeatureConfig.java new file mode 100644 index 00000000..35b91261 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/ConditionFeatureConfig.java @@ -0,0 +1,70 @@ +package org.betterx.bclib.api.v3.levelgen.features.config; + +import org.betterx.bclib.api.v3.levelgen.features.BCLFeature; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Holder; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +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; + +public class ConditionFeatureConfig implements FeatureConfiguration { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + PlacementModifier.CODEC.fieldOf("filter").forGetter(p -> p.filter), + PlacedFeature.CODEC.fieldOf("filter_pass").forGetter(p -> p.okFeature), + PlacedFeature.CODEC.optionalFieldOf("filter_fail").forGetter(p -> p.failFeature) + ).apply(instance, ConditionFeatureConfig::new) + ); + + public final PlacementModifier filter; + public final Holder okFeature; + public final Optional> failFeature; + + public ConditionFeatureConfig( + @NotNull PlacementFilter filter, + @NotNull BCLFeature okFeature + ) { + this(filter, okFeature.getPlacedFeature(), Optional.empty()); + + } + + public ConditionFeatureConfig( + @NotNull PlacementFilter filter, + @NotNull BCLFeature okFeature, + @NotNull BCLFeature failFeature + ) { + this(filter, okFeature.getPlacedFeature(), Optional.of(failFeature.getPlacedFeature())); + } + + public ConditionFeatureConfig( + @NotNull PlacementFilter filter, + @NotNull Holder okFeature + ) { + this(filter, okFeature, Optional.empty()); + + } + + public ConditionFeatureConfig( + @NotNull PlacementFilter filter, + @NotNull Holder okFeature, + @NotNull Holder failFeature + ) { + this(filter, okFeature, Optional.of(failFeature)); + } + + protected ConditionFeatureConfig( + @NotNull PlacementModifier filter, + @NotNull Holder okFeature, + @NotNull Optional> failFeature + ) { + this.filter = filter; + this.okFeature = okFeature; + this.failFeature = failFeature; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PillarFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PillarFeatureConfig.java new file mode 100644 index 00000000..030387ed --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PillarFeatureConfig.java @@ -0,0 +1,136 @@ +package org.betterx.bclib.api.v3.levelgen.features.config; + +import org.betterx.bclib.blocks.BlockProperties; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.util.StringRepresentable; +import net.minecraft.util.valueproviders.IntProvider; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider; + +public class PillarFeatureConfig implements FeatureConfiguration { + @FunctionalInterface + public interface StateTransform { + BlockState apply(int height, int maxHeight, BlockState inputState, BlockPos pos, RandomSource rnd); + } + + @FunctionalInterface + public interface PlacePredicate { + PlacePredicate ALLWAYS = (min, max, start, above, level, allow, rnd) -> true; + boolean at( + int minHeight, + int maxHeight, + BlockPos startPos, + BlockPos abovePos, + WorldGenLevel level, + BlockPredicate allowedPlacement, + RandomSource rnd + ); + } + + public enum KnownTransformers implements StringRepresentable { + SIZE_DECREASE( + "size_decrease", + (height, maxHeight, state, pos, rnd) -> state + .setValue(BlockProperties.SIZE, Math.max(0, Math.min(7, maxHeight - height))) + ), + SIZE_INCREASE( + "size_increase", + (height, maxHeight, state, pos, rnd) -> state + .setValue(BlockProperties.SIZE, Math.max(0, Math.min(7, height))) + ), + BOTTOM_GROW( + "bottom_grow", + (height, maxHeight, state, pos, rnd) -> state + .setValue(BlockProperties.BOTTOM, height == maxHeight) + ), + TRIPLE_SHAPE_FILL( + "triple_shape_fill", + (height, maxHeight, state, pos, rnd) -> { + if (height == 0) + return state.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.BOTTOM); + if (height == maxHeight) + return state.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.TOP); + return state.setValue(BlockProperties.TRIPLE_SHAPE, BlockProperties.TripleShape.MIDDLE); + }, + (minHeight, maxHeight, startPos, abovePos, level, allow, rnd) -> !allow.test(level, abovePos) + ); + + + public static final StringRepresentable.EnumCodec CODEC = StringRepresentable + .fromEnum(KnownTransformers::values); + + + public final String name; + public final StateTransform stateTransform; + public final PlacePredicate canPlace; + + KnownTransformers(String name, StateTransform stateTransform) { + this(name, stateTransform, PlacePredicate.ALLWAYS); + } + + KnownTransformers(String name, StateTransform stateTransform, PlacePredicate canPlace) { + this.name = name; + this.stateTransform = stateTransform; + this.canPlace = canPlace; + } + + @Override + public String toString() { + return this.name; + } + + @Override + public String getSerializedName() { + return this.name; + } + } + + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + IntProvider.CODEC.fieldOf("min_height").forGetter(o -> o.minHeight), + IntProvider.CODEC.fieldOf("max_height").forGetter(o -> o.maxHeight), + Direction.CODEC.fieldOf("direction").orElse(Direction.UP).forGetter(o -> o.direction), + BlockPredicate.CODEC.fieldOf("allowed_placement").forGetter(o -> o.allowedPlacement), + BlockStateProvider.CODEC.fieldOf("state").forGetter(o -> o.stateProvider), + KnownTransformers.CODEC.fieldOf("transform").forGetter(o -> o.transformer) + ) + .apply(instance, PillarFeatureConfig::new)); + + public final IntProvider maxHeight; + public final IntProvider minHeight; + public final BlockStateProvider stateProvider; + + public final KnownTransformers transformer; + public final Direction direction; + public final BlockPredicate allowedPlacement; + + + public PillarFeatureConfig( + IntProvider minHeight, + IntProvider maxHeight, + Direction direction, + BlockPredicate allowedPlacement, + BlockStateProvider stateProvider, + KnownTransformers transformer + ) { + this.minHeight = minHeight; + this.maxHeight = maxHeight; + this.stateProvider = stateProvider; + this.transformer = transformer; + this.direction = direction; + this.allowedPlacement = allowedPlacement; + } + + public BlockState transform(int currentHeight, int maxHeight, BlockPos pos, RandomSource rnd) { + BlockState state = stateProvider.getState(rnd, pos); + return transformer.stateTransform.apply(currentHeight, maxHeight, state, pos, rnd); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PlaceBlockFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PlaceBlockFeatureConfig.java new file mode 100644 index 00000000..8341bbfd --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PlaceBlockFeatureConfig.java @@ -0,0 +1,78 @@ +package org.betterx.bclib.api.v3.levelgen.features.config; + +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.util.random.SimpleWeightedRandomList; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider; +import net.minecraft.world.level.levelgen.feature.stateproviders.WeightedStateProvider; + +import java.util.List; + +public abstract class PlaceBlockFeatureConfig implements FeatureConfiguration { + + protected static RecordCodecBuilder blockStateCodec() { + return BlockStateProvider.CODEC + .fieldOf("entries") + .forGetter((T o) -> o.stateProvider); + } + + protected final BlockStateProvider stateProvider; + + + protected static SimpleWeightedRandomList buildWeightedList(List states) { + var builder = SimpleWeightedRandomList.builder(); + for (BlockState s : states) builder.add(s, 1); + return builder.build(); + } + + protected static SimpleWeightedRandomList buildWeightedList(BlockState state) { + return SimpleWeightedRandomList + .builder() + .add(state, 1) + .build(); + } + + public PlaceBlockFeatureConfig(Block block) { + this(block.defaultBlockState()); + } + + public PlaceBlockFeatureConfig(BlockState state) { + this(BlockStateProvider.simple(state)); + } + + + public PlaceBlockFeatureConfig(List states) { + this(buildWeightedList(states)); + } + + public PlaceBlockFeatureConfig(SimpleWeightedRandomList blocks) { + this.stateProvider = new WeightedStateProvider(blocks); + } + + public PlaceBlockFeatureConfig(BlockStateProvider blocks) { + this.stateProvider = blocks; + } + + public BlockState getRandomBlock(RandomSource random, BlockPos pos) { + return this.stateProvider.getState(random, pos); + } + + public boolean place(FeaturePlaceContext ctx) { + BlockState state = getRandomBlock(ctx.random(), ctx.origin()); + return placeBlock(ctx, ctx.level(), ctx.origin(), state); + } + + + protected abstract boolean placeBlock( + FeaturePlaceContext ctx, + WorldGenLevel level, + BlockPos pos, + BlockState targetState + ); +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PlaceFacingBlockConfig.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PlaceFacingBlockConfig.java new file mode 100644 index 00000000..feaa9325 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/PlaceFacingBlockConfig.java @@ -0,0 +1,91 @@ +package org.betterx.bclib.api.v3.levelgen.features.config; + +import org.betterx.bclib.util.BlocksHelper; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.util.random.SimpleWeightedRandomList; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.stateproviders.BlockStateProvider; +import net.minecraft.world.level.levelgen.feature.stateproviders.WeightedStateProvider; + +import java.util.List; + +public class PlaceFacingBlockConfig extends PlaceBlockFeatureConfig { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + blockStateCodec(), + ExtraCodecs.nonEmptyList(Direction.CODEC.listOf()) + .fieldOf("dir") + .orElse(List.of(Direction.NORTH)) + .forGetter(a -> a.directions) + ).apply(instance, PlaceFacingBlockConfig::new) + ); + public static final List HORIZONTAL = List.of( + Direction.NORTH, + Direction.EAST, + Direction.WEST, + Direction.SOUTH + ); + public static final List VERTICAL = List.of(Direction.UP, Direction.DOWN); + public static final List ALL = List.of( + Direction.NORTH, + Direction.EAST, + Direction.SOUTH, + Direction.WEST, + Direction.UP, + Direction.DOWN + ); + + private final List directions; + + public PlaceFacingBlockConfig(Block block, List dir) { + this(block.defaultBlockState(), dir); + } + + public PlaceFacingBlockConfig(BlockState state, List dir) { + this(BlockStateProvider.simple(state), dir); + } + + public PlaceFacingBlockConfig(List states, List dir) { + this(buildWeightedList(states), dir); + } + + public PlaceFacingBlockConfig(SimpleWeightedRandomList blocks, List dir) { + this(new WeightedStateProvider(blocks), dir); + } + + public PlaceFacingBlockConfig(BlockStateProvider blocks, List dir) { + super(blocks); + directions = dir; + } + + @Override + public boolean placeBlock( + FeaturePlaceContext ctx, + WorldGenLevel level, + BlockPos pos, + BlockState targetState + ) { + BlockState lookupState; + BlockPos testPos; + for (Direction dir : directions) { + testPos = pos.relative(dir); + lookupState = targetState.setValue(HorizontalDirectionalBlock.FACING, dir); + if (level.getBlockState(testPos).isAir() && lookupState.canSurvive(level, testPos)) { + lookupState.canSurvive(level, testPos); + BlocksHelper.setWithoutUpdate(level, testPos, lookupState); + return true; + } + } + + return false; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/SequenceFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/SequenceFeatureConfig.java new file mode 100644 index 00000000..f0e5f300 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/SequenceFeatureConfig.java @@ -0,0 +1,42 @@ +package org.betterx.bclib.api.v3.levelgen.features.config; + +import org.betterx.bclib.api.v3.levelgen.features.BCLFeature; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.Holder; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import java.util.List; + +public class SequenceFeatureConfig implements FeatureConfiguration { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> + instance.group( + ExtraCodecs.nonEmptyList(PlacedFeature.CODEC.listOf()) + .fieldOf("features") + .forGetter(a -> a.features) + ).apply(instance, SequenceFeatureConfig::new) + ); + + private final List> features; + + public static SequenceFeatureConfig createSequence(List> features) { + return new SequenceFeatureConfig(features.stream().map(f -> f.getPlacedFeature()).toList()); + } + + public SequenceFeatureConfig(List> features) { + this.features = features; + } + + public boolean placeAll(FeaturePlaceContext ctx) { + boolean placed = false; + for (Holder f : features) { + placed |= f.value().place(ctx.level(), ctx.chunkGenerator(), ctx.random(), ctx.origin()); + } + return placed; + + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/TemplateFeatureConfig.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/TemplateFeatureConfig.java new file mode 100644 index 00000000..73b47c8e --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/config/TemplateFeatureConfig.java @@ -0,0 +1,42 @@ +package org.betterx.bclib.api.v3.levelgen.features.config; + +import org.betterx.bclib.api.v2.levelgen.structures.StructurePlacementType; +import org.betterx.bclib.api.v2.levelgen.structures.StructureWorldNBT; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; + +import java.util.List; + +public class TemplateFeatureConfig implements FeatureConfiguration { + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + ExtraCodecs.nonEmptyList(StructureWorldNBT.CODEC.listOf()) + .fieldOf("structures") + .forGetter((TemplateFeatureConfig cfg) -> cfg.structures) + ) + .apply(instance, TemplateFeatureConfig::new) + ); + + public final List structures; + + public static StructureWorldNBT cfg( + ResourceLocation location, + int offsetY, + StructurePlacementType type, + float chance + ) { + return StructureWorldNBT.create(location, offsetY, type, chance); + } + + public TemplateFeatureConfig(ResourceLocation location, int offsetY, StructurePlacementType type) { + this(List.of(cfg(location, offsetY, type, 1.0f))); + } + + public TemplateFeatureConfig(List structures) { + this.structures = structures; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/ConditionFeature.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/ConditionFeature.java new file mode 100644 index 00000000..8ceb7de2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/ConditionFeature.java @@ -0,0 +1,38 @@ +package org.betterx.bclib.api.v3.levelgen.features.features; + +import org.betterx.bclib.api.v3.levelgen.features.config.ConditionFeatureConfig; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.placement.PlacementContext; + +import java.util.Optional; +import java.util.stream.Stream; + +public class ConditionFeature extends Feature { + public ConditionFeature() { + super(ConditionFeatureConfig.CODEC); + } + + @Override + public boolean place(FeaturePlaceContext ctx) { + final ConditionFeatureConfig cfg = ctx.config(); + final WorldGenLevel level = ctx.level(); + final RandomSource random = ctx.random(); + final BlockPos pos = ctx.origin(); + + final PlacementContext c = new PlacementContext(level, ctx.chunkGenerator(), Optional.empty()); + + Stream stream = cfg.filter.getPositions(c, ctx.random(), pos); + Holder state = (stream.findFirst().isPresent() ? cfg.okFeature : cfg.failFeature.orElse(null)); + if (state != null) { + return state.value().place(level, ctx.chunkGenerator(), random, pos); + } + return false; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/MarkPostProcessingFeature.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/MarkPostProcessingFeature.java new file mode 100644 index 00000000..888a8103 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/MarkPostProcessingFeature.java @@ -0,0 +1,20 @@ +package org.betterx.bclib.api.v3.levelgen.features.features; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; +import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; + +public class MarkPostProcessingFeature extends Feature { + public MarkPostProcessingFeature() { + super(NoneFeatureConfiguration.CODEC); + } + + @Override + public boolean place(FeaturePlaceContext ctx) { + BlockPos pos = ctx.origin(); + ctx.level().getChunk(pos.getX() >> 4, pos.getZ() >> 4) + .markPosForPostprocessing(new BlockPos(pos.getX() & 15, pos.getY(), pos.getZ() & 15)); + return true; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/PillarFeature.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/PillarFeature.java new file mode 100644 index 00000000..2675fb52 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/PillarFeature.java @@ -0,0 +1,59 @@ +package org.betterx.bclib.api.v3.levelgen.features.features; + +import org.betterx.bclib.api.v3.levelgen.features.config.PillarFeatureConfig; +import org.betterx.bclib.util.BlocksHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +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; + +public class PillarFeature extends Feature { + public PillarFeature() { + super(PillarFeatureConfig.CODEC); + } + + + @Override + public boolean place(FeaturePlaceContext featurePlaceContext) { + int height; + final WorldGenLevel level = featurePlaceContext.level(); + final PillarFeatureConfig config = featurePlaceContext.config(); + final RandomSource rnd = featurePlaceContext.random(); + int maxHeight = config.maxHeight.sample(rnd); + int minHeight = config.minHeight.sample(rnd); + BlockPos.MutableBlockPos posnow = featurePlaceContext.origin().mutable(); + posnow.move(config.direction); + + for (height = 1; height < maxHeight; ++height) { + if (!config.allowedPlacement.test(level, posnow)) { + maxHeight = height; + break; + } + posnow.move(config.direction); + } + if (maxHeight < minHeight) return false; + + if (!config.transformer.canPlace.at( + minHeight, + maxHeight, + featurePlaceContext.origin(), + posnow, + level, + config.allowedPlacement, + rnd + )) { + return false; + } + posnow = featurePlaceContext.origin().mutable(); + for (height = 0; height < maxHeight; ++height) { + BlockState state = config.transform(height, maxHeight - 1, posnow, rnd); + BlocksHelper.setWithoutUpdate(level, posnow, state); + posnow.move(config.direction); + } + + return true; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/PlaceBlockFeature.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/PlaceBlockFeature.java new file mode 100644 index 00000000..257bf0d2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/PlaceBlockFeature.java @@ -0,0 +1,18 @@ +package org.betterx.bclib.api.v3.levelgen.features.features; + +import org.betterx.bclib.api.v3.levelgen.features.config.PlaceBlockFeatureConfig; + +import com.mojang.serialization.Codec; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; + +public class PlaceBlockFeature extends Feature { + public PlaceBlockFeature(Codec codec) { + super(codec); + } + + @Override + public boolean place(FeaturePlaceContext ctx) { + return ctx.config().place(ctx); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/SequenceFeature.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/SequenceFeature.java new file mode 100644 index 00000000..c42735c7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/SequenceFeature.java @@ -0,0 +1,17 @@ +package org.betterx.bclib.api.v3.levelgen.features.features; + +import org.betterx.bclib.api.v3.levelgen.features.config.SequenceFeatureConfig; + +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; + +public class SequenceFeature extends Feature { + public SequenceFeature() { + super(SequenceFeatureConfig.CODEC); + } + + @Override + public boolean place(FeaturePlaceContext featurePlaceContext) { + return featurePlaceContext.config().placeAll(featurePlaceContext); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/TemplateFeature.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/TemplateFeature.java new file mode 100644 index 00000000..91e28907 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/features/TemplateFeature.java @@ -0,0 +1,43 @@ +package org.betterx.bclib.api.v3.levelgen.features.features; + +import org.betterx.bclib.api.v2.levelgen.structures.StructureNBT; +import org.betterx.bclib.api.v2.levelgen.structures.StructureWorldNBT; +import org.betterx.bclib.api.v3.levelgen.features.config.TemplateFeatureConfig; + +import com.mojang.serialization.Codec; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; + +public class TemplateFeature extends Feature { + public TemplateFeature(Codec codec) { + super(codec); + } + + protected StructureWorldNBT randomStructure(TemplateFeatureConfig cfg, RandomSource random) { + if (cfg.structures.size() > 1) { + final float chanceSum = cfg.structures.parallelStream().map(c -> c.chance).reduce(0.0f, (p, c) -> p + c); + float rnd = random.nextFloat() * chanceSum; + + for (StructureWorldNBT c : cfg.structures) { + rnd -= c.chance; + if (rnd <= 0) return c; + } + } else { + return cfg.structures.get(0); + } + + return null; + } + + @Override + public boolean place(FeaturePlaceContext ctx) { + StructureWorldNBT structure = randomStructure(ctx.config(), ctx.random()); + return structure.generateIfPlaceable( + ctx.level(), + ctx.origin(), + StructureNBT.getRandomRotation(ctx.random()), + StructureNBT.getRandomMirror(ctx.random()) + ); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/All.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/All.java new file mode 100644 index 00000000..05e3ba8c --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/All.java @@ -0,0 +1,34 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class All extends PlacementModifier { + private static final All INSTANCE = new All(); + public static final Codec CODEC = Codec.unit(All::new); + + @Override + public Stream getPositions( + PlacementContext placementContext, + RandomSource randomSource, + BlockPos blockPos + ) { + return IntStream.range(0, 16 * 16 - 1).mapToObj(i -> blockPos.offset(i & 0xF, 0, i >> 4)); + } + + public static PlacementModifier simple() { + return INSTANCE; + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.ALL; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Debug.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Debug.java new file mode 100644 index 00000000..66c844fb --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Debug.java @@ -0,0 +1,45 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import org.betterx.bclib.BCLib; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.stream.Stream; + +public class Debug extends PlacementModifier { + public static final Debug INSTANCE = new Debug("Placing at {}"); + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + Codec.STRING + .fieldOf("caption") + .orElse("Placing at {}") + .forGetter(cfg -> cfg.caption) + ) + .apply(instance, Debug::new)); + private final String caption; + + public Debug(String caption) { + this.caption = caption; + } + + @Override + public Stream getPositions( + PlacementContext placementContext, + RandomSource randomSource, + BlockPos blockPos + ) { + BCLib.LOGGER.info(caption, blockPos); + return Stream.of(blockPos); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.DEBUG; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Extend.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Extend.java new file mode 100644 index 00000000..21436043 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Extend.java @@ -0,0 +1,57 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.util.valueproviders.IntProvider; +import net.minecraft.util.valueproviders.UniformInt; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.stream.Stream; + +public class Extend extends PlacementModifier { + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + Direction.CODEC + .fieldOf("direction") + .orElse(Direction.DOWN) + .forGetter(cfg -> cfg.direction), + IntProvider.codec(0, 16) + .fieldOf("length") + .orElse(UniformInt.of(0, 3)) + .forGetter(cfg -> cfg.length) + ) + .apply(instance, Extend::new)); + + private final Direction direction; + private final IntProvider length; + + public Extend(Direction direction, IntProvider length) { + this.direction = direction; + this.length = length; + } + + @Override + public Stream getPositions( + PlacementContext placementContext, + RandomSource random, + BlockPos blockPos + ) { + var builder = Stream.builder(); + final int count = length.sample(random); + builder.add(blockPos); + for (int y = 1; y < count + 1; y++) { + builder.add(blockPos.relative(direction, y)); + } + return builder.build(); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.EXTEND; + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/FindSolidInDirection.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/FindSolidInDirection.java new file mode 100644 index 00000000..6ba80c04 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/FindSolidInDirection.java @@ -0,0 +1,155 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import org.betterx.bclib.util.BlocksHelper; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.SectionPos; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.util.RandomSource; +import net.minecraft.util.valueproviders.IntProvider; +import net.minecraft.util.valueproviders.UniformInt; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.List; +import java.util.stream.Stream; + +public class FindSolidInDirection extends PlacementModifier { + + public static final Codec CODEC = RecordCodecBuilder + .create((instance) -> instance.group( + ExtraCodecs.nonEmptyList(Direction.CODEC.listOf()) + .fieldOf("dir") + .orElse(List.of(Direction.DOWN)) + .forGetter(a -> a.direction), + Codec.intRange(1, 32).fieldOf("dist").orElse(12).forGetter((p) -> p.maxSearchDistance), + Codec.BOOL.fieldOf("random_select").orElse(true).forGetter(p -> p.randomSelect), + Codec.INT.fieldOf("offset_in_dir").orElse(0).forGetter(p -> p.offsetInDir) + ) + .apply( + instance, + FindSolidInDirection::new + )); + protected static final FindSolidInDirection DOWN = new FindSolidInDirection(Direction.DOWN, 6, 0); + protected static final FindSolidInDirection UP = new FindSolidInDirection(Direction.UP, 6, 0); + private final List direction; + private final int maxSearchDistance; + + private final int offsetInDir; + private final boolean randomSelect; + private final IntProvider provider; + + + public FindSolidInDirection(Direction direction, int maxSearchDistance, int offsetInDir) { + this(List.of(direction), maxSearchDistance, false, offsetInDir); + } + + public FindSolidInDirection(List direction, int maxSearchDistance, int offsetInDir) { + this(direction, maxSearchDistance, direction.size() > 1, offsetInDir); + } + + public FindSolidInDirection( + List direction, + int maxSearchDistance, + boolean randomSelect, + int offsetInDir + ) { + this.direction = direction; + this.maxSearchDistance = maxSearchDistance; + this.provider = UniformInt.of(0, direction.size() - 1); + this.randomSelect = randomSelect; + this.offsetInDir = offsetInDir; + } + + public static PlacementModifier down() { + return DOWN; + } + + public static PlacementModifier up() { + return UP; + } + + public static PlacementModifier down(int dist) { + if (dist == DOWN.maxSearchDistance && 0 == DOWN.offsetInDir) return DOWN; + return new FindSolidInDirection(Direction.DOWN, dist, 0); + } + + public static PlacementModifier up(int dist) { + if (dist == UP.maxSearchDistance && 0 == UP.offsetInDir) return UP; + return new FindSolidInDirection(Direction.UP, dist, 0); + } + + public static PlacementModifier down(int dist, int offset) { + if (dist == DOWN.maxSearchDistance && 0 == DOWN.offsetInDir) return DOWN; + return new FindSolidInDirection(Direction.DOWN, dist, offset); + } + + public static PlacementModifier up(int dist, int offset) { + if (dist == UP.maxSearchDistance && offset == UP.offsetInDir) return UP; + return new FindSolidInDirection(Direction.UP, dist, offset); + } + + public Direction randomDirection(RandomSource random) { + return direction.get(provider.sample(random)); + } + + @Override + public Stream getPositions( + PlacementContext placementContext, + RandomSource randomSource, + BlockPos blockPos + ) { + var builder = Stream.builder(); + if (randomSelect) { + submitSingle(placementContext, blockPos, builder, randomDirection(randomSource)); + } else { + for (Direction d : direction) { + submitSingle(placementContext, blockPos, builder, d); + } + } + + return builder.build(); + } + + private void submitSingle( + PlacementContext placementContext, + BlockPos blockPos, + Stream.Builder builder, + Direction d + ) { + int searchDist; + BlockPos.MutableBlockPos POS = blockPos.mutable(); + if (d == Direction.EAST) { //+x + searchDist = Math.min(maxSearchDistance, 15 - SectionPos.sectionRelative(blockPos.getX())); + } else if (d == Direction.WEST) { //-x + searchDist = Math.min(maxSearchDistance, SectionPos.sectionRelative(blockPos.getX())); + } else if (d == Direction.SOUTH) { //+z + searchDist = Math.min(maxSearchDistance, 15 - SectionPos.sectionRelative(blockPos.getZ())); + } else if (d == Direction.NORTH) { //-z + searchDist = Math.min(maxSearchDistance, SectionPos.sectionRelative(blockPos.getZ())); + } else { + searchDist = maxSearchDistance; + } + if (BlocksHelper.findOnSurroundingSurface( + placementContext.getLevel(), + POS, + d, + searchDist, + BlocksHelper::isTerrain + )) { + if (offsetInDir != 0) + builder.add(POS.move(d, offsetInDir)); + else + builder.add(POS); + } + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.SOLID_IN_DIR; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/ForAll.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/ForAll.java new file mode 100644 index 00000000..0a511a08 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/ForAll.java @@ -0,0 +1,47 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.List; +import java.util.stream.Stream; + +public class ForAll extends PlacementModifier { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + ExtraCodecs.nonEmptyList(PlacementModifier.CODEC.listOf()) + .fieldOf("modifiers") + .forGetter(a -> a.modifiers) + ) + .apply(instance, ForAll::new)); + + private final List modifiers; + + public ForAll(List modifiers) { + this.modifiers = modifiers; + } + + @Override + public Stream getPositions( + PlacementContext placementContext, + RandomSource randomSource, + BlockPos blockPos + ) { + Stream.Builder stream = Stream.builder(); + for (PlacementModifier p : modifiers) { + p.getPositions(placementContext, randomSource, blockPos).forEach(pp -> stream.add(pp)); + } + return stream.build(); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.FOR_ALL; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/InBiome.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/InBiome.java new file mode 100644 index 00000000..71f85b89 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/InBiome.java @@ -0,0 +1,69 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementFilter; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.List; +import java.util.Optional; + +public class InBiome extends PlacementFilter { + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + Codec.BOOL + .fieldOf("negate") + .orElse(false) + .forGetter(cfg -> cfg.negate), + Codec.list(ResourceLocation.CODEC) + .fieldOf("biomes") + .forGetter(cfg -> cfg.biomeIDs) + ) + .apply(instance, InBiome::new)); + + public final List biomeIDs; + public final boolean negate; + + protected InBiome(boolean negate, List biomeIDs) { + this.biomeIDs = biomeIDs; + this.negate = negate; + } + + public static InBiome matchingID(ResourceLocation... id) { + return new InBiome(false, List.of(id)); + } + + public static InBiome matchingID(List ids) { + return new InBiome(false, ids); + } + + public static InBiome notMatchingID(ResourceLocation... id) { + return new InBiome(true, List.of(id)); + } + + public static InBiome notMatchingID(List ids) { + return new InBiome(true, ids); + } + + @Override + protected boolean shouldPlace(PlacementContext ctx, RandomSource random, BlockPos pos) { + Holder holder = ctx.getLevel().getBiome(pos); + Optional biomeLocation = holder.unwrapKey().map(key -> key.location()); + if (biomeLocation.isPresent()) { + boolean contains = biomeIDs.contains(biomeLocation.get()); + return negate != contains; + } + return false; + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.IN_BIOME; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Is.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Is.java new file mode 100644 index 00000000..56f3066f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Is.java @@ -0,0 +1,59 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Vec3i; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementFilter; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.Optional; + +public class Is extends PlacementFilter { + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + BlockPredicate.CODEC + .fieldOf("predicate") + .forGetter(cfg -> cfg.predicate), + Vec3i.CODEC + .optionalFieldOf("offset") + .forGetter(cfg -> cfg.offset) + ) + .apply(instance, Is::new)); + + private final BlockPredicate predicate; + private final Optional offset; + + public Is(BlockPredicate predicate, Optional offset) { + this.predicate = predicate; + this.offset = offset; + } + + public static Is simple(BlockPredicate predicate) { + return new Is(predicate, Optional.empty()); + } + + public static Is below(BlockPredicate predicate) { + return new Is(predicate, Optional.of(Direction.DOWN.getNormal())); + } + + public static Is above(BlockPredicate predicate) { + return new Is(predicate, Optional.of(Direction.UP.getNormal())); + } + + @Override + protected boolean shouldPlace(PlacementContext ctx, RandomSource random, BlockPos pos) { + WorldGenLevel level = ctx.getLevel(); + return predicate.test(level, offset.map(v -> pos.offset(v.getX(), v.getY(), v.getZ())).orElse(pos)); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.IS; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/IsBasin.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/IsBasin.java new file mode 100644 index 00000000..a8d856a0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/IsBasin.java @@ -0,0 +1,64 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementFilter; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.Optional; + +public class IsBasin extends PlacementFilter { + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + BlockPredicate.CODEC + .fieldOf("predicate") + .forGetter(cfg -> cfg.predicate), + BlockPredicate.CODEC + .optionalFieldOf("top_predicate") + .orElse(Optional.empty()) + .forGetter(cfg -> cfg.topPredicate) + ) + .apply(instance, IsBasin::new)); + + private final BlockPredicate predicate; + private final Optional topPredicate; + + public IsBasin(BlockPredicate predicate) { + this(predicate, Optional.empty()); + } + + public IsBasin(BlockPredicate predicate, Optional topPredicate) { + this.predicate = predicate; + this.topPredicate = topPredicate; + } + + public static PlacementFilter simple(BlockPredicate predicate) { + return new IsBasin(predicate); + } + + public static IsBasin openTop(BlockPredicate predicate) { + return new IsBasin(predicate, Optional.of(BlockPredicate.ONLY_IN_AIR_PREDICATE)); + } + + @Override + protected boolean shouldPlace(PlacementContext ctx, RandomSource random, BlockPos pos) { + WorldGenLevel level = ctx.getLevel(); + if (topPredicate.isPresent() && !topPredicate.get().test(level, pos.above())) return false; + + return predicate.test(level, pos.below()) + && predicate.test(level, pos.west()) + && predicate.test(level, pos.east()) + && predicate.test(level, pos.north()) + && predicate.test(level, pos.south()); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.IS_BASIN; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/IsNextTo.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/IsNextTo.java new file mode 100644 index 00000000..fbb7c48f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/IsNextTo.java @@ -0,0 +1,64 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementFilter; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.Optional; +import org.jetbrains.annotations.NotNull; + +public class IsNextTo extends PlacementFilter { + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + BlockPredicate.CODEC + .fieldOf("predicate") + .forGetter(cfg -> cfg.predicate), + Vec3i.CODEC + .optionalFieldOf("offset") + .forGetter(cfg -> Optional.of(cfg.offset)) + ) + .apply(instance, IsNextTo::new)); + + private final BlockPredicate predicate; + private final Vec3i offset; + + public IsNextTo(BlockPredicate predicate) { + this(predicate, Optional.of(Vec3i.ZERO)); + } + + public IsNextTo(BlockPredicate predicate, Optional offset) { + this(predicate, offset.orElse(Vec3i.ZERO)); + } + + public IsNextTo(@NotNull BlockPredicate predicate, @NotNull Vec3i offset) { + this.predicate = predicate; + this.offset = offset; + } + + public static PlacementFilter simple(BlockPredicate predicate) { + return new IsBasin(predicate); + } + + @Override + protected boolean shouldPlace(PlacementContext ctx, RandomSource random, BlockPos pos) { + WorldGenLevel level = ctx.getLevel(); + + pos = pos.offset(this.offset); + return predicate.test(level, pos.west()) + || predicate.test(level, pos.east()) + || predicate.test(level, pos.north()) + || predicate.test(level, pos.south()); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.IS_NEXT_TO; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/NoiseFilter.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/NoiseFilter.java new file mode 100644 index 00000000..25770f6f --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/NoiseFilter.java @@ -0,0 +1,60 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import org.betterx.bclib.noise.Noises; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementFilter; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +public class NoiseFilter extends PlacementFilter { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + ResourceKey.codec(Registries.NOISE).fieldOf("noise").forGetter(o -> o.noise), + Codec.DOUBLE.fieldOf("min_noise_level").forGetter(o -> o.minNoiseLevel), + Codec.DOUBLE.fieldOf("max_noise_level").orElse(Double.MAX_VALUE).forGetter(o -> o.maxNoiseLevel), + Codec.FLOAT.fieldOf("scale_xz").orElse(1f).forGetter(o -> o.scaleXZ), + Codec.FLOAT.fieldOf("scale_y").orElse(1f).forGetter(o -> o.scaleY) + ) + .apply(instance, NoiseFilter::new)); + + private final ResourceKey noise; + + private final double minNoiseLevel; + private final double maxNoiseLevel; + private final float scaleXZ; + private final float scaleY; + + + public NoiseFilter( + ResourceKey noise, + double minNoiseLevel, + double maxNoiseLevel, + float scaleXZ, + float scaleY + ) { + this.noise = noise; + this.minNoiseLevel = minNoiseLevel; + this.maxNoiseLevel = maxNoiseLevel; + this.scaleXZ = scaleXZ; + this.scaleY = scaleY; + } + + @Override + protected boolean shouldPlace(PlacementContext ctx, RandomSource random, BlockPos pos) { + final NormalNoise normalNoise = Noises.getOrCreateNoise(ctx.getLevel().registryAccess(), random, this.noise); + final double v = normalNoise.getValue(pos.getX() * scaleXZ, pos.getY() * scaleY, pos.getZ() * scaleXZ); + return v > minNoiseLevel && v < maxNoiseLevel; + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.NOISE_FILTER; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Offset.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Offset.java new file mode 100644 index 00000000..69ba3ac5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Offset.java @@ -0,0 +1,56 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Vec3i; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.stream.Stream; + +public class Offset extends PlacementModifier { + private static final Map DIRECTIONS = Maps.newHashMap(); + public static final Codec CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + Vec3i.CODEC + .fieldOf("blocks") + .forGetter(cfg -> cfg.offset) + ) + .apply(instance, Offset::new)); + + private final Vec3i offset; + + public Offset(Vec3i offset) { + this.offset = offset; + } + + public static Offset inDirection(Direction dir) { + return DIRECTIONS.get(dir); + } + + @Override + public Stream getPositions( + PlacementContext placementContext, + RandomSource randomSource, + BlockPos blockPos + ) { + return Stream.of(blockPos.offset(offset)); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.OFFSET; + } + + static { + for (Direction d : Direction.values()) + DIRECTIONS.put(d, new Offset(d.getNormal())); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/OnEveryLayer.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/OnEveryLayer.java new file mode 100644 index 00000000..4a2ccb4b --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/OnEveryLayer.java @@ -0,0 +1,93 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import org.betterx.bclib.util.BlocksHelper; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.Optional; +import java.util.stream.Stream; + +public class OnEveryLayer + extends PlacementModifier { + private static final OnEveryLayer INSTANCE = new OnEveryLayer(Optional.empty(), Optional.empty()); + private static final OnEveryLayer INSTANCE_MIN_4 = new OnEveryLayer(Optional.of(4), Optional.empty()); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + Codec.INT.optionalFieldOf("min").forGetter(o -> o.minHeight), + Codec.INT.optionalFieldOf("max").forGetter(o -> o.maxHeight) + ).apply(instance, OnEveryLayer::new)); + + + private final Optional minHeight; + private final Optional maxHeight; + + protected OnEveryLayer(Optional minHeight, Optional maxHeight) { + this.minHeight = minHeight; + + this.maxHeight = maxHeight; + } + + public static OnEveryLayer simple() { + return INSTANCE; + } + + public static OnEveryLayer min4() { + return INSTANCE_MIN_4; + } + + @Override + public Stream getPositions( + PlacementContext ctx, + RandomSource random, + BlockPos pos + ) { + + Stream.Builder builder = Stream.builder(); + + final int z = pos.getZ(); + final int x = pos.getX(); + final int levelHeight = ctx.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z); + final int minLevelHeight = ctx.getMinBuildHeight(); + int y = maxHeight.map(h -> Math.min(levelHeight, h)).orElse(levelHeight); + final int minHeight = this.minHeight.map(h -> Math.max(minLevelHeight, h)).orElse(minLevelHeight); + + int layerY; + do { + layerY = OnEveryLayer.findOnGroundYPosition(ctx, x, y, z, minHeight); + if (layerY != Integer.MAX_VALUE) { + builder.add(new BlockPos(x, layerY, z)); + y = layerY - 1; + } + + } while (layerY != Integer.MAX_VALUE); + return builder.build(); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.ON_EVERY_LAYER; + } + + private static int findOnGroundYPosition(PlacementContext ctx, int x, int startY, int z, int minHeight) { + BlockPos.MutableBlockPos mPos = new BlockPos.MutableBlockPos(x, startY, z); + BlockState nowState = ctx.getBlockState(mPos); + for (int y = startY; y >= minHeight + 1; --y) { + mPos.setY(y - 1); + BlockState belowState = ctx.getBlockState(mPos); + if (BlocksHelper.isTerrain(belowState) && BlocksHelper.isFreeOrFluid(nowState) && !belowState.is(Blocks.BEDROCK)) { + return mPos.getY() + 1; + } + nowState = belowState; + } + return Integer.MAX_VALUE; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/PlacementModifiers.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/PlacementModifiers.java new file mode 100644 index 00000000..7cad9435 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/PlacementModifiers.java @@ -0,0 +1,96 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import org.betterx.bclib.BCLib; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +public class PlacementModifiers { + public static final PlacementModifierType STENCIL = register( + "stencil", + Stencil.CODEC + ); + public static final PlacementModifierType IS_NEXT_TO = register( + "is_next_to", + IsNextTo.CODEC + ); + public static final PlacementModifierType NOISE_FILTER = register( + "noise_filter", + NoiseFilter.CODEC + ); + public static final PlacementModifierType DEBUG = register( + "debug", + Debug.CODEC + ); + + public static final PlacementModifierType FOR_ALL = register( + "for_all", + ForAll.CODEC + ); + + public static final PlacementModifierType SOLID_IN_DIR = register( + "solid_in_dir", + FindSolidInDirection.CODEC + ); + + public static final PlacementModifierType ALL = register( + "all", + All.CODEC + ); + + public static final PlacementModifierType IS_BASIN = register( + "is_basin", + IsBasin.CODEC + ); + + public static final PlacementModifierType IS = register( + "is", + Is.CODEC + ); + + public static final PlacementModifierType OFFSET = register( + "offset", + Offset.CODEC + ); + + public static final PlacementModifierType EXTEND = register( + "extend", + Extend.CODEC + ); + + public static final PlacementModifierType ON_EVERY_LAYER = register( + "on_every_layer", + OnEveryLayer.CODEC + ); + + public static final PlacementModifierType UNDER_EVERY_LAYER = register( + "under_every_layer", + UnderEveryLayer.CODEC + ); + + public static final PlacementModifierType IN_BIOME = register( + "in_biome", + InBiome.CODEC + ); + + + private static

PlacementModifierType

register(String path, Codec

codec) { + return register(BCLib.makeID(path), codec); + } + + public static

PlacementModifierType

register( + ResourceLocation location, + Codec

codec + ) { + return Registry.register(BuiltInRegistries.PLACEMENT_MODIFIER_TYPE, location, () -> codec); + } + + public static void ensureStaticInitialization() { + + } +} + diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Stencil.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Stencil.java new file mode 100644 index 00000000..64470958 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/Stencil.java @@ -0,0 +1,345 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +public class Stencil extends PlacementModifier { + public static final Codec CODEC; + private static final Boolean[] BN_STENCIL; + private final List stencil; + private static final Stencil DEFAULT; + private static final Stencil DEFAULT4; + private final int selectOneIn; + + private static List convert(Boolean[] s) { + return Arrays.stream(s).toList(); + } + + public Stencil(Boolean[] stencil, int selectOneIn) { + this(convert(stencil), selectOneIn); + } + + public Stencil(List stencil, int selectOneIn) { + this.stencil = stencil; + this.selectOneIn = selectOneIn; + } + + public static Stencil all() { + return DEFAULT; + } + + public static Stencil oneIn4() { + return DEFAULT4; + } + + @Override + public Stream getPositions( + PlacementContext placementContext, + RandomSource randomSource, + BlockPos blockPos + ) { + List pos = new ArrayList<>(16 * 16); + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + if (stencil.get(x << 4 | y)) { + pos.add(blockPos.offset(x, 0, y)); + } + } + } + + return pos.stream(); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.STENCIL; + } + + static { + BN_STENCIL = new Boolean[]{ + false, + true, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + true, + false, + false, + true, + true, + true, + false, + false, + false, + true, + true, + false, + false, + false, + true, + false, + false, + true, + true, + true, + false, + false, + true, + true, + true, + true, + false, + true, + true, + true, + true, + false, + false, + false, + true, + true, + false, + false, + true, + true, + true, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + true, + false, + false, + false, + true, + true, + true, + true, + false, + false, + false, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + false, + false, + true, + true, + false, + true, + true, + false, + false, + false, + true, + false, + false, + true, + false, + false, + false, + false, + false, + true, + true, + true, + false, + false, + false, + false, + true, + false, + false, + true, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + false, + false, + true, + true, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + true, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + false, + true, + true, + false, + false, + true, + false, + false, + false, + true, + true, + true, + true, + true, + true, + false, + true, + false, + true, + true, + true, + true, + true, + true, + true, + false, + false, + false, + false, + true, + false, + false, + false, + false, + true, + true, + false, + false, + false, + true, + false, + false, + false, + false, + false, + true, + true, + false, + false + }; + + DEFAULT = new Stencil(BN_STENCIL, 1); + DEFAULT4 = new Stencil(BN_STENCIL, 4); + CODEC = RecordCodecBuilder.create((instance) -> instance + .group( + ExtraCodecs.nonEmptyList(Codec.BOOL.listOf()) + .fieldOf("structures") + .orElse(convert(BN_STENCIL)) + .forGetter((Stencil a) -> a.stencil), + Codec.INT + .fieldOf("one_in") + .orElse(1) + .forGetter((Stencil a) -> a.selectOneIn) + ) + .apply(instance, Stencil::new) + ); + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/UnderEveryLayer.java b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/UnderEveryLayer.java new file mode 100644 index 00000000..eb4c7d57 --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/levelgen/features/placement/UnderEveryLayer.java @@ -0,0 +1,93 @@ +package org.betterx.bclib.api.v3.levelgen.features.placement; + +import org.betterx.bclib.util.BlocksHelper; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.placement.PlacementContext; +import net.minecraft.world.level.levelgen.placement.PlacementModifier; +import net.minecraft.world.level.levelgen.placement.PlacementModifierType; + +import java.util.Optional; +import java.util.stream.Stream; + +public class UnderEveryLayer + extends PlacementModifier { + private static final UnderEveryLayer INSTANCE = new UnderEveryLayer(Optional.empty(), Optional.empty()); + private static final UnderEveryLayer INSTANCE_MIN_4 = new UnderEveryLayer(Optional.of(4), Optional.empty()); + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + Codec.INT.optionalFieldOf("min").forGetter(o -> o.minHeight), + Codec.INT.optionalFieldOf("max").forGetter(o -> o.maxHeight) + ).apply(instance, UnderEveryLayer::new)); + + + private final Optional minHeight; + private final Optional maxHeight; + + protected UnderEveryLayer(Optional minHeight, Optional maxHeight) { + this.minHeight = minHeight; + + this.maxHeight = maxHeight; + } + + public static UnderEveryLayer simple() { + return INSTANCE; + } + + public static UnderEveryLayer min4() { + return INSTANCE_MIN_4; + } + + @Override + public Stream getPositions( + PlacementContext ctx, + RandomSource random, + BlockPos pos + ) { + + Stream.Builder builder = Stream.builder(); + + final int z = pos.getZ(); + final int x = pos.getX(); + final int levelHeight = ctx.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z); + final int minLevelHeight = ctx.getMinBuildHeight(); + int y = maxHeight.map(h -> Math.min(levelHeight, h)).orElse(levelHeight); + final int minHeight = this.minHeight.map(h -> Math.max(minLevelHeight, h)).orElse(minLevelHeight); + + int layerY; + do { + layerY = findUnderGroundYPosition(ctx, x, y, z, minHeight); + if (layerY != Integer.MAX_VALUE) { + builder.add(new BlockPos(x, layerY, z)); + y = layerY - 1; + } + + } while (layerY != Integer.MAX_VALUE); + return builder.build(); + } + + @Override + public PlacementModifierType type() { + return PlacementModifiers.UNDER_EVERY_LAYER; + } + + private static int findUnderGroundYPosition(PlacementContext ctx, int x, int startY, int z, int minHeight) { + BlockPos.MutableBlockPos mPos = new BlockPos.MutableBlockPos(x, startY, z); + BlockState nowState = ctx.getBlockState(mPos); + for (int y = startY; y >= minHeight + 1; --y) { + mPos.setY(y - 1); + BlockState belowState = ctx.getBlockState(mPos); + if (BlocksHelper.isTerrain(nowState) && BlocksHelper.isFreeOrFluid(belowState) && !nowState.is(Blocks.BEDROCK)) { + return mPos.getY(); + } + nowState = belowState; + } + return Integer.MAX_VALUE; + } +} diff --git a/src/main/java/org/betterx/bclib/api/v3/tag/BCLBlockTags.java b/src/main/java/org/betterx/bclib/api/v3/tag/BCLBlockTags.java new file mode 100644 index 00000000..0f30054b --- /dev/null +++ b/src/main/java/org/betterx/bclib/api/v3/tag/BCLBlockTags.java @@ -0,0 +1,38 @@ +package org.betterx.bclib.api.v3.tag; + +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; + +import org.jetbrains.annotations.ApiStatus; + +public class BCLBlockTags { + public static final TagKey BONEMEAL_SOURCE_NETHERRACK = TagManager.BLOCKS.makeTogetherTag( + "bonemeal/source/netherrack" + ); + public static final TagKey BONEMEAL_TARGET_NETHERRACK = TagManager.BLOCKS.makeTogetherTag( + "bonemeal/target/netherrack" + ); + public static final TagKey BONEMEAL_SOURCE_END_STONE = TagManager.BLOCKS.makeTogetherTag( + "bonemeal/source/end_stone" + ); + public static final TagKey BONEMEAL_TARGET_END_STONE = TagManager.BLOCKS.makeTogetherTag( + "bonemeal/target/end_stone" + ); + public static final TagKey BONEMEAL_SOURCE_OBSIDIAN = TagManager.BLOCKS.makeTogetherTag( + "bonemeal/source/obsidian" + ); + public static final TagKey BONEMEAL_TARGET_OBSIDIAN = TagManager.BLOCKS.makeTogetherTag( + "bonemeal/target/obsidian" + ); + + @ApiStatus.Internal + public static void ensureStaticallyLoaded() { + TagManager.BLOCKS.add(BONEMEAL_SOURCE_NETHERRACK, Blocks.WARPED_NYLIUM, Blocks.CRIMSON_NYLIUM); + TagManager.BLOCKS.add(BONEMEAL_TARGET_NETHERRACK, Blocks.NETHERRACK); + TagManager.BLOCKS.add(BONEMEAL_TARGET_END_STONE, Blocks.END_STONE); + TagManager.BLOCKS.add(BONEMEAL_TARGET_OBSIDIAN, Blocks.OBSIDIAN); + } +} diff --git a/src/main/java/org/betterx/bclib/behaviours/BehaviourBuilders.java b/src/main/java/org/betterx/bclib/behaviours/BehaviourBuilders.java new file mode 100644 index 00000000..56338d47 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/BehaviourBuilders.java @@ -0,0 +1,239 @@ +package org.betterx.bclib.behaviours; + +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Rarity; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.material.PushReaction; + +public class BehaviourBuilders { + public static BlockBehaviour.Properties createPlant() { + return createPlant(MapColor.PLANT); + } + + public static BlockBehaviour.Properties createPlant(MapColor color) { + return createWalkablePlant(color).noCollission(); + } + + public static BlockBehaviour.Properties createWalkablePlant() { + return createWalkablePlant(MapColor.PLANT); + } + + public static BlockBehaviour.Properties createWalkablePlant(MapColor color) { + return BlockBehaviour.Properties + .of() + .mapColor(color) + .noOcclusion() + .instabreak() + .sound(SoundType.GRASS) + .pushReaction(PushReaction.DESTROY); + } + + public static BlockBehaviour.Properties createVine() { + return createVine(MapColor.PLANT); + } + + public static Item.Properties createDisc() { + return new Item.Properties().stacksTo(1).rarity(Rarity.RARE); + } + + public static BlockBehaviour.Properties createStaticVine(MapColor color) { + return createPlant(color) + .replaceable() + .noCollission() + .strength(0.2f) + .sound(SoundType.VINE); + } + public static BlockBehaviour.Properties createVine(MapColor color) { + return createStaticVine(color) + .randomTicks(); + } + + public static BlockBehaviour.Properties createGrass(MapColor color) { + return createPlant(color) + .noCollission() + .noOcclusion() + .offsetType(BlockBehaviour.OffsetType.XZ) + .sound(SoundType.GRASS); + } + + public static BlockBehaviour.Properties createSeed(MapColor color) { + return createPlant(color) + .noCollission() + .randomTicks() + .sound(SoundType.HARD_CROP) + .offsetType(BlockBehaviour.OffsetType.XZ); + } + + public static BlockBehaviour.Properties createPlantCover(MapColor color) { + return createPlant(color).forceSolidOn() + .noCollission() + .replaceable() + .strength(0.2f) + .sound(SoundType.GLOW_LICHEN); + } + + public static BlockBehaviour.Properties createWaterPlant() { + return createWaterPlant(MapColor.WATER); + } + + public static BlockBehaviour.Properties createWaterPlant(MapColor color) { + return BlockBehaviour.Properties.of() + .mapColor(color) + .instabreak() + .noOcclusion() + .noCollission() + .sound(SoundType.WET_GRASS) + .offsetType(BlockBehaviour.OffsetType.XZ) + .pushReaction(PushReaction.DESTROY); + + } + + public static BlockBehaviour.Properties createReplaceableWaterPlant() { + return createWaterPlant().replaceable(); + } + + public static BlockBehaviour.Properties createLeaves() { + return createLeaves(MapColor.PLANT, true); + } + public static BlockBehaviour.Properties createStaticLeaves() { + return createStaticLeaves(MapColor.PLANT, true); + } + + public static BlockBehaviour.Properties createStaticLeaves(MapColor color, boolean flammable) { + final BlockBehaviour.Properties p = BlockBehaviour.Properties + .of() + .mapColor(color) + .strength(0.2f) + .noOcclusion() + .isValidSpawn(Blocks::ocelotOrParrot) + .isSuffocating(Blocks::never) + .isViewBlocking(Blocks::never) + .pushReaction(PushReaction.DESTROY) + .isRedstoneConductor(Blocks::never) + .sound(SoundType.GRASS); + if (flammable) { + p.ignitedByLava(); + } + return p; + } + + public static BlockBehaviour.Properties createLeaves(MapColor color, boolean flammable) { + return createStaticLeaves(color, flammable).randomTicks(); + } + + public static BlockBehaviour.Properties createCactus(MapColor color, boolean flammable) { + final BlockBehaviour.Properties p = BlockBehaviour.Properties + .of() + .mapColor(color) + .randomTicks() + .strength(0.4F) + .sound(SoundType.WOOL) + .pushReaction(PushReaction.DESTROY) + .noOcclusion(); + if (flammable) { + p.ignitedByLava(); + } + return p; + } + + public static BlockBehaviour.Properties createMetal() { + return createMetal(MapColor.METAL); + } + + public static BlockBehaviour.Properties createMetal(MapColor color) { + return BlockBehaviour.Properties.of() + .mapColor(color) + .instrument(NoteBlockInstrument.IRON_XYLOPHONE) + .strength(5.0F, 6.0F) + .sound(SoundType.METAL); + } + + public static BlockBehaviour.Properties createStone() { + return createStone(MapColor.STONE); + } + + public static BlockBehaviour.Properties createStone(MapColor color) { + return BlockBehaviour.Properties.of() + .mapColor(color) + .strength(1.5F, 6.0F) + .instrument(NoteBlockInstrument.BASEDRUM); + } + + public static BlockBehaviour.Properties createWood() { + return createWood(MapColor.WOOD, true); + } + + public static BlockBehaviour.Properties createWood(MapColor color, boolean flammable) { + final BlockBehaviour.Properties p = BlockBehaviour.Properties + .of() + .mapColor(color) + .instrument(NoteBlockInstrument.BASS) + .strength(2.0F) + .sound(SoundType.WOOD); + + + if (flammable) { + p.ignitedByLava(); + } + return p; + } + + public static BlockBehaviour.Properties createSign(MapColor color, boolean flammable) { + final BlockBehaviour.Properties p = BlockBehaviour.Properties + .of() + .mapColor(color) + .forceSolidOn() + .instrument(NoteBlockInstrument.BASS) + .noCollission() + .strength(1.0f); + if (flammable) { + p.ignitedByLava(); + } + return p; + } + + public static BlockBehaviour.Properties createWallSign(MapColor color, Block dropBlock, boolean flammable) { + return createSign(color, flammable).dropsLike(dropBlock); + } + + public static BlockBehaviour.Properties createTrapDoor(MapColor color, boolean flammable) { + final BlockBehaviour.Properties p = BlockBehaviour.Properties + .of() + .mapColor(color) + .instrument(NoteBlockInstrument.BASS) + .strength(3.0F) + .noOcclusion() + .isValidSpawn(Blocks::never); + if (flammable) { + p.ignitedByLava(); + } + return p; + } + + public static BlockBehaviour.Properties createGlass() { + return BlockBehaviour.Properties + .of() + .instrument(NoteBlockInstrument.HAT) + .strength(0.3F) + .sound(SoundType.GLASS) + .noOcclusion() + .isValidSpawn(Blocks::never) + .isRedstoneConductor(Blocks::never) + .isSuffocating(Blocks::never) + .isViewBlocking(Blocks::never); + } + + public static BlockBehaviour.Properties createSnow() { + return BlockBehaviour.Properties + .of() + .mapColor(MapColor.SNOW) + .requiresCorrectToolForDrops() + .strength(0.2F) + .sound(SoundType.SNOW); + } +} diff --git a/src/main/java/org/betterx/bclib/behaviours/BehaviourHelper.java b/src/main/java/org/betterx/bclib/behaviours/BehaviourHelper.java new file mode 100644 index 00000000..7051ef09 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/BehaviourHelper.java @@ -0,0 +1,129 @@ +package org.betterx.bclib.behaviours; + +import org.betterx.bclib.behaviours.interfaces.*; + +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.properties.BlockSetType; +import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; + +import java.util.function.BiFunction; +import java.util.function.Function; + +public class BehaviourHelper { + public static boolean isStone(Block source) { + return source instanceof BehaviourStone || source.defaultBlockState() + .instrument() + .equals(NoteBlockInstrument.BASEDRUM); + } + + public static boolean isStone(BlockSetType type) { + return type.soundType() == SoundType.STONE; + } + + public static boolean isMetal(Block source) { + return source instanceof BehaviourMetal; + } + + public static boolean isMetal(BlockSetType type) { + return type.soundType() == SoundType.METAL; + } + + public static boolean isWood(Block source) { + return source instanceof BehaviourWood; + } + + public static boolean isWood(BlockSetType type) { + return type.soundType() == SoundType.WOOD; + } + + public static boolean isObsidian(Block source) { + return source instanceof BehaviourObsidian; + } + + public static boolean isGlass(Block source) { + return source instanceof BehaviourGlass; + } + + public static T from( + Block source, + Function woodSupplier, + Function stoneSupplier + ) { + return from(source, woodSupplier, stoneSupplier, null, null, null); + } + + public static T from( + Block source, + Function woodSupplier, + Function stoneSupplier, + Function metalSupplier + ) { + return from(source, woodSupplier, stoneSupplier, metalSupplier, null, null); + } + + public static T from( + Block source, + Function woodSupplier, + Function stoneSupplier, + Function metalSupplier, + Function obsidianSupplier, + Function glassSupplier + ) { + if (metalSupplier != null && BehaviourHelper.isMetal(source)) + return metalSupplier.apply(source); + if (stoneSupplier != null && BehaviourHelper.isStone(source)) + return stoneSupplier.apply(source); + if (glassSupplier != null && BehaviourHelper.isGlass(source)) + return glassSupplier.apply(source); + if (obsidianSupplier != null && BehaviourHelper.isObsidian(source)) + return obsidianSupplier.apply(source); + + + if (woodSupplier != null) + return woodSupplier.apply(source); + //fallback if no wood supplier is present + if (stoneSupplier != null) + return stoneSupplier.apply(source); + //fallback if neither wood or stone suppliers are present + if (metalSupplier != null) + return metalSupplier.apply(source); + if (glassSupplier != null) + return glassSupplier.apply(source); + if (obsidianSupplier != null) + return obsidianSupplier.apply(source); + return null; + } + + public static T from( + Block source, + BlockSetType type, + BiFunction woodSupplier, + BiFunction stoneSupplier + ) { + return from(source, type, woodSupplier, stoneSupplier, null); + } + + public static T from( + Block source, + BlockSetType type, + BiFunction woodSupplier, + BiFunction stoneSupplier, + BiFunction metalSupplier + ) { + if (metalSupplier != null && BehaviourHelper.isMetal(type)) + return metalSupplier.apply(source, type); + if (stoneSupplier != null && BehaviourHelper.isStone(type)) + return stoneSupplier.apply(source, type); + + if (woodSupplier != null) + return woodSupplier.apply(source, type); + //fallback if no wood supplier is present + if (stoneSupplier != null) + return stoneSupplier.apply(source, type); + //fallback if neither wood or stone suppliers are present + if (metalSupplier != null) + return metalSupplier.apply(source, type); + return null; + } +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourClimable.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourClimable.java new file mode 100644 index 00000000..a013363b --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourClimable.java @@ -0,0 +1,10 @@ +package org.betterx.bclib.behaviours.interfaces; + +/** + * Interface for blocks that can be climbed. + *

+ * {@link org.betterx.bclib.api.v2.PostInitAPI} will add the {@link net.minecraft.tags.BlockTags#CLIMBABLE} tag to all blocks that + * implement this interface. + */ +public interface BehaviourClimable extends BlockBehaviour{ +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourClimableVine.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourClimableVine.java new file mode 100644 index 00000000..668c6af6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourClimableVine.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.behaviours.interfaces; + +/** + * Interface for blocks that can be climbed and are vines. + *

+ * This will combine the {@link BehaviourClimable} behaviours with all {@link BehaviourVine} behaviours. + */ +public interface BehaviourClimableVine extends BehaviourClimable, BehaviourVine { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourCompostable.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourCompostable.java new file mode 100644 index 00000000..132b583e --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourCompostable.java @@ -0,0 +1,22 @@ +package org.betterx.bclib.behaviours.interfaces; + +/** + * Interface for blocks that can be composted. + *

+ * {@link org.betterx.bclib.api.v2.PostInitAPI} will add the + * {@link org.betterx.worlds.together.tag.v3.CommonItemTags#COMPOSTABLE} tag to the items of all blocks that + * implement this interface. It will also register the Block with the {@link org.betterx.bclib.api.v2.ComposterAPI} + */ +public interface BehaviourCompostable extends BlockBehaviour{ + + /** + * The chance that this block will be composted. + *

+ * The default value is 0.1f. + * + * @return The chance that this block will be composted. + */ + default float compostingChance() { + return 0.1f; + } +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourExplosionResistant.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourExplosionResistant.java new file mode 100644 index 00000000..02fc657d --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourExplosionResistant.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.behaviours.interfaces; + +public interface BehaviourExplosionResistant extends BlockBehaviour{ +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourGlass.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourGlass.java new file mode 100644 index 00000000..cba52ea5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourGlass.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineablePickaxe; + +public interface BehaviourGlass extends AddMineablePickaxe, BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourIce.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourIce.java new file mode 100644 index 00000000..8d4439ac --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourIce.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineablePickaxe; + +public interface BehaviourIce extends AddMineablePickaxe, BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourImmobile.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourImmobile.java new file mode 100644 index 00000000..70694c04 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourImmobile.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.behaviours.interfaces; + +public interface BehaviourImmobile extends BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourLeaves.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourLeaves.java new file mode 100644 index 00000000..1812aa7b --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourLeaves.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableHoe; +import org.betterx.bclib.interfaces.tools.AddMineableShears; + +/** + * Interface for leaves blocks. + *

+ * Adds composting chance, mineable with shears and hoe. + */ +public interface BehaviourLeaves extends AddMineableShears, AddMineableHoe, BehaviourCompostable { + @Override + default float compostingChance() { + return 0.3f; + } +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourMetal.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourMetal.java new file mode 100644 index 00000000..188e1ce2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourMetal.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineablePickaxe; + +public interface BehaviourMetal extends AddMineablePickaxe, BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourObsidian.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourObsidian.java new file mode 100644 index 00000000..9ffa09ef --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourObsidian.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineablePickaxe; + +public interface BehaviourObsidian extends AddMineablePickaxe, BehaviourImmobile { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourObsidianPortalFrame.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourObsidianPortalFrame.java new file mode 100644 index 00000000..d96042ea --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourObsidianPortalFrame.java @@ -0,0 +1,5 @@ +package org.betterx.bclib.behaviours.interfaces; + +public interface BehaviourObsidianPortalFrame extends BehaviourObsidian, BehaviourPortalFrame { + +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourOre.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourOre.java new file mode 100644 index 00000000..a4febff6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourOre.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineablePickaxe; + +public interface BehaviourOre extends AddMineablePickaxe, BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourPlant.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourPlant.java new file mode 100644 index 00000000..24e6084f --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourPlant.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableHoe; + +public interface BehaviourPlant extends AddMineableHoe, BehaviourCompostable, BehaviourPlantLike { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourPlantLike.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourPlantLike.java new file mode 100644 index 00000000..b56e88e2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourPlantLike.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.behaviours.interfaces; + +public interface BehaviourPlantLike extends BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourPortalFrame.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourPortalFrame.java new file mode 100644 index 00000000..17411b76 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourPortalFrame.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.behaviours.interfaces; + +public interface BehaviourPortalFrame extends BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSand.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSand.java new file mode 100644 index 00000000..de6f6fb1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSand.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableShovel; + +public interface BehaviourSand extends AddMineableShovel, BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSapling.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSapling.java new file mode 100644 index 00000000..202e48f0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSapling.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableHoe; + +public interface BehaviourSapling extends AddMineableHoe, BehaviourCompostable, BehaviourPlantLike, BehaviourSaplingLike { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSaplingLike.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSaplingLike.java new file mode 100644 index 00000000..2baf97ba --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSaplingLike.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.behaviours.interfaces; + +public interface BehaviourSaplingLike extends BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSeed.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSeed.java new file mode 100644 index 00000000..d3e4be00 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSeed.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableHoe; + +public interface BehaviourSeed extends AddMineableHoe, BehaviourCompostable, BehaviourPlantLike, BehaviourSeedLike { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSeedLike.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSeedLike.java new file mode 100644 index 00000000..bf0895b1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSeedLike.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.behaviours.interfaces; + +public interface BehaviourSeedLike extends BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourShearablePlant.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourShearablePlant.java new file mode 100644 index 00000000..74fcae79 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourShearablePlant.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableShears; + +public interface BehaviourShearablePlant extends AddMineableShears, BehaviourPlantLike, BehaviourCompostable { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSnow.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSnow.java new file mode 100644 index 00000000..470bce90 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourSnow.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableShovel; + +public interface BehaviourSnow extends AddMineableShovel, BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourStone.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourStone.java new file mode 100644 index 00000000..119d9965 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourStone.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineablePickaxe; + +public interface BehaviourStone extends AddMineablePickaxe, BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourVine.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourVine.java new file mode 100644 index 00000000..35f336f5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourVine.java @@ -0,0 +1,12 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableHoe; +import org.betterx.bclib.interfaces.tools.AddMineableShears; + +/** + * Interface for blocks that are vines. + *

+ * This will add the {@link AddMineableShears}, {@link AddMineableHoe} and {@link BehaviourCompostable} behaviours. + */ +public interface BehaviourVine extends AddMineableShears, AddMineableHoe, BehaviourPlantLike, BehaviourCompostable, BehaviourClimable { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlant.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlant.java new file mode 100644 index 00000000..5edb07da --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlant.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableHoe; + +public interface BehaviourWaterPlant extends AddMineableHoe, BehaviourWaterPlantLike, BehaviourCompostable { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlantLike.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlantLike.java new file mode 100644 index 00000000..988d95d9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlantLike.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.behaviours.interfaces; + +public interface BehaviourWaterPlantLike extends BehaviourPlantLike { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlantSapling.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlantSapling.java new file mode 100644 index 00000000..ee00edf9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlantSapling.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableHoe; + +public interface BehaviourWaterPlantSapling extends AddMineableHoe, BehaviourCompostable, BehaviourWaterPlantLike, BehaviourSaplingLike { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlantSeed.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlantSeed.java new file mode 100644 index 00000000..aec0f51a --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWaterPlantSeed.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableHoe; + +public interface BehaviourWaterPlantSeed extends AddMineableHoe, BehaviourCompostable, BehaviourWaterPlantLike, BehaviourSeedLike { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWood.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWood.java new file mode 100644 index 00000000..fd2c78b3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BehaviourWood.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.behaviours.interfaces; + +import org.betterx.bclib.interfaces.tools.AddMineableAxe; + +public interface BehaviourWood extends AddMineableAxe, BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/behaviours/interfaces/BlockBehaviour.java b/src/main/java/org/betterx/bclib/behaviours/interfaces/BlockBehaviour.java new file mode 100644 index 00000000..7d8743d5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/behaviours/interfaces/BlockBehaviour.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.behaviours.interfaces; + +public interface BlockBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/blockentities/BaseBarrelBlockEntity.java b/src/main/java/org/betterx/bclib/blockentities/BaseBarrelBlockEntity.java new file mode 100644 index 00000000..1162c7bc --- /dev/null +++ b/src/main/java/org/betterx/bclib/blockentities/BaseBarrelBlockEntity.java @@ -0,0 +1,184 @@ +package org.betterx.bclib.blockentities; + +import org.betterx.bclib.registry.BaseBlockEntities; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.NonNullList; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ChestMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BarrelBlock; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.ContainerOpenersCounter; +import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class BaseBarrelBlockEntity extends RandomizableContainerBlockEntity { + private NonNullList inventory; + private ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() { + + @Override + protected void onOpen(Level level, BlockPos blockPos, BlockState blockState) { + BaseBarrelBlockEntity.this.playSound(blockState, SoundEvents.BARREL_OPEN); + BaseBarrelBlockEntity.this.updateBlockState(blockState, true); + } + + @Override + protected void onClose(Level level, BlockPos blockPos, BlockState blockState) { + BaseBarrelBlockEntity.this.playSound(blockState, SoundEvents.BARREL_CLOSE); + BaseBarrelBlockEntity.this.updateBlockState(blockState, false); + } + + @Override + protected void openerCountChanged(Level level, BlockPos blockPos, BlockState blockState, int i, int j) { + } + + @Override + protected boolean isOwnContainer(Player player) { + if (player.containerMenu instanceof ChestMenu) { + Container container = ((ChestMenu) player.containerMenu).getContainer(); + return container == BaseBarrelBlockEntity.this; + } + return false; + } + }; + + private BaseBarrelBlockEntity(BlockEntityType type, BlockPos blockPos, BlockState blockState) { + super(type, blockPos, blockState); + this.inventory = NonNullList.withSize(27, ItemStack.EMPTY); + } + + public BaseBarrelBlockEntity(BlockPos blockPos, BlockState blockState) { + this(BaseBlockEntities.BARREL, blockPos, blockState); + } + + @Override + public void saveAdditional(CompoundTag tag) { + super.saveAdditional(tag); + if (!this.trySaveLootTable(tag)) { + ContainerHelper.saveAllItems(tag, this.inventory); + } + } + + @Override + public void load(CompoundTag tag) { + super.load(tag); + this.inventory = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); + if (!this.tryLoadLootTable(tag)) { + ContainerHelper.loadAllItems(tag, this.inventory); + } + } + + @Override + public int getContainerSize() { + return 27; + } + + @Override + protected NonNullList getItems() { + return this.inventory; + } + + @Override + protected void setItems(NonNullList list) { + this.inventory = list; + } + + @Override + protected Component getDefaultName() { + return Component.translatable("container.barrel"); + } + + @Override + protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { + return ChestMenu.threeRows(syncId, playerInventory, this); + } + + public void startOpen(Player player) { + if (!this.remove && !player.isSpectator()) { + this.openersCounter.incrementOpeners(player, this.getLevel(), this.getBlockPos(), this.getBlockState()); + } +// if (!player.isSpectator()) { +// if (viewerCount < 0) { +// viewerCount = 0; +// } +// +// ++viewerCount; +// BlockState blockState = this.getBlockState(); +// if (!blockState.getValue(BarrelBlock.OPEN)) { +// playSound(blockState, SoundEvents.BARREL_OPEN); +// updateBlockState(blockState, true); +// } +// +// if (level != null) { +// scheduleUpdate(); +// } +// } + } + + private void scheduleUpdate() { + level.scheduleTick(getBlockPos(), getBlockState().getBlock(), 5); + } + + public void tick() { +// if (level != null) { +// viewerCount = ChestBlockEntity.getOpenCount(level, worldPosition); +// if (viewerCount > 0) { +// scheduleUpdate(); +// } else { +// BlockState blockState = getBlockState(); +// if (!(blockState.getBlock() instanceof BaseBarrelBlock)) { +// setRemoved(); +// return; +// } +// if (blockState.getValue(BarrelBlock.OPEN)) { +// playSound(blockState, SoundEvents.BARREL_CLOSE); +// updateBlockState(blockState, false); +// } +// } +// } + } + + @Override + public void stopOpen(Player player) { + if (!this.remove && !player.isSpectator()) { + this.openersCounter.decrementOpeners(player, this.getLevel(), this.getBlockPos(), this.getBlockState()); + } + } + + public void recheckOpen() { + if (!this.remove) { + this.openersCounter.recheckOpeners(this.getLevel(), this.getBlockPos(), this.getBlockState()); + } + } + + private void updateBlockState(BlockState state, boolean open) { + if (level != null) { + level.setBlock(this.getBlockPos(), state.setValue(BarrelBlock.OPEN, open), 3); + } + } + + private void playSound(BlockState blockState, SoundEvent soundEvent) { + if (level != null) { + Vec3i facingDir = blockState.getValue(BarrelBlock.FACING).getNormal(); + double x = this.worldPosition.getX() + 0.5D + facingDir.getX() / 2.0D; + double y = this.worldPosition.getY() + 0.5D + facingDir.getY() / 2.0D; + double z = this.worldPosition.getZ() + 0.5D + facingDir.getZ() / 2.0D; + level.playSound( + null, x, y, z, + soundEvent, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F + ); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/blockentities/BaseChestBlockEntity.java b/src/main/java/org/betterx/bclib/blockentities/BaseChestBlockEntity.java new file mode 100644 index 00000000..258b113c --- /dev/null +++ b/src/main/java/org/betterx/bclib/blockentities/BaseChestBlockEntity.java @@ -0,0 +1,13 @@ +package org.betterx.bclib.blockentities; + +import org.betterx.bclib.registry.BaseBlockEntities; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class BaseChestBlockEntity extends ChestBlockEntity { + public BaseChestBlockEntity(BlockPos blockPos, BlockState blockState) { + super(BaseBlockEntities.CHEST, blockPos, blockState); + } +} diff --git a/src/main/java/org/betterx/bclib/blockentities/BaseFurnaceBlockEntity.java b/src/main/java/org/betterx/bclib/blockentities/BaseFurnaceBlockEntity.java new file mode 100644 index 00000000..53e964e2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blockentities/BaseFurnaceBlockEntity.java @@ -0,0 +1,26 @@ +package org.betterx.bclib.blockentities; + +import org.betterx.bclib.registry.BaseBlockEntities; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.FurnaceMenu; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class BaseFurnaceBlockEntity extends AbstractFurnaceBlockEntity { + public BaseFurnaceBlockEntity(BlockPos blockPos, BlockState blockState) { + super(BaseBlockEntities.FURNACE, blockPos, blockState, RecipeType.SMELTING); + } + + protected Component getDefaultName() { + return Component.translatable("container.furnace"); + } + + protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { + return new FurnaceMenu(syncId, playerInventory, this, this.dataAccess); + } +} diff --git a/src/main/java/org/betterx/bclib/blockentities/DynamicBlockEntityType.java b/src/main/java/org/betterx/bclib/blockentities/DynamicBlockEntityType.java new file mode 100644 index 00000000..93a7afa9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blockentities/DynamicBlockEntityType.java @@ -0,0 +1,44 @@ +package org.betterx.bclib.blockentities; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +import com.google.common.collect.Sets; + +import java.util.Collections; +import java.util.Set; +import org.jetbrains.annotations.Nullable; + +public class DynamicBlockEntityType extends BlockEntityType { + + private final Set validBlocks = Sets.newHashSet(); + private final BlockEntitySupplier factory; + + public DynamicBlockEntityType(BlockEntitySupplier supplier) { + super(null, Collections.emptySet(), null); + this.factory = supplier; + } + + @Override + @Nullable + public T create(BlockPos blockPos, BlockState blockState) { + return factory.create(blockPos, blockState); + } + + @Override + public boolean isValid(BlockState blockState) { + return validBlocks.contains(blockState.getBlock()); + } + + public void registerBlock(Block block) { + validBlocks.add(block); + } + + @FunctionalInterface + public interface BlockEntitySupplier { + T create(BlockPos blockPos, BlockState blockState); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseAnvilBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseAnvilBlock.java new file mode 100644 index 00000000..99711c62 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseAnvilBlock.java @@ -0,0 +1,157 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.CustomItemProvider; +import org.betterx.bclib.interfaces.tools.AddMineablePickaxe; +import org.betterx.bclib.items.BaseAnvilItem; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.LootUtil; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.AnvilBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LevelEvent; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseAnvilBlock extends AnvilBlock implements AddMineablePickaxe, BlockModelProvider, CustomItemProvider { + public static final IntegerProperty DESTRUCTION = BlockProperties.DESTRUCTION; + public IntegerProperty durability; + + public BaseAnvilBlock(MapColor color) { + this(Properties.copy(Blocks.ANVIL).mapColor(color)); + } + + public BaseAnvilBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + if (getMaxDurability() != 3) { + durability = IntegerProperty.create("durability", 0, getMaxDurability()); + } else { + durability = BlockProperties.DEFAULT_ANVIL_DURABILITY; + } + builder.add(DESTRUCTION, durability); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + return getBlockModel(blockId, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + int destruction = blockState.getValue(DESTRUCTION); + String name = blockId.getPath(); + Map textures = Maps.newHashMap(); + textures.put("%modid%", blockId.getNamespace()); + textures.put("%anvil%", name); + textures.put("%top%", name + "_top_" + destruction); + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_ANVIL, textures); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + int destruction = blockState.getValue(DESTRUCTION); + String modId = stateId.getNamespace(); + String modelId = "block/" + stateId.getPath() + "_top_" + destruction; + ResourceLocation modelLocation = new ResourceLocation(modId, modelId); + registerBlockModel(stateId, modelLocation, blockState, modelCache); + return ModelsHelper.createFacingModel(modelLocation, blockState.getValue(FACING), false, false); + } + + @Override + public BlockItem getCustomItem(ResourceLocation blockID, Item.Properties settings) { + return new BaseAnvilItem(this, settings); + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootParams.Builder builder) { + int destruction = state.getValue(DESTRUCTION); + int durability = state.getValue(getDurabilityProp()); + int value = destruction * getMaxDurability() + durability; + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (LootUtil.isCorrectTool(this, state, tool)) { + ItemStack itemStack = new ItemStack(this); + itemStack.getOrCreateTag().putInt(BaseAnvilItem.DESTRUCTION, value); + return Lists.newArrayList(itemStack); + } + return Collections.emptyList(); + } + + public IntegerProperty getDurabilityProp() { + return durability; + } + + public int getMaxDurability() { + return 5; + } + + public BlockState damageAnvilUse(BlockState state, RandomSource random) { + IntegerProperty durability = getDurabilityProp(); + int value = state.getValue(durability); + if (value < getMaxDurability()) { + return state.setValue(durability, value + 1); + } + value = state.getValue(DESTRUCTION); + return value < 2 ? state.setValue(DESTRUCTION, value + 1).setValue(durability, 0) : null; + } + + public BlockState damageAnvilFall(BlockState state) { + int destruction = state.getValue(DESTRUCTION); + return destruction < 2 ? state.setValue(DESTRUCTION, destruction + 1) : null; + } + + @ApiStatus.Internal + public static void destroyWhenNull(Level level, BlockPos blockPos, BlockState damaged) { + if (damaged == null) { + level.removeBlock(blockPos, false); + level.levelEvent(LevelEvent.SOUND_ANVIL_BROKEN, blockPos, 0); + } else { + level.setBlock(blockPos, damaged, BlocksHelper.FLAG_SEND_CLIENT_CHANGES); + level.levelEvent(LevelEvent.SOUND_ANVIL_USED, blockPos, 0); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseAttachedBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseAttachedBlock.java new file mode 100644 index 00000000..1ef4906e --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseAttachedBlock.java @@ -0,0 +1,111 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourGlass; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.util.BlocksHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.DirectionProperty; + +@SuppressWarnings("deprecation") +public abstract class BaseAttachedBlock extends BaseBlockNotFull { + public static final DirectionProperty FACING = BlockStateProperties.FACING; + + protected BaseAttachedBlock(Properties settings) { + super(settings); + registerDefaultState(defaultBlockState().setValue(FACING, Direction.UP)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(FACING); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { + BlockState blockState = defaultBlockState(); + LevelReader worldView = ctx.getLevel(); + BlockPos blockPos = ctx.getClickedPos(); + Direction[] directions = ctx.getNearestLookingDirections(); + for (Direction direction : directions) { + Direction direction2 = direction.getOpposite(); + blockState = blockState.setValue(FACING, direction2); + if (blockState.canSurvive(worldView, blockPos)) { + return blockState; + } + } + return null; + } + + @Override + public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { + Direction direction = state.getValue(FACING); + BlockPos blockPos = pos.relative(direction.getOpposite()); + return canSupportCenter(world, blockPos, direction) || world.getBlockState(blockPos).is(BlockTags.LEAVES); + } + + @Override + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (!canSurvive(state, world, pos)) { + return Blocks.AIR.defaultBlockState(); + } else { + return state; + } + } + + + @Override + public BlockState rotate(BlockState state, Rotation rotation) { + return BlocksHelper.rotateHorizontal(state, rotation, FACING); + } + + @Override + public BlockState mirror(BlockState state, Mirror mirror) { + return BlocksHelper.mirrorHorizontal(state, mirror, FACING); + } + + public static class Wood extends BaseAttachedBlock implements BehaviourWood { + public Wood(Properties settings) { + super(settings); + } + } + + public static class Stone extends BaseAttachedBlock implements BehaviourStone { + public Stone(Properties settings) { + super(settings); + } + } + + public static class Metal extends BaseAttachedBlock implements BehaviourMetal { + public Metal(Properties settings) { + super(settings); + } + } + + public static class Glass extends BaseAttachedBlock implements BehaviourGlass { + public Glass(Properties settings) { + super(settings); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseBarkBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseBarkBlock.java new file mode 100644 index 00000000..7a030eed --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBarkBlock.java @@ -0,0 +1,54 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.List; +import java.util.Optional; + +public abstract class BaseBarkBlock extends BaseRotatedPillarBlock { + protected BaseBarkBlock(Properties settings) { + super(settings); + } + + @Override + protected Optional createBlockPattern(ResourceLocation blockId) { + blockId = BuiltInRegistries.BLOCK.getKey(this); + return PatternsHelper.createJson(BasePatterns.BLOCK_BASE, replacePath(blockId)); + } + + private ResourceLocation replacePath(ResourceLocation blockId) { + String newPath = blockId.getPath().replace("_bark", "_log_side"); + return new ResourceLocation(blockId.getNamespace(), newPath); + } + + public static class Wood extends BaseBarkBlock implements BehaviourWood, TagProvider { + private final boolean flammable; + + public Wood(Properties settings, boolean flammable) { + super(flammable ? settings.ignitedByLava() : settings); + this.flammable = flammable; + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.LOGS); + itemTags.add(ItemTags.LOGS); + + if (flammable) { + blockTags.add(BlockTags.LOGS_THAT_BURN); + itemTags.add(ItemTags.LOGS_THAT_BURN); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseBarrelBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseBarrelBlock.java new file mode 100644 index 00000000..66c5fe78 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBarrelBlock.java @@ -0,0 +1,187 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.blockentities.BaseBarrelBlockEntity; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.bclib.registry.BaseBlockEntities; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; +import org.betterx.worlds.together.tag.v3.CommonItemTags; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.stats.Stats; +import net.minecraft.tags.TagKey; +import net.minecraft.util.RandomSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.monster.piglin.PiglinAi; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BarrelBlock; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseBarrelBlock extends BarrelBlock implements BlockModelProvider, TagProvider, DropSelfLootProvider { + BaseBarrelBlock(Block source) { + this(Properties.copy(source).noOcclusion()); + } + + BaseBarrelBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return BaseBlockEntities.BARREL.create(blockPos, blockState); + } + + @Override + public InteractionResult use( + BlockState state, + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hit + ) { + if (level.isClientSide) { + return InteractionResult.SUCCESS; + } else { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof BaseBarrelBlockEntity) { + player.openMenu((BaseBarrelBlockEntity) blockEntity); + player.awardStat(Stats.OPEN_BARREL); + PiglinAi.angerNearbyPiglins(player, true); + } + + return InteractionResult.CONSUME; + } + } + + @Override + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof BaseBarrelBlockEntity) { + ((BaseBarrelBlockEntity) blockEntity).recheckOpen(); + } + } + + @Override + public RenderShape getRenderShape(BlockState state) { + return RenderShape.MODEL; + } + + @Override + public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) { + if (itemStack.hasCustomHoverName()) { + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof BaseBarrelBlockEntity) { + ((BaseBarrelBlockEntity) blockEntity).setCustomName(itemStack.getHoverName()); + } + } + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + return getBlockModel(blockId, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + Optional pattern; + if (blockState.getValue(OPEN)) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARREL_OPEN, blockId); + } else { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOTTOM_TOP, blockId); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + String open = blockState.getValue(OPEN) ? "_open" : ""; + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + open); + registerBlockModel(stateId, modelId, blockState, modelCache); + Direction facing = blockState.getValue(FACING); + BlockModelRotation rotation = BlockModelRotation.X0_Y0; + switch (facing) { + case NORTH: + rotation = BlockModelRotation.X90_Y0; + break; + case EAST: + rotation = BlockModelRotation.X90_Y90; + break; + case SOUTH: + rotation = BlockModelRotation.X90_Y180; + break; + case WEST: + rotation = BlockModelRotation.X90_Y270; + break; + case DOWN: + rotation = BlockModelRotation.X180_Y0; + break; + default: + break; + } + return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(CommonBlockTags.BARREL); + itemTags.add(CommonItemTags.BARREL); + } + + public static class Wood extends BaseBarrelBlock implements BehaviourWood { + public Wood(Block source) { + super(source); + } + + public Wood(Properties properties) { + super(properties); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(CommonBlockTags.WOODEN_BARREL); + itemTags.add(CommonItemTags.WOODEN_BARREL); + } + } + + public static BaseBarrelBlock from(Block source) { + return new Wood(source); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseBarsBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseBarsBlock.java new file mode 100644 index 00000000..efffcbc4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBarsBlock.java @@ -0,0 +1,141 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.RenderLayerProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.IronBarsBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseBarsBlock extends IronBarsBlock implements BlockModelProvider, RenderLayerProvider, BehaviourMetal { + public BaseBarsBlock(Block source) { + this(Properties.copy(source).strength(5.0F, 6.0F).noOcclusion()); + } + + public BaseBarsBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootParams.Builder builder) { + return Collections.singletonList(new ItemStack(this)); + } + + public Optional getModelString(String block) { + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(this); + if (block.contains("item")) { + return PatternsHelper.createJson(BasePatterns.ITEM_BLOCK, blockId); + } + if (block.contains("post")) { + return PatternsHelper.createJson(BasePatterns.BLOCK_BARS_POST, blockId); + } else { + return PatternsHelper.createJson(BasePatterns.BLOCK_BARS_SIDE, blockId); + } + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createBlockItem(resourceLocation); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + ResourceLocation thisId = BuiltInRegistries.BLOCK.getKey(this); + String path = blockId.getPath(); + Optional pattern = Optional.empty(); + if (path.endsWith("_post")) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARS_POST, thisId); + } + if (path.endsWith("_side")) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARS_SIDE, thisId); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_post"); + ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_side"); + registerBlockModel(postId, postId, blockState, modelCache); + registerBlockModel(sideId, sideId, blockState, modelCache); + + ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition); + builder.part(postId) + .setCondition(state -> !state.getValue(NORTH) && !state.getValue(EAST) && !state.getValue(SOUTH) && !state + .getValue(WEST)) + .add(); + builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add(); + builder.part(sideId) + .setCondition(state -> state.getValue(EAST)) + .setTransformation(BlockModelRotation.X0_Y90.getRotation()) + .setUVLock(true) + .add(); + builder.part(sideId) + .setCondition(state -> state.getValue(SOUTH)) + .setTransformation(BlockModelRotation.X0_Y180.getRotation()) + .setUVLock(true) + .add(); + builder.part(sideId) + .setCondition(state -> state.getValue(WEST)) + .setTransformation(BlockModelRotation.X0_Y270.getRotation()) + .setUVLock(true) + .add(); + + return builder.build(); + } + + @Environment(EnvType.CLIENT) + public boolean skipRendering(BlockState state, BlockState stateFrom, Direction direction) { + if (direction.getAxis().isVertical() && stateFrom.getBlock() == this && !stateFrom.equals(state)) { + return false; + } + return super.skipRendering(state, stateFrom, direction); + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + public static class Metal extends BaseBarsBlock implements BehaviourMetal { + + public Metal(Block source) { + super(source); + } + + public Metal(Properties properties) { + super(properties); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseBlock.java new file mode 100644 index 00000000..202c56ac --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBlock.java @@ -0,0 +1,97 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.interfaces.BlockModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.storage.loot.LootParams; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +/** + * Base class for a default Block. + *

+ * This Block-Type will: + *

    + *
  • Drop itself
  • + *
  • Automatically create an Item-Model from the Block-Model
  • + *
+ */ +public class BaseBlock extends Block implements BlockModelProvider { + /** + * Creates a new Block with the passed properties + * + * @param settings The properties of the Block. + */ + public BaseBlock(Properties settings) { + super(settings); + } + + /** + * {@inheritDoc} + *

+ * This implementation will drop the Block itself + */ + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootParams.Builder builder) { + return Collections.singletonList(new ItemStack(this)); + } + + /** + * {@inheritDoc} + *

+ * This implementation will load the Block-Model and return it as the Item-Model + */ + @Override + public BlockModel getItemModel(ResourceLocation blockId) { + return getBlockModel(blockId, defaultBlockState()); + } + + /** + * This method is used internally. + *

+ * It is called from Block-Contructors, to allow the augmentation of the blocks + * preset properties. + *

+ * For example in {@link BaseLeavesBlock#BaseLeavesBlock(Block, MapColor, Consumer)} + * + * @param customizeProperties A {@link Consumer} to call with the preset properties + * @param settings The properties as created by the Block + * @return The reconfigured {@code settings} + */ + static Properties acceptAndReturn( + Consumer customizeProperties, + Properties settings + ) { + customizeProperties.accept(settings); + return settings; + } + + public static class Wood extends BaseBlock implements BehaviourWood { + public Wood(Properties settings) { + super(settings); + } + } + + public static class Stone extends BaseBlock implements BehaviourStone { + public Stone(Properties settings) { + super(settings); + } + } + + public static class Metal extends BaseBlock implements BehaviourMetal { + public Metal(Properties settings) { + super(settings); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/blocks/BaseBlockNotFull.java b/src/main/java/org/betterx/bclib/blocks/BaseBlockNotFull.java new file mode 100644 index 00000000..266a7081 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBlockNotFull.java @@ -0,0 +1,46 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; + +public class BaseBlockNotFull extends BaseBlock { + public BaseBlockNotFull(Properties settings) { + super(settings); + } + + public boolean canSuffocate(BlockState state, BlockGetter view, BlockPos pos) { + return false; + } + + public boolean isSimpleFullBlock(BlockState state, BlockGetter view, BlockPos pos) { + return false; + } + + public boolean allowsSpawning(BlockState state, BlockGetter view, BlockPos pos, EntityType type) { + return false; + } + + public static class Wood extends BaseBlockNotFull implements BehaviourWood { + public Wood(Properties settings) { + super(settings); + } + } + + public static class Stone extends BaseBlockNotFull implements BehaviourStone { + public Stone(Properties settings) { + super(settings); + } + } + + public static class Metal extends BaseBlockNotFull implements BehaviourMetal { + public Metal(Properties settings) { + super(settings); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseBlockWithEntity.java b/src/main/java/org/betterx/bclib/blocks/BaseBlockWithEntity.java new file mode 100644 index 00000000..a29cbc3c --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBlockWithEntity.java @@ -0,0 +1,36 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; + +import java.util.Collections; +import java.util.List; + +public abstract class BaseBlockWithEntity extends BaseEntityBlock { + protected BaseBlockWithEntity(Properties settings) { + super(settings); + } + + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return null; + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootParams.Builder builder) { + return Collections.singletonList(new ItemStack(this)); + } + + public static class Stone extends BaseBlockWithEntity implements BehaviourStone { + public Stone(Properties settings) { + super(settings); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseBookshelfBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseBookshelfBlock.java new file mode 100644 index 00000000..5c051787 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseBookshelfBlock.java @@ -0,0 +1,99 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseBookshelfBlock extends BaseBlock implements TagProvider { + protected BaseBookshelfBlock(Block source) { + this(Properties.copy(source)); + } + + protected BaseBookshelfBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null) { + int silk = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool); + if (silk > 0) { + return Collections.singletonList(new ItemStack(this)); + } + } + return Collections.singletonList(new ItemStack(Items.BOOK, 3)); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOOKSHELF, replacePath(blockId)); + return ModelsHelper.fromPattern(pattern); + } + + protected ResourceLocation replacePath(ResourceLocation blockId) { + String newPath = blockId.getPath().replace("_bookshelf", ""); + return new ResourceLocation(blockId.getNamespace(), newPath); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(CommonBlockTags.BOOKSHELVES); + } + + public static class Wood extends BaseBookshelfBlock implements BehaviourWood { + public Wood(Block source) { + super(source); + } + + public Wood(Properties properties) { + super(properties); + } + } + + public static class VanillaWood extends Wood { + public VanillaWood(Block source) { + super(source); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + Optional pattern = PatternsHelper.createJson( + BasePatterns.VANILLA_WOOD_BOOKSHELF, + replacePath(blockId) + ); + return ModelsHelper.fromPattern(pattern); + } + } + + public static BaseBookshelfBlock from(Block source) { + return new BaseBookshelfBlock.Wood(source); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseButtonBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseButtonBlock.java new file mode 100644 index 00000000..c3eefae8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseButtonBlock.java @@ -0,0 +1,167 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.ButtonBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.AttachFace; +import net.minecraft.world.level.block.state.properties.BlockSetType; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseButtonBlock extends ButtonBlock implements BlockModelProvider, TagProvider, DropSelfLootProvider { + private final Block parent; + + protected BaseButtonBlock(Block parent, Properties properties, boolean sensitive, BlockSetType type) { + this(parent, properties, 30, sensitive, type); + } + + protected BaseButtonBlock( + Block parent, + Properties properties, + int ticksToStayPressed, + boolean sensitive, + BlockSetType type + ) { + super( + properties.noCollission(), type, ticksToStayPressed, sensitive + ); + this.parent = parent; + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_BUTTON, parentId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + Optional pattern = blockState.getValue(POWERED) + ? PatternsHelper.createJson( + BasePatterns.BLOCK_BUTTON_PRESSED, + parentId + ) + : PatternsHelper.createJson(BasePatterns.BLOCK_BUTTON, parentId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + String powered = blockState.getValue(POWERED) ? "_powered" : ""; + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + powered); + registerBlockModel(stateId, modelId, blockState, modelCache); + AttachFace face = blockState.getValue(FACE); + boolean isCeiling = face == AttachFace.CEILING; + int x = 0, y = 0; + switch (face) { + case CEILING: + x = 180; + break; + case WALL: + x = 90; + break; + default: + break; + } + switch (blockState.getValue(FACING)) { + case NORTH: + if (isCeiling) { + y = 180; + } + break; + case EAST: + y = isCeiling ? 270 : 90; + break; + case SOUTH: + if (!isCeiling) { + y = 180; + } + break; + case WEST: + y = isCeiling ? 90 : 270; + break; + default: + break; + } + BlockModelRotation rotation = BlockModelRotation.by(x, y); + return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), face == AttachFace.WALL); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.BUTTONS); + itemTags.add(ItemTags.BUTTONS); + } + + public static class Metal extends BaseButtonBlock implements BehaviourMetal { + public Metal(Block source, BlockSetType type) { + super(source, Properties.copy(source).noOcclusion(), false, type); + } + } + + public static class Stone extends BaseButtonBlock implements BehaviourStone { + public Stone(Block source, BlockSetType type) { + super(source, Properties.copy(source).noOcclusion(), false, type); + } + + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(BlockTags.STONE_BUTTONS); + itemTags.add(ItemTags.STONE_BUTTONS); + } + } + + public static class Wood extends BaseButtonBlock implements BehaviourWood { + public Wood(Block source, BlockSetType type) { + super(source, Properties.copy(source).strength(0.5F, 0.5F).noOcclusion(), true, type); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(BlockTags.WOODEN_BUTTONS); + itemTags.add(ItemTags.WOODEN_BUTTONS); + } + } + + public static BaseButtonBlock from(Block source, BlockSetType type) { + return BehaviourHelper.from(source, type, Wood::new, Stone::new, Metal::new); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseChainBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseChainBlock.java new file mode 100644 index 00000000..95eaf993 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseChainBlock.java @@ -0,0 +1,80 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.RenderLayerProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.ChainBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.MapColor; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseChainBlock extends ChainBlock implements BlockModelProvider, RenderLayerProvider, DropSelfLootProvider { + public BaseChainBlock(MapColor color) { + this(Properties.copy(Blocks.CHAIN).mapColor(color)); + } + + public BaseChainBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + return ModelsHelper.createItemModel(blockId); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CHAIN, blockId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + Direction.Axis axis = blockState.getValue(AXIS); + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createRotatedModel(modelId, axis); + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + public static class Metal extends BaseChainBlock implements BehaviourMetal { + + public Metal(MapColor color) { + super(color); + } + + public Metal(Properties properties) { + super(properties); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseChestBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseChestBlock.java new file mode 100644 index 00000000..b6c0655b --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseChestBlock.java @@ -0,0 +1,81 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.bclib.registry.BaseBlockEntities; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; +import org.betterx.worlds.together.tag.v3.CommonItemTags; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.ChestBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseChestBlock extends ChestBlock implements BlockModelProvider, TagProvider, DropSelfLootProvider { + private final Block parent; + + protected BaseChestBlock(Block source) { + super(Properties.copy(source).noOcclusion(), () -> BaseBlockEntities.CHEST); + this.parent = source; + } + + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return BaseBlockEntities.CHEST.create(blockPos, blockState); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_CHEST, blockId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + return ModelsHelper.createBlockEmpty(parentId); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(CommonBlockTags.CHEST); + itemTags.add(CommonItemTags.CHEST); + } + + public static class Wood extends BaseChestBlock implements BehaviourWood { + public Wood(Block source) { + super(source); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(CommonBlockTags.WOODEN_CHEST); + itemTags.add(CommonItemTags.WOODEN_CHEST); + } + } + + public static BaseChestBlock from(Block source) { + return new BaseChestBlock.Wood(source); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseComposterBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseComposterBlock.java new file mode 100644 index 00000000..e42c6fe5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseComposterBlock.java @@ -0,0 +1,97 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; +import org.betterx.worlds.together.tag.v3.CommonPoiTags; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.ComposterBlock; +import net.minecraft.world.level.block.state.BlockState; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseComposterBlock extends ComposterBlock implements BlockModelProvider, TagProvider, DropSelfLootProvider { + protected BaseComposterBlock(Block source) { + super(Properties.copy(source)); + } + + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_COMPOSTER, blockId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); + registerBlockModel(stateId, modelId, blockState, modelCache); + + ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition); + LEVEL.getPossibleValues().forEach(level -> { + if (level > 0) { + ResourceLocation contentId; + if (level > 7) { + contentId = new ResourceLocation("block/composter_contents_ready"); + } else { + contentId = new ResourceLocation("block/composter_contents" + level); + } + builder.part(contentId).setCondition(state -> state.getValue(LEVEL).equals(level)).add(); + } + }); + builder.part(modelId).add(); + + return builder.build(); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(CommonBlockTags.COMPOSTER); + blockTags.add(CommonPoiTags.FARMER_WORKSTATION); + } + + public static class Wood extends BaseComposterBlock implements BehaviourWood { + public Wood(Block source) { + super(source); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(CommonBlockTags.WOODEN_COMPOSTER); + } + } + + public static BaseComposterBlock from(Block source) { + return new BaseComposterBlock.Wood(source); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseCraftingTableBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseCraftingTableBlock.java new file mode 100644 index 00000000..d83669d1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseCraftingTableBlock.java @@ -0,0 +1,86 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; +import org.betterx.worlds.together.tag.v3.CommonItemTags; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.CraftingTableBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseCraftingTableBlock extends CraftingTableBlock implements BlockModelProvider, TagProvider, DropSelfLootProvider { + protected BaseCraftingTableBlock(Block source) { + this(Properties.copy(source)); + } + + protected BaseCraftingTableBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + String blockName = blockId.getPath(); + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SIDED, new HashMap() { + private static final long serialVersionUID = 1L; + + { + put("%modid%", blockId.getNamespace()); + put("%particle%", blockName + "_front"); + put("%down%", blockName + "_bottom"); + put("%up%", blockName + "_top"); + put("%north%", blockName + "_front"); + put("%south%", blockName + "_side"); + put("%west%", blockName + "_front"); + put("%east%", blockName + "_side"); + } + }); + return ModelsHelper.fromPattern(pattern); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(CommonBlockTags.WORKBENCHES); + itemTags.add(CommonItemTags.WORKBENCHES); + } + + public static class Wood extends BaseCraftingTableBlock implements BehaviourWood { + public Wood(Block source) { + super(source); + } + + public Wood(BlockBehaviour.Properties properties) { + super(properties); + } + } + + public static BaseCraftingTableBlock from(Block source) { + return new BaseCraftingTableBlock.Wood(source); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseCropBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseCropBlock.java new file mode 100644 index 00000000..de722f1c --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseCropBlock.java @@ -0,0 +1,123 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.interfaces.SurvivesOnBlocks; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.LootUtil; +import org.betterx.bclib.util.MHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import com.google.common.collect.Lists; + +import java.util.Collections; +import java.util.List; + +public class BaseCropBlock extends BasePlantBlock implements SurvivesOnBlocks { + public static final IntegerProperty AGE = IntegerProperty.create("age", 0, 3); + private static final VoxelShape SHAPE = box(2, 0, 2, 14, 14, 14); + + private final List terrain; + private final Item drop; + + public BaseCropBlock(Item drop, Block... terrain) { + this( + BehaviourBuilders.createPlant().randomTicks().sound(SoundType.CROP).offsetType(OffsetType.XZ), + drop, + terrain + ); + } + + protected BaseCropBlock(BlockBehaviour.Properties properties, Item drop, Block... terrain) { + super(properties); + this.drop = drop; + this.terrain = List.of(terrain); + this.registerDefaultState(defaultBlockState().setValue(AGE, 0)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(AGE); + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + if (state.getValue(AGE) < 3) { + return Collections.singletonList(new ItemStack(this)); + } + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (LootUtil.isCorrectTool(this, state, tool)) { + int enchantment = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool); + if (enchantment > 0) { + int countSeeds = MHelper.randRange(Mth.clamp(1 + enchantment, 1, 3), 3, MHelper.RANDOM_SOURCE); + int countDrops = MHelper.randRange(Mth.clamp(1 + enchantment, 1, 2), 2, MHelper.RANDOM_SOURCE); + return Lists.newArrayList(new ItemStack(this, countSeeds), new ItemStack(drop, countDrops)); + } + } + int countSeeds = MHelper.randRange(1, 3, MHelper.RANDOM_SOURCE); + int countDrops = MHelper.randRange(1, 2, MHelper.RANDOM_SOURCE); + return Lists.newArrayList(new ItemStack(this, countSeeds), new ItemStack(drop, countDrops)); + } + + @Override + public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { + int age = state.getValue(AGE); + if (age < 3) { + BlocksHelper.setWithUpdate(level, pos, state.setValue(AGE, age + 1)); + } + } + + @Override + public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) { + return state.getValue(AGE) < 3; + } + + @Override + public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) { + return state.getValue(AGE) < 3; + } + + @Override + @SuppressWarnings("deprecation") + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + super.tick(state, world, pos, random); + if (isBonemealSuccess(world, random, pos, state) && random.nextInt(8) == 0) { + performBonemeal(world, random, pos, state); + } + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPE; + } + + @Override + public List getSurvivableBlocks() { + return terrain; + } + + @Override + public boolean isTerrain(BlockState state) { + return SurvivesOnBlocks.super.isTerrain(state); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseDoorBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseDoorBlock.java new file mode 100644 index 00000000..bbbe90ae --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseDoorBlock.java @@ -0,0 +1,229 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.DoorBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockSetType; +import net.minecraft.world.level.block.state.properties.DoorHingeSide; +import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; +import net.minecraft.world.level.storage.loot.LootParams; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseDoorBlock extends DoorBlock implements RenderLayerProvider, BlockModelProvider, TagProvider { + protected BaseDoorBlock(Block source, BlockSetType type) { + this(Properties.copy(source).strength(3F, 3F).noOcclusion(), type); + } + + protected BaseDoorBlock(BlockBehaviour.Properties properties, BlockSetType type) { + super(properties, type); + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootParams.Builder builder) { + if (state.getValue(HALF) == DoubleBlockHalf.LOWER) + return Collections.singletonList(new ItemStack(this.asItem())); + else return Collections.emptyList(); + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + DoorType doorType = getDoorType(blockState); + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_BOTTOM, resourceLocation); + switch (doorType) { + case TOP_HINGE: + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_TOP_HINGE, resourceLocation); + break; + case BOTTOM_HINGE: + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_BOTTOM_HINGE, resourceLocation); + break; + case TOP: + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_TOP, resourceLocation); + break; + default: + break; + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + Direction facing = blockState.getValue(FACING); + DoorType doorType = getDoorType(blockState); + boolean open = blockState.getValue(OPEN); + boolean hinge = doorType.isHinge(); + BlockModelRotation rotation = BlockModelRotation.X0_Y0; + switch (facing) { + case EAST: + if (hinge && open) { + rotation = BlockModelRotation.X0_Y90; + } else if (open) { + rotation = BlockModelRotation.X0_Y270; + } + break; + case SOUTH: + if (!hinge && !open || hinge && !open) { + rotation = BlockModelRotation.X0_Y90; + } else if (hinge) { + rotation = BlockModelRotation.X0_Y180; + } + break; + case WEST: + if (!hinge && !open || hinge && !open) { + rotation = BlockModelRotation.X0_Y180; + } else if (hinge) { + rotation = BlockModelRotation.X0_Y270; + } else { + rotation = BlockModelRotation.X0_Y90; + } + break; + case NORTH: + default: + if (!hinge && !open || hinge && !open) { + rotation = BlockModelRotation.X0_Y270; + } else if (!hinge) { + rotation = BlockModelRotation.X0_Y180; + } + break; + } + ResourceLocation modelId = new ResourceLocation( + stateId.getNamespace(), + "block/" + stateId.getPath() + "_" + doorType + ); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); + } + + protected DoorType getDoorType(BlockState blockState) { + boolean isHinge = isHinge(blockState.getValue(HINGE), blockState.getValue(OPEN)); + switch (blockState.getValue(HALF)) { + case UPPER: { + return isHinge ? DoorType.TOP_HINGE : DoorType.TOP; + } + case LOWER: { + return isHinge ? DoorType.BOTTOM_HINGE : DoorType.BOTTOM; + } + } + return DoorType.BOTTOM; + } + + private boolean isHinge(DoorHingeSide hingeSide, boolean open) { + boolean isHinge = hingeSide == DoorHingeSide.RIGHT; + return isHinge && !open || !isHinge && open; + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.DOORS); + itemTags.add(ItemTags.DOORS); + } + + protected enum DoorType implements StringRepresentable { + BOTTOM_HINGE("bottom_hinge"), TOP_HINGE("top_hinge"), BOTTOM("bottom"), TOP("top"); + + private final String name; + + DoorType(String name) { + this.name = name; + } + + public boolean isHinge() { + return this == BOTTOM_HINGE || this == TOP_HINGE; + } + + @Override + public String toString() { + return getSerializedName(); + } + + @Override + public String getSerializedName() { + return name; + } + } + + public static class Wood extends BaseDoorBlock implements BehaviourWood { + public Wood(Block source, BlockSetType type) { + super(source, type); + } + + public Wood(Properties properties, BlockSetType type) { + super(properties, type); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(BlockTags.WOODEN_DOORS); + itemTags.add(ItemTags.WOODEN_DOORS); + } + } + + public static class Metal extends BaseDoorBlock implements BehaviourMetal { + public Metal(Block source, BlockSetType type) { + super(source, type); + } + + public Metal(Properties properties, BlockSetType type) { + super(properties, type); + } + } + + public static class Stone extends BaseDoorBlock implements BehaviourStone { + public Stone(Block source, BlockSetType type) { + super(source, type); + } + + public Stone(Properties properties, BlockSetType type) { + super(properties, type); + } + } + + + public static BaseDoorBlock from(Block source, BlockSetType type) { + return BehaviourHelper.from(source, type, Wood::new, Stone::new, Metal::new); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseDoublePlantBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseDoublePlantBlock.java new file mode 100644 index 00000000..7434578b --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseDoublePlantBlock.java @@ -0,0 +1,167 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.LootUtil; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.BonemealableBlock; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import com.google.common.collect.Lists; + +import java.util.List; + +public abstract class BaseDoublePlantBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock { + private static final VoxelShape SHAPE = box(4, 2, 4, 12, 16, 12); + public static final IntegerProperty ROTATION = BlockProperties.ROTATION; + public static final BooleanProperty TOP = BooleanProperty.create("top"); + + public BaseDoublePlantBlock() { + this( + BehaviourBuilders + .createPlant() + .sound(SoundType.GRASS) + .offsetType(BlockBehaviour.OffsetType.NONE) + ); + } + + public BaseDoublePlantBlock(int light) { + this( + BehaviourBuilders + .createPlant() + .sound(SoundType.GRASS) + .lightLevel((state) -> state.getValue(TOP) ? light : 0) + .offsetType(BlockBehaviour.OffsetType.NONE) + ); + } + + public BaseDoublePlantBlock(BlockBehaviour.Properties properties) { + super(properties); + this.registerDefaultState(this.stateDefinition.any().setValue(TOP, false)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(TOP, ROTATION); + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + Vec3 vec3d = state.getOffset(view, pos); + return SHAPE.move(vec3d.x, vec3d.y, vec3d.z); + } + + @Override + @SuppressWarnings("deprecation") + public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { + BlockState down = world.getBlockState(pos.below()); + BlockState up = world.getBlockState(pos.above()); + return state.getValue(TOP) ? down.getBlock() == this : isTerrain(down) && (up.canBeReplaced()); + } + + public boolean canStayAt(BlockState state, LevelReader world, BlockPos pos) { + BlockState down = world.getBlockState(pos.below()); + BlockState up = world.getBlockState(pos.above()); + return state.getValue(TOP) ? down.getBlock() == this : isTerrain(down) && (up.getBlock() == this); + } + + protected abstract boolean isTerrain(BlockState state); + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (!canStayAt(state, world, pos)) { + return Blocks.AIR.defaultBlockState(); + } else { + return state; + } + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (LootUtil.isCorrectTool(this, state, tool) || EnchantmentHelper.getItemEnchantmentLevel( + Enchantments.SILK_TOUCH, + tool + ) > 0) { + return Lists.newArrayList(new ItemStack(this)); + } else { + return Lists.newArrayList(); + } + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + public boolean isValidBonemealTarget( + LevelReader levelReader, + BlockPos blockPos, + BlockState blockState, + boolean isClient + ) { + return true; + } + + @Override + public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { + ItemEntity item = new ItemEntity( + level, + pos.getX() + 0.5, + pos.getY() + 0.5, + pos.getZ() + 0.5, + new ItemStack(this) + ); + level.addFreshEntity(item); + } + + @Override + public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) { + int rot = world.random.nextInt(4); + BlockState bs = this.defaultBlockState().setValue(ROTATION, rot); + BlocksHelper.setWithoutUpdate(world, pos, bs); + BlocksHelper.setWithoutUpdate(world, pos.above(), bs.setValue(TOP, true)); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseFenceBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseFenceBlock.java new file mode 100644 index 00000000..3fbdb3ce --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseFenceBlock.java @@ -0,0 +1,121 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FenceBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockSetType; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseFenceBlock extends FenceBlock implements BlockModelProvider, TagProvider, DropSelfLootProvider { + private final Block parent; + + protected BaseFenceBlock(Block source) { + super(Properties.copy(source).noOcclusion()); + this.parent = source; + } + + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_FENCE, parentId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + String path = blockId.getPath(); + Optional pattern = Optional.empty(); + if (path.endsWith("_post")) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FENCE_POST, parentId); + } + if (path.endsWith("_side")) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FENCE_SIDE, parentId); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_post"); + ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_side"); + registerBlockModel(postId, postId, blockState, modelCache); + registerBlockModel(sideId, sideId, blockState, modelCache); + + ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition); + builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add(); + builder.part(sideId) + .setCondition(state -> state.getValue(EAST)) + .setTransformation(BlockModelRotation.X0_Y90.getRotation()) + .setUVLock(true) + .add(); + builder.part(sideId) + .setCondition(state -> state.getValue(SOUTH)) + .setTransformation(BlockModelRotation.X0_Y180.getRotation()) + .setUVLock(true) + .add(); + builder.part(sideId) + .setCondition(state -> state.getValue(WEST)) + .setTransformation(BlockModelRotation.X0_Y270.getRotation()) + .setUVLock(true) + .add(); + builder.part(postId).add(); + + return builder.build(); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.FENCES); + itemTags.add(ItemTags.FENCES); + } + + public static class Wood extends BaseFenceBlock implements BehaviourWood { + public Wood(Block source, BlockSetType type) { + super(source); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(BlockTags.WOODEN_FENCES); + itemTags.add(ItemTags.WOODEN_FENCES); + } + } + + public static BaseFenceBlock from(Block source, BlockSetType type) { + return new BaseFenceBlock.Wood(source, type); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseFurnaceBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseFurnaceBlock.java new file mode 100644 index 00000000..e8b2da4d --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseFurnaceBlock.java @@ -0,0 +1,157 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.blockentities.BaseFurnaceBlockEntity; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.registry.BaseBlockEntities; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.stats.Stats; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FurnaceBlock; +import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +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 org.jetbrains.annotations.Nullable; + +public abstract class BaseFurnaceBlock extends FurnaceBlock implements BlockModelProvider, RenderLayerProvider { + public BaseFurnaceBlock(Block source) { + this(Properties.copy(source).lightLevel(state -> state.getValue(LIT) ? 13 : 0)); + } + + public BaseFurnaceBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return new BaseFurnaceBlockEntity(blockPos, blockState); + } + + @Override + protected void openContainer(Level world, BlockPos pos, Player player) { + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity instanceof BaseFurnaceBlockEntity) { + player.openMenu((MenuProvider) blockEntity); + player.awardStat(Stats.INTERACT_WITH_FURNACE); + } + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + String blockName = blockId.getPath(); + Map textures = Maps.newHashMap(); + textures.put("%modid%", blockId.getNamespace()); + textures.put("%top%", blockName + "_top"); + textures.put("%side%", blockName + "_side"); + Optional pattern; + if (blockState.getValue(LIT)) { + textures.put("%front%", blockName + "_front_on"); + textures.put("%glow%", blockName + "_glow"); + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE_LIT, textures); + } else { + textures.put("%front%", blockName + "_front"); + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE, textures); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + String lit = blockState.getValue(LIT) ? "_lit" : ""; + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + lit); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true); + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootParams.Builder builder) { + List drop = Lists.newArrayList(new ItemStack(this)); + BlockEntity blockEntity = builder.getOptionalParameter(LootContextParams.BLOCK_ENTITY); + if (blockEntity instanceof BaseFurnaceBlockEntity) { + BaseFurnaceBlockEntity entity = (BaseFurnaceBlockEntity) blockEntity; + for (int i = 0; i < entity.getContainerSize(); i++) { + drop.add(entity.getItem(i)); + } + } + return drop; + } + + @Override + @Nullable + public BlockEntityTicker getTicker( + Level level, + BlockState blockState, + BlockEntityType blockEntityType + ) { + return createFurnaceTicker(level, blockEntityType, BaseBlockEntities.FURNACE); + } + + @Nullable + protected static BlockEntityTicker createFurnaceTicker( + Level level, + BlockEntityType blockEntityType, + BlockEntityType blockEntityType2 + ) { + return level.isClientSide ? null : createTickerHelper( + blockEntityType, + blockEntityType2, + AbstractFurnaceBlockEntity::serverTick + ); + } + + public static class Stone extends BaseFurnaceBlock implements BehaviourStone { + public Stone(Block source) { + super(source); + } + + public Stone(BlockBehaviour.Properties properties) { + super(properties); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseGateBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseGateBlock.java new file mode 100644 index 00000000..993ad72f --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseGateBlock.java @@ -0,0 +1,100 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FenceGateBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.WoodType; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseGateBlock extends FenceGateBlock implements BlockModelProvider, TagProvider, DropSelfLootProvider { + private final Block parent; + + protected BaseGateBlock(Block source, WoodType type) { + super(Properties.copy(source).noOcclusion(), type); + this.parent = source; + } + + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + boolean inWall = blockState.getValue(IN_WALL); + boolean isOpen = blockState.getValue(OPEN); + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + Optional pattern; + if (inWall) { + pattern = isOpen + ? PatternsHelper.createJson( + BasePatterns.BLOCK_GATE_OPEN_WALL, + parentId + ) + : PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED_WALL, parentId); + } else { + pattern = isOpen + ? PatternsHelper.createJson( + BasePatterns.BLOCK_GATE_OPEN, + parentId + ) + : PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED, parentId); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + boolean inWall = blockState.getValue(IN_WALL); + boolean isOpen = blockState.getValue(OPEN); + String state = "" + (inWall ? "_wall" : "") + (isOpen ? "_open" : "_closed"); + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), true, false); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.FENCE_GATES); + } + + public static class Wood extends BaseGateBlock implements BehaviourWood { + public Wood(Block source, WoodType type) { + super(source, type); + } + } + + public static BaseGateBlock from(Block source, WoodType type) { + return new BaseGateBlock.Wood(source, type); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/blocks/BaseGlassBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseGlassBlock.java new file mode 100644 index 00000000..531d53dc --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseGlassBlock.java @@ -0,0 +1,65 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.interfaces.tools.AddMineablePickaxe; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Collections; +import java.util.List; + +public class BaseGlassBlock extends BaseBlockNotFull implements AddMineablePickaxe, RenderLayerProvider { + public BaseGlassBlock(Block block) { + this(block, 0.3f); + } + + public BaseGlassBlock(Block block, float resistance) { + super(Properties.copy(block) + .explosionResistance(resistance) + .noOcclusion() + .isSuffocating((arg1, arg2, arg3) -> false) + .isViewBlocking((arg1, arg2, arg3) -> false)); + } + + @Environment(EnvType.CLIENT) + public float getShadeBrightness(BlockState state, BlockGetter view, BlockPos pos) { + return 1.0F; + } + + @Override + public boolean propagatesSkylightDown(BlockState state, BlockGetter view, BlockPos pos) { + return true; + } + + @Environment(EnvType.CLIENT) + public boolean skipRendering(BlockState state, BlockState neighbor, Direction facing) { + return neighbor.getBlock() == this || super.skipRendering(state, neighbor, facing); + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { + return Collections.singletonList(new ItemStack(this)); + } + return Collections.emptyList(); + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.TRANSLUCENT; + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseLadderBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseLadderBlock.java new file mode 100644 index 00000000..88f87f43 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseLadderBlock.java @@ -0,0 +1,94 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourClimable; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.RenderLayerProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.LadderBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseLadderBlock extends LadderBlock implements RenderLayerProvider, BlockModelProvider, BehaviourClimable, DropSelfLootProvider { + protected BaseLadderBlock(Block block) { + this(Properties.copy(block).noOcclusion()); + } + + public BaseLadderBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + return ModelsHelper.createBlockItem(blockId); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_LADDER, blockId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true); + } + + public static class Wood extends BaseLadderBlock implements BehaviourWood { + public Wood(Block block) { + super(block); + } + + public Wood(Properties properties) { + super(properties); + } + } + + public static class Metal extends BaseLadderBlock implements BehaviourMetal { + public Metal(Block block) { + super(block); + } + + public Metal(Properties properties) { + super(properties); + } + } + + public static BaseLadderBlock from(Block source) { + return BehaviourHelper.from(source, + Wood::new, null, Metal::new + ); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseLeavesBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseLeavesBlock.java new file mode 100644 index 00000000..2bbdd592 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseLeavesBlock.java @@ -0,0 +1,125 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.behaviours.interfaces.BehaviourLeaves; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.bclib.util.MHelper; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.LeavesBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import com.google.common.collect.Lists; + +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +public class BaseLeavesBlock extends LeavesBlock implements BlockModelProvider, RenderLayerProvider, BehaviourLeaves { + protected final Block sapling; + + public BaseLeavesBlock( + Block sapling, + BlockBehaviour.Properties properties + ) { + super(properties); + this.sapling = sapling; + } + + @Deprecated(forRemoval = true) + public BaseLeavesBlock( + Block sapling, + MapColor color, + Consumer customizeProperties + ) { + super(BaseBlock.acceptAndReturn(customizeProperties, BehaviourBuilders.createLeaves(color, true))); + this.sapling = sapling; + } + + @Deprecated(forRemoval = true) + public BaseLeavesBlock( + Block sapling, + MapColor color, + int light, + Consumer customizeProperties + ) { + super(BaseBlock.acceptAndReturn( + customizeProperties, + BehaviourBuilders.createLeaves(color, true).lightLevel(state -> light) + )); + this.sapling = sapling; + } + + @Deprecated(forRemoval = true) + public BaseLeavesBlock(Block sapling, MapColor color) { + super(BehaviourBuilders.createLeaves(color, true)); + this.sapling = sapling; + } + + @Deprecated(forRemoval = true) + public BaseLeavesBlock(Block sapling, MapColor color, int light) { + super(BehaviourBuilders.createLeaves(color, true).lightLevel(state -> light)); + this.sapling = sapling; + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootParams.Builder builder) { + return BaseLeavesBlock.getLeaveDrops(this, this.sapling, builder, 16, 16); + } + + public static List getLeaveDrops( + ItemLike leaveBlock, + Block sapling, + LootParams.Builder builder, + int fortuneRate, + int dropRate + ) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null) { + if (tool != null && BaseShearsItem.isShear(tool) || EnchantmentHelper.getItemEnchantmentLevel( + Enchantments.SILK_TOUCH, + tool + ) > 0) { + return Collections.singletonList(new ItemStack(leaveBlock)); + } + int fortune = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool); + if (MHelper.RANDOM.nextInt(fortuneRate) <= fortune) { + return Lists.newArrayList(new ItemStack(sapling)); + } + return Lists.newArrayList(); + } + return MHelper.RANDOM.nextInt(dropRate) == 0 + ? Lists.newArrayList(new ItemStack(sapling)) + : Lists.newArrayList(); + } + + @Override + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + + @Override + public float compostingChance() { + return 0.3f; + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseOreBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseOreBlock.java new file mode 100644 index 00000000..723b09db --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseOreBlock.java @@ -0,0 +1,149 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.behaviours.interfaces.BehaviourOre; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.bclib.util.LootUtil; +import org.betterx.bclib.util.MHelper; +import org.betterx.worlds.together.tag.v3.MineableTags; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.util.Mth; +import net.minecraft.util.valueproviders.UniformInt; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TieredItem; +import net.minecraft.world.item.Tiers; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.DropExperienceBlock; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +public class BaseOreBlock extends DropExperienceBlock implements BlockModelProvider, TagProvider, BehaviourOre { + private final Supplier dropItem; + private final int minCount; + private final int maxCount; + private final int miningLevel; + + public BaseOreBlock(Supplier drop, int minCount, int maxCount, int experience) { + this(drop, minCount, maxCount, experience, 0); + } + + public BaseOreBlock(Supplier drop, int minCount, int maxCount, int experience, int miningLevel) { + this( + BehaviourBuilders + .createStone(MapColor.SAND) + .requiresCorrectToolForDrops() + .destroyTime(3F) + .explosionResistance(9F) + .sound(SoundType.STONE), + drop, minCount, maxCount, experience, miningLevel + ); + } + + public BaseOreBlock(Properties properties, Supplier drop, int minCount, int maxCount, int experience) { + this(properties, drop, minCount, maxCount, experience, 0); + } + + public BaseOreBlock( + Properties properties, + Supplier drop, + int minCount, + int maxCount, + int experience, + int miningLevel + ) { + super(properties, UniformInt.of(experience > 0 ? 1 : 0, experience)); + this.dropItem = drop; + this.minCount = minCount; + this.maxCount = maxCount; + this.miningLevel = miningLevel; + } + + @Override + @SuppressWarnings("deprecation") + public List getDrops(BlockState state, LootParams.Builder builder) { + return LootUtil + .getDrops(this, state, builder) + .orElseGet( + () -> BaseOreBlock.getDroppedItems( + this, + dropItem.get(), + maxCount, + minCount, + miningLevel, + state, + builder + ) + ); + } + + public static List getDroppedItems( + ItemLike block, + Item dropItem, + int maxCount, + int minCount, + int miningLevel, + BlockState state, + LootParams.Builder builder + ) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && tool.isCorrectToolForDrops(state) && dropItem != null) { + boolean canMine = miningLevel == 0; + if (tool.getItem() instanceof TieredItem tired) { + canMine = tired.getTier().getLevel() >= miningLevel; + } + if (canMine) { + if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { + return Collections.singletonList(new ItemStack(block)); + } + int count; + int enchantment = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool); + if (enchantment > 0) { + int min = Mth.clamp(minCount + enchantment, minCount, maxCount); + int max = maxCount + (enchantment / Enchantments.BLOCK_FORTUNE.getMaxLevel()); + if (min == max) { + return Collections.singletonList(new ItemStack(dropItem, max)); + } + count = MHelper.randRange(min, max, MHelper.RANDOM_SOURCE); + } else { + count = MHelper.randRange(minCount, maxCount, MHelper.RANDOM_SOURCE); + } + return Collections.singletonList(new ItemStack(dropItem, count)); + } + } + return Collections.emptyList(); + } + + @Override + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + if (this.miningLevel == Tiers.STONE.getLevel()) { + blockTags.add(BlockTags.NEEDS_STONE_TOOL); + } else if (this.miningLevel == Tiers.IRON.getLevel()) { + blockTags.add(BlockTags.NEEDS_IRON_TOOL); + } else if (this.miningLevel == Tiers.DIAMOND.getLevel()) { + blockTags.add(BlockTags.NEEDS_DIAMOND_TOOL); + } else if (this.miningLevel == Tiers.NETHERITE.getLevel()) { + blockTags.add(MineableTags.NEEDS_NETHERITE_TOOL); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BasePathBlock.java b/src/main/java/org/betterx/bclib/blocks/BasePathBlock.java new file mode 100644 index 00000000..af5ebee2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BasePathBlock.java @@ -0,0 +1,110 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Maps; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BasePathBlock extends BaseBlockNotFull { + private static final VoxelShape SHAPE = box(0, 0, 0, 16, 15, 16); + + private Block baseBlock; + + public BasePathBlock(Block source) { + super(Properties.copy(source).isValidSpawn((state, world, pos, type) -> false)); + this.baseBlock = Blocks.DIRT; + if (source instanceof BaseTerrainBlock) { + BaseTerrainBlock terrain = (BaseTerrainBlock) source; + this.baseBlock = terrain.getBaseBlock(); + terrain.setPathBlock(this); + } + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { + return Collections.singletonList(new ItemStack(this)); + } + return Collections.singletonList(new ItemStack(Blocks.END_STONE)); + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPE; + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getCollisionShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPE; + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + return getBlockModel(blockId, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + String name = blockId.getPath(); + ResourceLocation bottomId = BuiltInRegistries.BLOCK.getKey(baseBlock); + String bottom = bottomId.getNamespace() + ":block/" + bottomId.getPath(); + Map textures = Maps.newHashMap(); + textures.put("%modid%", blockId.getNamespace()); + textures.put("%top%", name + "_top"); + textures.put("%side%", name.replace("_path", "") + "_side"); + textures.put("%bottom%", bottom); + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PATH, textures); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createRandomTopModel(modelId); + } + + public static class Stone extends BasePathBlock implements BehaviourStone { + public Stone(Block source) { + super(source); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BasePlanks.java b/src/main/java/org/betterx/bclib/blocks/BasePlanks.java new file mode 100644 index 00000000..5b04556f --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BasePlanks.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.List; + +public abstract class BasePlanks extends BaseBlock implements TagProvider { + /** + * Creates a new Block with the passed properties + * + * @param settings The properties of the Block. + */ + protected BasePlanks(Properties settings) { + super(settings); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.PLANKS); + itemTags.add(ItemTags.PLANKS); + } + + public static class Wood extends BasePlanks implements BehaviourWood { + /** + * Creates a new Block with the passed properties + * + * @param settings The properties of the Block. + */ + public Wood(Properties settings) { + super(settings); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BasePlantBlock.java b/src/main/java/org/betterx/bclib/blocks/BasePlantBlock.java new file mode 100644 index 00000000..cdc00b9a --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BasePlantBlock.java @@ -0,0 +1,136 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.util.LootUtil; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.BonemealableBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BasePlantBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock { + private static final VoxelShape SHAPE = box(4, 0, 4, 12, 14, 12); + + protected BasePlantBlock(Properties settings) { + super(settings); + } + + protected abstract boolean isTerrain(BlockState state); + + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + Vec3 vec3d = state.getOffset(view, pos); + return SHAPE.move(vec3d.x, vec3d.y, vec3d.z); + } + + @Override + @SuppressWarnings("deprecation") + public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { + BlockState down = level.getBlockState(pos.below()); + return isTerrain(down); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (!canSurvive(state, world, pos)) { + return Blocks.AIR.defaultBlockState(); + } else { + return state; + } + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (LootUtil.isCorrectTool(this, state, tool) || EnchantmentHelper.getItemEnchantmentLevel( + Enchantments.SILK_TOUCH, + tool + ) > 0) { + return Lists.newArrayList(new ItemStack(this)); + } else { + return Lists.newArrayList(); + } + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) { + return true; + } + + @Override + public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { + ItemEntity item = new ItemEntity( + level, + pos.getX() + 0.5, + pos.getY() + 0.5, + pos.getZ() + 0.5, + new ItemStack(this) + ); + level.addFreshEntity(item); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createBlockItem(resourceLocation); + } + + @Override + @Nullable + @Environment(EnvType.CLIENT) + public BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CROSS, resourceLocation); + return ModelsHelper.fromPattern(pattern); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BasePlantWithAgeBlock.java b/src/main/java/org/betterx/bclib/blocks/BasePlantWithAgeBlock.java new file mode 100644 index 00000000..57374a82 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BasePlantWithAgeBlock.java @@ -0,0 +1,50 @@ +package org.betterx.bclib.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; + +public abstract class BasePlantWithAgeBlock extends BasePlantBlock { + public static final IntegerProperty AGE = BlockProperties.AGE; + + protected BasePlantWithAgeBlock(Properties settings) { + super(settings.randomTicks()); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(AGE); + } + + public abstract void growAdult(WorldGenLevel world, RandomSource random, BlockPos pos); + + @Override + public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { + int age = state.getValue(AGE); + if (age < 3) { + level.setBlockAndUpdate(pos, state.setValue(AGE, age + 1)); + } else { + growAdult(level, random, pos); + } + } + + @Override + public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + @SuppressWarnings("deprecation") + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + super.tick(state, world, pos, random); + if (random.nextInt(8) == 0) { + performBonemeal(world, random, pos, state); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BasePressurePlateBlock.java b/src/main/java/org/betterx/bclib/blocks/BasePressurePlateBlock.java new file mode 100644 index 00000000..b57949d4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BasePressurePlateBlock.java @@ -0,0 +1,114 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.PressurePlateBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockSetType; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BasePressurePlateBlock extends PressurePlateBlock implements BlockModelProvider, TagProvider, DropSelfLootProvider { + private final Block parent; + + protected BasePressurePlateBlock(Sensitivity rule, Block source, BlockSetType type) { + super( + rule, Properties.copy(source).noCollission().noOcclusion().strength(0.5F), + type + ); + this.parent = source; + } + + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + Optional pattern; + if (blockState.getValue(POWERED)) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_DOWN, parentId); + } else { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_UP, parentId); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + String state = blockState.getValue(POWERED) ? "_down" : "_up"; + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createBlockSimple(modelId); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.PRESSURE_PLATES); + } + + public static class Wood extends BasePressurePlateBlock implements BehaviourWood { + public Wood(Block source, BlockSetType type) { + super(Sensitivity.EVERYTHING, source, type); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(BlockTags.WOODEN_PRESSURE_PLATES); + itemTags.add(ItemTags.WOODEN_PRESSURE_PLATES); + } + } + + public static class Stone extends BasePressurePlateBlock implements BehaviourStone { + public Stone(Block source, BlockSetType type) { + super(Sensitivity.MOBS, source, type); + } + } + + public static class Metal extends BasePressurePlateBlock implements BehaviourMetal { + public Metal(Block source, BlockSetType type) { + super(Sensitivity.MOBS, source, type); + } + } + + public static BasePressurePlateBlock from(Block source, BlockSetType type) { + return BehaviourHelper.from(source, type, + Wood::new, Stone::new, Metal::new + ); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseRotatedPillarBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseRotatedPillarBlock.java new file mode 100644 index 00000000..949c70db --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseRotatedPillarBlock.java @@ -0,0 +1,106 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.RotatedPillarBlock; +import net.minecraft.world.level.block.state.BlockState; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseRotatedPillarBlock extends RotatedPillarBlock implements BlockModelProvider, DropSelfLootProvider { + protected BaseRotatedPillarBlock(Properties settings) { + super(settings); + } + + protected BaseRotatedPillarBlock(Block block) { + this(Properties.copy(block)); + } + + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + return getBlockModel(blockId, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + Optional pattern = createBlockPattern(blockId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createRotatedModel(modelId, blockState.getValue(AXIS)); + } + + protected Optional createBlockPattern(ResourceLocation blockId) { + return PatternsHelper.createBlockPillar(blockId); + } + + public static class Wood extends BaseRotatedPillarBlock implements BehaviourWood { + protected final boolean flammable; + + public Wood(Properties settings, boolean flammable) { + super(flammable ? settings.ignitedByLava() : settings); + this.flammable = flammable; + } + + public Wood(Block block, boolean flammable) { + this(Properties.copy(block), flammable); + } + } + + public static class Stone extends BaseRotatedPillarBlock implements BehaviourStone { + public Stone(Properties settings) { + super(settings); + } + + public Stone(Block block) { + super(block); + } + } + + public static class Metal extends BaseRotatedPillarBlock implements BehaviourMetal { + public Metal(Properties settings) { + super(settings); + } + + public Metal(Block block) { + super(block); + } + } + + public static BaseRotatedPillarBlock from(Block source, boolean flammable) { + return BehaviourHelper.from( + source, + (s) -> new Wood(s, flammable), + Stone::new, + Metal::new + ); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseSlabBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseSlabBlock.java new file mode 100644 index 00000000..1a56b308 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseSlabBlock.java @@ -0,0 +1,156 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourObsidian; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.CustomItemProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SlabBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.SlabType; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseSlabBlock extends SlabBlock implements BlockModelProvider, CustomItemProvider, TagProvider, DropSelfLootProvider { + private final Block parent; + public final boolean fireproof; + + protected BaseSlabBlock(Block source, boolean fireproof) { + super(Properties.copy(source)); + this.parent = source; + this.fireproof = fireproof; + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + Optional pattern; + if (blockState.getValue(TYPE) == SlabType.DOUBLE) { + pattern = PatternsHelper.createBlockSimple(parentId); + } else { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SLAB, parentId); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + SlabType type = blockState.getValue(TYPE); + ResourceLocation modelId = new ResourceLocation( + stateId.getNamespace(), + "block/" + stateId.getPath() + "_" + type + ); + registerBlockModel(stateId, modelId, blockState, modelCache); + if (type == SlabType.TOP) { + return ModelsHelper.createMultiVariant(modelId, BlockModelRotation.X180_Y0.getRotation(), true); + } + return ModelsHelper.createBlockSimple(modelId); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.SLABS); + itemTags.add(ItemTags.SLABS); + } + + @Override + public BlockItem getCustomItem(ResourceLocation blockID, Item.Properties settings) { + if (fireproof) settings = settings.fireResistant(); + return new BlockItem(this, settings); + } + + public static class Stone extends BaseSlabBlock implements BehaviourStone { + public Stone(Block source) { + this(source, true); + } + + public Stone(Block source, boolean fireproof) { + super(source, fireproof); + } + } + + public static class Metal extends BaseSlabBlock implements BehaviourMetal { + public Metal(Block source) { + this(source, true); + } + + public Metal(Block source, boolean fireproof) { + super(source, fireproof); + } + } + + public static class Wood extends BaseSlabBlock implements BehaviourWood { + public Wood(Block source) { + this(source, false); + } + + public Wood(Block source, boolean fireproof) { + super(source, fireproof); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(BlockTags.WOODEN_SLABS); + itemTags.add(ItemTags.WOODEN_SLABS); + } + } + + public static class Obsidian extends BaseSlabBlock implements BehaviourObsidian { + public Obsidian(Block source) { + super(source, true); + } + + public Obsidian(Block source, boolean fireproof) { + super(source, fireproof); + } + } + + public static BaseSlabBlock from(Block source, boolean flammable) { + return BehaviourHelper.from( + source, + (s) -> new BaseSlabBlock.Wood(s, !flammable), + (s) -> new BaseSlabBlock.Stone(s, !flammable), + (s) -> new BaseSlabBlock.Metal(s, !flammable), + (s) -> new BaseSlabBlock.Obsidian(s, !flammable), + null + ); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseStairsBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseStairsBlock.java new file mode 100644 index 00000000..d3d5f257 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseStairsBlock.java @@ -0,0 +1,184 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourObsidian; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.CustomItemProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.StairBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Half; +import net.minecraft.world.level.block.state.properties.StairsShape; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseStairsBlock extends StairBlock implements BlockModelProvider, CustomItemProvider, TagProvider, DropSelfLootProvider { + + + private final Block parent; + public final boolean fireproof; + + protected BaseStairsBlock(Block source, boolean fireproof) { + super(source.defaultBlockState(), Properties.copy(source)); + this.parent = source; + this.fireproof = fireproof; + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + Optional pattern = PatternsHelper.createJson(switch (blockState.getValue(SHAPE)) { + case STRAIGHT -> BasePatterns.BLOCK_STAIR; + case INNER_LEFT, INNER_RIGHT -> BasePatterns.BLOCK_STAIR_INNER; + case OUTER_LEFT, OUTER_RIGHT -> BasePatterns.BLOCK_STAIR_OUTER; + }, parentId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + String state; + StairsShape shape = blockState.getValue(SHAPE); + state = switch (shape) { + case INNER_LEFT, INNER_RIGHT -> "_inner"; + case OUTER_LEFT, OUTER_RIGHT -> "_outer"; + default -> ""; + }; + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state); + registerBlockModel(stateId, modelId, blockState, modelCache); + + boolean isTop = blockState.getValue(HALF) == Half.TOP; + boolean isLeft = shape == StairsShape.INNER_LEFT || shape == StairsShape.OUTER_LEFT; + boolean isRight = shape == StairsShape.INNER_RIGHT || shape == StairsShape.OUTER_RIGHT; + int y = 0; + int x = isTop ? 180 : 0; + switch (blockState.getValue(FACING)) { + case NORTH: + if (isTop && !isRight) y = 270; + else if (!isTop) y = isLeft ? 180 : 270; + break; + case EAST: + if (isTop && isRight) y = 90; + else if (!isTop && isLeft) y = 270; + break; + case SOUTH: + if (isTop) y = isRight ? 180 : 90; + else if (!isLeft) y = 90; + break; + case WEST: + default: + y = (isTop && isRight) ? 270 : (!isTop && isLeft) ? 90 : 180; + break; + } + BlockModelRotation rotation = BlockModelRotation.by(x, y); + return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), true); + } + + @Override + public BlockItem getCustomItem(ResourceLocation blockID, Item.Properties settings) { + if (fireproof) settings = settings.fireResistant(); + return new BlockItem(this, settings); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.STAIRS); + itemTags.add(ItemTags.STAIRS); + } + + public static class Stone extends BaseStairsBlock implements BehaviourStone { + public Stone(Block source) { + this(source, true); + } + + public Stone(Block source, boolean fireproof) { + super(source, fireproof); + } + } + + public static class Metal extends BaseStairsBlock implements BehaviourMetal { + public Metal(Block source) { + this(source, true); + } + + public Metal(Block source, boolean fireproof) { + super(source, fireproof); + } + } + + public static class Wood extends BaseStairsBlock implements BehaviourWood { + public Wood(Block source) { + this(source, false); + } + + public Wood(Block source, boolean fireproof) { + super(source, fireproof); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(BlockTags.WOODEN_STAIRS); + itemTags.add(ItemTags.WOODEN_STAIRS); + } + } + + public static class Obsidian extends BaseStairsBlock implements BehaviourObsidian { + public Obsidian(Block source) { + this(source, true); + } + + public Obsidian(Block source, boolean fireproof) { + super(source, fireproof); + } + } + + public static BaseStairsBlock from(Block source, boolean flammable) { + return BehaviourHelper.from( + source, + (block) -> new Wood(block, flammable), + (block) -> new Stone(block, !flammable), + (block) -> new Metal(block, !flammable), + (block) -> new Obsidian(block, !flammable), + null + ); + + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseStripableBarkBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseStripableBarkBlock.java new file mode 100644 index 00000000..f3c83323 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseStripableBarkBlock.java @@ -0,0 +1,89 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.worlds.together.tag.v3.MineableTags; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.phys.BlockHitResult; + +import java.util.List; + + +public abstract class BaseStripableBarkBlock extends BaseBarkBlock { + private final Block stripedBlock; + + protected BaseStripableBarkBlock(Block stripedBlock, Properties settings) { + super(settings); + this.stripedBlock = stripedBlock; + } + + @Override + @SuppressWarnings("deprecation") + public InteractionResult use( + BlockState state, + Level world, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hit + ) { + if (TagManager.isToolWithMineableTag(player.getMainHandItem(), MineableTags.AXE)) { + world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); + if (!world.isClientSide) { + world.setBlock( + pos, + stripedBlock.defaultBlockState() + .setValue(AXIS, state.getValue(AXIS)), + 11 + ); + if (!player.isCreative()) { + player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player); + } + } + return InteractionResult.SUCCESS; + } + return InteractionResult.FAIL; + } + + + public static class Wood extends BaseStripableBarkBlock implements BehaviourWood, TagProvider { + private final boolean flammable; + + public Wood(MapColor color, Block stripedBlock, boolean flammable) { + super( + stripedBlock, + (flammable + ? Properties.copy(stripedBlock).ignitedByLava() + : Properties.copy(stripedBlock)).mapColor(color) + ); + this.flammable = flammable; + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.LOGS); + itemTags.add(ItemTags.LOGS); + + if (flammable) { + blockTags.add(BlockTags.LOGS_THAT_BURN); + itemTags.add(ItemTags.LOGS_THAT_BURN); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseStripableLogBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseStripableLogBlock.java new file mode 100644 index 00000000..057f4d7f --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseStripableLogBlock.java @@ -0,0 +1,87 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.interfaces.TagProvider; +import org.betterx.worlds.together.tag.v3.MineableTags; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.RotatedPillarBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.phys.BlockHitResult; + +import java.util.List; + + +public abstract class BaseStripableLogBlock extends BaseRotatedPillarBlock { + private final Block striped; + + protected BaseStripableLogBlock(Block striped, Properties settings) { + super(settings); + this.striped = striped; + } + + @Override + @SuppressWarnings("deprecation") + public InteractionResult use( + BlockState state, + Level world, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hit + ) { + if (TagManager.isToolWithMineableTag(player.getMainHandItem(), MineableTags.AXE)) { + world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); + if (!world.isClientSide) { + world.setBlock( + pos, + striped.defaultBlockState() + .setValue(RotatedPillarBlock.AXIS, state.getValue(RotatedPillarBlock.AXIS)), + 11 + ); + if (!player.isCreative()) { + player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player); + } + } + return InteractionResult.SUCCESS; + } + return InteractionResult.FAIL; + } + + public static class Wood extends BaseStripableLogBlock implements BehaviourWood, TagProvider { + private final boolean flammable; + + public Wood(MapColor color, Block striped, boolean flammable) { + super( + striped, + (flammable ? Properties.copy(striped).ignitedByLava() : Properties.copy(striped)).mapColor(color) + ); + this.flammable = flammable; + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.LOGS); + itemTags.add(ItemTags.LOGS); + + if (flammable) { + blockTags.add(BlockTags.LOGS_THAT_BURN); + itemTags.add(ItemTags.LOGS_THAT_BURN); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseTerrainBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseTerrainBlock.java new file mode 100644 index 00000000..48126e01 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseTerrainBlock.java @@ -0,0 +1,164 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.sound.BlockSounds; +import org.betterx.worlds.together.tag.v3.MineableTags; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.RandomSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.SnowLayerBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.lighting.LightEngine; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.BlockHitResult; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Maps; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("deprecation") +public class BaseTerrainBlock extends BaseBlock { + private final Block baseBlock; + private Block pathBlock; + + public BaseTerrainBlock(Block baseBlock, MapColor color) { + super(Properties + .copy(baseBlock) + .mapColor(color) + .sound(BlockSounds.TERRAIN_SOUND) + .randomTicks() + ); + this.baseBlock = baseBlock; + } + + public void setPathBlock(Block roadBlock) { + this.pathBlock = roadBlock; + } + + public Block getBaseBlock() { + return baseBlock; + } + + @Override + public InteractionResult use( + BlockState state, + Level world, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hit + ) { + if (pathBlock != null && TagManager.isToolWithMineableTag(player.getMainHandItem(), MineableTags.SHOVEL)) { + world.playSound(player, pos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); + if (!world.isClientSide) { + world.setBlockAndUpdate(pos, pathBlock.defaultBlockState()); + if (!player.isCreative()) { + player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player); + } + } + return InteractionResult.SUCCESS; + } + return InteractionResult.FAIL; + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { + return Collections.singletonList(new ItemStack(this)); + } + return Collections.singletonList(new ItemStack(getBaseBlock())); + } + + @Override + public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + if (random.nextInt(16) == 0 && !canStay(state, world, pos)) { + world.setBlockAndUpdate(pos, getBaseBlock().defaultBlockState()); + } + } + + public boolean canStay(BlockState state, LevelReader worldView, BlockPos pos) { + BlockPos blockPos = pos.above(); + BlockState blockState = worldView.getBlockState(blockPos); + if (blockState.is(Blocks.SNOW) && blockState.getValue(SnowLayerBlock.LAYERS) == 1) { + return true; + } else if (blockState.getFluidState().getAmount() == 8) { + return false; + } else { + int i = LightEngine.getLightBlockInto( + worldView, + state, + pos, + blockState, + blockPos, + Direction.UP, + blockState.getLightBlock(worldView, blockPos) + ); + return i < 5; + } + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + return getBlockModel(blockId, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + ResourceLocation baseId = BuiltInRegistries.BLOCK.getKey(getBaseBlock()); + String modId = blockId.getNamespace(); + String path = blockId.getPath(); + String bottom = baseId.getNamespace() + ":block/" + baseId.getPath(); + Map textures = Maps.newHashMap(); + textures.put("%top%", modId + ":block/" + path + "_top"); + textures.put("%side%", modId + ":block/" + path + "_side"); + textures.put("%bottom%", bottom); + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_TOP_SIDE_BOTTOM, textures); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createRandomTopModel(modelId); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseTrapdoorBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseTrapdoorBlock.java new file mode 100644 index 00000000..6376c39e --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseTrapdoorBlock.java @@ -0,0 +1,161 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.TrapDoorBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockSetType; +import net.minecraft.world.level.block.state.properties.Half; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseTrapdoorBlock extends TrapDoorBlock implements RenderLayerProvider, BlockModelProvider, TagProvider, DropSelfLootProvider { + protected BaseTrapdoorBlock(BlockBehaviour.Properties properties, BlockSetType type) { + super(properties, type); + } + + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + String name = resourceLocation.getPath(); + Optional pattern = PatternsHelper.createJson( + BasePatterns.BLOCK_TRAPDOOR, + new HashMap() { + private static final long serialVersionUID = 1L; + + { + put("%modid%", resourceLocation.getNamespace()); + put("%texture%", name); + put("%side%", name.replace("trapdoor", "door_side")); + } + } + ); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); + registerBlockModel(stateId, modelId, blockState, modelCache); + boolean isTop = blockState.getValue(HALF) == Half.TOP; + boolean isOpen = blockState.getValue(OPEN); + int y = 0; + int x = (isTop && isOpen) ? 270 : isTop ? 180 : isOpen ? 90 : 0; + switch (blockState.getValue(FACING)) { + case EAST: + y = (isTop && isOpen) ? 270 : 90; + break; + case NORTH: + if (isTop && isOpen) y = 180; + break; + case SOUTH: + y = (isTop && isOpen) ? 0 : 180; + break; + case WEST: + y = (isTop && isOpen) ? 90 : 270; + break; + default: + break; + } + BlockModelRotation rotation = BlockModelRotation.by(x, y); + return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.TRAPDOORS); + itemTags.add(ItemTags.TRAPDOORS); + } + + public static class Wood extends BaseTrapdoorBlock implements BehaviourWood { + public Wood(Block source, BlockSetType type, boolean flammable) { + this(BehaviourBuilders.createTrapDoor(source.defaultMapColor(), flammable).sound(SoundType.WOOD), type); + } + + public Wood(Properties properties, BlockSetType type) { + super(properties, type); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + super.addTags(blockTags, itemTags); + blockTags.add(BlockTags.WOODEN_TRAPDOORS); + itemTags.add(ItemTags.WOODEN_TRAPDOORS); + } + } + + public static class Stone extends BaseTrapdoorBlock implements BehaviourStone { + public Stone(Block source, BlockSetType type) { + this(BehaviourBuilders.createTrapDoor(source.defaultMapColor(), false).sound(SoundType.STONE), type); + } + + public Stone(Properties properties, BlockSetType type) { + super(properties, type); + } + } + + public static class Metal extends BaseTrapdoorBlock implements BehaviourMetal { + public Metal(Block source, BlockSetType type) { + this(BehaviourBuilders.createTrapDoor(source.defaultMapColor(), false).sound(SoundType.METAL), type); + } + + public Metal(Properties properties, BlockSetType type) { + super(properties, type); + } + } + + public static BaseTrapdoorBlock from(Block source, BlockSetType type, boolean flammable) { + return BehaviourHelper.from(source, type, + (s, t) -> new Wood(s, t, flammable), + Stone::new, + Metal::new + ); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseUnderwaterWallPlantBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseUnderwaterWallPlantBlock.java new file mode 100644 index 00000000..5155c804 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseUnderwaterWallPlantBlock.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourWaterPlant; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.LiquidBlockContainer; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; + +public abstract class BaseUnderwaterWallPlantBlock extends BaseWallPlantBlock implements LiquidBlockContainer, BehaviourWaterPlant { + public BaseUnderwaterWallPlantBlock(Properties settings) { + super(settings); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return false; + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public FluidState getFluidState(BlockState state) { + return Fluids.WATER.getSource(false); + } + + @Override + public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { + return level.getFluidState(pos).getType() == Fluids.WATER && super.canSurvive(state, level, pos); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseVineBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseVineBlock.java new file mode 100644 index 00000000..df9b5c7c --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseVineBlock.java @@ -0,0 +1,163 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.behaviours.interfaces.BehaviourVine; +import org.betterx.bclib.blocks.BlockProperties.TripleShape; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.LootUtil; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.BlockTags; +import net.minecraft.util.RandomSource; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.BonemealableBlock; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.function.Function; + +@SuppressWarnings("deprecation") +public class BaseVineBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock, BehaviourVine { + public static final EnumProperty SHAPE = BlockProperties.TRIPLE_SHAPE; + private static final VoxelShape VOXEL_SHAPE = box(2, 0, 2, 14, 16, 14); + + public BaseVineBlock() { + this(0, false); + } + + public BaseVineBlock(int light) { + this(light, false); + } + + public BaseVineBlock(int light, boolean bottomOnly) { + this(light, bottomOnly, p -> p); + } + + public BaseVineBlock(int light, boolean bottomOnly, Function propMod) { + this( + propMod.apply(BehaviourBuilders + .createPlant() + .sound(SoundType.GRASS) + .lightLevel((state) -> bottomOnly + ? state.getValue(SHAPE) == TripleShape.BOTTOM + ? light + : 0 + : light) + .offsetType(BlockBehaviour.OffsetType.XZ)) + ); + } + + public BaseVineBlock(BlockBehaviour.Properties properties) { + super(properties.offsetType(BlockBehaviour.OffsetType.XZ)); + this.registerDefaultState(this.stateDefinition.any().setValue(SHAPE, TripleShape.BOTTOM)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(SHAPE); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + Vec3 vec3d = state.getOffset(view, pos); + return VOXEL_SHAPE.move(vec3d.x, vec3d.y, vec3d.z); + } + + public boolean canGenerate(BlockState state, LevelReader world, BlockPos pos) { + return isSupport(state, world, pos); + } + + @Override + public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { + return isSupport(state, world, pos); + } + + protected boolean isSupport(BlockState state, LevelReader world, BlockPos pos) { + BlockState up = world.getBlockState(pos.above()); + return up.is(this) || up.is(BlockTags.LEAVES) || canSupportCenter(world, pos.above(), Direction.DOWN); + } + + @Override + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (!canSurvive(state, world, pos)) { + return Blocks.AIR.defaultBlockState(); + } else { + if (world.getBlockState(pos.below()).getBlock() != this) return state.setValue(SHAPE, TripleShape.BOTTOM); + else if (world.getBlockState(pos.above()).getBlock() != this) return state.setValue(SHAPE, TripleShape.TOP); + return state.setValue(SHAPE, TripleShape.MIDDLE); + } + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (LootUtil.isCorrectTool(this, state, tool) || EnchantmentHelper.getItemEnchantmentLevel( + Enchantments.SILK_TOUCH, + tool + ) > 0) { + return Lists.newArrayList(new ItemStack(this)); + } else { + return Lists.newArrayList(); + } + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) { + while (world.getBlockState(pos).getBlock() == this) { + pos = pos.below(); + } + return world.getBlockState(pos).isAir(); + } + + @Override + public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) { + while (level.getBlockState(pos).getBlock() == this) { + pos = pos.below(); + } + return level.isEmptyBlock(pos); + } + + @Override + public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { + while (level.getBlockState(pos).getBlock() == this) { + pos = pos.below(); + } + level.setBlockAndUpdate(pos, defaultBlockState()); + BlocksHelper.setWithoutUpdate(level, pos, defaultBlockState()); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseWallBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseWallBlock.java new file mode 100644 index 00000000..6f0d90ae --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseWallBlock.java @@ -0,0 +1,142 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.WallBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.WallSide; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class BaseWallBlock extends WallBlock implements BlockModelProvider, TagProvider, DropSelfLootProvider { + private final Block parent; + + protected BaseWallBlock(Block source) { + super(Properties.copy(source).noOcclusion()); + this.parent = source; + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_WALL, parentId); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + String path = blockId.getPath(); + Optional pattern = Optional.empty(); + if (path.endsWith("_post")) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_WALL_POST, parentId); + } + if (path.endsWith("_side")) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_WALL_SIDE, parentId); + } + if (path.endsWith("_side_tall")) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_WALL_SIDE_TALL, parentId); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_post"); + ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + "_side"); + ResourceLocation sideTallId = new ResourceLocation( + stateId.getNamespace(), + "block/" + stateId.getPath() + "_side_tall" + ); + registerBlockModel(postId, postId, blockState, modelCache); + registerBlockModel(sideId, sideId, blockState, modelCache); + registerBlockModel(sideTallId, sideTallId, blockState, modelCache); + + ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition); + builder.part(sideId).setCondition(state -> state.getValue(NORTH_WALL) == WallSide.LOW).setUVLock(true).add(); + builder.part(sideId) + .setCondition(state -> state.getValue(EAST_WALL) == WallSide.LOW) + .setTransformation(BlockModelRotation.X0_Y90.getRotation()) + .setUVLock(true) + .add(); + builder.part(sideId) + .setCondition(state -> state.getValue(SOUTH_WALL) == WallSide.LOW) + .setTransformation(BlockModelRotation.X0_Y180.getRotation()) + .setUVLock(true) + .add(); + builder.part(sideId) + .setCondition(state -> state.getValue(WEST_WALL) == WallSide.LOW) + .setTransformation(BlockModelRotation.X0_Y270.getRotation()) + .setUVLock(true) + .add(); + builder.part(sideTallId) + .setCondition(state -> state.getValue(NORTH_WALL) == WallSide.TALL) + .setUVLock(true) + .add(); + builder.part(sideTallId) + .setCondition(state -> state.getValue(EAST_WALL) == WallSide.TALL) + .setTransformation(BlockModelRotation.X0_Y90.getRotation()) + .setUVLock(true) + .add(); + builder.part(sideTallId) + .setCondition(state -> state.getValue(SOUTH_WALL) == WallSide.TALL) + .setTransformation(BlockModelRotation.X0_Y180.getRotation()) + .setUVLock(true) + .add(); + builder.part(sideTallId) + .setCondition(state -> state.getValue(WEST_WALL) == WallSide.TALL) + .setTransformation(BlockModelRotation.X0_Y270.getRotation()) + .setUVLock(true) + .add(); + builder.part(postId).setCondition(state -> state.getValue(UP)).add(); + + return builder.build(); + } + + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.WALLS); + } + + public static class Stone extends BaseWallBlock implements BehaviourStone { + public Stone(Block source) { + super(source); + } + } + + public static class Wood extends BaseWallBlock implements BehaviourWood { + public Wood(Block source) { + super(source); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseWallPlantBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseWallPlantBlock.java new file mode 100644 index 00000000..4c9c06d2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseWallPlantBlock.java @@ -0,0 +1,104 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.util.BlocksHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import java.util.EnumMap; + +public abstract class BaseWallPlantBlock extends BasePlantBlock { + private static final EnumMap SHAPES = Maps.newEnumMap(ImmutableMap.of( + Direction.NORTH, box(1, 1, 8, 15, 15, 16), + Direction.SOUTH, box(1, 1, 0, 15, 15, 8), + Direction.WEST, box(8, 1, 1, 16, 15, 15), + Direction.EAST, box(0, 1, 1, 8, 15, 15) + )); + public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; + + protected BaseWallPlantBlock(Properties settings) { + super(settings.offsetType(OffsetType.NONE)); + } + + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(FACING); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPES.get(state.getValue(FACING)); + } + + @Override + public boolean canSurvive(BlockState state, LevelReader level, BlockPos pos) { + Direction direction = state.getValue(FACING); + BlockPos blockPos = pos.relative(direction.getOpposite()); + BlockState blockState = level.getBlockState(blockPos); + return isSupport(level, blockPos, blockState, direction); + } + + public boolean isSupport(LevelReader world, BlockPos pos, BlockState blockState, Direction direction) { + return blockState.isSolid() && blockState.isFaceSturdy(world, pos, direction); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { + BlockState blockState = this.defaultBlockState(); + LevelReader worldView = ctx.getLevel(); + BlockPos blockPos = ctx.getClickedPos(); + Direction[] directions = ctx.getNearestLookingDirections(); + for (Direction direction : directions) { + if (direction.getAxis().isHorizontal()) { + Direction direction2 = direction.getOpposite(); + blockState = blockState.setValue(FACING, direction2); + if (blockState.canSurvive(worldView, blockPos)) { + return blockState; + } + } + } + return null; + } + + @Override + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (!canSurvive(state, world, pos)) { + return Blocks.AIR.defaultBlockState(); + } else { + return state; + } + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rotation) { + return BlocksHelper.rotateHorizontal(state, rotation, FACING); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirror) { + return BlocksHelper.mirrorHorizontal(state, mirror, FACING); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BaseWeightedPlateBlock.java b/src/main/java/org/betterx/bclib/blocks/BaseWeightedPlateBlock.java new file mode 100644 index 00000000..73cbdd52 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BaseWeightedPlateBlock.java @@ -0,0 +1,72 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.BlockModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.WeightedPressurePlateBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockSetType; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class BaseWeightedPlateBlock extends WeightedPressurePlateBlock implements BlockModelProvider, DropSelfLootProvider { + private final Block parent; + + public BaseWeightedPlateBlock(Block source, BlockSetType type) { + super( + 15, + Properties.copy(source) + .noCollission() + .noOcclusion() + .requiresCorrectToolForDrops() + .strength(0.5F), + type + ); + this.parent = source; + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return getBlockModel(resourceLocation, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + ResourceLocation parentId = BuiltInRegistries.BLOCK.getKey(parent); + Optional pattern; + if (blockState.getValue(POWER) > 0) { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_DOWN, parentId); + } else { + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_UP, parentId); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + String state = blockState.getValue(POWER) > 0 ? "_down" : "_up"; + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createBlockSimple(modelId); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/BlockProperties.java b/src/main/java/org/betterx/bclib/blocks/BlockProperties.java new file mode 100644 index 00000000..50a02974 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/BlockProperties.java @@ -0,0 +1,80 @@ +package org.betterx.bclib.blocks; + +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.IntegerProperty; + +public class BlockProperties { + public static final EnumProperty TRIPLE_SHAPE = EnumProperty.create("shape", TripleShape.class); + public static final EnumProperty PENTA_SHAPE = EnumProperty.create("shape", PentaShape.class); + + public static final BooleanProperty TRANSITION = BooleanProperty.create("transition"); + public static final BooleanProperty HAS_LIGHT = BooleanProperty.create("has_light"); + public static final BooleanProperty IS_FLOOR = BooleanProperty.create("is_floor"); + public static final BooleanProperty NATURAL = BooleanProperty.create("natural"); + public static final BooleanProperty ACTIVE = BooleanProperty.create("active"); + public static final BooleanProperty SMALL = BooleanProperty.create("small"); + + public static final IntegerProperty DEFAULT_ANVIL_DURABILITY = IntegerProperty.create("durability", 0, 3); + public static final IntegerProperty DESTRUCTION = IntegerProperty.create("destruction", 0, 2); + public static final IntegerProperty ROTATION = IntegerProperty.create("rotation", 0, 3); + public static final IntegerProperty FULLNESS = IntegerProperty.create("fullness", 0, 3); + public static final IntegerProperty COLOR = IntegerProperty.create("color", 0, 7); + public static final IntegerProperty SIZE = IntegerProperty.create("size", 0, 7); + public static final IntegerProperty AGE = BlockStateProperties.AGE_3; + public static final IntegerProperty AGE_THREE = BlockStateProperties.AGE_2; + public static final BooleanProperty BOTTOM = BooleanProperty.create("bottom"); + public static final BooleanProperty TOP = BooleanProperty.create("top"); + + public enum TripleShape implements StringRepresentable { + TOP("top", 0), MIDDLE("middle", 1), BOTTOM("bottom", 2); + + private final String name; + private final int index; + + TripleShape(String name, int index) { + this.name = name; + this.index = index; + } + + @Override + public String getSerializedName() { + return name; + } + + @Override + public String toString() { + return name; + } + + public int getIndex() { + return index; + } + + public static TripleShape fromIndex(int index) { + return index > 1 ? BOTTOM : index == 1 ? MIDDLE : TOP; + } + } + + public enum PentaShape implements StringRepresentable { + BOTTOM("bottom"), PRE_BOTTOM("pre_bottom"), MIDDLE("middle"), PRE_TOP("pre_top"), TOP("top"); + + private final String name; + + PentaShape(String name) { + this.name = name; + } + + @Override + public String getSerializedName() { + return name; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/FeatureHangingSaplingBlock.java b/src/main/java/org/betterx/bclib/blocks/FeatureHangingSaplingBlock.java new file mode 100644 index 00000000..b42a8bce --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/FeatureHangingSaplingBlock.java @@ -0,0 +1,47 @@ +package org.betterx.bclib.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +public abstract class FeatureHangingSaplingBlock, FC extends FeatureConfiguration> extends FeatureSaplingBlock { + + private static final VoxelShape SHAPE = Block.box(4, 2, 4, 12, 16, 12); + + public FeatureHangingSaplingBlock(FeatureSupplier featureSupplier) { + super(featureSupplier); + } + + public FeatureHangingSaplingBlock( + FeatureSupplier featureSupplier, + int light + ) { + super(light, featureSupplier); + } + + public FeatureHangingSaplingBlock( + BlockBehaviour.Properties properties, + FeatureSupplier featureSupplier + ) { + super(properties, featureSupplier); + } + + @Override + public boolean canSurvive(BlockState blockState, LevelReader levelReader, BlockPos blockPos) { + final BlockPos target = blockPos.above(); + return this.mayPlaceOn(levelReader.getBlockState(target), levelReader, target); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPE; + } + +} diff --git a/src/main/java/org/betterx/bclib/blocks/FeatureSaplingBlock.java b/src/main/java/org/betterx/bclib/blocks/FeatureSaplingBlock.java new file mode 100644 index 00000000..70108212 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/FeatureSaplingBlock.java @@ -0,0 +1,163 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.api.v3.levelgen.features.BCLConfigureFeature; +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.RenderLayerProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.SaplingBlock; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public class FeatureSaplingBlock, FC extends FeatureConfiguration> extends SaplingBlock implements RenderLayerProvider, BlockModelProvider, DropSelfLootProvider { + + @FunctionalInterface + public interface FeatureSupplier, FC extends FeatureConfiguration> { + BCLConfigureFeature get(BlockState state); + } + + private static final VoxelShape SHAPE = Block.box(4, 0, 4, 12, 14, 12); + private final FeatureSupplier feature; + + public FeatureSaplingBlock(FeatureSupplier featureSupplier) { + this(0, featureSupplier); + } + + public FeatureSaplingBlock(int light, FeatureSupplier featureSupplier) { + this( + BehaviourBuilders.createPlant().randomTicks() + .noCollission() + .lightLevel(state -> light) + .sound(SoundType.GRASS), + featureSupplier + ); + } + + public FeatureSaplingBlock( + BlockBehaviour.Properties properties, + FeatureSupplier featureSupplier + ) { + super(null, properties); + this.feature = featureSupplier; + } + + protected BCLConfigureFeature getConfiguredFeature(BlockState state) { + return feature != null ? feature.get(state) : null; + } + + @Override + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (!canSurvive(state, world, pos)) return Blocks.AIR.defaultBlockState(); + else return state; + } + + @Override + public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) { + return random.nextInt(16) == 0; + } + + @Override + public void advanceTree(ServerLevel world, BlockPos pos, BlockState blockState, RandomSource random) { + if (blockState.getValue(STAGE) == 0) { + world.setBlock(pos, blockState.cycle(STAGE), 4); + } else { + BCLConfigureFeature conf = getConfiguredFeature(blockState); + growFeature(conf, world, pos, blockState, random); + } + } + + protected boolean growFeature( + BCLConfigureFeature feature, + ServerLevel serverLevel, + BlockPos blockPos, + BlockState originalBlockState, + RandomSource randomSource + ) { + if (feature == null) { + return false; + } else { + BlockState emptyState = serverLevel.getFluidState(blockPos).createLegacyBlock(); + serverLevel.setBlock(blockPos, emptyState, 4); + if (feature.placeInWorld(serverLevel, blockPos, randomSource)) { + if (serverLevel.getBlockState(blockPos) == emptyState) { + serverLevel.sendBlockUpdated(blockPos, originalBlockState, emptyState, 2); + } + + return true; + } else { + serverLevel.setBlock(blockPos, originalBlockState, 4); + return false; + } + } + } + + @Override + public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + this.tick(state, world, pos, random); + } + + @Override + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + super.tick(state, world, pos, random); + if (isBonemealSuccess(world, random, pos, state)) { + performBonemeal(world, random, pos, state); + } + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createBlockItem(resourceLocation); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CROSS, resourceLocation); + return ModelsHelper.fromPattern(pattern); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPE; + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/LeveledAnvilBlock.java b/src/main/java/org/betterx/bclib/blocks/LeveledAnvilBlock.java new file mode 100644 index 00000000..b42a7f28 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/LeveledAnvilBlock.java @@ -0,0 +1,56 @@ +package org.betterx.bclib.blocks; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.world.item.Tiers; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.material.MapColor; + +import java.util.List; + +public class LeveledAnvilBlock extends BaseAnvilBlock { + protected final int level; + + public LeveledAnvilBlock(MapColor color, int level) { + super(color); + this.level = level; + } + + public static int getAnvilCraftingLevel(Block anvil) { + if (anvil instanceof LeveledAnvilBlock l) return l.getCraftingLevel(); + if (anvil == Blocks.ANVIL || anvil == Blocks.CHIPPED_ANVIL || anvil == Blocks.DAMAGED_ANVIL) + return Tiers.IRON.getLevel() - 1; + return 0; + } + + public static boolean canHandle(Block anvil, int level) { + return getAnvilCraftingLevel(anvil) >= level; + } + + public static List getAnvils() { + return BuiltInRegistries.BLOCK + .stream() + .filter(b -> b instanceof LeveledAnvilBlock || b == Blocks.ANVIL) + .toList(); + } + + public static List getNamesForLevel(int level) { + MutableComponent names = getAnvils() + .stream() + .filter(b -> canHandle(b, level)) + .map(Block::getName) + .reduce( + null, + (p, c) -> p == null ? c : p.append(net.minecraft.network.chat.Component.literal(", ")).append(c) + ); + if (names == null) return List.of(); + return Minecraft.getInstance().font.split(names, 200); + } + + public int getCraftingLevel() { + return level; + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/SimpleLeavesBlock.java b/src/main/java/org/betterx/bclib/blocks/SimpleLeavesBlock.java new file mode 100644 index 00000000..7af15c4f --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/SimpleLeavesBlock.java @@ -0,0 +1,44 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.behaviours.interfaces.BehaviourLeaves; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; + +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.MapColor; + +public class SimpleLeavesBlock extends BaseBlockNotFull implements RenderLayerProvider, BehaviourLeaves { + public SimpleLeavesBlock(MapColor color) { + this( + BehaviourBuilders + .createStaticLeaves(color, true) + .sound(SoundType.GRASS) + ); + } + + public SimpleLeavesBlock(MapColor color, int light) { + this( + BehaviourBuilders + .createStaticLeaves(color, true) + .lightLevel(ignored -> light) + .sound(SoundType.GRASS) + ); + } + + public SimpleLeavesBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + + @Override + public float compostingChance() { + return 0.3f; + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/blocks/StalactiteBlock.java b/src/main/java/org/betterx/bclib/blocks/StalactiteBlock.java new file mode 100644 index 00000000..bda014de --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/StalactiteBlock.java @@ -0,0 +1,281 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlockContainer; +import net.minecraft.world.level.block.SimpleWaterloggedBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +@SuppressWarnings("deprecation") +public abstract class StalactiteBlock extends BaseBlockNotFull implements SimpleWaterloggedBlock, LiquidBlockContainer, RenderLayerProvider { + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + public static final BooleanProperty IS_FLOOR = BlockProperties.IS_FLOOR; + public static final IntegerProperty SIZE = BlockProperties.SIZE; + private static final VoxelShape[] SHAPES; + + public StalactiteBlock(Block source) { + this(Properties.copy(source).noOcclusion()); + } + + public StalactiteBlock(BlockBehaviour.Properties properties) { + super(properties); + this.registerDefaultState(getStateDefinition().any() + .setValue(SIZE, 0) + .setValue(IS_FLOOR, true) + .setValue(WATERLOGGED, false)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(WATERLOGGED, IS_FLOOR, SIZE); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPES[state.getValue(SIZE)]; + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { + LevelReader world = ctx.getLevel(); + BlockPos pos = ctx.getClickedPos(); + Direction dir = ctx.getClickedFace(); + boolean water = world.getFluidState(pos).getType() == Fluids.WATER; + + if (dir == Direction.DOWN) { + if (isThis(world, pos.above()) || canSupportCenter(world, pos.above(), Direction.DOWN)) { + return defaultBlockState().setValue(IS_FLOOR, false).setValue(WATERLOGGED, water); + } else if (isThis(world, pos.below()) || canSupportCenter(world, pos.below(), Direction.UP)) { + return defaultBlockState().setValue(IS_FLOOR, true).setValue(WATERLOGGED, water); + } else { + return null; + } + } else { + if (isThis(world, pos.below()) || canSupportCenter(world, pos.below(), Direction.UP)) { + return defaultBlockState().setValue(IS_FLOOR, true).setValue(WATERLOGGED, water); + } else if (isThis(world, pos.above()) || canSupportCenter(world, pos.above(), Direction.DOWN)) { + return defaultBlockState().setValue(IS_FLOOR, false).setValue(WATERLOGGED, water); + } else { + return null; + } + } + } + + @Override + public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) { + boolean hasUp = isThis(world, pos.above()); + boolean hasDown = isThis(world, pos.below()); + MutableBlockPos mut = new MutableBlockPos(); + if (hasUp && hasDown) { + boolean floor = state.getValue(IS_FLOOR); + BlockPos second = floor ? pos.above() : pos.below(); + BlockState bState = world.getBlockState(second); + world.setBlockAndUpdate(pos, state.setValue(SIZE, 1).setValue(IS_FLOOR, floor)); + world.setBlockAndUpdate(second, bState.setValue(SIZE, 1).setValue(IS_FLOOR, !floor)); + + bState = state; + int startSize = floor ? 1 : 2; + mut.set(pos.getX(), pos.getY() + 1, pos.getZ()); + for (int i = 0; i < 8 && isThis(bState); i++) { + world.setBlockAndUpdate( + mut, + bState.setValue(SIZE, Math.min(7, startSize + i)).setValue(IS_FLOOR, false) + ); + mut.setY(mut.getY() + 1); + bState = world.getBlockState(mut); + } + + bState = state; + startSize = floor ? 2 : 1; + mut.set(pos.getX(), pos.getY() - 1, pos.getZ()); + for (int i = 0; i < 8 && isThis(bState); i++) { + world.setBlockAndUpdate( + mut, + bState.setValue(SIZE, Math.min(7, startSize + i)).setValue(IS_FLOOR, true) + ); + mut.setY(mut.getY() - 1); + bState = world.getBlockState(mut); + } + } else if (hasDown) { + mut.setX(pos.getX()); + mut.setZ(pos.getZ()); + for (int i = 1; i < 8; i++) { + mut.setY(pos.getY() - i); + if (isThis(world, mut)) { + BlockState state2 = world.getBlockState(mut); + int size = state2.getValue(SIZE); + if (size < i) { + world.setBlockAndUpdate(mut, state2.setValue(SIZE, i).setValue(IS_FLOOR, true)); + } else { + break; + } + } else { + break; + } + } + } else if (hasUp) { + mut.setX(pos.getX()); + mut.setZ(pos.getZ()); + for (int i = 1; i < 8; i++) { + mut.setY(pos.getY() + i); + if (isThis(world, mut)) { + BlockState state2 = world.getBlockState(mut); + int size = state2.getValue(SIZE); + if (size < i) { + world.setBlockAndUpdate(mut, state2.setValue(SIZE, i).setValue(IS_FLOOR, false)); + } else { + break; + } + } else { + break; + } + } + } + } + + private boolean isThis(LevelReader world, BlockPos pos) { + return isThis(world.getBlockState(pos)); + } + + private boolean isThis(BlockState state) { + return state.getBlock() instanceof StalactiteBlock; + } + + @Override + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (!canSurvive(state, world, pos)) { + return Blocks.AIR.defaultBlockState(); + } + return state; + } + + @Override + public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { + int size = state.getValue(SIZE); + return checkUp(world, pos, size) || checkDown(world, pos, size); + } + + private boolean checkUp(BlockGetter world, BlockPos pos, int size) { + BlockPos p = pos.above(); + BlockState state = world.getBlockState(p); + return (isThis(state) && state.getValue(SIZE) >= size) || state.isCollisionShapeFullBlock(world, p); + } + + private boolean checkDown(BlockGetter world, BlockPos pos, int size) { + BlockPos p = pos.below(); + BlockState state = world.getBlockState(p); + return (isThis(state) && state.getValue(SIZE) >= size) || state.isCollisionShapeFullBlock(world, p); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CROSS_SHADED, resourceLocation); + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + BlockModelRotation rotation = blockState.getValue(IS_FLOOR) + ? BlockModelRotation.X0_Y0 + : BlockModelRotation.X180_Y0; + ResourceLocation modelId = new ResourceLocation( + stateId.getNamespace(), + stateId.getPath() + "_" + blockState.getValue(SIZE) + ); + registerBlockModel(modelId, modelId, blockState, modelCache); + return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return false; + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return false; + } + + @Override + public FluidState getFluidState(BlockState state) { + return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : Fluids.EMPTY.defaultFluidState(); + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + public static class Stone extends StalactiteBlock implements BehaviourStone { + + public Stone(Block source) { + super(source); + } + + public Stone(Properties properties) { + super(properties); + } + } + + static { + float end = 2F / 8F; + float start = 5F / 8F; + SHAPES = new VoxelShape[8]; + for (int i = 0; i < 8; i++) { + int side = Mth.floor(Mth.lerp(i / 7F, start, end) * 8F + 0.5F); + SHAPES[i] = box(side, 0, side, 16 - side, 16, 16 - side); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/blocks/TripleTerrainBlock.java b/src/main/java/org/betterx/bclib/blocks/TripleTerrainBlock.java new file mode 100644 index 00000000..a0502851 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/TripleTerrainBlock.java @@ -0,0 +1,182 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.blocks.BlockProperties.TripleShape; +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.renderer.block.model.MultiVariant; +import net.minecraft.client.renderer.block.model.Variant; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.material.MapColor; +import net.minecraft.world.phys.BlockHitResult; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +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 org.jetbrains.annotations.Nullable; + +public class TripleTerrainBlock extends BaseTerrainBlock { + public static final EnumProperty SHAPE = BlockProperties.TRIPLE_SHAPE; + + public TripleTerrainBlock(Block baseBlock) { + super(baseBlock, baseBlock.defaultMapColor()); + this.registerDefaultState(defaultBlockState().setValue(SHAPE, TripleShape.BOTTOM)); + } + + public TripleTerrainBlock(Block baseBlock, MapColor color) { + super(baseBlock, color); + this.registerDefaultState(defaultBlockState().setValue(SHAPE, TripleShape.BOTTOM)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(SHAPE); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { + Direction dir = ctx.getClickedFace(); + TripleShape shape = dir == Direction.UP + ? TripleShape.BOTTOM + : dir == Direction.DOWN ? TripleShape.TOP : TripleShape.MIDDLE; + return defaultBlockState().setValue(SHAPE, shape); + } + + @Override + public InteractionResult use( + BlockState state, + Level world, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hit + ) { + TripleShape shape = state.getValue(SHAPE); + if (shape == TripleShape.BOTTOM) { + return super.use(state, world, pos, player, hand, hit); + } + return InteractionResult.FAIL; + } + + @Override + public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + TripleShape shape = state.getValue(SHAPE); + if (shape == TripleShape.BOTTOM) { + super.randomTick(state, world, pos, random); + } else if (random.nextInt(16) == 0) { + boolean bottom = canStayBottom(world, pos); + if (shape == TripleShape.TOP) { + if (!bottom) { + world.setBlockAndUpdate(pos, Blocks.END_STONE.defaultBlockState()); + } + } else { + boolean top = canStay(state, world, pos) || isMiddle(world.getBlockState(pos.above())); + if (!top && !bottom) { + world.setBlockAndUpdate(pos, Blocks.END_STONE.defaultBlockState()); + } else if (top && !bottom) { + world.setBlockAndUpdate(pos, state.setValue(SHAPE, TripleShape.BOTTOM)); + } else if (!top) { + world.setBlockAndUpdate(pos, state.setValue(SHAPE, TripleShape.TOP)); + } + } + } + } + + protected boolean canStayBottom(LevelReader world, BlockPos pos) { + BlockPos blockPos = pos.below(); + BlockState blockState = world.getBlockState(blockPos); + if (isMiddle(blockState)) { + return true; + } else if (blockState.getFluidState().getAmount() == 8) { + return false; + } else { + return !blockState.isFaceSturdy(world, blockPos, Direction.UP); + } + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation blockId) { + return getBlockModel(blockId, defaultBlockState()); + } + + @Override + @Environment(EnvType.CLIENT) + public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { + String path = blockId.getPath(); + Optional pattern; + if (isMiddle(blockState)) { + ResourceLocation topId = new ResourceLocation(blockId.getNamespace(), path + "_top"); + pattern = PatternsHelper.createBlockSimple(topId); + } else { + Map textures = Maps.newHashMap(); + textures.put("%top%", "betterend:block/" + path + "_top"); + textures.put("%side%", "betterend:block/" + path + "_side"); + textures.put("%bottom%", "minecraft:block/end_stone"); + pattern = PatternsHelper.createJson(BasePatterns.BLOCK_TOP_SIDE_BOTTOM, textures); + } + return ModelsHelper.fromPattern(pattern); + } + + @Override + @Environment(EnvType.CLIENT) + public UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + boolean isMiddle = isMiddle(blockState); + String middle = isMiddle ? "_middle" : ""; + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + middle); + registerBlockModel(stateId, modelId, blockState, modelCache); + if (isMiddle) { + List variants = Lists.newArrayList(); + for (BlockModelRotation rotation : BlockModelRotation.values()) { + variants.add(new Variant(modelId, rotation.getRotation(), false, 1)); + } + return new MultiVariant(variants); + } else if (blockState.getValue(SHAPE) == TripleShape.TOP) { + return new MultiVariant(Lists.newArrayList( + new Variant( + modelId, + BlockModelRotation.X180_Y0.getRotation(), + false, + 1 + ), + new Variant(modelId, BlockModelRotation.X180_Y90.getRotation(), false, 1), + new Variant(modelId, BlockModelRotation.X180_Y180.getRotation(), false, 1), + new Variant(modelId, BlockModelRotation.X180_Y270.getRotation(), false, 1) + )); + } + return ModelsHelper.createRandomTopModel(modelId); + } + + protected boolean isMiddle(BlockState blockState) { + return blockState.is(this) && blockState.getValue(SHAPE) == TripleShape.MIDDLE; + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/UnderwaterPlantBlock.java b/src/main/java/org/betterx/bclib/blocks/UnderwaterPlantBlock.java new file mode 100644 index 00000000..e66f19ef --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/UnderwaterPlantBlock.java @@ -0,0 +1,134 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.util.LootUtil; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.BonemealableBlock; +import net.minecraft.world.level.block.LiquidBlockContainer; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import com.google.common.collect.Lists; + +import java.util.List; + +public abstract class UnderwaterPlantBlock extends BaseBlockNotFull implements RenderLayerProvider, BonemealableBlock, LiquidBlockContainer { + private static final VoxelShape SHAPE = box(4, 0, 4, 12, 14, 12); + + public UnderwaterPlantBlock(Properties settings) { + super(settings); + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + Vec3 vec3d = state.getOffset(view, pos); + return SHAPE.move(vec3d.x, vec3d.y, vec3d.z); + } + + @Override + @SuppressWarnings("deprecation") + public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { + BlockState down = world.getBlockState(pos.below()); + state = world.getBlockState(pos); + return isTerrain(down) && state.getFluidState().getType().equals(Fluids.WATER.getSource()); + } + + protected abstract boolean isTerrain(BlockState state); + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (!canSurvive(state, world, pos)) { + world.destroyBlock(pos, true); + return Blocks.WATER.defaultBlockState(); + } else { + return state; + } + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (LootUtil.isCorrectTool(this, state, tool) || EnchantmentHelper.getItemEnchantmentLevel( + Enchantments.SILK_TOUCH, + tool + ) > 0) { + return Lists.newArrayList(new ItemStack(this)); + } else { + return Lists.newArrayList(); + } + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) { + return true; + } + + @Override + public boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { + ItemEntity item = new ItemEntity( + level, + pos.getX() + 0.5, + pos.getY() + 0.5, + pos.getZ() + 0.5, + new ItemStack(this) + ); + level.addFreshEntity(item); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return false; + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public FluidState getFluidState(BlockState state) { + return Fluids.WATER.getSource(false); + } + +} diff --git a/src/main/java/org/betterx/bclib/blocks/UnderwaterPlantWithAgeBlock.java b/src/main/java/org/betterx/bclib/blocks/UnderwaterPlantWithAgeBlock.java new file mode 100644 index 00000000..319a36b1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/UnderwaterPlantWithAgeBlock.java @@ -0,0 +1,58 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourBuilders; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; + +public abstract class UnderwaterPlantWithAgeBlock extends UnderwaterPlantBlock { + public static final IntegerProperty AGE = BlockProperties.AGE; + + public UnderwaterPlantWithAgeBlock(BlockBehaviour.Properties properties) { + super(properties.randomTicks()); + } + + public UnderwaterPlantWithAgeBlock() { + this(BehaviourBuilders + .createWaterPlant() + .sound(SoundType.WET_GRASS) + .offsetType(BlockBehaviour.OffsetType.XZ) + ); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(AGE); + } + + public abstract void grow(WorldGenLevel world, RandomSource random, BlockPos pos); + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + if (random.nextInt(4) == 0) { + int age = state.getValue(AGE); + if (age < 3) { + world.setBlockAndUpdate(pos, state.setValue(AGE, age + 1)); + } else { + grow(world, random, pos); + } + } + } + + @Override + @SuppressWarnings("deprecation") + public void tick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + super.tick(state, world, pos, random); + if (isBonemealSuccess(world, random, pos, state)) { + performBonemeal(world, random, pos, state); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/UpDownPlantBlock.java b/src/main/java/org/betterx/bclib/blocks/UpDownPlantBlock.java new file mode 100644 index 00000000..ffcc7053 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/UpDownPlantBlock.java @@ -0,0 +1,115 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.behaviours.interfaces.BehaviourPlant; +import org.betterx.bclib.client.render.BCLRenderLayer; +import org.betterx.bclib.interfaces.RenderLayerProvider; +import org.betterx.bclib.interfaces.tools.AddMineableShears; +import org.betterx.bclib.util.LootUtil; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import com.google.common.collect.Lists; + +import java.util.List; + +public abstract class UpDownPlantBlock extends BaseBlockNotFull implements RenderLayerProvider, AddMineableShears, BehaviourPlant { + private static final VoxelShape SHAPE = box(4, 0, 4, 12, 16, 12); + + public UpDownPlantBlock() { + this(BehaviourBuilders + .createPlant() + .sound(SoundType.GRASS) + ); + } + + public UpDownPlantBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + protected abstract boolean isTerrain(BlockState state); + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPE; + } + + @Override + @SuppressWarnings("deprecation") + public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { + BlockState down = world.getBlockState(pos.below()); + BlockState up = world.getBlockState(pos.above()); + return (isTerrain(down) || down.getBlock() == this) && (isSupport(up, world, pos) || up.getBlock() == this); + } + + protected boolean isSupport(BlockState state, LevelReader world, BlockPos pos) { + return canSupportCenter(world, pos.above(), Direction.UP); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (!canSurvive(state, world, pos)) { + return Blocks.AIR.defaultBlockState(); + } else { + return state; + } + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + ItemStack tool = builder.getParameter(LootContextParams.TOOL); + if (LootUtil.isCorrectTool(this, state, tool) || EnchantmentHelper.getItemEnchantmentLevel( + Enchantments.SILK_TOUCH, + tool + ) > 0) { + return Lists.newArrayList(new ItemStack(this)); + } else { + return Lists.newArrayList(); + } + } + + @Override + public BCLRenderLayer getRenderLayer() { + return BCLRenderLayer.CUTOUT; + } + + @Override + public void playerDestroy( + Level world, + Player player, + BlockPos pos, + BlockState state, + BlockEntity blockEntity, + ItemStack stack + ) { + super.playerDestroy(world, player, pos, state, blockEntity, stack); + world.neighborChanged(pos, Blocks.AIR, pos.below()); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/WallMushroomBlock.java b/src/main/java/org/betterx/bclib/blocks/WallMushroomBlock.java new file mode 100644 index 00000000..2ff7115b --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/WallMushroomBlock.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.blocks; + +import org.betterx.bclib.behaviours.BehaviourBuilders; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.LootParams; + +import com.google.common.collect.Lists; + +import java.util.List; + +public abstract class WallMushroomBlock extends BaseWallPlantBlock { + public WallMushroomBlock(int light) { + super(BehaviourBuilders.createPlant() + .destroyTime(0.2F) + .lightLevel(state -> light) + .sound(SoundType.WOOD) + ); + } + + protected WallMushroomBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + return Lists.newArrayList(new ItemStack(this)); + } + + @Override + public boolean isSupport(LevelReader world, BlockPos pos, BlockState blockState, Direction direction) { + return blockState.isSolid() && blockState.isFaceSturdy(world, pos, direction); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/signs/BaseHangingSignBlock.java b/src/main/java/org/betterx/bclib/blocks/signs/BaseHangingSignBlock.java new file mode 100644 index 00000000..8d1fbbf9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/signs/BaseHangingSignBlock.java @@ -0,0 +1,116 @@ +package org.betterx.bclib.blocks.signs; + +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.complexmaterials.BCLWoodTypeWrapper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.CustomItemProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.HangingSignItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.CeilingHangingSignBlock; +import net.minecraft.world.level.block.StandingSignBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.RotationSegment; +import net.minecraft.world.level.block.state.properties.WoodType; +import net.minecraft.world.level.material.MapColor; + +import java.util.List; +import java.util.function.Supplier; + +public abstract class BaseHangingSignBlock extends CeilingHangingSignBlock implements BlockModelProvider, CustomItemProvider, TagProvider { + protected final Supplier wallSign; + private BlockItem customItem; + private BaseWallHangingSignBlock wallSignBlock; + + @FunctionalInterface + public interface WallSignProvider { + BaseWallHangingSignBlock create(Properties properties, WoodType woodType); + } + + protected BaseHangingSignBlock(WoodType type, MapColor color, boolean flammable, WallSignProvider provider) { + super(BehaviourBuilders.createSign(color, flammable), type); + this.wallSign = () -> provider.create(BehaviourBuilders.createWallSign(color, this, flammable), type); + } + + public BaseWallHangingSignBlock getWallSignBlock() { + if (wallSignBlock == null) { + wallSignBlock = wallSign.get(); + } + return wallSignBlock; + } + + @Override + public float getYRotationDegrees(BlockState blockState) { + return RotationSegment.convertToDegrees(blockState.getValue(StandingSignBlock.ROTATION)); + } + + @Override + public BlockItem getCustomItem(ResourceLocation blockID, Item.Properties settings) { + if (customItem == null) { + customItem = new HangingSignItem(this, getWallSignBlock(), settings.stacksTo(16)); + } + return customItem; + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.CEILING_HANGING_SIGNS); + itemTags.add(ItemTags.HANGING_SIGNS); + } + + public static class Wood extends BaseHangingSignBlock implements BehaviourWood { + public Wood(WoodType type) { + this(type, MapColor.WOOD, true); + } + + public Wood(BCLWoodTypeWrapper type) { + this(type.type, type.color, type.flammable); + } + + public Wood(WoodType type, MapColor color, boolean flammable) { + super(type, color, flammable, BaseWallHangingSignBlock.Wood::new); + } + } + + public static class Stone extends BaseHangingSignBlock implements BehaviourStone { + public Stone(WoodType type) { + this(type, MapColor.WOOD, true); + } + + public Stone(BCLWoodTypeWrapper type) { + this(type.type, type.color, type.flammable); + } + + public Stone(WoodType type, MapColor color, boolean flammable) { + super(type, color, flammable, BaseWallHangingSignBlock.Stone::new); + } + } + + public static class Metal extends BaseHangingSignBlock implements BehaviourMetal { + public Metal(WoodType type) { + this(type, MapColor.WOOD, true); + } + + public Metal(BCLWoodTypeWrapper type) { + this(type.type, type.color, type.flammable); + } + + public Metal(WoodType type, MapColor color, boolean flammable) { + super(type, color, flammable, BaseWallHangingSignBlock.Stone::new); + } + } + + public static BaseHangingSignBlock from(WoodType type) { + return new BaseHangingSignBlock.Wood(type); + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/signs/BaseSignBlock.java b/src/main/java/org/betterx/bclib/blocks/signs/BaseSignBlock.java new file mode 100644 index 00000000..96ecab7a --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/signs/BaseSignBlock.java @@ -0,0 +1,89 @@ +package org.betterx.bclib.blocks.signs; + +import org.betterx.bclib.api.v3.datagen.DropSelfLootProvider; +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.behaviours.interfaces.BehaviourExplosionResistant; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.complexmaterials.BCLWoodTypeWrapper; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.CustomItemProvider; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.SignItem; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.StandingSignBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.RotationSegment; +import net.minecraft.world.level.block.state.properties.WoodType; +import net.minecraft.world.level.material.MapColor; + +import java.util.List; +import java.util.function.Supplier; + +@SuppressWarnings("deprecation") +public abstract class BaseSignBlock extends StandingSignBlock implements BlockModelProvider, CustomItemProvider, TagProvider, DropSelfLootProvider, BehaviourExplosionResistant { + protected final Supplier wallSign; + private BlockItem customItem; + private BaseWallSignBlock wallSignBlock; + + + @FunctionalInterface + public interface WallSignProvider { + BaseWallSignBlock create(Properties properties, WoodType woodType); + } + + protected BaseSignBlock(WoodType type, MapColor color, boolean flammable, WallSignProvider provider) { + super(BehaviourBuilders.createSign(color, flammable), type); + this.wallSign = () -> provider.create(BehaviourBuilders.createWallSign(color, this, flammable), type); + } + + public BaseWallSignBlock getWallSignBlock() { + if (wallSignBlock == null) { + wallSignBlock = wallSign.get(); + } + return wallSignBlock; + } + + @Override + public float getYRotationDegrees(BlockState blockState) { + return RotationSegment.convertToDegrees(blockState.getValue(StandingSignBlock.ROTATION)); + } + + @Override + public BlockItem getCustomItem(ResourceLocation blockID, Item.Properties settings) { + if (customItem == null) { + customItem = new SignItem(settings, this, getWallSignBlock()); + } + return customItem; + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.STANDING_SIGNS); + itemTags.add(ItemTags.SIGNS); + } + + public static class Wood extends BaseSignBlock implements BehaviourWood { + public Wood(WoodType type) { + this(type, MapColor.WOOD, true); + } + + public Wood(BCLWoodTypeWrapper type) { + this(type.type, type.color, type.flammable); + } + + public Wood(WoodType type, MapColor color, boolean flammable) { + super(type, color, flammable, BaseWallSignBlock.Wood::new); + } + } + + public static BaseSignBlock from(BCLWoodTypeWrapper type) { + return new BaseSignBlock.Wood(type); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/blocks/signs/BaseWallHangingSignBlock.java b/src/main/java/org/betterx/bclib/blocks/signs/BaseWallHangingSignBlock.java new file mode 100644 index 00000000..9fb84e9d --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/signs/BaseWallHangingSignBlock.java @@ -0,0 +1,47 @@ +package org.betterx.bclib.blocks.signs; + +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.WallHangingSignBlock; +import net.minecraft.world.level.block.state.properties.WoodType; + +import java.util.List; + +public abstract class BaseWallHangingSignBlock extends WallHangingSignBlock implements TagProvider { + protected BaseWallHangingSignBlock( + Properties properties, + WoodType woodType + ) { + super(properties, woodType); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.WALL_HANGING_SIGNS); + } + + public static class Wood extends BaseWallHangingSignBlock implements BehaviourWood { + public Wood(Properties properties, WoodType woodType) { + super(properties, woodType); + } + } + + public static class Stone extends BaseWallHangingSignBlock implements BehaviourStone { + public Stone(Properties properties, WoodType woodType) { + super(properties, woodType); + } + } + + public static class Metal extends BaseWallHangingSignBlock implements BehaviourMetal { + public Metal(Properties properties, WoodType woodType) { + super(properties, woodType); + } + } +} diff --git a/src/main/java/org/betterx/bclib/blocks/signs/BaseWallSignBlock.java b/src/main/java/org/betterx/bclib/blocks/signs/BaseWallSignBlock.java new file mode 100644 index 00000000..14fed92d --- /dev/null +++ b/src/main/java/org/betterx/bclib/blocks/signs/BaseWallSignBlock.java @@ -0,0 +1,30 @@ +package org.betterx.bclib.blocks.signs; + +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.interfaces.TagProvider; + +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.WallSignBlock; +import net.minecraft.world.level.block.state.properties.WoodType; + +import java.util.List; + +public abstract class BaseWallSignBlock extends WallSignBlock implements TagProvider { + protected BaseWallSignBlock(Properties properties, WoodType woodType) { + super(properties, woodType); + } + + @Override + public void addTags(List> blockTags, List> itemTags) { + blockTags.add(BlockTags.WALL_SIGNS); + } + + public static class Wood extends BaseWallSignBlock implements BehaviourWood { + public Wood(Properties properties, WoodType woodType) { + super(properties, woodType); + } + } +} diff --git a/src/main/java/org/betterx/bclib/client/BCLibClient.java b/src/main/java/org/betterx/bclib/client/BCLibClient.java new file mode 100644 index 00000000..fba4f034 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/BCLibClient.java @@ -0,0 +1,70 @@ +package org.betterx.bclib.client; + +import org.betterx.bclib.api.v2.ModIntegrationAPI; +import org.betterx.bclib.api.v2.PostInitAPI; +import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI; +import org.betterx.bclib.client.models.CustomModelBakery; +import org.betterx.bclib.client.textures.AtlasSetManager; +import org.betterx.bclib.client.textures.SpriteLister; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.registry.BaseBlockEntityRenders; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.client.WorldsTogetherClient; + +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.model.*; + +import org.jetbrains.annotations.Nullable; + +public class BCLibClient implements ClientModInitializer, ModelResourceProvider, ModelVariantProvider { + private static CustomModelBakery modelBakery; + + public static CustomModelBakery lazyModelbakery() { + if (modelBakery == null) { + modelBakery = new CustomModelBakery(); + } + return modelBakery; + } + + @Override + public void onInitializeClient() { + modelBakery = new CustomModelBakery(); + + WorldsTogetherClient.onInitializeClient(); + ModIntegrationAPI.registerAll(); + BaseBlockEntityRenders.register(); + DataExchangeAPI.prepareClientside(); + PostInitAPI.postInit(true); + ModelLoadingRegistry.INSTANCE.registerResourceProvider(rm -> this); + ModelLoadingRegistry.INSTANCE.registerVariantProvider(rm -> this); + + WorldsTogether.SURPRESS_EXPERIMENTAL_DIALOG = Configs.CLIENT_CONFIG.suppressExperimentalDialog(); + + AtlasSetManager.addSource(AtlasSetManager.VANILLA_BLOCKS, new SpriteLister("entity/chest")); + AtlasSetManager.addSource(AtlasSetManager.VANILLA_BLOCKS, new SpriteLister("blocks")); + } + + @Override + public @Nullable UnbakedModel loadModelResource( + ResourceLocation resourceId, + ModelProviderContext context + ) throws ModelProviderException { + return modelBakery.getBlockModel(resourceId); + } + + @Override + public @Nullable UnbakedModel loadModelVariant( + ModelResourceLocation modelId, + ModelProviderContext context + ) throws ModelProviderException { + return modelId.getVariant().equals("inventory") + ? modelBakery.getItemModel(modelId) + : modelBakery.getBlockModel(modelId); + } + + +} diff --git a/src/main/java/org/betterx/bclib/client/gui/modmenu/EntryPoint.java b/src/main/java/org/betterx/bclib/client/gui/modmenu/EntryPoint.java new file mode 100644 index 00000000..29998626 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/modmenu/EntryPoint.java @@ -0,0 +1,12 @@ +package org.betterx.bclib.client.gui.modmenu; + +//import org.betterx.bclib.integration.modmenu.ModMenuIntegration; +// +//@Deprecated() +//public class EntryPoint extends ModMenuIntegration { +// //public static final Object entrypointObject = createEntrypoint(new EntryPoint()); +// +// public EntryPoint() { +// super(MainScreen::new); +// } +//} diff --git a/src/main/java/org/betterx/bclib/client/gui/modmenu/MainScreen.java b/src/main/java/org/betterx/bclib/client/gui/modmenu/MainScreen.java new file mode 100644 index 00000000..d46e821c --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/modmenu/MainScreen.java @@ -0,0 +1,134 @@ +package org.betterx.bclib.client.gui.modmenu; + +import de.ambertation.wunderlib.ui.layout.components.*; +import de.ambertation.wunderlib.ui.vanilla.LayoutScreenWithIcon; +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.ConfigKeeper; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.config.NamedPathConfig; +import org.betterx.bclib.config.NamedPathConfig.ConfigTokenDescription; +import org.betterx.bclib.config.NamedPathConfig.DependendConfigToken; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import org.jetbrains.annotations.Nullable; + +public class MainScreen extends LayoutScreenWithIcon { + static final ResourceLocation BCLIB_LOGO_LOCATION = new ResourceLocation(BCLib.MOD_ID, "icon.png"); + + public MainScreen(@Nullable Screen parent) { + super(parent, BCLIB_LOGO_LOCATION, Component.translatable("title.bclib.modmenu.main"), 10, 10, 20); + } + + protected Component getComponent(NamedPathConfig config, ConfigTokenDescription option, String type) { + return Component.translatable(type + ".config." + config.configID + option.getPath()); + } + + Map> dependentWidgets = new HashMap<>(); + + protected void updateEnabledState() { + dependentWidgets.forEach((cb, supl) -> cb.setEnabled(supl.get())); + } + + @SuppressWarnings("unchecked") + protected void addRow(VerticalStack grid, NamedPathConfig config, ConfigTokenDescription option) { + if (ConfigKeeper.BooleanEntry.class.isAssignableFrom(option.token.type)) { + addCheckbox(grid, config, (ConfigTokenDescription) option); + } else if (ConfigKeeper.FloatEntry.class.isAssignableFrom(option.token.type)) { + addFloat(grid, config, (ConfigTokenDescription) option); + } + + grid.addSpacer(2); + } + + protected void addFloat(VerticalStack grid, NamedPathConfig config, ConfigTokenDescription option) { + if (option.topPadding > 0) { + grid.addSpacer(option.topPadding); + } + HorizontalStack row = grid.addRow(); + if (option.leftPadding > 0) { + row.addSpacer(option.leftPadding); + } + Range cb = row.addRange( + fixed(200), fit(), + getComponent(config, option, "title"), + option.minRange, + option.maxRange, + config.getRaw(option.token) + ).onChange( + (caller, state) -> { + config.set(option.token, state); + } + ); + } + + protected void addCheckbox(VerticalStack grid, NamedPathConfig config, ConfigTokenDescription option) { + if (option.topPadding > 0) { + grid.addSpacer(option.topPadding); + } + HorizontalStack row = grid.addRow(); + if (option.leftPadding > 0) { + row.addSpacer(option.leftPadding); + } + Checkbox cb = row.addCheckbox( + fit(), fit(), + getComponent(config, option, "title"), + config.getRaw(option.token) + ).onChange( + (caller, state) -> { + config.set(option.token, state); + updateEnabledState(); + } + ); + + if (option.token instanceof DependendConfigToken) { + dependentWidgets.put(cb, () -> option.token.dependenciesTrue(config)); + cb.setEnabled(option.token.dependenciesTrue(config)); + } + } + + @Override + public boolean shouldCloseOnEsc() { + return false; + } + + + @Override + protected LayoutComponent initContent() { + + VerticalStack content = new VerticalStack(fit(), fit()).setDebugName("content"); + + Configs.GENERATOR_CONFIG.getAllOptions() + .stream() + .filter(o -> !o.hidden) + .forEach(o -> addRow(content, Configs.GENERATOR_CONFIG, o)); + content.addSpacer(12); + Configs.MAIN_CONFIG.getAllOptions() + .stream() + .filter(o -> !o.hidden) + .forEach(o -> addRow(content, Configs.MAIN_CONFIG, o)); + content.addSpacer(12); + Configs.CLIENT_CONFIG.getAllOptions() + .stream() + .filter(o -> !o.hidden) + .forEach(o -> addRow(content, Configs.CLIENT_CONFIG, o)); + + + VerticalStack grid = new VerticalStack(fill(), fill()).setDebugName("main grid"); + grid.addScrollable(content); + grid.addSpacer(8); + grid.addButton(fit(), fit(), CommonComponents.GUI_DONE).onPress((button) -> { + Configs.CLIENT_CONFIG.saveChanges(); + Configs.GENERATOR_CONFIG.saveChanges(); + Configs.MAIN_CONFIG.saveChanges(); + onClose(); + }).alignRight(); + return grid; + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/modmenu/TestScreen.java b/src/main/java/org/betterx/bclib/client/gui/modmenu/TestScreen.java new file mode 100644 index 00000000..d01974ac --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/modmenu/TestScreen.java @@ -0,0 +1,181 @@ +package org.betterx.bclib.client.gui.modmenu; + +import de.ambertation.wunderlib.ui.ColorHelper; +import de.ambertation.wunderlib.ui.layout.components.*; +import de.ambertation.wunderlib.ui.layout.values.Size; +import de.ambertation.wunderlib.ui.vanilla.LayoutScreen; +import org.betterx.bclib.BCLib; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class TestScreen extends LayoutScreen { + public TestScreen(Component component) { + super(component); + } + + public TestScreen(Screen parent, Component component) { + super(parent, component); + } + + @Override + protected LayoutComponent initContent() { + VerticalStack page1 = new VerticalStack(fill(), fit()); + page1.addText(fit(), fit(), Component.literal("Page 1")).alignLeft().centerVertical(); + page1.addButton(fit(), fit(), Component.literal("A1")).onPress((bt) -> System.out.println("A1")) + .centerHorizontal(); + page1.addButton(fit(), fit(), Component.literal("A2")).onPress((bt) -> System.out.println("A2")) + .centerHorizontal(); + page1.addButton(fit(), fit(), Component.literal("A3")).onPress((bt) -> System.out.println("A3")) + .centerHorizontal(); + page1.addButton(fit(), fit(), Component.literal("A4")).onPress((bt) -> System.out.println("A4")) + .centerHorizontal(); + page1.addRange(fixed(100), fit(), Component.literal("N1"), 0, 10, 5); + + VerticalStack page2 = new VerticalStack(fill(), fit()); + page2.addText(fit(), fit(), Component.literal("Page 2")).alignRight().centerVertical(); + page2.addButton(fit(), fit(), Component.literal("B1")).onPress((bt) -> System.out.println("B1")) + .centerHorizontal(); + page2.addButton(fit(), fit(), Component.literal("B2")).onPress((bt) -> System.out.println("B2")) + .centerHorizontal(); + page2.addButton(fit(), fit(), Component.literal("B3")).onPress((bt) -> System.out.println("B3")) + .centerHorizontal(); + page2.addButton(fit(), fit(), Component.literal("B4")).onPress((bt) -> System.out.println("B4")) + .centerHorizontal(); + page2.addRange(fixed(100), fit(), Component.literal("N2"), 0, 10, 5); + + + Container c = new Container(fill(), fixed(40)); + c.addChild(new Button(fit(), fit(), Component.literal("Containerd")).onPress(bt -> { + System.out.println("Containerd"); + }).centerHorizontal().centerVertical()); + c.setBackgroundColor(0x77000000); + VerticalStack rows = new VerticalStack(fit(), fitOrFill()); + + rows.addFiller(); + rows.add(new Text( + fitOrFill(), fixed(20), + Component.literal("Some Text") + ).alignRight() + ); + rows.add(new Text( + fitOrFill(), fixed(20), + Component.literal("Some other, longer Text") + ).centerHorizontal() + ); + rows.addHorizontalSeparator(16).alignTop(); + rows.add(new Tabs(fixed(300), fixed(80)).addPage( + Component.literal("PAGE 1"), + VerticalScroll.create(page1) + ) + .addPage( + Component.literal("PAGE 2"), + VerticalScroll.create(page2) + )); + rows.add(new Input(fitOrFill(), fit(), Component.literal("Input"), "0xff00ff")); + rows.add(new ColorSwatch(fit(), fit(), ColorHelper.LIGHT_PURPLE).centerHorizontal()); + rows.add(new ColorPicker( + fill(), + fit(), + Component.literal("Color"), + ColorHelper.GREEN + ).centerHorizontal()); + rows.add(new Text( + fitOrFill(), fixed(20), + Component.literal("Some blue text") + ).centerHorizontal().setColor(ColorHelper.BLUE) + ); + rows.addHLine(fixed(32), fixed(16)); + rows.add(c); + rows.addCheckbox( + fitOrFill(), + fit(), + Component.literal("Hide"), + false + ).onChange( + (cb, state) -> c.setVisible(!state) + ); + rows.addSpacer(16); + rows.add(new Image( + fixed(24), fixed(24), + BCLib.makeID("icon.png"), + new Size(512, 512) + ).centerHorizontal() + ); + rows.add(new MultiLineText( + fill(), fit(), + Component.literal( + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.") + ).setColor(ColorHelper.LIGHT_PURPLE).centerHorizontal() + ); + + rows.addHorizontalLine(16); + rows.add(new Range<>( + fill(), fit(), + Component.literal("Integer"), + 10, 90, 20 + ).onChange( + (slider, value) -> { + System.out.println(value); + } + )); + rows.addSpacer(8); + rows.add(new Range<>( + fill(), fit(), + Component.literal("Float"), + 10f, 90f, 20f + ).onChange( + (slider, value) -> { + System.out.println(value); + } + )); + rows.addSpacer(16); + Checkbox cb1 = new Checkbox( + fit(), fit(), + Component.literal("Some Sub-State"), + false, true + ).onChange( + (checkbox, value) -> { + System.out.println(value); + } + ); + rows.add(new Checkbox( + fit(), fit(), + Component.literal("Some Selectable State"), + false, true + ).onChange( + (checkbox, value) -> { + System.out.println(value); + cb1.setEnabled(value); + } + )); + rows.add(cb1); + rows.addSpacer(16); + rows.add(new Button( + fit(), fit(), + Component.literal("test") + ).onPress( + (bt) -> { + System.out.println("clicked test"); + } + ).centerHorizontal() + ); + rows.addSpacer(8); + rows.add(new Button( + fit(), fit(), + Component.literal("Hello World") + ).onPress( + (bt) -> { + System.out.println("clicked hello"); + } + ).centerHorizontal() + ); + rows.addFiller(); + + return HorizontalStack.centered(VerticalScroll.create(fit(), relative(1), rows)); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/AtomicProgressListener.java b/src/main/java/org/betterx/bclib/client/gui/screens/AtomicProgressListener.java new file mode 100644 index 00000000..91754da1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/AtomicProgressListener.java @@ -0,0 +1,10 @@ +package org.betterx.bclib.client.gui.screens; + +import net.minecraft.network.chat.Component; + +public interface AtomicProgressListener { + void incAtomic(int maxProgress); + void resetAtomic(); + void stop(); + void progressStage(Component component); +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/BCLibLayoutScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/BCLibLayoutScreen.java new file mode 100644 index 00000000..612d63ec --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/BCLibLayoutScreen.java @@ -0,0 +1,38 @@ +package org.betterx.bclib.client.gui.screens; + +import de.ambertation.wunderlib.ui.vanilla.LayoutScreenWithIcon; +import org.betterx.bclib.BCLib; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import org.jetbrains.annotations.Nullable; + +public abstract class BCLibLayoutScreen extends LayoutScreenWithIcon { + static final ResourceLocation BCLIB_LOGO_LOCATION = new ResourceLocation(BCLib.MOD_ID, "icon.png"); + static final ResourceLocation BCLIB_LOGO_WHITE_LOCATION = new ResourceLocation(BCLib.MOD_ID, "icon_bright.png"); + + public BCLibLayoutScreen( + Component component + ) { + super(BCLIB_LOGO_LOCATION, component); + } + + public BCLibLayoutScreen( + @Nullable Screen parent, + Component component + ) { + super(parent, BCLIB_LOGO_LOCATION, component); + } + + public BCLibLayoutScreen( + @Nullable Screen parent, + Component component, + int topPadding, + int bottomPadding, + int sidePadding + ) { + super(parent, BCLIB_LOGO_LOCATION, component, topPadding, bottomPadding, sidePadding); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/ConfirmFixScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/ConfirmFixScreen.java new file mode 100644 index 00000000..79e52ebf --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/ConfirmFixScreen.java @@ -0,0 +1,67 @@ +package org.betterx.bclib.client.gui.screens; + + +import de.ambertation.wunderlib.ui.layout.components.Checkbox; +import de.ambertation.wunderlib.ui.layout.components.HorizontalStack; +import de.ambertation.wunderlib.ui.layout.components.LayoutComponent; +import de.ambertation.wunderlib.ui.layout.components.VerticalStack; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import org.jetbrains.annotations.Nullable; + +@Environment(EnvType.CLIENT) +public class ConfirmFixScreen extends BCLibLayoutScreen { + protected final ConfirmFixScreen.Listener listener; + private final Component description; + protected int id; + + public ConfirmFixScreen(@Nullable Screen parent, ConfirmFixScreen.Listener listener) { + super(parent, Component.translatable("bclib.datafixer.backupWarning.title")); + this.listener = listener; + + this.description = Component.translatable("bclib.datafixer.backupWarning.message"); + } + + public boolean shouldCloseOnEsc() { + return true; + } + + @Override + protected LayoutComponent initContent() { + VerticalStack grid = new VerticalStack(fill(), fill()); + grid.addFiller(); + grid.addMultilineText(fill(), fit(), this.description).centerHorizontal(); + grid.addSpacer(8); + Checkbox backup = grid.addCheckbox( + fit(), fit(), + Component.translatable("bclib.datafixer.backupWarning.backup"), + true + ); + grid.addSpacer(4); + Checkbox fix = grid.addCheckbox( + fit(), fit(), + Component.translatable("bclib.datafixer.backupWarning.fix"), + true + ); + grid.addSpacer(20); + + HorizontalStack row = grid.addRow().centerHorizontal(); + row.addButton(fit(), fit(), CommonComponents.GUI_CANCEL).onPress((button) -> onClose()); + row.addSpacer(4); + row.addButton(fit(), fit(), CommonComponents.GUI_PROCEED) + .onPress((button) -> this.listener.proceed(backup.isChecked(), fix.isChecked())); + + return grid; + } + + @Environment(EnvType.CLIENT) + public interface Listener { + void proceed(boolean createBackup, boolean applyPatches); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/ConfirmRestartScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/ConfirmRestartScreen.java new file mode 100644 index 00000000..947d7469 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/ConfirmRestartScreen.java @@ -0,0 +1,52 @@ +package org.betterx.bclib.client.gui.screens; + +import de.ambertation.wunderlib.ui.layout.components.LayoutComponent; +import de.ambertation.wunderlib.ui.layout.components.VerticalStack; +import de.ambertation.wunderlib.ui.layout.values.Value; + +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + + +@Environment(EnvType.CLIENT) +public class ConfirmRestartScreen extends BCLibLayoutScreen { + private final Component description; + private final ConfirmRestartScreen.Listener listener; + + public ConfirmRestartScreen(ConfirmRestartScreen.Listener listener) { + this(listener, null); + } + + public ConfirmRestartScreen(ConfirmRestartScreen.Listener listener, Component message) { + super(Component.translatable("title.bclib.confirmrestart")); + + this.description = message == null ? Component.translatable("message.bclib.confirmrestart") : message; + this.listener = listener; + } + + public boolean shouldCloseOnEsc() { + return false; + } + + @Override + protected LayoutComponent initContent() { + VerticalStack grid = new VerticalStack(fill(), fill()); + grid.addFiller(); + grid.addMultilineText(Value.relative(0.9), fit(), this.description).centerHorizontal(); + grid.addSpacer(10); + grid.addButton(fit(), fit(), CommonComponents.GUI_PROCEED) + .onPress((button) -> listener.proceed()) + .centerHorizontal(); + grid.addFiller(); + + return grid; + } + + @Environment(EnvType.CLIENT) + public interface Listener { + void proceed(); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/LevelFixErrorScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/LevelFixErrorScreen.java new file mode 100644 index 00000000..d8dec75e --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/LevelFixErrorScreen.java @@ -0,0 +1,74 @@ +package org.betterx.bclib.client.gui.screens; + +import de.ambertation.wunderlib.ui.ColorHelper; +import de.ambertation.wunderlib.ui.layout.components.HorizontalStack; +import de.ambertation.wunderlib.ui.layout.components.LayoutComponent; +import de.ambertation.wunderlib.ui.layout.components.VerticalStack; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class LevelFixErrorScreen extends BCLibLayoutScreen { + private final String[] errors; + final Listener onContinue; + + public LevelFixErrorScreen(Screen parent, String[] errors, Listener onContinue) { + super(parent, Component.translatable("title.bclib.datafixer.error"), 10, 10, 10); + this.errors = errors; + this.onContinue = onContinue; + } + + + @Override + protected LayoutComponent initContent() { + VerticalStack grid = new VerticalStack(fill(), fill()); + grid.addSpacer(4); + grid.addMultilineText(fill(), fit(), Component.translatable("message.bclib.datafixer.error")) + .centerHorizontal(); + grid.addSpacer(8); + + HorizontalStack row = new HorizontalStack(fill(), fit()); + row.addSpacer(10); + VerticalStack col = row.addColumn(fixed(300), fit()); + grid.addScrollable(row); + + for (String error : errors) { + Component dash = Component.literal("-"); + row = col.addRow(); + row.addText(fit(), fit(), dash); + + row.addSpacer(4); + row.addText(fit(), fit(), Component.literal(error)).setColor(ColorHelper.RED); + } + grid.addSpacer(8); + + row = grid.addRow().centerHorizontal(); + row.addButton( + fit(), fit(), + Component.translatable("title.bclib.datafixer.error.continue") + ).setAlpha(0.5f).onPress((n) -> { + onClose(); + onContinue.doContinue(true); + }); + row.addSpacer(4); + row.addButton( + fit(), fit(), + CommonComponents.GUI_CANCEL + ).onPress((n) -> { + this.minecraft.setScreen(null); + }); + + + return grid; + } + + @Environment(EnvType.CLIENT) + public interface Listener { + void doContinue(boolean markFixed); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/ModListScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/ModListScreen.java new file mode 100644 index 00000000..105fe662 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/ModListScreen.java @@ -0,0 +1,268 @@ +package org.betterx.bclib.client.gui.screens; + +import de.ambertation.wunderlib.ui.ColorHelper; +import de.ambertation.wunderlib.ui.layout.components.HorizontalStack; +import de.ambertation.wunderlib.ui.layout.components.LayoutComponent; +import de.ambertation.wunderlib.ui.layout.components.Text; +import de.ambertation.wunderlib.ui.layout.components.VerticalStack; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.HelloClient; +import org.betterx.bclib.util.Triple; +import org.betterx.worlds.together.util.ModUtil; +import org.betterx.worlds.together.util.PathUtil; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.metadata.ModEnvironment; + +import java.util.*; +import java.util.stream.Collectors; + +@Environment(EnvType.CLIENT) +public class ModListScreen extends BCLibLayoutScreen { + private final List mods; + private final HelloClient.IServerModMap serverInfo; + private final Component description; + private final Component buttonTitle; + + private static List extractModList(Map mods) { + List list = new LinkedList(); + ModUtil.getMods().forEach((id, info) -> list.add(info)); + return list; + } + + public ModListScreen( + Screen parent, + Component title, + Component description, + Map mods, + HelloClient.IServerModMap serverInfo + ) { + this(parent, title, description, CommonComponents.GUI_BACK, mods, serverInfo); + } + + public ModListScreen( + Screen parent, + Component title, + Component description, + List mods, + HelloClient.IServerModMap serverInfo + ) { + this(parent, title, description, CommonComponents.GUI_BACK, mods, serverInfo); + } + + public ModListScreen( + Screen parent, + Component title, + Component description, + Component button, + Map mods, + HelloClient.IServerModMap serverInfo + ) { + this(parent, title, description, button, extractModList(mods), serverInfo); + } + + public ModListScreen( + Screen parent, + Component title, + Component description, + Component button, + List mods, + HelloClient.IServerModMap serverInfo + ) { + super(parent, title); + this.mods = mods; + this.serverInfo = serverInfo; + this.description = description; + this.buttonTitle = button; + } + + public static List localMissing(HelloClient.IServerModMap serverInfo) { + return serverInfo.keySet() + .stream() + .filter(modid -> !ModUtil.getMods() + .keySet() + .stream() + .filter(mod -> mod.equals(modid)) + .findFirst() + .isPresent()).collect(Collectors.toList()); + } + + public static List serverMissing(HelloClient.IServerModMap serverInfo) { + return ModUtil.getMods().entrySet() + .stream() + .filter(entry -> entry.getValue().metadata.getEnvironment() != ModEnvironment.CLIENT) + .map(entry -> entry.getKey()) + .filter(modid -> !serverInfo.keySet() + .stream() + .filter(mod -> mod.equals(modid)) + .findFirst() + .isPresent()).collect(Collectors.toList()); + } + + + public static void addModDesc( + VerticalStack grid, + java.util.List mods, + HelloClient.IServerModMap serverInfo + ) { + final int STATE_OK = 6; + final int STATE_SERVER_MISSING_CLIENT_MOD = 5; + final int STATE_MISSING_NOT_OFFERED = 4; + final int STATE_VERSION_CLIENT_ONLY = 7; + final int STATE_VERSION_NOT_OFFERED = 3; + final int STATE_VERSION = 2; + final int STATE_SERVER_MISSING = 1; + final int STATE_MISSING = 0; + + + List> items = new LinkedList<>(); + if (serverInfo != null) { + serverInfo.keySet() + .stream() + .filter(modid -> !mods.stream() + .filter(mod -> mod.metadata.getId().equals(modid)) + .findFirst() + .isPresent()) + .forEach(modid -> { + HelloClient.OfferedModInfo nfo = serverInfo.get(modid); + String stateString = nfo.version(); + if (nfo.size() > 0) { + stateString = "Version: " + stateString + ", Size: " + PathUtil.humanReadableFileSize(nfo.size()); + } + if (nfo.canDownload()) { + stateString += ", offered by server"; + } + + items.add(new Triple<>( + modid, + nfo.canDownload() ? STATE_MISSING : STATE_MISSING_NOT_OFFERED, + stateString + )); + }); + } + + mods.forEach(mod -> { + String serverVersion = null; + int serverSize = 0; + int state = STATE_OK; + if (serverInfo != null) { + final String modID = mod.metadata.getId(); + + + HelloClient.OfferedModInfo data = serverInfo.get(modID); + if (data != null) { + final String modVer = data.version(); + final int size = data.size(); + if (!modVer.equals(mod.getVersion())) { + if (mod.metadata.getEnvironment() == ModEnvironment.CLIENT) + state = STATE_VERSION_CLIENT_ONLY; + else + state = data.canDownload() ? STATE_VERSION : STATE_VERSION_NOT_OFFERED; + serverVersion = modVer; + serverSize = size; + } + } else if (mod.metadata.getEnvironment() == ModEnvironment.CLIENT) { + state = STATE_SERVER_MISSING_CLIENT_MOD; + } else { + state = STATE_SERVER_MISSING; + } + } + + String stateString = mod.metadata.getVersion().toString(); + if (serverVersion != null) { + stateString = "Client: " + stateString; + stateString += ", Server: " + serverVersion; + if (serverSize > 0) { + stateString += ", Size: " + PathUtil.humanReadableFileSize(serverSize); + } + } + if (mod.metadata.getEnvironment() == ModEnvironment.CLIENT) { + stateString += ", client-only"; + } else if (mod.metadata.getEnvironment() == ModEnvironment.SERVER) { + stateString += ", server-only"; + } + items.add(new Triple<>(mod.metadata.getName(), state, stateString)); + }); + + items.stream() + .filter(t -> t.second != STATE_SERVER_MISSING) + .sorted(Comparator.comparing(a -> a.second + a.first.toLowerCase(Locale.ROOT))) + .forEach(t -> { + final String name = t.first; + final int state = t.second; + final String stateString = t.third; + + int color = ColorHelper.RED; + final String typeText; + if (state == STATE_VERSION || state == STATE_VERSION_NOT_OFFERED || state == STATE_VERSION_CLIENT_ONLY) { + typeText = "[VERSION]"; + if (state == STATE_VERSION_NOT_OFFERED) { + color = ColorHelper.YELLOW; + } else if (state == STATE_VERSION_CLIENT_ONLY) { + color = ColorHelper.DARK_GREEN; + } + } else if (state == STATE_MISSING || state == STATE_MISSING_NOT_OFFERED) { + typeText = "[MISSING]"; + if (state == STATE_MISSING_NOT_OFFERED) { + color = ColorHelper.YELLOW; + } + } else if (state == STATE_SERVER_MISSING || state == STATE_SERVER_MISSING_CLIENT_MOD) { + if (state == STATE_SERVER_MISSING_CLIENT_MOD) { + color = ColorHelper.AQUA; + typeText = "[OK]"; + } else { + typeText = "[NOT ON SERVER]"; + } + } else { + color = ColorHelper.DARK_GREEN; + typeText = "[OK]"; + } + Component dash = Component.literal("-"); + Component typeTextComponent = Component.literal(typeText); + HorizontalStack row = grid.addRow(); + + Text dashText = row.addText(fit(), fit(), dash); + + row.addSpacer(4); + row.addText(fit(), fit(), Component.literal(name)); + + row.addSpacer(4); + row.addText(fit(), fit(), typeTextComponent).setColor(color); + + if (!stateString.isEmpty()) { + row = grid.addRow(); + row.addSpacer(4 + dashText.getContentWidth()); + row.addText(fit(), fit(), Component.literal(stateString)) + .setColor(ColorHelper.GRAY); + } + + grid.addSpacer(4); + }); + } + + + @Override + protected LayoutComponent initContent() { + VerticalStack grid = new VerticalStack(fill(), fill()); + if (description != null) { + grid.addSpacer(4); + grid.addMultilineText(fill(), fit(), description).centerHorizontal(); + grid.addSpacer(8); + } + + HorizontalStack row = new HorizontalStack(fill(), fit()); + row.addSpacer(10); + VerticalStack col = row.addColumn(fixed(200), fit()); + addModDesc(col, mods, serverInfo); + grid.addScrollable(row); + + grid.addSpacer(8); + grid.addButton(fit(), fit(), buttonTitle).onPress((n) -> onClose()).centerHorizontal(); + + return grid; + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/ProgressScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/ProgressScreen.java new file mode 100644 index 00000000..daee3ebe --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/ProgressScreen.java @@ -0,0 +1,242 @@ +package org.betterx.bclib.client.gui.screens; + +import de.ambertation.wunderlib.ui.ColorHelper; +import de.ambertation.wunderlib.ui.layout.components.*; +import de.ambertation.wunderlib.ui.layout.values.Rectangle; +import de.ambertation.wunderlib.ui.layout.values.Value; +import de.ambertation.wunderlib.ui.vanilla.LayoutScreen; +import org.betterx.bclib.BCLib; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ProgressListener; + +import java.util.concurrent.atomic.AtomicInteger; +import org.jetbrains.annotations.Nullable; + +class ProgressLogoRender extends CustomRenderComponent { + public static final int SIZE = 64; + public static final int LOGO_SIZE = 512; + public static final int PIXELATED_SIZE = 512; + float percentage = 0; + double time = 0; + + protected ProgressLogoRender() { + super(Value.fixed(SIZE), Value.fixed(SIZE)); + } + + @Override + public int getContentWidth() { + return SIZE; + } + + @Override + public int getContentHeight() { + return SIZE; + } + + @Override + protected void customRender( + GuiGraphics guiGraphics, + int x, + int y, + float deltaTicks, + Rectangle transform, + Rectangle clipRect + ) { + //time += 0.03; + time += deltaTicks * 0.1; + + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.enableBlend(); + RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0f); + + final int yBarLocal = (int) (transform.height * percentage); + final int yBar = yBarLocal; + + final float fScale = (float) (0.3 * ((Math.sin(time) + 1.0) * 0.5) + 0.7); + int height = (int) (transform.height * fScale); + int width = (int) (transform.width * fScale); + width -= ((transform.width - width) % 2); + height -= ((transform.height - height) % 2); + + final int yOffset = (transform.height - height) / 2; + final int xOffset = (transform.width - width) / 2; + + + final int yBarImage = Math.max(0, Math.min(height, yBarLocal - yOffset)); + final float relativeY = ((float) yBarImage / height); + + if (yBarImage > 0) { + final int uvTopLogo = (int) (relativeY * LOGO_SIZE); + guiGraphics.blit(BCLibLayoutScreen.BCLIB_LOGO_LOCATION, + xOffset, + yOffset, + width, + yBarImage, + 0, 0, LOGO_SIZE, uvTopLogo, + LOGO_SIZE, LOGO_SIZE + ); + } + + if (yBarImage < height) { + final int uvTopPixelated = (int) (relativeY * PIXELATED_SIZE); + RenderSystem.setShaderTexture(0, ProgressScreen.BCLIB_LOGO_PIXELATED_LOCATION); + guiGraphics.blit(ProgressScreen.BCLIB_LOGO_PIXELATED_LOCATION, + xOffset, + yOffset + yBarImage, + width, + height - yBarImage, + 0, uvTopPixelated, PIXELATED_SIZE, PIXELATED_SIZE - uvTopPixelated, + PIXELATED_SIZE, PIXELATED_SIZE + ); + } + + if (percentage > 0 && percentage < 1.0) { + guiGraphics.fill( + 0, + yBar, + transform.width, + yBar + 1, + 0x3FFFFFFF + ); + } + } + + private boolean focused; + + @Override + public boolean isFocused() { + return focused; + } + + @Override + public void setFocused(boolean bl) { + focused = bl; + } +} + +public class ProgressScreen extends LayoutScreen implements ProgressListener, AtomicProgressListener { + + static final ResourceLocation BCLIB_LOGO_PIXELATED_LOCATION = new ResourceLocation( + BCLib.MOD_ID, + "iconpixelated.png" + ); + + public ProgressScreen(@Nullable Screen parent, Component title, Component description) { + super(parent, title); + this.description = description; + } + + + Component description; + private Component stageComponent; + private MultiLineText stage; + private HorizontalStack stageRow; + private Text progress; + private ProgressLogoRender progressImage; + private int currentProgress = 0; + private AtomicInteger atomicCounter; + + @Override + public void incAtomic(int maxProgress) { + if (atomicCounter != null) { + progressStagePercentage((100 * atomicCounter.incrementAndGet()) / maxProgress); + } + } + + @Override + public void resetAtomic() { + progressStagePercentage(0); + atomicCounter = new AtomicInteger(0); + } + + public boolean shouldCloseOnEsc() { + return false; + } + + public Component getProgressComponent() { + return getProgressComponent(currentProgress); + } + + private Component getProgressComponent(int pg) { + return Component.translatable("title.bclib.progress").append(": " + pg + "%"); + } + + + @Override + public void progressStartNoAbort(Component text) { + this.progressStage(text); + } + + @Override + public void progressStart(Component text) { + this.progressStage(text); + this.progressStagePercentage(0); + } + + @Override + public void progressStage(Component text) { + stageComponent = text; + if (stage != null) stage.setText(text); + if (stageRow != null) stageRow.reCalculateLayout(); + } + + @Override + public void progressStagePercentage(int progress) { + if (progress != currentProgress) { + currentProgress = progress; + if (progressImage != null) progressImage.percentage = currentProgress / 100.0f; + if (this.progress != null) this.progress.setText(getProgressComponent()); + } + } + + @Override + public void stop() { + + } + + @Override + protected LayoutComponent createScreen(LayoutComponent content) { + return content; + } + + @Override + protected LayoutComponent initContent() { + VerticalStack grid = new VerticalStack(fill(), fill()).setDebugName("grid"); + grid.addFiller(); + grid.add(createTitle()); + grid.addSpacer(4); + + HorizontalStack contentRow = grid.addRow(fit(), fit()) + .centerHorizontal() + .setDebugName("contentRow"); + + progressImage = new ProgressLogoRender(); + progressImage.percentage = currentProgress / 100.0f; + contentRow.add(progressImage); + contentRow.addSpacer(8); + + VerticalStack textCol = contentRow.addColumn(fit(), fit()).setDebugName("textCol").centerVertical(); + textCol.addText(fit(), fit(), description); + textCol.addSpacer(4); + progress = textCol.addText(fit(), fit(), getProgressComponent()).setColor(ColorHelper.GRAY); + + + grid.addSpacer(20); + stageRow = grid.addRow(fill(), fit()); + stage = stageRow.addMultilineText( + fill(), fit(), + stageComponent != null ? stageComponent : Component.literal("") + ).centerHorizontal(); + grid.addFiller(); + + return grid; + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/SyncFilesScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/SyncFilesScreen.java new file mode 100644 index 00000000..4834a556 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/SyncFilesScreen.java @@ -0,0 +1,144 @@ +package org.betterx.bclib.client.gui.screens; + +import de.ambertation.wunderlib.ui.layout.components.Checkbox; +import de.ambertation.wunderlib.ui.layout.components.HorizontalStack; +import de.ambertation.wunderlib.ui.layout.components.LayoutComponent; +import de.ambertation.wunderlib.ui.layout.components.VerticalStack; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.HelloClient; +import org.betterx.worlds.together.util.ModUtil; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class SyncFilesScreen extends BCLibLayoutScreen { + private final Component description; + private final SyncFilesScreen.Listener listener; + private final boolean hasConfigFiles; + private final boolean hasFiles; + private final boolean hasMods; + private final boolean shouldDelete; + private final HelloClient.IServerModMap serverInfo; + + public SyncFilesScreen( + int modFiles, + int configFiles, + int singleFiles, + int folderFiles, + int deleteFiles, + HelloClient.IServerModMap serverInfo, + Listener listener + ) { + super(Component.translatable("title.bclib.syncfiles")); + + this.serverInfo = serverInfo; + this.description = Component.translatable("message.bclib.syncfiles"); + this.listener = listener; + + this.hasConfigFiles = configFiles > 0; + this.hasFiles = singleFiles + folderFiles > 0; + this.hasMods = modFiles > 0; + this.shouldDelete = deleteFiles > 0; + } + + + public boolean shouldCloseOnEsc() { + return false; + } + + @Override + protected LayoutComponent initContent() { + VerticalStack grid = new VerticalStack(fill(), fill()); + final int BUTTON_HEIGHT = 20; + + grid.addMultilineText(fill(), fit(), this.description).centerHorizontal(); + + grid.addSpacer(10); + + HorizontalStack row; + + final Checkbox mods; + row = grid.addRow(); + mods = row.addCheckbox( + fit(), fit(), + Component.translatable("message.bclib.syncfiles.mods"), + hasMods + ); + mods.setEnabled(hasMods); + + row.addSpacer(4); + row.addButton( + fit(), fit(), + Component.translatable("title.bclib.syncfiles.modInfo") + ).onPress((button) -> { + ModListScreen scr = new ModListScreen( + this, + Component.translatable("title.bclib.syncfiles.modlist"), + Component.translatable("message.bclib.syncfiles.modlist"), + ModUtil.getMods(), + serverInfo + ); + Minecraft.getInstance().setScreen(scr); + }); + + grid.addSpacer(4); + + + final Checkbox configs; + row = grid.addRow(); + configs = row.addCheckbox( + fit(), fit(), + Component.translatable("message.bclib.syncfiles.configs"), + hasConfigFiles + ); + configs.setEnabled(hasConfigFiles); + + grid.addSpacer(4); + + row = grid.addRow(); + + final Checkbox folder; + folder = row.addCheckbox( + fit(), fit(), + Component.translatable("message.bclib.syncfiles.folders"), + hasFiles + ); + folder.setEnabled(hasFiles); + row.addSpacer(4); + + Checkbox delete; + delete = row.addCheckbox( + fit(), fit(), + Component.translatable("message.bclib.syncfiles.delete"), + shouldDelete + ); + delete.setEnabled(shouldDelete); + + + grid.addSpacer(30); + row = grid.addRow().centerHorizontal(); + row.addButton(fit(), fit(), CommonComponents.GUI_NO).onPress((button) -> { + listener.proceed(false, false, false, false); + }); + row.addSpacer(4); + row.addButton(fit(), fit(), CommonComponents.GUI_YES).onPress((button) -> { + listener.proceed( + mods.isChecked(), + configs.isChecked(), + folder.isChecked(), + delete.isChecked() + ); + }); + + return grid; + } + + @Environment(EnvType.CLIENT) + public interface Listener { + void proceed(boolean downloadMods, boolean downloadConfigs, boolean downloadFiles, boolean removeFiles); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/UpdatesScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/UpdatesScreen.java new file mode 100644 index 00000000..68794ed1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/UpdatesScreen.java @@ -0,0 +1,180 @@ +package org.betterx.bclib.client.gui.screens; + +import de.ambertation.wunderlib.ui.ColorHelper; +import de.ambertation.wunderlib.ui.layout.components.HorizontalStack; +import de.ambertation.wunderlib.ui.layout.components.LayoutComponent; +import de.ambertation.wunderlib.ui.layout.components.VerticalStack; +import de.ambertation.wunderlib.ui.layout.values.Size; +import de.ambertation.wunderlib.ui.layout.values.Value; +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.networking.VersionChecker; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.metadata.CustomValue; + +@Environment(EnvType.CLIENT) +public class UpdatesScreen extends BCLibLayoutScreen { + public static final String DONATION_URL = "https://www.buymeacoffee.com/quiqueck"; + static final ResourceLocation UPDATE_LOGO_LOCATION = new ResourceLocation(BCLib.MOD_ID, "icon_updater.png"); + + public UpdatesScreen(Screen parent) { + super(parent, Component.translatable("bclib.updates.title"), 10, 10, 10); + } + + public static void showUpdateUI() { + if (!RenderSystem.isOnRenderThread()) { + RenderSystem.recordRenderCall(() -> Minecraft.getInstance() + .setScreen(new UpdatesScreen(Minecraft.getInstance().screen))); + } else { + Minecraft.getInstance().setScreen(new UpdatesScreen(Minecraft.getInstance().screen)); + } + } + + public ResourceLocation getUpdaterIcon(String modID) { + if (modID.equals(BCLib.MOD_ID)) { + return UPDATE_LOGO_LOCATION; + } + ModContainer nfo = FabricLoader.getInstance().getModContainer(modID).orElse(null); + if (nfo != null) { + CustomValue element = nfo.getMetadata().getCustomValue("bclib"); + if (element != null) { + CustomValue.CvObject obj = element.getAsObject(); + if (obj != null) { + CustomValue icon = obj.get("updater_icon"); + return new ResourceLocation(modID, icon.getAsString()); + } + } + } + return null; + } + + @Override + protected LayoutComponent initContent() { + VerticalStack rows = new VerticalStack(relative(1), fit()).centerHorizontal(); + rows.addMultilineText(fill(), fit(), Component.translatable("bclib.updates.description")) + .centerHorizontal(); + + rows.addSpacer(8); + + VersionChecker.forEachUpdate((mod, cur, updated) -> { + ModContainer nfo = FabricLoader.getInstance().getModContainer(mod).orElse(null); + ResourceLocation icon = getUpdaterIcon(mod); + HorizontalStack row = rows.addRow(fixed(320), fit()).centerHorizontal(); + if (icon != null) { + row.addImage(Value.fit(), Value.fit(), icon, Size.of(32)); + row.addSpacer(4); + } else { + row.addSpacer(36); + } + if (nfo != null) { + row.addText(fit(), fit(), Component.literal(nfo.getMetadata().getName())) + .setColor(ColorHelper.WHITE); + } else { + row.addText(fit(), fit(), Component.literal(mod)).setColor(ColorHelper.WHITE); + } + row.addSpacer(4); + row.addText(fit(), fit(), Component.literal(cur)); + row.addText(fit(), fit(), Component.literal(" -> ")); + row.addText(fit(), fit(), Component.literal(updated)).setColor(ColorHelper.GREEN); + row.addFiller(); + boolean createdDownloadLink = false; + if (nfo != null + && nfo.getMetadata().getCustomValue("bclib") != null + && nfo.getMetadata().getCustomValue("bclib").getAsObject().get("downloads") != null) { + CustomValue.CvObject downloadLinks = nfo.getMetadata() + .getCustomValue("bclib") + .getAsObject() + .get("downloads") + .getAsObject(); + String link = null; + Component name = null; + if (Configs.CLIENT_CONFIG.preferModrinthForUpdates() && downloadLinks.get("modrinth") != null) { + link = downloadLinks.get("modrinth").getAsString(); + name = Component.translatable("bclib.updates.modrinth_link"); +// row.addButton(fit(), fit(), Component.translatable("bclib.updates.modrinth_link")) +// .onPress((bt) -> { +// this.openLink(downloadLinks.get("modrinth").getAsString()); +// }).centerVertical(); +// createdDownloadLink = true; + } else if (downloadLinks.get("curseforge") != null) { + link = downloadLinks.get("curseforge").getAsString(); + name = Component.translatable("bclib.updates.curseforge_link"); +// row.addButton(fit(), fit(), Component.translatable("bclib.updates.curseforge_link")) +// .onPress((bt) -> { +// this.openLink(downloadLinks.get("curseforge").getAsString()); +// }).centerVertical(); +// createdDownloadLink = true; + } + + if (link != null) { + createdDownloadLink = true; + final String finalLink = link; + row.addButton(fit(), fit(), name) + .onPress((bt) -> { + this.openLink(finalLink); + }).centerVertical(); + } + } + + if (!createdDownloadLink && nfo != null && nfo.getMetadata().getContact().get("homepage").isPresent()) { + row.addButton(fit(), fit(), Component.translatable("bclib.updates.download_link")) + .onPress((bt) -> { + this.openLink(nfo.getMetadata().getContact().get("homepage").get()); + }).centerVertical(); + } + }); + + VerticalStack layout = new VerticalStack(relative(1), fill()).centerHorizontal(); + //layout.addSpacer(8); + layout.addScrollable(rows); + layout.addSpacer(8); + + + HorizontalStack footer = layout.addRow(fill(), fit()); + if (Configs.CLIENT_CONFIG.isDonor()) { + footer.addButton( + fit(), + fit(), + Component.translatable("bclib.updates.donate").setStyle(Style.EMPTY.withColor(ColorHelper.YELLOW)) + ) + .onPress((bt) -> openLink(DONATION_URL)); + footer.addSpacer(2); + footer.addMultilineText(fit(), fit(), Component.translatable("bclib.updates.donate_pre")) + .alignBottom(); + } + + footer.addFiller(); + footer.addCheckbox( + fit(), fit(), + Component.translatable("Disable Check"), + !Configs.CLIENT_CONFIG.checkVersions() + ) + .onChange((cb, state) -> { + Configs.CLIENT_CONFIG.setCheckVersions(!state); + Configs.CLIENT_CONFIG.saveChanges(); + }); + footer.addSpacer(4); + footer.addButton(fit(), fit(), CommonComponents.GUI_DONE).onPress((bt -> { + onClose(); + })); + return layout; + } + + @Override + protected void renderBackgroundLayer(GuiGraphics guiGraphics, int i, int j, float f) { + guiGraphics.fill(0, 0, width, height, 0xBD343444); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/WarnBCLibVersionMismatch.java b/src/main/java/org/betterx/bclib/client/gui/screens/WarnBCLibVersionMismatch.java new file mode 100644 index 00000000..380bda90 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/WarnBCLibVersionMismatch.java @@ -0,0 +1,49 @@ +package org.betterx.bclib.client.gui.screens; + +import de.ambertation.wunderlib.ui.layout.components.HorizontalStack; +import de.ambertation.wunderlib.ui.layout.components.LayoutComponent; +import de.ambertation.wunderlib.ui.layout.components.VerticalStack; + +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class WarnBCLibVersionMismatch extends BCLibLayoutScreen { + private final Component description; + private final Listener listener; + + public WarnBCLibVersionMismatch(Listener listener) { + super(Component.translatable("title.bclib.bclibmissmatch")); + + this.description = Component.translatable("message.bclib.bclibmissmatch"); + this.listener = listener; + } + + public boolean shouldCloseOnEsc() { + return false; + } + + @Override + protected LayoutComponent initContent() { + VerticalStack grid = new VerticalStack(fill(), fill()); + grid.addFiller(); + grid.addMultilineText(fill(), fit(), this.description).centerHorizontal(); + grid.addSpacer(20); + + HorizontalStack row = grid.addRow().centerHorizontal(); + row.addButton(fit(), fit(), CommonComponents.GUI_NO).onPress((button) -> listener.proceed(false)); + row.addSpacer(4); + row.addButton(fit(), fit(), CommonComponents.GUI_YES).onPress((button) -> listener.proceed(true)); + + grid.addFiller(); + return grid; + } + + @Environment(EnvType.CLIENT) + public interface Listener { + void proceed(boolean download); + } +} diff --git a/src/main/java/org/betterx/bclib/client/gui/screens/WelcomeScreen.java b/src/main/java/org/betterx/bclib/client/gui/screens/WelcomeScreen.java new file mode 100644 index 00000000..60213ae7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/gui/screens/WelcomeScreen.java @@ -0,0 +1,185 @@ +package org.betterx.bclib.client.gui.screens; + +import de.ambertation.wunderlib.ui.ColorHelper; +import de.ambertation.wunderlib.ui.layout.components.*; +import de.ambertation.wunderlib.ui.layout.values.Size; +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.networking.VersionChecker; +import org.betterx.worlds.together.WorldsTogether; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; + +public class WelcomeScreen extends BCLibLayoutScreen { + static final ResourceLocation BETTERX_LOCATION = new ResourceLocation(BCLib.MOD_ID, "betterx.png"); + static final ResourceLocation BACKGROUND = new ResourceLocation(BCLib.MOD_ID, "header.jpg"); + static final ResourceLocation ICON_BETTERNETHER = new ResourceLocation(BCLib.MOD_ID, "icon_betternether.png"); + static final ResourceLocation ICON_BETTEREND = new ResourceLocation(BCLib.MOD_ID, "icon_betterend.png"); + + public WelcomeScreen(Screen parent) { + super(parent, translatable("bclib.welcome.title")); + } + + @Override + protected LayoutComponent initContent() { + VerticalStack content = new VerticalStack(fill(), fit()).setDebugName("content"); + + content.addImage(fill(), fit(), BACKGROUND, new Size(854 / 2, 200 / 2)); + content.addHorizontalLine(1).setColor(ColorHelper.BLACK); + content.addSpacer(16); + HorizontalStack headerRow = content.addRow(fit(), fit()).setDebugName("title bar").centerHorizontal(); + headerRow.addIcon(icon, Size.of(512)).setDebugName("icon"); + headerRow.addSpacer(4); + headerRow.addText(fit(), fit(), title).centerHorizontal().setColor(ColorHelper.WHITE).setDebugName("title"); + headerRow.addImage(fixed(178 / 2), fixed(40 / 2), BETTERX_LOCATION, Size.of(178, 40)).setDebugName("betterx"); + content.addSpacer(16); + + content.addMultilineText(fill(), fit(), MultiLineText.parse(translatable("bclib.welcome.description"))) + .centerHorizontal(); + + Container padContainer = new Container(fill(), fit()).setPadding(10, 0, 10, 10).setDebugName("padContainer"); + VerticalStack innerContent = new VerticalStack(fill(), fit()).setDebugName("innerContent"); + padContainer.addChild(innerContent); + content.add(padContainer); + if (Configs.CLIENT_CONFIG.isDonor()) { + addSeparator(innerContent, ICON_BETTEREND); + HorizontalStack donationRow = innerContent.addRow(relative(0.9), fit()) + .setDebugName("donationRow") + .centerHorizontal(); + + donationRow.addMultilineText(fill(), fit(), translatable("bclib.welcome.donation")) + .alignLeft() + .alignRight(); + donationRow.addSpacer(4); + donationRow.addButton( + fit(), fit(), + Component.translatable("bclib.updates.donate").setStyle(Style.EMPTY.withColor(ColorHelper.YELLOW)) + ) + .onPress((bt) -> openLink(UpdatesScreen.DONATION_URL)).centerVertical(); + } + + addSeparator(innerContent, ICON_BETTERNETHER); + + // Do Update Checks + Checkbox check = innerContent.addCheckbox( + fit(), + fit(), + translatable("title.config.bclib.client.version.check"), + Configs.CLIENT_CONFIG.checkVersions() + ) + .onChange((cb, state) -> { + Configs.CLIENT_CONFIG.setCheckVersions(state); + }); + innerContent.addSpacer(2); + HorizontalStack dscBox = innerContent.indent(24); + dscBox.addMultilineText(fill(), fit(), translatable("description.config.bclib.client.version.check")) + .setColor(ColorHelper.GRAY); + dscBox.addSpacer(8); + + // Hide Experimental Dialog + innerContent.addSpacer(8); + Checkbox experimental = innerContent.addCheckbox( + fit(), + fit(), + translatable("title.config.bclib.client.ui.suppressExperimentalDialogOnLoad"), + Configs.CLIENT_CONFIG.suppressExperimentalDialog() + ) + .onChange((cb, state) -> { + Configs.CLIENT_CONFIG.setSuppressExperimentalDialog(state); + }); + innerContent.addSpacer(2); + dscBox = innerContent.indent(24); + dscBox.addMultilineText( + fill(), + fit(), + translatable("description.config.bclib.client.ui.suppressExperimentalDialogOnLoad") + ) + .setColor(ColorHelper.GRAY); + dscBox.addSpacer(8); + + // Use BetterX WorldType + innerContent.addSpacer(8); + Checkbox betterx = innerContent.addCheckbox( + fit(), + fit(), + translatable("title.config.bclib.client.ui.forceBetterXPreset"), + Configs.CLIENT_CONFIG.forceBetterXPreset() + ) + .onChange((cb, state) -> { + Configs.CLIENT_CONFIG.setForceBetterXPreset(state); + }); + innerContent.addSpacer(2); + dscBox = innerContent.indent(24); + dscBox.addMultilineText( + fill(), fit(), + translatable("warning.config.bclib.client.ui.forceBetterXPreset") + .setStyle(Style.EMPTY + .withBold(true) + .withColor(ColorHelper.RED) + ) + .append(translatable( + "description.config.bclib.client.ui.forceBetterXPreset").setStyle( + Style.EMPTY + .withBold(false) + .withColor(ColorHelper.GRAY)) + ) + ) + .setColor(ColorHelper.GRAY); + dscBox.addSpacer(8); + + innerContent.addSpacer(16); + innerContent.addButton(fit(), fit(), CommonComponents.GUI_PROCEED).onPress((bt) -> { + Configs.CLIENT_CONFIG.setDidShowWelcomeScreen(); + Configs.CLIENT_CONFIG.setCheckVersions(check.isChecked()); + Configs.CLIENT_CONFIG.setSuppressExperimentalDialog(experimental.isChecked()); + Configs.CLIENT_CONFIG.setForceBetterXPreset(betterx.isChecked()); + Configs.CLIENT_CONFIG.saveChanges(); + + WorldsTogether.SURPRESS_EXPERIMENTAL_DIALOG = Configs.CLIENT_CONFIG.suppressExperimentalDialog(); + VersionChecker.startCheck(true); + onClose(); + }).alignRight(); + + return VerticalScroll.create(fill(), fill(), content).setScrollerPadding(0); + } + + private void addSeparator(VerticalStack innerContent, ResourceLocation image) { + final int sepWidth = (int) (427 / 1.181) / 2; + HorizontalStack separator = new HorizontalStack(fit(), fit()).centerHorizontal(); + separator.addHLine(fixed((sepWidth - 32) / 2), fixed(32)).centerVertical(); + separator.addSpacer(1); + separator.addImage(fixed(32), fixed(32), image, Size.of(64)).alignBottom(); + separator.addHLine(fixed((sepWidth - 32) / 2), fixed(32)).centerVertical(); + innerContent.addSpacer(16); + innerContent.add(separator); + innerContent.addSpacer(4); + } + + @Override + protected LayoutComponent createScreen(LayoutComponent content) { + return content; + } + + @Override + protected void renderBackgroundLayer(GuiGraphics guiGraphics, int i, int j, float f) { + guiGraphics.fill(0, 0, width, height, 0xBD343444); +// Rectangle BANNER_UV = new Rectangle(0, 0, 427, 100); +// Size BANNER_RESOURCE_SIZE = BANNER_UV.size(); +// Size BANNER_SIZE = BANNER_UV.sizeFromWidth(this.width); +// +// RenderHelper.renderImage( +// poseStack, +// BANNER_SIZE.width(), +// BANNER_SIZE.height(), +// BACKGROUND, +// BANNER_UV, +// BANNER_RESOURCE_SIZE, +// 1.0f +// ); + } +} diff --git a/src/main/java/org/betterx/bclib/client/models/BaseChestBlockModel.java b/src/main/java/org/betterx/bclib/client/models/BaseChestBlockModel.java new file mode 100644 index 00000000..6acde89a --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/BaseChestBlockModel.java @@ -0,0 +1,112 @@ +package org.betterx.bclib.client.models; + +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.model.geom.builders.*; + +public class BaseChestBlockModel { + public final ModelPart partA; + public final ModelPart partC; + public final ModelPart partB; + public final ModelPart partRightA; + public final ModelPart partRightC; + public final ModelPart partRightB; + public final ModelPart partLeftA; + public final ModelPart partLeftC; + public final ModelPart partLeftB; + + public static LayerDefinition getTexturedModelData() { + MeshDefinition modelData = new MeshDefinition(); + PartDefinition modelPartData = modelData.getRoot(); + CubeDeformation deformation_partC = new CubeDeformation(0.0f); + modelPartData.addOrReplaceChild( + "partC", + CubeListBuilder.create().texOffs(0, 19).addBox(1.0f, 0.0f, 1.0f, 14.0f, 9.0f, 14.0f, deformation_partC), + PartPose.ZERO + ); + + CubeDeformation deformation_partA = new CubeDeformation(0.0f); + modelPartData.addOrReplaceChild( + "partA", + CubeListBuilder.create().texOffs(0, 0).addBox(1.0f, 0.0f, 0.0f, 14.0f, 5.0f, 14.0f, deformation_partA), + PartPose.offset(0.0f, 9.0f, 1.0f) + ); + + CubeDeformation deformation_partB = new CubeDeformation(0.0f); + modelPartData.addOrReplaceChild( + "partB", + CubeListBuilder.create().texOffs(0, 0).addBox(7.0f, -1.0f, 15.0f, 2.0f, 4.0f, 1.0f, deformation_partB), + PartPose.offset(0.0f, 8.0f, 0.0f) + ); + + CubeDeformation deformation_partRightC = new CubeDeformation(0.0f); + modelPartData.addOrReplaceChild( + "partRightC", + CubeListBuilder.create() + .texOffs(0, 19) + .addBox(1.0f, 0.0f, 1.0f, 15.0f, 9.0f, 14.0f, deformation_partRightC), + PartPose.ZERO + ); + + CubeDeformation deformation_partRightA = new CubeDeformation(0.0f); + modelPartData.addOrReplaceChild( + "partRightA", + CubeListBuilder.create() + .texOffs(0, 0) + .addBox(1.0f, 0.0f, 0.0f, 15.0f, 5.0f, 14.0f, deformation_partRightA), + PartPose.offset(0.0f, 9.0f, 1.0f) + ); + + CubeDeformation deformation_partRightB = new CubeDeformation(0.0f); + PartDefinition partRightB = modelPartData.addOrReplaceChild( + "partRightB", + CubeListBuilder.create() + .texOffs(0, 0) + .addBox(15.0f, -1.0f, 15.0f, 1.0f, 4.0f, 1.0f, deformation_partRightB), + PartPose.offset(0.0f, 8.0f, 0.0f) + ); + + CubeDeformation deformation_partLeftC = new CubeDeformation(0.0f); + modelPartData.addOrReplaceChild( + "partLeftC", + CubeListBuilder.create() + .texOffs(0, 19) + .addBox(0.0f, 0.0f, 1.0f, 15.0f, 9.0f, 14.0f, deformation_partLeftC), + PartPose.ZERO + ); + + CubeDeformation deformation_partLeftA = new CubeDeformation(0.0f); + modelPartData.addOrReplaceChild( + "partLeftA", + CubeListBuilder.create() + .texOffs(0, 0) + .addBox(0.0f, 0.0f, 0.0f, 15.0f, 5.0f, 14.0f, deformation_partLeftA), + PartPose.offset(0.0f, 9.0f, 1.0f) + ); + + CubeDeformation deformation_partLeftB = new CubeDeformation(0.0f); + modelPartData.addOrReplaceChild( + "partLeftB", + CubeListBuilder.create() + .texOffs(0, 0) + .addBox(0.0f, -1.0f, 15.0f, 1.0f, 4.0f, 1.0f, deformation_partLeftB), + PartPose.offset(0.0f, 8.0f, 0.0f) + ); + + return LayerDefinition.create(modelData, 64, 64); + } + + public BaseChestBlockModel(ModelPart modelPart) { + super(); + + partC = modelPart.getChild("partC"); + partA = modelPart.getChild("partA"); + partB = modelPart.getChild("partB"); + partRightC = modelPart.getChild("partRightC"); + partRightA = modelPart.getChild("partRightA"); + partRightB = modelPart.getChild("partRightB"); + partLeftC = modelPart.getChild("partLeftC"); + partLeftA = modelPart.getChild("partLeftA"); + partLeftB = modelPart.getChild("partLeftB"); + } +} diff --git a/src/main/java/org/betterx/bclib/client/models/BasePatterns.java b/src/main/java/org/betterx/bclib/client/models/BasePatterns.java new file mode 100644 index 00000000..db14c841 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/BasePatterns.java @@ -0,0 +1,64 @@ +package org.betterx.bclib.client.models; + +import org.betterx.bclib.BCLib; + +import net.minecraft.resources.ResourceLocation; + +public class BasePatterns { + //Block Models + public final static ResourceLocation BLOCK_EMPTY = BCLib.makeID("patterns/block/empty.json"); + public final static ResourceLocation BLOCK_BASE = BCLib.makeID("patterns/block/block.json"); + public final static ResourceLocation BLOCK_SIDED = BCLib.makeID("patterns/block/block_sided.json"); + public final static ResourceLocation BLOCK_BOTTOM_TOP = BCLib.makeID("patterns/block/block_bottom_top.json"); + public final static ResourceLocation BLOCK_SLAB = BCLib.makeID("patterns/block/slab.json"); + public final static ResourceLocation BLOCK_STAIR = BCLib.makeID("patterns/block/stairs.json"); + public final static ResourceLocation BLOCK_STAIR_INNER = BCLib.makeID("patterns/block/stairs_inner.json"); + public final static ResourceLocation BLOCK_STAIR_OUTER = BCLib.makeID("patterns/block/stairs_outer.json"); + public final static ResourceLocation BLOCK_WALL_POST = BCLib.makeID("patterns/block/wall_post.json"); + public final static ResourceLocation BLOCK_WALL_SIDE = BCLib.makeID("patterns/block/wall_side.json"); + public final static ResourceLocation BLOCK_WALL_SIDE_TALL = BCLib.makeID("patterns/block/wall_side_tall.json"); + public final static ResourceLocation BLOCK_FENCE_POST = BCLib.makeID("patterns/block/fence_post.json"); + public final static ResourceLocation BLOCK_FENCE_SIDE = BCLib.makeID("patterns/block/fence_side.json"); + public final static ResourceLocation BLOCK_BUTTON = BCLib.makeID("patterns/block/button.json"); + public final static ResourceLocation BLOCK_BUTTON_PRESSED = BCLib.makeID("patterns/block/button_pressed.json"); + public final static ResourceLocation BLOCK_PILLAR = BCLib.makeID("patterns/block/pillar.json"); + public final static ResourceLocation BLOCK_PLATE_UP = BCLib.makeID("patterns/block/pressure_plate_up.json"); + public final static ResourceLocation BLOCK_PLATE_DOWN = BCLib.makeID("patterns/block/pressure_plate_down.json"); + public final static ResourceLocation BLOCK_DOOR_TOP = BCLib.makeID("patterns/block/door_top.json"); + public final static ResourceLocation BLOCK_DOOR_TOP_HINGE = BCLib.makeID("patterns/block/door_top_hinge.json"); + public final static ResourceLocation BLOCK_DOOR_BOTTOM = BCLib.makeID("patterns/block/door_bottom.json"); + public final static ResourceLocation BLOCK_DOOR_BOTTOM_HINGE = BCLib.makeID("patterns/block/door_bottom_hinge.json"); + public final static ResourceLocation BLOCK_CROSS = BCLib.makeID("patterns/block/cross.json"); + public final static ResourceLocation BLOCK_CROSS_SHADED = BCLib.makeID("patterns/block/cross_shaded.json"); + public final static ResourceLocation BLOCK_GATE_CLOSED = BCLib.makeID("patterns/block/fence_gate_closed.json"); + public final static ResourceLocation BLOCK_GATE_CLOSED_WALL = BCLib.makeID("patterns/block/wall_gate_closed.json"); + public final static ResourceLocation BLOCK_GATE_OPEN = BCLib.makeID("patterns/block/fence_gate_open.json"); + public final static ResourceLocation BLOCK_GATE_OPEN_WALL = BCLib.makeID("patterns/block/wall_gate_open.json"); + public final static ResourceLocation BLOCK_TRAPDOOR = BCLib.makeID("patterns/block/trapdoor.json"); + public final static ResourceLocation BLOCK_LADDER = BCLib.makeID("patterns/block/ladder.json"); + public final static ResourceLocation BLOCK_BARREL_OPEN = BCLib.makeID("patterns/block/barrel_open.json"); + public final static ResourceLocation BLOCK_BOOKSHELF = BCLib.makeID("patterns/block/bookshelf.json"); + public final static ResourceLocation BLOCK_COMPOSTER = BCLib.makeID("patterns/block/composter.json"); + public final static ResourceLocation BLOCK_COLORED = BCLib.makeID("patterns/block/block_colored.json"); + public final static ResourceLocation BLOCK_BARS_POST = BCLib.makeID("patterns/block/bars_post.json"); + public final static ResourceLocation BLOCK_BARS_SIDE = BCLib.makeID("patterns/block/bars_side.json"); + public final static ResourceLocation BLOCK_ANVIL = BCLib.makeID("patterns/block/anvil.json"); + public final static ResourceLocation BLOCK_CHAIN = BCLib.makeID("patterns/block/chain.json"); + public final static ResourceLocation BLOCK_FURNACE = BCLib.makeID("patterns/block/furnace.json"); + public final static ResourceLocation BLOCK_FURNACE_LIT = BCLib.makeID("patterns/block/furnace_glow.json"); + public final static ResourceLocation BLOCK_TOP_SIDE_BOTTOM = BCLib.makeID("patterns/block/top_side_bottom.json"); + public final static ResourceLocation BLOCK_PATH = BCLib.makeID("patterns/block/path.json"); + + //Item Models + public final static ResourceLocation ITEM_WALL = BCLib.makeID("patterns/item/pattern_wall.json"); + public final static ResourceLocation ITEM_FENCE = BCLib.makeID("patterns/item/pattern_fence.json"); + public final static ResourceLocation ITEM_BUTTON = BCLib.makeID("patterns/item/pattern_button.json"); + public final static ResourceLocation ITEM_CHEST = BCLib.makeID("patterns/item/pattern_chest.json"); + public final static ResourceLocation ITEM_BLOCK = BCLib.makeID("patterns/item/pattern_block_item.json"); + public final static ResourceLocation ITEM_GENERATED = BCLib.makeID("patterns/item/pattern_item_generated.json"); + public final static ResourceLocation ITEM_HANDHELD = BCLib.makeID("patterns/item/pattern_item_handheld.json"); + public final static ResourceLocation ITEM_SPAWN_EGG = BCLib.makeID("patterns/item/pattern_item_spawn_egg.json"); + + public final static ResourceLocation VANILLA_WOOD_BOOKSHELF = BCLib.makeID( + "patterns/block/vanilla_wood_bookshelf.json"); +} diff --git a/src/main/java/org/betterx/bclib/client/models/CustomModelBakery.java b/src/main/java/org/betterx/bclib/client/models/CustomModelBakery.java new file mode 100644 index 00000000..f75dba28 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/CustomModelBakery.java @@ -0,0 +1,111 @@ +package org.betterx.bclib.client.models; + +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.ItemModelProvider; +import org.betterx.bclib.models.RecordItemModelProvider; + +import net.minecraft.client.renderer.block.BlockModelShaper; +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.renderer.block.model.multipart.MultiPart; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; + +import java.util.Map; + +public class CustomModelBakery { + private final Map models = Maps.newConcurrentMap(); + + public UnbakedModel getBlockModel(ResourceLocation location) { + return models.get(location); + } + + public UnbakedModel getItemModel(ResourceLocation location) { + return models.get(location); + } + + public void loadCustomModels(ResourceManager resourceManager) { + BuiltInRegistries.BLOCK.stream() + .parallel() + .filter(block -> block instanceof BlockModelProvider) + .forEach(block -> { + ResourceLocation blockID = BuiltInRegistries.BLOCK.getKey(block); + ResourceLocation storageID = new ResourceLocation( + blockID.getNamespace(), + "blockstates/" + blockID.getPath() + ".json" + ); + if (resourceManager.getResource(storageID).isEmpty()) { + addBlockModel(blockID, block); + } + storageID = new ResourceLocation( + blockID.getNamespace(), + "models/item/" + blockID.getPath() + ".json" + ); + if (resourceManager.getResource(storageID).isEmpty()) { + addItemModel(blockID, (ItemModelProvider) block); + } + }); + + BuiltInRegistries.ITEM.stream() + .parallel() + .filter(item -> item instanceof ItemModelProvider || RecordItemModelProvider.has(item)) + .forEach(item -> { + ResourceLocation registryID = BuiltInRegistries.ITEM.getKey(item); + ResourceLocation storageID = new ResourceLocation( + registryID.getNamespace(), + "models/item/" + registryID.getPath() + ".json" + ); + final ItemModelProvider provider = (item instanceof ItemModelProvider) + ? (ItemModelProvider) item + : RecordItemModelProvider.get(item); + + if (resourceManager.getResource(storageID).isEmpty()) { + addItemModel(registryID, provider); + } + }); + } + + private void addBlockModel(ResourceLocation blockID, Block block) { + BlockModelProvider provider = (BlockModelProvider) block; + ImmutableList states = block.getStateDefinition().getPossibleStates(); + BlockState defaultState = block.defaultBlockState(); + + ResourceLocation defaultStateID = BlockModelShaper.stateToModelLocation(blockID, defaultState); + UnbakedModel defaultModel = provider.getModelVariant(defaultStateID, defaultState, models); + + if (defaultModel instanceof MultiPart) { + states.forEach(blockState -> { + ResourceLocation stateID = BlockModelShaper.stateToModelLocation(blockID, blockState); + models.put(stateID, defaultModel); + }); + } else { + states.forEach(blockState -> { + ResourceLocation stateID = BlockModelShaper.stateToModelLocation(blockID, blockState); + UnbakedModel model = stateID.equals(defaultStateID) + ? defaultModel + : provider.getModelVariant(stateID, blockState, models); + models.put(stateID, model); + }); + } + } + + private void addItemModel(ResourceLocation itemID, ItemModelProvider provider) { + ModelResourceLocation modelLocation = new ModelResourceLocation( + itemID.getNamespace(), + itemID.getPath(), + "inventory" + ); + if (models.containsKey(modelLocation)) { + return; + } + BlockModel model = provider.getItemModel(modelLocation); + models.put(modelLocation, model); + } +} diff --git a/src/main/java/org/betterx/bclib/client/models/CustomModelData.java b/src/main/java/org/betterx/bclib/client/models/CustomModelData.java new file mode 100644 index 00000000..35698fbf --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/CustomModelData.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.client.models; + +import net.minecraft.resources.ResourceLocation; + +import com.google.common.collect.Sets; + +import java.util.Set; + +public class CustomModelData { + private static final Set TRANSPARENT_EMISSION = Sets.newConcurrentHashSet(); + + public static void clear() { + TRANSPARENT_EMISSION.clear(); + } + + public static void addTransparent(ResourceLocation blockID) { + TRANSPARENT_EMISSION.add(blockID); + } + + public static boolean isTransparentEmissive(ResourceLocation rawLocation) { + String name = rawLocation.getPath().replace("materialmaps/block/", "").replace(".json", ""); + return TRANSPARENT_EMISSION.contains(new ResourceLocation(rawLocation.getNamespace(), name)); + } +} diff --git a/src/main/java/org/betterx/bclib/client/models/ModelsHelper.java b/src/main/java/org/betterx/bclib/client/models/ModelsHelper.java new file mode 100644 index 00000000..ec51963d --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/ModelsHelper.java @@ -0,0 +1,166 @@ +package org.betterx.bclib.client.models; + +import com.mojang.math.Transformation; +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.renderer.block.model.MultiVariant; +import net.minecraft.client.renderer.block.model.Variant; +import net.minecraft.client.renderer.block.model.multipart.Condition; +import net.minecraft.client.renderer.block.model.multipart.MultiPart; +import net.minecraft.client.renderer.block.model.multipart.Selector; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Lists; + +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +@Environment(EnvType.CLIENT) +public class ModelsHelper { + public static BlockModel fromPattern(Optional pattern) { + return pattern.map(BlockModel::fromString).orElse(null); + } + + public static BlockModel createItemModel(ResourceLocation resourceLocation) { + return fromPattern(PatternsHelper.createItemGenerated(resourceLocation)); + } + + public static BlockModel createHandheldItem(ResourceLocation resourceLocation) { + return fromPattern(PatternsHelper.createItemHandheld(resourceLocation)); + } + + public static BlockModel createBlockItem(ResourceLocation resourceLocation) { + Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_BLOCK, resourceLocation); + return fromPattern(pattern); + } + + public static BlockModel createBlockEmpty(ResourceLocation resourceLocation) { + Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_EMPTY, resourceLocation); + return fromPattern(pattern); + } + + public static MultiVariant createMultiVariant( + ResourceLocation resourceLocation, + Transformation transform, + boolean uvLock + ) { + Variant variant = new Variant(resourceLocation, transform, uvLock, 1); + return new MultiVariant(Lists.newArrayList(variant)); + } + + public static MultiVariant createBlockSimple(ResourceLocation resourceLocation) { + return createMultiVariant(resourceLocation, Transformation.identity(), false); + } + + public static MultiVariant createFacingModel( + ResourceLocation resourceLocation, + Direction facing, + boolean uvLock, + boolean inverted + ) { + if (inverted) { + facing = facing.getOpposite(); + } + BlockModelRotation rotation = BlockModelRotation.by(0, (int) facing.toYRot()); + return createMultiVariant(resourceLocation, rotation.getRotation(), uvLock); + } + + public static MultiVariant createRotatedModel(ResourceLocation resourceLocation, Direction.Axis axis) { + BlockModelRotation rotation = BlockModelRotation.X0_Y0; + switch (axis) { + case X: + rotation = BlockModelRotation.X90_Y90; + break; + case Z: + rotation = BlockModelRotation.X90_Y0; + break; + default: + break; + } + return createMultiVariant(resourceLocation, rotation.getRotation(), false); + } + + public static MultiVariant createRandomTopModel(ResourceLocation resourceLocation) { + return new MultiVariant(Lists.newArrayList( + new Variant(resourceLocation, Transformation.identity(), false, 1), + new Variant(resourceLocation, BlockModelRotation.X0_Y90.getRotation(), false, 1), + new Variant(resourceLocation, BlockModelRotation.X0_Y180.getRotation(), false, 1), + new Variant(resourceLocation, BlockModelRotation.X0_Y270.getRotation(), false, 1) + )); + } + + public static class MultiPartBuilder { + + //private final static MultiPartBuilder BUILDER = new MultiPartBuilder(); + + public static MultiPartBuilder create(StateDefinition stateDefinition) { + // BUILDER.stateDefinition = stateDefinition; + //BUILDER.modelParts.clear(); + // return BUILDER; + return new MultiPartBuilder(stateDefinition); + } + + private final List modelParts = Lists.newArrayList(); + private final StateDefinition stateDefinition; + + private MultiPartBuilder(StateDefinition stateDefinition) { + this.stateDefinition = stateDefinition; + } + + public ModelPart part(ResourceLocation modelId) { + ModelPart part = new ModelPart(modelId); + return part; + } + + public MultiPart build() { + if (modelParts.size() > 0) { + List selectors = Lists.newArrayList(); + modelParts.forEach(modelPart -> { + MultiVariant variant = createMultiVariant(modelPart.modelId, modelPart.transform, modelPart.uvLock); + selectors.add(new Selector(modelPart.condition, variant)); + }); + modelParts.clear(); + return new MultiPart(stateDefinition, selectors); + } + throw new IllegalStateException("At least one model part need to be created."); + } + + public class ModelPart { + private final ResourceLocation modelId; + private Transformation transform = Transformation.identity(); + private Condition condition = Condition.TRUE; + private boolean uvLock = false; + + private ModelPart(ResourceLocation modelId) { + this.modelId = modelId; + } + + public ModelPart setCondition(Function condition) { + this.condition = stateDefinition -> condition::apply; + return this; + } + + public ModelPart setTransformation(Transformation transform) { + this.transform = transform; + return this; + } + + public ModelPart setUVLock(boolean value) { + this.uvLock = value; + return this; + } + + public void add() { + modelParts.add(this); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/client/models/OBJBlockModel.java b/src/main/java/org/betterx/bclib/client/models/OBJBlockModel.java new file mode 100644 index 00000000..6673f621 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/OBJBlockModel.java @@ -0,0 +1,298 @@ +package org.betterx.bclib.client.models; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.MHelper; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemOverrides; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.*; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.joml.Vector3f; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; +import java.util.function.Function; +import org.jetbrains.annotations.Nullable; + +@Environment(EnvType.CLIENT) +public class OBJBlockModel implements UnbakedModel, BakedModel { + private static final Vector3f[] POSITIONS = new Vector3f[]{new Vector3f(), new Vector3f(), new Vector3f()}; + + protected final Map> quadsUnbakedMap = Maps.newEnumMap(Direction.class); + protected final Map> quadsBakedMap = Maps.newEnumMap(Direction.class); + protected final List quadsUnbaked = Lists.newArrayList(); + protected final List quadsBaked = Lists.newArrayList(); + + protected TextureAtlasSprite[] sprites; + protected ItemTransforms transforms; + protected ItemOverrides overrides; + + protected List materials; + protected boolean useCulling; + protected boolean useShading; + protected byte particleIndex; + + public OBJBlockModel( + ResourceLocation location, + Vector3f offset, + boolean useCulling, + boolean useShading, + byte particleIndex, + ResourceLocation... textureIDs + ) { + for (Direction dir : BlocksHelper.DIRECTIONS) { + quadsUnbakedMap.put(dir, Lists.newArrayList()); + quadsBakedMap.put(dir, Lists.newArrayList()); + } + + transforms = ItemTransforms.NO_TRANSFORMS; + overrides = ItemOverrides.EMPTY; + materials = new ArrayList<>(textureIDs.length); + sprites = new TextureAtlasSprite[textureIDs.length]; + this.particleIndex = particleIndex; + this.useCulling = useCulling; + this.useShading = useShading; + loadModel(location, offset, (byte) (textureIDs.length - 1)); + + for (int i = 0; i < textureIDs.length; i++) { + materials.add(new Material(TextureAtlas.LOCATION_BLOCKS, textureIDs[i])); + } + } + + // UnbakedModel // + + @Override + public Collection getDependencies() { + return Collections.emptyList(); + } + + @Override + public void resolveParents(Function function) { + } + + @Nullable + @Override + public BakedModel bake( + ModelBaker modelBakery, + Function textureGetter, + ModelState modelState, + ResourceLocation resourceLocation + ) { + for (int i = 0; i < sprites.length; i++) { + sprites[i] = textureGetter.apply(materials.get(i)); + } + quadsBaked.clear(); + quadsUnbaked.forEach(quad -> quadsBaked.add(quad.bake(sprites, modelState))); + for (Direction dir : BlocksHelper.DIRECTIONS) { + List unbaked = quadsUnbakedMap.get(dir); + List baked = quadsBakedMap.get(dir); + baked.clear(); + unbaked.forEach(quad -> baked.add(quad.bake(sprites, modelState))); + } + return this; + } + + // Baked Model // + + @Override + public List getQuads( + @Nullable BlockState blockState, + @Nullable Direction direction, + RandomSource random + ) { + return direction == null ? quadsBaked : quadsBakedMap.get(direction); + } + + @Override + public boolean useAmbientOcclusion() { + return true; + } + + @Override + public boolean isGui3d() { + return true; + } + + @Override + public boolean usesBlockLight() { + return true; + } + + @Override + public boolean isCustomRenderer() { + return false; + } + + @Override + public TextureAtlasSprite getParticleIcon() { + return sprites[particleIndex]; + } + + @Override + public ItemTransforms getTransforms() { + return transforms; + } + + @Override + public ItemOverrides getOverrides() { + return overrides; + } + + private Resource getResource(ResourceLocation location) { + return Minecraft.getInstance().getResourceManager().getResource(location).orElse(null); + } + + private void loadModel(ResourceLocation location, Vector3f offset, byte maxIndex) { + Resource resource = getResource(location); + if (resource == null) { + return; + } + InputStream input = null; + try { + input = resource.open(); + } catch (IOException e) { + BCLib.LOGGER.error("Unable to load Model", e); + throw new RuntimeException(e); + } + + List vertecies = new ArrayList<>(12); + List uvs = new ArrayList<>(8); + + List vertexIndex = new ArrayList<>(4); + List uvIndex = new ArrayList<>(4); + + byte materialIndex = -1; + + try { + InputStreamReader streamReader = new InputStreamReader(input); + BufferedReader reader = new BufferedReader(streamReader); + String string; + + while ((string = reader.readLine()) != null) { + if (string.startsWith("usemtl")) { + materialIndex++; + if (materialIndex > maxIndex) { + materialIndex = maxIndex; + } + } else if (string.startsWith("vt")) { + String[] uv = string.split(" "); + uvs.add(Float.parseFloat(uv[1])); + uvs.add(Float.parseFloat(uv[2])); + } else if (string.startsWith("v")) { + String[] vert = string.split(" "); + for (int i = 1; i < 4; i++) { + vertecies.add(Float.parseFloat(vert[i])); + } + } else if (string.startsWith("f")) { + String[] members = string.split(" "); + if (members.length != 5) { + System.out.println("Only quads in OBJ are supported! Model [" + location + "] has n-gons or triangles!"); + continue; + } + vertexIndex.clear(); + uvIndex.clear(); + + for (int i = 1; i < members.length; i++) { + String member = members[i]; + + if (member.contains("/")) { + String[] sub = member.split("/"); + vertexIndex.add(Integer.parseInt(sub[0]) - 1); // Vertex + uvIndex.add(Integer.parseInt(sub[1]) - 1); // UV + } else { + vertexIndex.add(Integer.parseInt(member) - 1); // Vertex + } + } + + boolean hasUV = !uvIndex.isEmpty(); + UnbakedQuad quad = new UnbakedQuad(); + for (int i = 0; i < 4; i++) { + int index = vertexIndex.get(i) * 3; + int quadIndex = i * 5; + quad.addData(quadIndex++, vertecies.get(index++) + offset.x()); // X + quad.addData(quadIndex++, vertecies.get(index++) + offset.y()); // Y + quad.addData(quadIndex++, vertecies.get(index) + offset.z()); // Z + if (hasUV) { + index = uvIndex.get(i) * 2; + quad.addData(quadIndex++, uvs.get(index++) * 16F); // U + quad.addData(quadIndex, (1 - uvs.get(index)) * 16F); // V + } + } + quad.setSpriteIndex(materialIndex); + if (useShading) { + Direction dir = getNormalDirection(quad); + quad.setDirection(dir); + quad.setShading(true); + } + if (useCulling) { + Direction dir = getCullingDirection(quad); + if (dir == null) { + quadsUnbaked.add(quad); + } else { + quadsUnbakedMap.get(dir).add(quad); + } + } else { + quadsUnbaked.add(quad); + } + } + } + + reader.close(); + streamReader.close(); + input.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + if (materialIndex < 0) { + quadsUnbaked.forEach(quad -> quad.setSpriteIndex(0)); + quadsUnbakedMap.values().forEach(list -> list.forEach(quad -> quad.setSpriteIndex(0))); + } + } + + private Direction getNormalDirection(UnbakedQuad quad) { + Vector3f pos = quad.getPos(0, POSITIONS[0]); + Vector3f dirA = quad.getPos(1, POSITIONS[1]); + Vector3f dirB = quad.getPos(2, POSITIONS[2]); + dirA.sub(pos); + dirB.sub(pos); + pos = MHelper.cross(dirA, dirB); + return Direction.getNearest(pos.x(), pos.y(), pos.z()); + } + + @Nullable + private Direction getCullingDirection(UnbakedQuad quad) { + Direction dir = null; + for (int i = 0; i < 4; i++) { + Vector3f pos = quad.getPos(i, POSITIONS[0]); + if (pos.x() < 1 && pos.x() > 0 && pos.y() < 1 && pos.y() > 0 && pos.z() < 1 && pos.z() > 0) { + return null; + } + Direction newDir = Direction.getNearest(pos.x() - 0.5F, pos.y() - 0.5F, pos.z() - 0.5F); + if (dir == null) { + dir = newDir; + } else if (newDir != dir) { + return null; + } + } + return dir; + } +} diff --git a/src/main/java/org/betterx/bclib/client/models/OBJModelBuilder.java b/src/main/java/org/betterx/bclib/client/models/OBJModelBuilder.java new file mode 100644 index 00000000..c17ed39f --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/OBJModelBuilder.java @@ -0,0 +1,112 @@ +package org.betterx.bclib.client.models; + +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Lists; +import org.joml.Vector3f; + +import java.util.List; + +@Environment(EnvType.CLIENT) +public class OBJModelBuilder { + private static final OBJModelBuilder INSTANCE = new OBJModelBuilder(); + private final List textures = Lists.newArrayList(); + private final Vector3f offset = new Vector3f(); + private ResourceLocation modelLocation; + private ResourceLocation particles; + private boolean useCulling; + private boolean useShading; + + private OBJModelBuilder() { + } + + /** + * Start a new bodel building process, clears data of previous builder. + * + * @return {@link OBJModelBuilder} instance. + */ + public static OBJModelBuilder start(ResourceLocation modelLocation) { + INSTANCE.modelLocation = modelLocation; + INSTANCE.offset.set(0, 0, 0); + INSTANCE.useCulling = true; + INSTANCE.useShading = true; + INSTANCE.particles = null; + INSTANCE.textures.clear(); + return INSTANCE; + } + + /** + * Add texture to the model. All textures have indexes with same order as in source OBJ model. + * + * @param texture {@link ResourceLocation} texture ID. + * @return this {@link OBJModelBuilder}. + */ + public OBJModelBuilder addTexture(ResourceLocation texture) { + textures.add(texture); + return this; + } + + /** + * Culling used to remove block faces if they are on block faces or outside of the block to reduce faces count in rendering. + * Opaque blocks shoud have this as true to reduce geometry issues, block like plants should have this as false. + * Default value is {@code true}. + * + * @param useCulling {@link Boolean}. + * @return this {@link OBJModelBuilder}. + */ + public OBJModelBuilder useCulling(boolean useCulling) { + this.useCulling = useCulling; + return this; + } + + /** + * Shading tints block faces in shades of gray to immitate volume in MC rendering. + * Blocks like plants don't have shading, most full opaque blocks - have. + * Default value is {@code true}. + * + * @param useShading {@link Boolean}. + * @return this {@link OBJModelBuilder}. + */ + public OBJModelBuilder useShading(boolean useShading) { + this.useShading = useShading; + return this; + } + + /** + * Set particle texture for this model. + * Not required, if texture is not selected the first texture will be used instead of it. + * + * @param texture {@link ResourceLocation} texture ID. + * @return this {@link OBJModelBuilder}. + */ + public OBJModelBuilder setParticlesTexture(ResourceLocation texture) { + this.particles = texture; + return this; + } + + public OBJModelBuilder setOffset(float x, float y, float z) { + this.offset.set(x, y, z); + return this; + } + + /** + * Builds model from all required data. + * + * @return {@link OBJBlockModel}. + */ + public OBJBlockModel build() { + byte particleIndex = 0; + if (particles != null) { + particleIndex = (byte) textures.indexOf(particles); + if (particleIndex < 0) { + particleIndex = (byte) textures.size(); + textures.add(particles); + } + } + ResourceLocation[] sprites = textures.toArray(new ResourceLocation[textures.size()]); + return new OBJBlockModel(modelLocation, offset, useCulling, useShading, particleIndex, sprites); + } +} diff --git a/src/main/java/org/betterx/bclib/client/models/PatternsHelper.java b/src/main/java/org/betterx/bclib/client/models/PatternsHelper.java new file mode 100644 index 00000000..0547df55 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/PatternsHelper.java @@ -0,0 +1,76 @@ +package org.betterx.bclib.client.models; + +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; + +import com.google.common.collect.Maps; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +public class PatternsHelper { + private static final Map JSON_CACHE = Maps.newConcurrentMap(); + + public static Optional createItemGenerated(ResourceLocation itemId) { + return createJson(BasePatterns.ITEM_GENERATED, itemId); + } + + public static Optional createItemHandheld(ResourceLocation itemId) { + return createJson(BasePatterns.ITEM_HANDHELD, itemId); + } + + public static Optional createBlockSimple(ResourceLocation blockId) { + return createJson(BasePatterns.BLOCK_BASE, blockId); + } + + public static Optional createBlockEmpty(ResourceLocation blockId) { + return createJson(BasePatterns.BLOCK_EMPTY, blockId); + } + + public static Optional createBlockPillar(ResourceLocation blockId) { + return createJson(BasePatterns.BLOCK_PILLAR, blockId); + } + + public static Optional createBlockBottomTop(ResourceLocation blockId) { + return createJson(BasePatterns.BLOCK_BOTTOM_TOP, blockId); + } + + public static Optional createBlockColored(ResourceLocation blockId) { + return createJson(BasePatterns.BLOCK_COLORED, blockId); + } + + public static Optional createJson(ResourceLocation patternId, ResourceLocation blockId) { + Map textures = Maps.newHashMap(); + textures.put("%modid%", blockId.getNamespace()); + textures.put("%texture%", blockId.getPath()); + return createJson(patternId, textures); + } + + public static Optional createJson(ResourceLocation patternId, Map textures) { + ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); + Optional patternRes = resourceManager.getResource(patternId); + if (patternRes.isEmpty()) return Optional.empty(); + + try (InputStream input = patternRes.get().open()) { + String json = JSON_CACHE.get(patternId); + if (json == null) { + json = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)).lines() + .collect(Collectors.joining()); + JSON_CACHE.put(patternId, json); + } + for (Map.Entry texture : textures.entrySet()) { + json = json.replace(texture.getKey(), texture.getValue()); + } + return Optional.of(json); + } catch (Exception ex) { + return Optional.empty(); + } + } +} diff --git a/src/main/java/org/betterx/bclib/client/models/UnbakedQuad.java b/src/main/java/org/betterx/bclib/client/models/UnbakedQuad.java new file mode 100644 index 00000000..a9b167d6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/models/UnbakedQuad.java @@ -0,0 +1,70 @@ +package org.betterx.bclib.client.models; + +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.core.Direction; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector4f; + +@Environment(EnvType.CLIENT) +public class UnbakedQuad { + private static final Vector4f POS = new Vector4f(); + private final float[] data = new float[20]; // 4 points with 3 positions and 2 uvs, 4 * (3 + 2) + private Direction dir = Direction.UP; + private boolean useShading = false; + private int spriteIndex; + + public void addData(int index, float value) { + data[index] = value; + } + + public void setSpriteIndex(int index) { + spriteIndex = index; + } + + public void setDirection(Direction dir) { + this.dir = dir; + } + + public void setShading(boolean useShading) { + this.useShading = useShading; + } + + public Vector3f getPos(int index, Vector3f result) { + int dataIndex = index * 5; + float x = data[dataIndex++]; + float y = data[dataIndex++]; + float z = data[dataIndex]; + result.set(x, y, z); + return result; + } + + public BakedQuad bake(TextureAtlasSprite[] sprites, ModelState modelState) { + Matrix4f matrix = modelState.getRotation().getMatrix(); + TextureAtlasSprite sprite = sprites[spriteIndex]; + int[] vertexData = new int[32]; + for (int i = 0; i < 4; i++) { + int index = i << 3; + int dataIndex = i * 5; + float x = data[dataIndex++]; // X + float y = data[dataIndex++]; // Y + float z = data[dataIndex++]; // Z + POS.set(x, y, z, 0); + POS.mul(matrix); + vertexData[index] = Float.floatToIntBits(POS.x()); // X + vertexData[index | 1] = Float.floatToIntBits(POS.y()); // Y + vertexData[index | 2] = Float.floatToIntBits(POS.z()); // Z + vertexData[index | 3] = -1; // Unknown constant + vertexData[index | 4] = Float.floatToIntBits(sprite.getU(data[dataIndex++])); // U + vertexData[index | 5] = Float.floatToIntBits(sprite.getV(data[dataIndex])); // V + } + // vertices, tint index, direction, sprite, shade + return new BakedQuad(vertexData, 0, dir, sprites[spriteIndex], useShading); + } +} diff --git a/src/main/java/org/betterx/bclib/client/render/BCLRenderLayer.java b/src/main/java/org/betterx/bclib/client/render/BCLRenderLayer.java new file mode 100644 index 00000000..8d21bb5b --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/render/BCLRenderLayer.java @@ -0,0 +1,5 @@ +package org.betterx.bclib.client.render; + +public enum BCLRenderLayer { + CUTOUT, TRANSLUCENT +} diff --git a/src/main/java/org/betterx/bclib/client/render/BaseChestBlockEntityRenderer.java b/src/main/java/org/betterx/bclib/client/render/BaseChestBlockEntityRenderer.java new file mode 100644 index 00000000..cd3a6bc1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/render/BaseChestBlockEntityRenderer.java @@ -0,0 +1,194 @@ +package org.betterx.bclib.client.render; + +import org.betterx.bclib.blockentities.BaseChestBlockEntity; +import org.betterx.bclib.client.models.BaseChestBlockModel; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Axis; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.client.renderer.blockentity.BrightnessCombiner; +import net.minecraft.core.Direction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.DoubleBlockCombiner.NeighborCombineResult; +import net.minecraft.world.level.block.entity.ChestBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.ChestType; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Maps; +import it.unimi.dsi.fastutil.ints.Int2IntFunction; + +import java.util.HashMap; + +@Environment(EnvType.CLIENT) +public class BaseChestBlockEntityRenderer implements BlockEntityRenderer { + private static final HashMap LAYERS = Maps.newHashMap(); + private static final RenderType[] RENDER_TYPES; + + private static final int ID_NORMAL = 0; + private static final int ID_LEFT = 1; + private static final int ID_RIGHT = 2; + + private final BaseChestBlockModel chestModel; + + public BaseChestBlockEntityRenderer(BlockEntityRendererProvider.Context ctx) { + super(); + chestModel = new BaseChestBlockModel(BaseChestBlockModel.getTexturedModelData().bakeRoot()); + } + + public void render( + BaseChestBlockEntity entity, + float tickDelta, + PoseStack matrices, + MultiBufferSource vertexConsumers, + int light, + int overlay + ) { + Level world = entity.getLevel(); + boolean worldExists = world != null; + BlockState blockState = worldExists ? entity.getBlockState() : Blocks.CHEST.defaultBlockState() + .setValue( + ChestBlock.FACING, + Direction.SOUTH + ); + ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) + ? blockState.getValue(ChestBlock.TYPE) + : ChestType.SINGLE; + Block block = blockState.getBlock(); + if (block instanceof AbstractChestBlock) { + AbstractChestBlock abstractChestBlock = (AbstractChestBlock) block; + boolean isDouble = chestType != ChestType.SINGLE; + float f = blockState.getValue(ChestBlock.FACING).toYRot(); + NeighborCombineResult propertySource; + + matrices.pushPose(); + matrices.translate(0.5D, 0.5D, 0.5D); + matrices.mulPose(Axis.YP.rotationDegrees(-f)); + matrices.translate(-0.5D, -0.5D, -0.5D); + + if (worldExists) { + propertySource = abstractChestBlock.combine(blockState, world, entity.getBlockPos(), true); + } else { + propertySource = DoubleBlockCombiner.Combiner::acceptNone; + } + + float pitch = propertySource.apply(ChestBlock.opennessCombiner(entity)).get( + tickDelta); + pitch = 1.0F - pitch; + pitch = 1.0F - pitch * pitch * pitch; + @SuppressWarnings({ + "unchecked", + "rawtypes" + }) int blockLight = ((Int2IntFunction) propertySource.apply(new BrightnessCombiner())).applyAsInt(light); + + VertexConsumer vertexConsumer = getConsumer(vertexConsumers, block, chestType); + + if (isDouble) { + if (chestType == ChestType.LEFT) { + renderParts( + matrices, + vertexConsumer, + chestModel.partLeftA, + chestModel.partLeftB, + chestModel.partLeftC, + pitch, + blockLight, + overlay + ); + } else { + renderParts( + matrices, + vertexConsumer, + chestModel.partRightA, + chestModel.partRightB, + chestModel.partRightC, + pitch, + blockLight, + overlay + ); + } + } else { + renderParts( + matrices, + vertexConsumer, + chestModel.partA, + chestModel.partB, + chestModel.partC, + pitch, + blockLight, + overlay + ); + } + + matrices.popPose(); + } + } + + private void renderParts( + PoseStack matrices, + VertexConsumer vertices, + ModelPart modelPart, + ModelPart modelPart2, + ModelPart modelPart3, + float pitch, + int light, + int overlay + ) { + modelPart.xRot = -(pitch * 1.5707964F); + modelPart2.xRot = modelPart.xRot; + modelPart.render(matrices, vertices, light, overlay); + modelPart2.render(matrices, vertices, light, overlay); + modelPart3.render(matrices, vertices, light, overlay); + } + + private static RenderType getChestTexture(ChestType type, RenderType[] layers) { + return switch (type) { + case LEFT -> layers[ID_LEFT]; + case RIGHT -> layers[ID_RIGHT]; + default -> layers[ID_NORMAL]; + }; + } + + public static VertexConsumer getConsumer(MultiBufferSource provider, Block block, ChestType chestType) { + RenderType[] layers = LAYERS.getOrDefault(block, RENDER_TYPES); + return provider.getBuffer(getChestTexture(chestType, layers)); + } + + public static void registerRenderLayer(Block block) { + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(block); + String modId = blockId.getNamespace(); + String path = blockId.getPath(); + LAYERS.put( + block, + new RenderType[]{ + RenderType.entityCutout(new ResourceLocation(modId, "textures/entity/chest/" + path + ".png")), + RenderType.entityCutout(new ResourceLocation( + modId, + "textures/entity/chest/" + path + "_left.png" + )), + RenderType.entityCutout(new ResourceLocation( + modId, + "textures/entity/chest/" + path + "_right.png" + )) + } + ); + } + + static { + RENDER_TYPES = new RenderType[]{ + RenderType.entityCutout(new ResourceLocation("textures/entity/chest/normal.png")), + RenderType.entityCutout(new ResourceLocation("textures/entity/chest/normal_left.png")), + RenderType.entityCutout(new ResourceLocation("textures/entity/chest/normal_right.png")) + }; + } +} diff --git a/src/main/java/org/betterx/bclib/client/render/BoatRenderer.java b/src/main/java/org/betterx/bclib/client/render/BoatRenderer.java new file mode 100644 index 00000000..ca8dd84c --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/render/BoatRenderer.java @@ -0,0 +1,82 @@ +package org.betterx.bclib.client.render; + +import org.betterx.bclib.items.boat.BoatTypeOverride; +import org.betterx.bclib.items.boat.CustomBoatTypeOverride; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Axis; +import net.minecraft.client.model.ListModel; +import net.minecraft.client.model.WaterPatchModel; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.vehicle.Boat; +import net.minecraft.world.entity.vehicle.ChestBoat; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import org.joml.Quaternionf; + +@Environment(value = EnvType.CLIENT) +public class BoatRenderer { + + public static boolean render( + Boat boat, + float f, + float g, + PoseStack poseStack, + MultiBufferSource multiBufferSource, + int i + ) { + if (boat instanceof CustomBoatTypeOverride cbto) { + BoatTypeOverride type = cbto.bcl_getCustomType(); + if (type != null) { + boolean hasChest = boat instanceof ChestBoat; + float k; + poseStack.pushPose(); + poseStack.translate(0.0, 0.375, 0.0); + poseStack.mulPose(Axis.YP.rotationDegrees(180.0f - f)); + float h = (float) boat.getHurtTime() - g; + float j = boat.getDamage() - g; + if (j < 0.0f) { + j = 0.0f; + } + if (h > 0.0f) { + poseStack.mulPose(Axis.XP.rotationDegrees(Mth.sin(h) * h * j / 10.0f * (float) boat.getHurtDir())); + } + if (!Mth.equal(k = boat.getBubbleAngle(g), 0.0f)) { + poseStack.mulPose(new Quaternionf().setAngleAxis( + boat.getBubbleAngle(g) * ((float) Math.PI / 180), + 1.0f, 0.0f, 1.0f + )); + } + ResourceLocation resourceLocation = hasChest ? type.chestBoatTexture : type.boatTexture; + ListModel boatModel = type.getBoatModel(hasChest); + poseStack.scale(-1.0f, -1.0f, 1.0f); + poseStack.mulPose(Axis.YP.rotationDegrees(90.0f)); + boatModel.setupAnim(boat, g, 0.0f, -0.1f, 0.0f, 0.0f); + VertexConsumer vertexConsumer = multiBufferSource.getBuffer(boatModel.renderType(resourceLocation)); + boatModel.renderToBuffer( + poseStack, vertexConsumer, i, + OverlayTexture.NO_OVERLAY, + 1.0f, 1.0f, 1.0f, 1.0f + ); + if (!boat.isUnderWater()) { + VertexConsumer vertexConsumer2 = multiBufferSource.getBuffer(RenderType.waterMask()); + if (boatModel instanceof WaterPatchModel waterPatchModel) { + waterPatchModel.waterPatch().render(poseStack, vertexConsumer2, i, OverlayTexture.NO_OVERLAY); + } + } + poseStack.popPose(); + + return true; + } + + } + return false; + } +} diff --git a/src/main/java/org/betterx/bclib/client/render/CustomFogRenderer.java b/src/main/java/org/betterx/bclib/client/render/CustomFogRenderer.java new file mode 100644 index 00000000..a6cf7cbd --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/render/CustomFogRenderer.java @@ -0,0 +1,165 @@ +package org.betterx.bclib.client.render; + +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.util.BackgroundInfo; +import org.betterx.bclib.util.MHelper; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Camera; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.material.FogType; + +public class CustomFogRenderer { + private static final MutableBlockPos LAST_POS = new MutableBlockPos(0, -100, 0); + private static final MutableBlockPos MUT_POS = new MutableBlockPos(); + private static final float[] FOG_DENSITY = new float[8]; + private static final int GRID_SIZE = 32; + private static float fogStart = 0; + private static float fogEnd = 192; + + public static boolean applyFogDensity(Camera camera, float viewDistance, boolean thickFog) { + if (!Configs.CLIENT_CONFIG.renderCustomFog()) { + return false; + } + + FogType fogType = camera.getFluidInCamera(); + if (fogType != FogType.NONE) { + BackgroundInfo.fogDensity = 1; + return false; + } + Entity entity = camera.getEntity(); + + if (!isForcedDimension(entity.level()) && shouldIgnoreArea( + entity.level(), + (int) entity.getX(), + (int) entity.getEyeY(), + (int) entity.getZ() + )) { + BackgroundInfo.fogDensity = 1; + return false; + } + + float fog = getFogDensity( + entity.level(), + entity.getX(), + entity.getEyeY(), + entity.getZ() + ) * Configs.CLIENT_CONFIG.fogDensity(); + BackgroundInfo.fogDensity = fog; + + if (thickFog(thickFog, entity.level())) { + fogStart = viewDistance * 0.05F / fog; + fogEnd = Math.min(viewDistance, 192.0F) * 0.5F / fog; + } else { + fogStart = viewDistance * 0.25F / fog; // In vanilla - 0 + fogEnd = viewDistance / fog; + } + + if (entity instanceof LivingEntity) { + LivingEntity livingEntity = (LivingEntity) entity; + MobEffectInstance effect = livingEntity.getEffect(MobEffects.BLINDNESS); + if (effect != null) { + int duration = effect.getDuration(); + if (duration > 20) { + fogStart = 0; + fogEnd *= 0.03F; + BackgroundInfo.blindness = 1; + } else { + float delta = (float) duration / 20F; + BackgroundInfo.blindness = delta; + fogStart = Mth.lerp(delta, fogStart, 0); + fogEnd = Mth.lerp(delta, fogEnd, fogEnd * 0.03F); + } + } else { + BackgroundInfo.blindness = 0; + } + } + RenderSystem.setShaderFogStart(fogStart); + RenderSystem.setShaderFogEnd(fogEnd); + + return true; + } + + private static boolean thickFog(boolean thickFog, Level level) { + if (!thickFog) { + return false; + } + if (level.dimension() == Level.NETHER) { + return Configs.CLIENT_CONFIG.netherThickFog(); + } + return true; + } + + private static boolean isForcedDimension(Level level) { + return level.dimension() == Level.END || level.dimension() == Level.NETHER; + } + + private static boolean shouldIgnoreArea(Level level, int x, int y, int z) { + for (int i = -8; i <= 8; i += 8) { + for (int j = -8; j <= 8; j += 8) { + if (!shouldIgnore(level, x + i, y, z + j)) { + return false; + } + } + } + return true; + } + + private static boolean shouldIgnore(Level level, int x, int y, int z) { + Biome biome = level.getBiome(MUT_POS.set(x, y, z)).value(); + return BCLBiomeRegistry.isEmptyBiome(BiomeAPI.getRenderBiome(biome)); + } + + private static float getFogDensityI(Level level, int x, int y, int z) { + Biome biome = level.getBiome(MUT_POS.set(x, y, z)).value(); + BCLBiome renderBiome = BiomeAPI.getRenderBiome(biome); + if (renderBiome == null) { + return BCLBiomeRegistry.EMPTY_BIOME.settings.getFogDensity(); + } + return renderBiome.settings.getFogDensity(); + } + + private static float getFogDensity(Level level, double x, double y, double z) { + int x1 = MHelper.floor(x / GRID_SIZE) * GRID_SIZE; + int y1 = MHelper.floor(y / GRID_SIZE) * GRID_SIZE; + int z1 = MHelper.floor(z / GRID_SIZE) * GRID_SIZE; + float dx = (float) (x - x1) / GRID_SIZE; + float dy = (float) (y - y1) / GRID_SIZE; + float dz = (float) (z - z1) / GRID_SIZE; + + if (LAST_POS.getX() != x1 || LAST_POS.getY() != y1 || LAST_POS.getZ() != z1) { + int x2 = x1 + GRID_SIZE; + int y2 = y1 + GRID_SIZE; + int z2 = z1 + GRID_SIZE; + LAST_POS.set(x1, y1, z1); + FOG_DENSITY[0] = getFogDensityI(level, x1, y1, z1); + FOG_DENSITY[1] = getFogDensityI(level, x2, y1, z1); + FOG_DENSITY[2] = getFogDensityI(level, x1, y2, z1); + FOG_DENSITY[3] = getFogDensityI(level, x2, y2, z1); + FOG_DENSITY[4] = getFogDensityI(level, x1, y1, z2); + FOG_DENSITY[5] = getFogDensityI(level, x2, y1, z2); + FOG_DENSITY[6] = getFogDensityI(level, x1, y2, z2); + FOG_DENSITY[7] = getFogDensityI(level, x2, y2, z2); + } + + float a = Mth.lerp(dx, FOG_DENSITY[0], FOG_DENSITY[1]); + float b = Mth.lerp(dx, FOG_DENSITY[2], FOG_DENSITY[3]); + float c = Mth.lerp(dx, FOG_DENSITY[4], FOG_DENSITY[5]); + float d = Mth.lerp(dx, FOG_DENSITY[6], FOG_DENSITY[7]); + + a = Mth.lerp(dy, a, b); + b = Mth.lerp(dy, c, d); + + return Mth.lerp(dz, a, b); + } +} diff --git a/src/main/java/org/betterx/bclib/client/render/HumanoidArmorRenderer.java b/src/main/java/org/betterx/bclib/client/render/HumanoidArmorRenderer.java new file mode 100644 index 00000000..5819e3d8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/render/HumanoidArmorRenderer.java @@ -0,0 +1,117 @@ +package org.betterx.bclib.client.render; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ArmorItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer; + +import org.jetbrains.annotations.NotNull; + +@Environment(EnvType.CLIENT) +public abstract class HumanoidArmorRenderer implements ArmorRenderer { + public interface CopyExtraState { + void copyPropertiesFrom(HumanoidModel parentModel); + } + + @Override + public void render( + PoseStack pose, MultiBufferSource buffer, + ItemStack stack, LivingEntity entity, EquipmentSlot slot, + int light, HumanoidModel parentModel + ) { + HumanoidModel model = getModelForSlot(entity, slot); + if (model != null) { + Item item = stack.getItem(); + if (!(item instanceof ArmorItem)) { + return; + } + ArmorItem armorItem = (ArmorItem) item; + if (armorItem.getEquipmentSlot() != slot) { + return; + } + parentModel.copyPropertiesTo(model); + if (model instanceof CopyExtraState mdl) { + mdl.copyPropertiesFrom(parentModel); + } + setPartVisibility(model, slot); + renderModel( + pose, buffer, light, model, + getTextureForSlot(slot, usesInnerModel(slot)), + 1.0f, 1.0f, 1.0f + ); + + if (stack.hasFoil()) { + this.renderGlint(pose, buffer, light, model); + } + } + } + + @NotNull + protected abstract ResourceLocation getTextureForSlot(EquipmentSlot slot, boolean innerLayer); + protected abstract HumanoidModel getModelForSlot(LivingEntity entity, EquipmentSlot slot); + + protected boolean usesInnerModel(EquipmentSlot equipmentSlot) { + return equipmentSlot == EquipmentSlot.LEGS; + } + + protected void renderModel( + PoseStack pose, MultiBufferSource buffer, + int light, + HumanoidModel humanoidModel, ResourceLocation texture, + float r, float g, float b + ) { + VertexConsumer vertexConsumer = buffer.getBuffer(RenderType.armorCutoutNoCull(texture)); + humanoidModel.renderToBuffer(pose, vertexConsumer, light, OverlayTexture.NO_OVERLAY, r, g, b, 1.0f); + } + + protected void renderGlint( + PoseStack pose, + MultiBufferSource buffer, + int light, + HumanoidModel humanoidModel + ) { + humanoidModel.renderToBuffer( + pose, buffer.getBuffer(RenderType.armorEntityGlint()), light, OverlayTexture.NO_OVERLAY, + 1.0f, 1.0f, 1.0f, 1.0f + ); + } + + protected void setPartVisibility(HumanoidModel humanoidModel, EquipmentSlot equipmentSlot) { + humanoidModel.setAllVisible(false); + switch (equipmentSlot) { + case HEAD: { + humanoidModel.head.visible = true; + humanoidModel.hat.visible = true; + break; + } + case CHEST: { + humanoidModel.body.visible = true; + humanoidModel.rightArm.visible = true; + humanoidModel.leftArm.visible = true; + break; + } + case LEGS: { + humanoidModel.body.visible = true; + humanoidModel.rightLeg.visible = true; + humanoidModel.leftLeg.visible = true; + break; + } + case FEET: { + humanoidModel.rightLeg.visible = true; + humanoidModel.leftLeg.visible = true; + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/client/sound/BlockSounds.java b/src/main/java/org/betterx/bclib/client/sound/BlockSounds.java new file mode 100644 index 00000000..fd4f6b80 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/sound/BlockSounds.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.client.sound; + +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.level.block.SoundType; + +public class BlockSounds { + public static final SoundType TERRAIN_SOUND = new SoundType( + 1.0F, + 1.0F, + SoundEvents.STONE_BREAK, + SoundEvents.WART_BLOCK_STEP, + SoundEvents.STONE_PLACE, + SoundEvents.STONE_HIT, + SoundEvents.STONE_FALL + ); +} diff --git a/src/main/java/org/betterx/bclib/client/textures/AtlasSetManager.java b/src/main/java/org/betterx/bclib/client/textures/AtlasSetManager.java new file mode 100644 index 00000000..6bae95c5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/textures/AtlasSetManager.java @@ -0,0 +1,29 @@ +package org.betterx.bclib.client.textures; + +import net.minecraft.client.renderer.texture.atlas.SpriteSource; +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +@Environment(value = EnvType.CLIENT) +public class AtlasSetManager { + public static final ResourceLocation VANILLA_BLOCKS = new ResourceLocation("blocks"); + private static Map> additionalSets = new HashMap<>(); + + public static void addSource(ResourceLocation type, SpriteSource source) { + additionalSets.computeIfAbsent(type, (t) -> new LinkedList<>()).add(source); + } + + public static void onLoadResources(ResourceLocation type, List sources) { + List additionalSources = additionalSets.get(type); + if (additionalSources != null) { + sources.addAll(additionalSources); + } + } +} diff --git a/src/main/java/org/betterx/bclib/client/textures/SpriteLister.java b/src/main/java/org/betterx/bclib/client/textures/SpriteLister.java new file mode 100644 index 00000000..8a616b85 --- /dev/null +++ b/src/main/java/org/betterx/bclib/client/textures/SpriteLister.java @@ -0,0 +1,13 @@ +package org.betterx.bclib.client.textures; + +import net.minecraft.client.renderer.texture.atlas.sources.DirectoryLister; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(value = EnvType.CLIENT) +public class SpriteLister extends DirectoryLister { + public SpriteLister(String string) { + super(string, string + "/"); + } +} diff --git a/src/main/java/org/betterx/bclib/commands/CommandRegistry.java b/src/main/java/org/betterx/bclib/commands/CommandRegistry.java new file mode 100644 index 00000000..f8250e7e --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/CommandRegistry.java @@ -0,0 +1,209 @@ +package org.betterx.bclib.commands; + +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; + +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; + +import java.util.HashMap; +import java.util.Map; + +public class CommandRegistry { + public static void register() { + CommandRegistrationCallback.EVENT.register(CommandRegistry::register); + } + + private static void register( + CommandDispatcher dispatcher, + CommandBuildContext commandBuildContext, + Commands.CommandSelection commandSelection + ) { + LiteralArgumentBuilder bnContext = Commands.literal("bclib") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)); + + bnContext = PlaceCommand.register(bnContext, commandBuildContext); + bnContext = PrintInfo.register(bnContext); + bnContext = DumpDatapack.register(bnContext); + + dispatcher.register( + bnContext + .then(Commands.literal("request_garbage_collection") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(CommandRegistry::requestGC) + ) + .then(Commands.literal("debug_ore") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(CommandRegistry::revealOre) + ) + .then(Commands.literal("sliceZ") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(ctx -> slice(ctx, true)) + ) + .then(Commands.literal("sliceX") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(ctx -> slice(ctx, false)) + ) + ); + } + + private static int requestGC(CommandContext ctx) { + System.gc(); + return Command.SINGLE_SUCCESS; + } + + private static final Map, BlockState> biomeMap = new HashMap<>(); + private static int biomeMapIdx = 0; + private static final BlockState[] states = { + Blocks.RED_STAINED_GLASS.defaultBlockState(), + Blocks.BLUE_STAINED_GLASS.defaultBlockState(), + Blocks.YELLOW_STAINED_GLASS.defaultBlockState(), + Blocks.LIME_STAINED_GLASS.defaultBlockState(), + Blocks.PINK_STAINED_GLASS.defaultBlockState(), + Blocks.GREEN_STAINED_GLASS.defaultBlockState(), + Blocks.WHITE_STAINED_GLASS.defaultBlockState(), + Blocks.BLACK_STAINED_GLASS.defaultBlockState(), + Blocks.ORANGE_STAINED_GLASS.defaultBlockState(), + Blocks.LIGHT_BLUE_STAINED_GLASS.defaultBlockState() + }; + private static final BlockState[] states2 = { + Blocks.RED_CONCRETE.defaultBlockState(), + Blocks.BLUE_CONCRETE.defaultBlockState(), + Blocks.YELLOW_CONCRETE.defaultBlockState(), + Blocks.LIME_CONCRETE.defaultBlockState(), + Blocks.PINK_CONCRETE.defaultBlockState(), + Blocks.GREEN_CONCRETE.defaultBlockState(), + Blocks.WHITE_CONCRETE.defaultBlockState(), + Blocks.BLACK_CONCRETE.defaultBlockState(), + Blocks.ORANGE_CONCRETE.defaultBlockState(), + Blocks.LIGHT_BLUE_CONCRETE.defaultBlockState() + }; + + private static int revealOre(CommandContext ctx) throws CommandSyntaxException { + final CommandSourceStack source = ctx.getSource(); + final ServerLevel level = source.getLevel(); + final Vec3 pos = source.getPosition(); + + MutableBlockPos bp = new MutableBlockPos(); + BlockState state; + BlockState fillState; + final BlockState AIR = Blocks.AIR.defaultBlockState(); + + for (int y = 1; y < level.getHeight(); y++) { + bp.setY(y); + for (int x = -64; x < 64; x++) { + bp.setX((int) pos.x + x); + for (int z = -64; z < 64; z++) { + bp.setZ((int) pos.z + z); + if (y == 1) { + Holder b = level.getBiome(bp); + fillState = biomeMap.computeIfAbsent(b, (bb) -> { + biomeMapIdx = (biomeMapIdx + 1) % states.length; + return states[biomeMapIdx]; + }); + } else { + fillState = AIR; + } + + state = level.getBlockState(bp); + if (y == 1 || !state.is(Blocks.AIR)) { + if (!(state.is(CommonBlockTags.NETHER_ORES) + || state.is(CommonBlockTags.END_ORES) + || state.is(BlockTags.COAL_ORES) + || state.is(BlockTags.COPPER_ORES) + || state.is(BlockTags.DIAMOND_ORES) + || state.is(BlockTags.EMERALD_ORES) + || state.is(BlockTags.GOLD_ORES) + || state.is(BlockTags.IRON_ORES) + || state.is(BlockTags.LAPIS_ORES) + || state.is(BlockTags.REDSTONE_ORES) + || state.is(Blocks.NETHER_QUARTZ_ORE) + || state.is(Blocks.NETHER_GOLD_ORE) + || state.is(Blocks.ANCIENT_DEBRIS))) { + BlocksHelper.setWithoutUpdate(level, bp, fillState); + } + } + } + } + } + return Command.SINGLE_SUCCESS; + } + + private static int slice(CommandContext ctx, boolean constX) throws CommandSyntaxException { + final CommandSourceStack source = ctx.getSource(); + final ServerLevel level = source.getLevel(); + final Vec3 pos = source.getPosition(); + + BlockState AIR = Blocks.AIR.defaultBlockState(); + MutableBlockPos bp = new MutableBlockPos(); + BlockState state; + BlockState fillState; + + + for (int y = 1; y < level.getHeight(); y++) { + bp.setY(y); + for (int x = constX ? 0 : -64; x < 64; x++) { + bp.setX((int) pos.x + x); + for (int z = constX ? -64 : 0; z < 64; z++) { + bp.setZ((int) pos.z + z); + if (y == 1) { + Holder b = level.getBiome(bp); + fillState = biomeMap.computeIfAbsent(b, (bb) -> { + biomeMapIdx = (biomeMapIdx + 1) % states.length; + return states[biomeMapIdx]; + }); + } else { + fillState = AIR; + } + + BlocksHelper.setWithoutUpdate(level, bp, fillState); + } + } + } + return Command.SINGLE_SUCCESS; + } + + private static int findSurface(CommandContext ctx) throws CommandSyntaxException { + final CommandSourceStack source = ctx.getSource(); + final ServerPlayer player = source.getPlayerOrException(); + Vec3 pos = source.getPosition(); + final ServerLevel level = source.getLevel(); + MutableBlockPos mPos = new BlockPos((int) pos.x, (int) pos.y, (int) pos.z).mutable(); + System.out.println("Staring at: " + mPos + " -> " + level.getBlockState(mPos)); + boolean found = org.betterx.bclib.util.BlocksHelper.findSurroundingSurface( + level, + mPos, + Direction.DOWN, + 12, + state -> BlocksHelper.isTerrain(state) + ); + System.out.println("Ending at: " + mPos + " -> " + level.getBlockState(mPos) + " = " + found); + org.betterx.bclib.util.BlocksHelper.setWithoutUpdate( + level, + new BlockPos((int) pos.x, (int) pos.y, (int) pos.z), + Blocks.YELLOW_CONCRETE + ); + org.betterx.bclib.util.BlocksHelper.setWithoutUpdate(level, mPos, Blocks.LIGHT_BLUE_CONCRETE); + return Command.SINGLE_SUCCESS; + } +} + diff --git a/src/main/java/org/betterx/bclib/commands/DumpDatapack.java b/src/main/java/org/betterx/bclib/commands/DumpDatapack.java new file mode 100644 index 00000000..b9552ff9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/DumpDatapack.java @@ -0,0 +1,481 @@ +package org.betterx.bclib.commands; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeData; +import org.betterx.bclib.blocks.BaseStairsBlock; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.KeyDispatchCodec; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.TagEntry; +import net.minecraft.tags.TagFile; +import net.minecraft.util.KeyDispatchDataCodec; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.levelgen.DensityFunction; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorPreset; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.presets.WorldPreset; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; +import net.minecraft.world.level.levelgen.structure.templatesystem.PosRuleTestType; +import net.minecraft.world.level.levelgen.structure.templatesystem.RuleTestType; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorType; +import net.minecraft.world.level.levelgen.synth.NormalNoise; +import net.minecraft.world.level.material.Fluid; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.awt.Taskbar.Feature; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public class DumpDatapack { + public static LiteralArgumentBuilder register(LiteralArgumentBuilder bnContext) { + return bnContext + .then(Commands.literal("dump_datapack") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(DumpDatapack::dumpDatapack) + ); + } + + private record Dumper, H extends Holder>( + Function codecFunction, + Function, T> contentTransform + ) { + Dumper(Function codecFunction) { + this(codecFunction, h -> h.value()); + } + } + + private static final Map DUMPERS = new HashMap<>(); + + static int dumpDatapack(CommandContext ctx) { + File base = new File(System.getProperty("user.dir"), "bclib_datapack_dump"); + dumpDatapack(base, ctx.getSource().getLevel().registryAccess(), ctx); + + + ctx.getSource().sendSuccess( + () -> Component.literal("Succesfully written to:\n ").append( + Component.literal(base.toString()).setStyle(Style.EMPTY.withUnderlined(true)) + ), + false + ); + return Command.SINGLE_SUCCESS; + } + + public static void dumpDatapack(File base, RegistryAccess registryAccess, CommandContext ctx) { + final RegistryOps registryOps = RegistryOps.create(JsonOps.INSTANCE, registryAccess); + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder = gsonBuilder.setPrettyPrinting(); + Gson gson = gsonBuilder.create(); + + registryAccess.registries().forEach(r -> dump(base, r, registryOps, gson)); + + BCLib.LOGGER.info("- Serializing Dimensions "); + + for (ServerLevel serverLevel : ctx.getSource().getLevel().getServer().getAllLevels()) { + File f1 = new File(base, serverLevel.dimension().location().getNamespace()); + f1 = new File(f1, "dimension"); + f1 = new File(f1, serverLevel.dimension().location().getPath() + ".json"); + f1.getParentFile().mkdirs(); + + try { + LevelStem stem = new LevelStem( + serverLevel.dimensionTypeRegistration(), + serverLevel.getChunkSource().getGenerator() + ); + Codec codec = LevelStem.CODEC; + var o = codec + .encodeStart(registryOps, stem) + .result() + .orElse(new JsonObject()); + + String content = gson.toJson(o); + try { + Files.writeString(f1.toPath(), content, StandardCharsets.UTF_8); + } catch (IOException e) { + BCLib.LOGGER.error(" ->> Unable to WRITE: " + e.getMessage()); + } + } catch (Exception e) { + BCLib.LOGGER.error(" ->> Unable to encode: " + e.getMessage()); + } + } + + } + + + private static void dump( + File base, RegistryAccess.RegistryEntry registry, + RegistryOps registryOps, + Gson gson + ) { + BCLib.LOGGER.info("- Serializing: " + registry.key().toString()); + DUMPERS.clear(); + DUMPERS.put(Registries.BIOME.location(), new Dumper<>((Biome v) -> Biome.DIRECT_CODEC)); + DUMPERS.put( + Registries.CONFIGURED_FEATURE.location(), + new Dumper<>((ConfiguredFeature v) -> ConfiguredFeature.DIRECT_CODEC) + ); + DUMPERS.put( + Registries.WORLD_PRESET.location(), + new Dumper<>((WorldPreset v) -> WorldPreset.DIRECT_CODEC) + ); + DUMPERS.put( + Registries.NOISE_SETTINGS.location(), + new Dumper<>((NoiseGeneratorSettings v) -> NoiseGeneratorSettings.DIRECT_CODEC) + ); + DUMPERS.put(Registries.STRUCTURE.location(), new Dumper<>((Structure v) -> Structure.DIRECT_CODEC)); + DUMPERS.put( + Registries.DIMENSION_TYPE.location(), + new Dumper<>((DimensionType v) -> DimensionType.DIRECT_CODEC) + ); + DUMPERS.put(BCLBiomeRegistry.BCL_BIOMES_REGISTRY.location(), new Dumper<>((BCLBiome v) -> v.codec().codec())); + + DUMPERS.put( + Registries.CONFIGURED_CARVER.location(), + new Dumper<>((ConfiguredWorldCarver v) -> ConfiguredWorldCarver.DIRECT_CODEC) + ); + DUMPERS.put( + Registries.PROCESSOR_LIST.location(), + new Dumper<>((StructureProcessorList v) -> StructureProcessorType.DIRECT_CODEC) + ); + DUMPERS.put( + Registries.FLAT_LEVEL_GENERATOR_PRESET.location(), + new Dumper<>((FlatLevelGeneratorPreset v) -> FlatLevelGeneratorPreset.DIRECT_CODEC) + ); + DUMPERS.put( + Registries.DENSITY_FUNCTION.location(), + new Dumper<>((DensityFunction v) -> DensityFunction.DIRECT_CODEC) + ); + DUMPERS.put( + Registries.PLACED_FEATURE.location(), + new Dumper<>((PlacedFeature v) -> PlacedFeature.DIRECT_CODEC) + ); + DUMPERS.put( + Registries.NOISE.location(), + new Dumper<>((NormalNoise.NoiseParameters v) -> NormalNoise.NoiseParameters.DIRECT_CODEC) + ); + + DUMPERS.put( + Registries.TEMPLATE_POOL.location(), + new Dumper<>((StructureTemplatePool v) -> StructureTemplatePool.DIRECT_CODEC) + ); + DUMPERS.put( + Registries.STRUCTURE_SET.location(), + new Dumper<>((StructureSet v) -> StructureSet.DIRECT_CODEC) + ); + + + Dumper d = DUMPERS.getOrDefault(registry.key().location(), new Dumper(v -> registry.value().byNameCodec())); + //Dumper d = DUMPERS.get(registry.key().location()); + if (d != null) + dump(base, registry, registryOps, gson, d.codecFunction, d.contentTransform); + else + BCLib.LOGGER.warning(" No Codec Found"); + + } + + private static void dump( + File base, RegistryAccess.RegistryEntry registry, + RegistryOps registryOps, + Gson gson, + Function> codecFunction, + Function, Object> contentTransform + ) { + BCLib.LOGGER.info(" - Serializing Tags"); + dumpTags(base, registry, registryOps, gson); + + BCLib.LOGGER.info(" - Serializing Content"); + int[] count = {0, 0}; + registry + .value() + .entrySet() + .stream() + .map(e -> e.getKey()).map(key -> registry.value().getHolder(key).get()) + .forEach(holder -> { + File f1 = new File(base, holder.unwrapKey().get().location().getNamespace()); + f1 = new File(f1, registry.key().location().getPath()); + f1.mkdirs(); + f1 = new File(f1, holder.unwrapKey().get().location().getPath() + ".json"); + f1.getParentFile().mkdirs(); + //BCLib.LOGGER.info(" - " + f1); + + Object obj = contentTransform.apply(holder); + try { + Codec codec = codecFunction.apply(obj); + var o = codec + .encodeStart(registryOps, obj) + .result() + .orElse(new JsonObject()); + + String content = gson.toJson(o); + try { + Files.writeString(f1.toPath(), content, StandardCharsets.UTF_8); + count[0]++; + } catch (IOException e) { + count[1]++; + BCLib.LOGGER.error(" ->> Unable to WRITE: " + e.getMessage()); + } + } catch (Exception e) { + count[1]++; + BCLib.LOGGER.error(" ->> Unable to encode: " + e.getMessage()); + } + }); + BCLib.LOGGER.info(" -> Wrote " + count[0] + " files (" + count[1] + " errors)"); + } + + + private static void dumpDatapackOld( + File base, + RegistryAccess.RegistryEntry registry, + RegistryOps registryOps, + Gson gson + ) { + BCLib.LOGGER.info(registry.key().toString()); + dumpTags(base, registry, registryOps, gson); + + registry + .value() + .entrySet() + .stream() + .map(e -> e.getKey()).map(key -> registry.value().getHolder(key).get()) + .forEach(holder -> { + File f1 = new File(base, holder.unwrapKey().get().location().getNamespace()); + f1 = new File(f1, registry.key().location().getPath()); + f1.mkdirs(); + f1 = new File(f1, holder.unwrapKey().get().location().getPath() + ".json"); + f1.getParentFile().mkdirs(); + + Codec[] codec = {null}; + + //BCLib.LOGGER.info(" - " + f1); + Object obj = holder; + + while (obj instanceof Holder) { + obj = ((Holder) obj).value(); + } + + if (obj instanceof BiomeSource || obj instanceof Feature) { + System.out.print(""); + } + System.out.println(obj.getClass()); + + if (obj instanceof Structure s) { + codec[0] = s.type().codec(); + } else if (obj instanceof BCLBiome s) { + codec[0] = BiomeData.CODEC; + } else if (obj instanceof StructureProcessorList s) { + codec[0] = StructureProcessorType.LIST_OBJECT_CODEC; + } else if (obj instanceof GameEvent) { + return; + } else if (obj instanceof Fluid) { + return; + } else if (obj instanceof MobEffect) { + return; + } else if (obj instanceof BaseStairsBlock) { + return; + } else if (obj instanceof RuleTestType) { + codec[0] = registry.value().byNameCodec(); + } else if (obj instanceof PosRuleTestType) { + codec[0] = registry.value().byNameCodec(); + } else if (obj instanceof WorldGenSettings) { + codec[0] = registry.value().byNameCodec(); + } else if (obj instanceof LevelStem) { + codec[0] = registry.value().byNameCodec(); + } + + if (codec[0] == null) { + for (Method m : obj.getClass().getMethods()) { + if (!Modifier.isStatic(m.getModifiers())) { + m.setAccessible(true); + if (m.getParameterTypes().length == 0) { + if (Codec.class.isAssignableFrom(m.getReturnType())) { + try { + codec[0] = (Codec) m.invoke(obj); + BCLib.LOGGER.debug(" Got Codec from " + m); + break; + } catch (Exception e) { + BCLib.LOGGER.error(" !!! Unable to get Codec from " + m); + } + } else if (KeyDispatchCodec.class.isAssignableFrom(m.getReturnType())) { + try { + codec[0] = ((KeyDispatchCodec) m.invoke(obj)).codec(); + BCLib.LOGGER.debug(" Got Codec from " + m); + break; + } catch (Exception e) { + BCLib.LOGGER.error(" !!! Unable to get Codec from " + m); + } + } else if (KeyDispatchDataCodec.class.isAssignableFrom(m.getReturnType())) { + try { + codec[0] = ((KeyDispatchDataCodec) m.invoke(obj)).codec(); + BCLib.LOGGER.debug(" Got Codec from " + m); + break; + } catch (Exception e) { + BCLib.LOGGER.error(" !!! Unable to get Codec from " + m); + } + } + } + } + } + } + + if (codec[0] == null) { + //Try to find DIRECT_CODEC field + for (Field f : obj.getClass().getFields()) { + if (Modifier.isStatic(f.getModifiers())) { + if ("DIRECT_CODEC".equals(f.getName())) { + f.setAccessible(true); + try { + codec[0] = (Codec) f.get(null); + BCLib.LOGGER.debug(" Got Codec from " + f); + } catch (Exception e) { + BCLib.LOGGER.error(" !!! Unable to get Codec from " + f); + } + } + } + } + } + + //Try to find CODEC field + if (codec[0] == null) { + for (Field f : obj.getClass().getFields()) { + if (Modifier.isStatic(f.getModifiers())) { + if ("CODEC".equals(f.getName())) { + try { + f.setAccessible(true); + codec[0] = (Codec) f.get(null); + BCLib.LOGGER.debug(" Got Codec from " + f); + } catch (Exception e) { + BCLib.LOGGER.error(" !!! Unable to get Codec from " + f); + } + } + } + } + } + + //Try to find any Codec field + if (codec[0] == null) { + for (Field f : obj.getClass().getFields()) { + if (Modifier.isStatic(f.getModifiers())) { + if (Codec.class.isAssignableFrom(f.getType())) { + f.setAccessible(true); + try { + codec[0] = (Codec) f.get(null); + BCLib.LOGGER.debug(" Got Codec from " + f); + } catch (Exception e) { + BCLib.LOGGER.error(" !!! Unable to get Codec from " + f); + } + } + } + } + } + + if (codec[0] == null) { + codec[0] = registry.value().byNameCodec(); + } + + if (codec[0] == null) { + codec[0] = registry.value().byNameCodec(); + } + + + if (codec[0] != null) { + try { + var o = codec[0] + .encodeStart(registryOps, obj) + .result() + .orElse(new JsonObject()); + + String content = gson.toJson(o); + try { + Files.writeString(f1.toPath(), content, StandardCharsets.UTF_8); + } catch (IOException e) { + BCLib.LOGGER.error(" ->> Unable to WRITE: " + e.getMessage()); + } + } catch (Exception e) { + BCLib.LOGGER.error(" ->> Unable to encode: " + e.getMessage()); + } + } else { + BCLib.LOGGER.error(" !!! Could not determine Codec: " + obj.getClass()); + } + }); + + } + + private static void dumpTags( + File base, + RegistryAccess.RegistryEntry registry, + RegistryOps registryOps, + Gson gson + ) { + // Tag Output + registry.value() + .getTagNames() + .map(tagKey -> registry.value().getTag(tagKey)) + .filter(tag -> tag.isPresent()) + .map(tag -> tag.get()) + .forEach(tag -> { + File f1 = new File(base, tag.key().location().getNamespace()); + f1 = new File(f1, "tags"); + f1 = new File(f1, registry.key().location().getPath()); + f1 = new File(f1, tag.key().location().getPath() + ".json"); + f1.getParentFile().mkdirs(); + + TagFile tf = new TagFile( + tag.stream() + .map(holder -> holder.unwrapKey()) + .filter(k -> k.isPresent()) + .map(k -> TagEntry.element(k.get().location())) + .toList(), + true + ); + var o = TagFile.CODEC + .encodeStart(registryOps, tf) + .result() + .orElse(new JsonObject()); + String content = gson.toJson(o); + try { + Files.writeString(f1.toPath(), content, StandardCharsets.UTF_8); + } catch (IOException e) { + BCLib.LOGGER.error(" ->> Unable to WRITE: " + e.getMessage()); + } + }); + } +} diff --git a/src/main/java/org/betterx/bclib/commands/PlaceCommand.java b/src/main/java/org/betterx/bclib/commands/PlaceCommand.java new file mode 100644 index 00000000..ab6e69ad --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/PlaceCommand.java @@ -0,0 +1,643 @@ +package org.betterx.bclib.commands; + +import de.ambertation.wunderlib.math.Bounds; +import de.ambertation.wunderlib.math.Float3; +import org.betterx.bclib.api.v2.levelgen.structures.StructureNBT; +import org.betterx.bclib.commands.arguments.ConnectorArgument; +import org.betterx.bclib.commands.arguments.Float3ArgumentType; +import org.betterx.bclib.commands.arguments.PlacementDirections; +import org.betterx.bclib.commands.arguments.TemplatePlacementArgument; +import org.betterx.bclib.util.BlocksHelper; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.ResourceKeyArgument; +import net.minecraft.commands.arguments.ResourceLocationArgument; +import net.minecraft.commands.arguments.blocks.BlockInput; +import net.minecraft.commands.arguments.blocks.BlockStateArgument; +import net.minecraft.commands.arguments.blocks.BlockStateParser; +import net.minecraft.commands.arguments.coordinates.BlockPosArgument; +import net.minecraft.commands.arguments.coordinates.Coordinates; +import net.minecraft.core.*; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.*; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.AttachFace; +import net.minecraft.world.level.block.state.properties.StructureMode; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; + +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class PlaceCommandBuilder { + public static final String PATH = "path"; + public static final String NBT = "nbt"; + public static final String EMPTY = "empty"; + public static final String PLACEMENT = "placement"; + public static final String POS = "pos"; + public static final String RECURSION_DEPTH = "recursion_depth"; + public static final String SPAN = "span"; + public static final String BORDER = "border"; + public static final String ADD_CONTROLL_BLOCKS = "controller"; + public static final String FILL_VOID = "replaceair"; + public static final String JIGSAW = "jigsaw"; + public static final String CONNECTOR_NAME = "connector_name"; + public static final String REPLACE_WITH = "replace_with"; + public static final String ROLLABLE = "rollable"; + public static final String REPLACE_FROM_WORLD = "fromWorld"; + + public void register( + CommandBuildContext ctx, + LiteralArgumentBuilder command + ) { + final Supplier> path = () -> Commands.argument( + PATH, + ResourceLocationArgument.id() + ); + final Supplier> placement = () -> Commands.argument( + PLACEMENT, + TemplatePlacementArgument.templatePlacement() + ); + final Supplier> pos = () -> Commands.argument( + POS, + BlockPosArgument.blockPos() + ); + final Supplier> recursionDepth = () -> Commands.argument( + RECURSION_DEPTH, + IntegerArgumentType.integer(0, 16) + ); + final Function> placeIt = (hasRecursionArg) -> placement + .get() + .then( + addOptionalsAndExecute( + ctx, + pos.get(), + hasRecursionArg, + PlaceCommandBuilder::placeNBT + ) + ); + + final var nbtTree = Commands.literal(NBT).then( + path.get() + .then(recursionDepth.get().then(placeIt.apply(true))) + .then(placeIt.apply(false)) + ); + + final var emptyTree = Commands.literal(EMPTY).then( + path.get().then( + placement.get().then( + pos.get().then( + addOptionalsAndExecute( + ctx, + Commands.argument(SPAN, Float3ArgumentType.int3(0, 64)), + false, + PlaceCommandBuilder::placeEmpty + ) + ) + ) + ) + ); + final Supplier> replace = () -> Commands.argument( + REPLACE_WITH, + BlockStateArgument.block(ctx) + ); + final Supplier> replace_source = () -> Commands.literal( + REPLACE_FROM_WORLD); + final Supplier> rotate = () -> Commands.literal(ROLLABLE); + + final var jigsawTree = Commands.literal(JIGSAW).then( + Commands.argument(PlaceCommand.POOL, ResourceKeyArgument.key(Registries.TEMPLATE_POOL)).then( + Commands.argument(CONNECTOR_NAME, ConnectorArgument.id()) + .then(replace.get() + .then(rotate.get() + .then(pos.get() + .executes(cc -> placeJigsaw(cc, true, true, false)))) + .then(pos.get().executes(cc -> placeJigsaw(cc, true, false, false))) + ) + .then(replace_source.get() + .then(rotate.get() + .then(pos.get() + .executes(cc -> placeJigsaw( + cc, + false, + true, + true + )))) + .then(pos.get().executes(cc -> placeJigsaw(cc, false, false, true))) + ) + .then(rotate.get() + .then(pos.get().executes(cc -> placeJigsaw(cc, false, true, false)))) + .then( + pos.get().executes(cc -> placeJigsaw(cc, false, false, false)) + ) + + ) + ); + + +// final var testSpaner = Commands.literal("spawner") +// .then(pos.get().executes(cc -> placeSpawner(cc))); + + command + .then(nbtTree) + .then(emptyTree) + .then(jigsawTree) + ; + } + + private RequiredArgumentBuilder addOptionalsAndExecute( + CommandBuildContext commandBuildContext, + RequiredArgumentBuilder root, + boolean hasRecursionArgs, + Executor runner + ) { + final Supplier> addControllers = () -> Commands.literal( + ADD_CONTROLL_BLOCKS); + final Supplier> replaceAir = () -> Commands.literal(FILL_VOID); + final Supplier> addBorder = () -> Commands.argument( + BORDER, + BlockStateArgument.block(commandBuildContext) + ); + + return root + .executes(c -> runner.exec(c, false, false, false, hasRecursionArgs)) + .then(addBorder.get() + .executes(c -> runner.exec(c, true, false, false, hasRecursionArgs)) + .then(addControllers.get() + .executes(c -> runner.exec(c, true, true, false, hasRecursionArgs))) + .then(addControllers.get() + .then(replaceAir.get() + .executes(c -> runner.exec( + c, + true, + true, + true, + hasRecursionArgs + ))) + ) + ) + .then(addControllers.get().executes(c -> runner.exec(c, false, true, false, hasRecursionArgs))) + .then(addControllers.get() + .then(replaceAir.get() + .executes(c -> runner.exec( + c, + false, + true, + true, + hasRecursionArgs + )))); + } + + interface Executor { + int exec( + CommandContext ctx, + boolean hasBorderArg, + boolean controlBlocks, + boolean replaceAir, + boolean hasRecursionArg + ) throws CommandSyntaxException; + } + + // /bclib place nbt betternether:city southOf 0 -59 0 controller + // /bclib place nbt minecraft:village/plains 0 southOf 0 -59 0 controller + protected static int placeNBT( + CommandContext ctx, + boolean hasBorderArg, + boolean controlBlocks, + boolean replaceAir, + boolean hasRecursionArg + ) throws CommandSyntaxException { + final ResourceLocation id = ResourceLocationArgument.getId(ctx, PATH); + final PlacementDirections searchDir = TemplatePlacementArgument.getPlacement(ctx, PLACEMENT); + final BlockInput blockInput = hasBorderArg ? BlockStateArgument.getBlock(ctx, BORDER) : null; + final BlockPos pos = BlockPosArgument.getLoadedBlockPos(ctx, POS); + final int recursionDepth = hasRecursionArg ? IntegerArgumentType.getInteger(ctx, RECURSION_DEPTH) : 1; + final List structures = StructureNBT.createResourcesFrom(id, recursionDepth); + if (structures != null) { + Bounds b = Bounds.of(pos); + Bounds all = Bounds.of(pos); + BlockPos pNew = pos; + BlockPos rowStart = pos; + String lastPrefix = null; + for (var s : structures) { + String prefix = s.location.getPath().contains("/") + ? s.location.getPath().replaceAll("/[^/]*$", "") + : ""; + if (lastPrefix != null && !lastPrefix.equals(prefix)) { + pNew = searchDir.resetStart(b, pNew, 10); + rowStart = pNew; + b = Bounds.of(pNew); + } + lastPrefix = prefix; + + Bounds bb = Bounds.of(Objects.requireNonNull(PlaceCommand.placeBlocks( + ctx.getSource(), + rowStart, + searchDir.getOffset(), + blockInput, + controlBlocks, + replaceAir, + true, + s.location, + (p) -> s.getBoundingBox(p, Rotation.NONE, Mirror.NONE), + (level, p) -> s.generateAt(level, p, Rotation.NONE, Mirror.NONE) + ))); + rowStart = searchDir.advanceStart(bb, rowStart); + ; + + b = b.encapsulate(bb); + all = all.encapsulate(bb); + + + if (searchDir.sizeInDirection(b) > 10 * 16) { + pNew = searchDir.resetStart(b, pNew); + rowStart = pNew; + b = Bounds.of(pNew); + } + } + Bounds finalAll = all; + ctx.getSource() + .sendSuccess(() -> Component.literal("Placed " + structures.size() + " NBTs: " + finalAll.toString()) + .setStyle(Style.EMPTY.withColor(ChatFormatting.LIGHT_PURPLE)), true); + + + return 0; + } else { + final StructureNBT structureNBT = StructureNBT.create(id); + + return PlaceCommand.placeBlocks( + ctx.getSource(), + pos, + searchDir.getOffset(), + blockInput, + controlBlocks, + replaceAir, + true, + structureNBT.location, + (p) -> structureNBT.getBoundingBox(p, Rotation.NONE, Mirror.NONE), + (level, p) -> structureNBT.generateAt(level, p, Rotation.NONE, Mirror.NONE) + ) == null ? Command.SINGLE_SUCCESS : -1; + } + } + + protected static int placeEmpty( + CommandContext ctx, + boolean hasBorderArg, + boolean controlBlocks, + boolean replaceAir, + boolean hasRecursionArg + ) throws CommandSyntaxException { + final ResourceLocation id = ResourceLocationArgument.getId(ctx, PATH); + final PlacementDirections searchDir = TemplatePlacementArgument.getPlacement(ctx, PLACEMENT); + final BlockInput blockInput = hasBorderArg ? BlockStateArgument.getBlock(ctx, BORDER) : null; + final BlockPos span = Float3ArgumentType.getFloat3(ctx, SPAN).toBlockPos(); + + return PlaceCommand.placeBlocks( + ctx.getSource(), + BlockPosArgument.getLoadedBlockPos(ctx, POS), + searchDir == null || searchDir.dir == Float3.ZERO ? null : searchDir.dir.toBlockPos(), + blockInput, + controlBlocks, + replaceAir, + false, id, + (p) -> BoundingBox.fromCorners(p, p.offset(span)), + (level, p) -> { + var box = BoundingBox.fromCorners(p, p.offset(span)); + PlaceCommand.fillStructureVoid(level, box); + if (blockInput != null) { + PlaceCommand.fill( + level, + new BoundingBox( + box.minX(), box.minY() - 1, box.minZ(), + box.maxX(), box.minY() - 1, box.maxZ() + ), + blockInput.getState() + ); + } + } + ) == null ? Command.SINGLE_SUCCESS : -1; + } + + public static int placeJigsaw( + CommandContext ctx, + boolean hasReplaceArg, + boolean rotate, + boolean replaceFromWorld + ) throws CommandSyntaxException { + // /bclib place jigsaw betterend:village/center_piece betterend:entrance fromWorld rollable 1235 -60 42 + final BlockPos pos = BlockPosArgument.getLoadedBlockPos(ctx, POS); + final Holder.Reference pool = ResourceKeyArgument.getStructureTemplatePool( + ctx, + PlaceCommand.POOL + ); + ResourceLocation connector = ResourceLocationArgument.getId(ctx, CONNECTOR_NAME); + if (connector.getNamespace().equals("-")) { + connector = new ResourceLocation(pool.key().location().getNamespace(), connector.getPath()); + } + BlockState replaceWith = hasReplaceArg + ? BlockStateArgument.getBlock(ctx, REPLACE_WITH).getState() + : Blocks.AIR.defaultBlockState(); + + final ServerLevel level = ctx.getSource().getLevel(); + final ServerPlayer player = ctx.getSource().getPlayer(); + final int deltaY = player.getBlockY() - pos.getY(); + + BlockState state = Blocks.JIGSAW.defaultBlockState(); + + + if (deltaY < 2 && deltaY > -2 && !rotate) { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(player.getDirection().getOpposite(), Direction.UP) + ); + } else if (deltaY < 0) { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(Direction.DOWN, player.getDirection().getOpposite()) + ); + } else { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(Direction.UP, player.getDirection().getOpposite()) + ); + } + + if (replaceFromWorld) { + replaceWith = level.getBlockState(pos); + } + level.setBlock(pos, state, BlocksHelper.SET_SILENT); + if (level.getBlockEntity(pos) instanceof JigsawBlockEntity entity) { + entity.setName(connector); + entity.setTarget(connector); + entity.setPool(pool.key()); + entity.setFinalState(BlockStateParser.serialize(replaceWith)); + if (rotate) { + entity.setJoint(JigsawBlockEntity.JointType.ROLLABLE); + } else { + entity.setJoint(JigsawBlockEntity.JointType.ALIGNED); + } + } + return Command.SINGLE_SUCCESS; + } + + public static int placeSpawner(CommandContext ctx) throws CommandSyntaxException { + final BlockPos pos = BlockPosArgument.getLoadedBlockPos(ctx, POS); + final ServerLevel level = ctx.getSource().getLevel(); + + level.setBlock(pos, Blocks.SPAWNER.defaultBlockState(), BlocksHelper.SET_SILENT); + + if (level.getBlockEntity(pos) instanceof SpawnerBlockEntity entity) { + CompoundTag tag = TagParser.parseTag( + "{SpawnData:{entity:{id:wither_skeleton,PersistenceRequired:1,HandItems:[{Count:1,id:netherite_sword},{Count:1,id:shield}],ArmorItems:[{Count:1,id:netherite_boots,tag:{Enchantments:[{id:protection,lvl:1}]}},{Count:1,id:netherite_leggings,tag:{Enchantments:[{id:protection,lvl:1}]}},{Count:1,id:netherite_chestplate,tag:{Enchantments:[{id:protection,lvl:1},{id:thorns,lvl:3}]}},{Count:1,id:netherite_helmet,tag:{Enchantments:[{id:protection,lvl:1}]}}],HandDropChances:[0.0f,0.0f],ArmorDropChances:[0.0f,0.0f,0.0f,0.0f]}, custom_spawn_rules:{sky_light_limit:{max_inclusive:13},block_light_limit:{max_inclusive:11}}},SpawnRange:4,SpawnCount:8,MaxNearbyEntities:18,Delay:499,MinSpawnDelay:300,MaxSpawnDelay:1600,RequiredPlayerRange:20}"); + entity.load(tag); + } + + return Command.SINGLE_SUCCESS; + } +} + +public class PlaceCommand { + + public static final String PLACE_COMMAND = "place"; + public static final String POOL = "pool"; + + public PlaceCommand() { + } + + public static LiteralArgumentBuilder register( + LiteralArgumentBuilder bnContext, + CommandBuildContext commandBuildContext + ) { + final var command = Commands + .literal(PLACE_COMMAND) + .requires(commandSourceStack -> commandSourceStack.hasPermission(2)); + + new PlaceCommandBuilder().register(commandBuildContext, command); + + return bnContext.then(command); + } + + + private static boolean isEmpty(Level level, BoundingBox bb) { + for (int x = bb.minX(); x <= bb.maxX(); x++) + for (int y = bb.minY(); y <= bb.maxY(); y++) + for (int z = bb.minZ(); z <= bb.maxZ(); z++) + if (!level.isEmptyBlock(new BlockPos(x, y, z))) { + return false; + } + + return true; + } + + private static void replaceAir(Level level, BoundingBox bb) { + BlocksHelper.forAllInBounds(bb, (bp) -> { + if (level.getBlockState(bp).is(Blocks.AIR)) { + level.setBlock(bp, Blocks.STRUCTURE_VOID.defaultBlockState(), BlocksHelper.SET_OBSERV); + } + }); + } + + private static void removeLootTableSeed(Level level, BoundingBox bb) { + BlocksHelper.forAllInBounds(bb, (bp) -> { + if (level.getBlockEntity(bp) instanceof RandomizableContainerBlockEntity rnd) { + rnd.setLootTable(rnd.lootTable, 0); + } + }); + } + + static void fill(Level level, BoundingBox bb, BlockState blockState) { + BlocksHelper.forAllInBounds(bb, (bp) -> level.setBlock(bp, blockState, BlocksHelper.SET_OBSERV)); + } + + static void fillStructureVoid(Level level, BoundingBox bb) { + fill(level, bb, Blocks.STRUCTURE_VOID.defaultBlockState()); + } + + //Draws a border around the bounding box + private static void outline(Level level, BoundingBox bb, BlockState outlineState) { + BlocksHelper.forOutlineInBounds(bb, (bp) -> level.setBlock(bp, outlineState, BlocksHelper.SET_OBSERV)); + } + + private static BoundingBox adapt(BoundingBox bb, boolean border, boolean structureBlock) { + if (border) { + return bb.inflatedBy(1); + } else if (structureBlock) { + return new BoundingBox( + bb.minX(), + bb.minY(), + bb.minZ(), + bb.maxX() + 1, + bb.maxY() + 1, + bb.maxZ() + 1 + ); + } + return bb; + } + + private static void createControlBlocks(CommandSourceStack stack, ResourceLocation location, BoundingBox bbNBT) { + BlockPos structureBlockPos = new BlockPos(bbNBT.minX() - 1, bbNBT.minY() - 1, bbNBT.minZ() - 1); + BlockPos commandBlockPos = new BlockPos(bbNBT.minX() - 1, bbNBT.minY() - 1, bbNBT.minZ()); + BlockPos buttonBlockPos = new BlockPos(bbNBT.minX() - 1, bbNBT.minY(), bbNBT.minZ()); + BlockState state = Blocks.STRUCTURE_BLOCK + .defaultBlockState() + .setValue(StructureBlock.MODE, StructureMode.SAVE); + + stack.getLevel().setBlock(structureBlockPos, state, BlocksHelper.SET_OBSERV); + if (stack.getLevel().getBlockEntity(structureBlockPos) instanceof StructureBlockEntity entity) { + entity.setIgnoreEntities(false); + entity.setShowAir(false); + entity.setMirror(Mirror.NONE); + entity.setRotation(Rotation.NONE); + entity.setShowBoundingBox(true); + entity.setStructureName(location); + entity.setStructurePos(new BlockPos(1, 1, 1)); + entity.setStructureSize(new Vec3i(bbNBT.getXSpan(), bbNBT.getYSpan(), bbNBT.getZSpan())); + } + + state = Blocks.COMMAND_BLOCK.defaultBlockState().setValue(CommandBlock.FACING, Direction.DOWN); + stack.getLevel().setBlock(commandBlockPos, state, BlocksHelper.SET_OBSERV); + if (stack.getLevel().getBlockEntity(commandBlockPos) instanceof CommandBlockEntity entity) { + entity.setAutomatic(false); + entity.setPowered(false); + entity.onlyOpCanSetNbt(); + entity.getCommandBlock().shouldInformAdmins(); + entity.getCommandBlock() + .setCommand( + "fill ~1 ~1 ~" + + " ~" + bbNBT.getXSpan() + " ~" + bbNBT.getYSpan() + " ~" + bbNBT.getZSpan() + + " " + BuiltInRegistries.BLOCK.getKey(Blocks.STRUCTURE_VOID) + + " replace " + BuiltInRegistries.BLOCK.getKey(Blocks.AIR) + ); + } + + state = Blocks.OAK_BUTTON.defaultBlockState() + .setValue(ButtonBlock.FACING, Direction.EAST) + .setValue(ButtonBlock.FACE, AttachFace.FLOOR); + stack.getLevel().setBlock(buttonBlockPos, state, BlocksHelper.SET_OBSERV); + } + + + static BoundingBox placeBlocks( + CommandSourceStack stack, + BlockPos pos, + BlockPos searchDir, + BlockInput blockInput, + boolean structureBlock, + boolean replaceAir, + boolean preFillStructureVoid, + ResourceLocation location, + Function getBounds, + BiConsumer generate + ) { + if (searchDir != null) { + int tries = 16 * 16; + while (tries > 0 && !isEmpty( + stack.getLevel(), + adapt( + getBounds.apply(pos), + blockInput != null, + structureBlock + ) + )) { + pos = pos.offset(searchDir); + tries--; + } + + if (tries <= 0) { + stack.sendFailure(Component.literal("Failed to find free space") + .setStyle(Style.EMPTY.withColor(ChatFormatting.RED))); + return null; + } + } + + if (structureBlock) { + pos = pos.offset(1, 0, 1); + } + final BoundingBox bbNBT = getBounds.apply(pos); + final BoundingBox bb; + if (blockInput != null) { + bb = adapt(bbNBT, true, structureBlock); + outline(stack.getLevel(), bb, blockInput.getState()); + stack.sendSuccess(() -> Component.literal("Placed border: " + bb.toString()) + .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN)), true); + } else { + bb = adapt(bbNBT, false, structureBlock); + } + + if (preFillStructureVoid) + replaceAir(stack.getLevel(), bb); + generate.accept(stack.getLevel(), pos); + if (replaceAir) { + replaceAir(stack.getLevel(), bbNBT); + } + removeLootTableSeed(stack.getLevel(), bbNBT); + + stack.sendSuccess(() -> Component.literal("Placed NBT: " + bbNBT.toString()) + .setStyle(Style.EMPTY.withColor(ChatFormatting.GREEN)), true); + + if (structureBlock) { + createControlBlocks(stack, location, bbNBT); + } + return bb; + } + + public static BlockState setJigsawOrientation( + boolean rollable, + Player player, + BlockPos pos, + BlockState state + ) { + final int deltaY = player.getBlockY() - pos.getY(); + if (deltaY < 2 && deltaY > -2 && rollable) { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(player.getDirection().getOpposite(), Direction.UP) + ); + } else if (deltaY < 0) { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(Direction.DOWN, player.getDirection().getOpposite()) + ); + } else { + state = state.setValue( + JigsawBlock.ORIENTATION, + FrontAndTop.fromFrontAndTop(Direction.UP, player.getDirection().getOpposite()) + ); + } + return state; + } +} + +/* + +/bclib place nbt "betternether:city/city_building_01" find northOf 0 -59 0 border glass structureblock +/bclib place nbt "betternether:city/city_center_04" find northOf 32 -59 0 border glass structureblock +/bclib place nbt "betternether:city/city_enchanter_02" find northOf 32 -59 0 border glass structureblock +/bclib place nbt "betternether:city/city_library_03" find northOf 64 -59 0 border glass structureblock +/bclib place nbt "betternether:city/city_park_02" find northOf 64 -59 0 border glass structureblock +/bclib place nbt "betternether:city/city_tower_04" find northOf 96 -59 0 border glass structureblock +/bclib place nbt "betternether:city/road_end_02" find northOf 96 -59 0 border glass structureblock + */ diff --git a/src/main/java/org/betterx/bclib/commands/PrintInfo.java b/src/main/java/org/betterx/bclib/commands/PrintInfo.java new file mode 100644 index 00000000..b06a2736 --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/PrintInfo.java @@ -0,0 +1,132 @@ +package org.betterx.bclib.commands; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.client.gui.screens.UpdatesScreen; +import org.betterx.bclib.config.Configs; +import org.betterx.bclib.networking.VersionChecker; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.Style; +import net.minecraft.world.level.Level; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; + +public class PrintInfo { + public static LiteralArgumentBuilder register(LiteralArgumentBuilder bnContext) { + return bnContext + .then(Commands.literal("print") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .then(Commands.literal("dimensions") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(PrintInfo::printDimensions) + ).then(Commands.literal("updates") + .requires(source -> source.hasPermission(Commands.LEVEL_OWNERS)) + .executes(ctx -> PrintInfo.printUpdates(ctx, true)) + ) + ); + } + + static int printDimensions(CommandContext ctx) { + MutableComponent result = Component.literal("World Dimensions: ") + .setStyle(Style.EMPTY.withBold(true).withColor(ChatFormatting.BLUE)); + + for (var serverLevel : ctx.getSource().getLevel().getServer().getAllLevels()) { + var generator = serverLevel.getChunkSource().getGenerator(); + String output = "\n - " + serverLevel.dimension().location().toString() + ": " + + "\n " + generator.toString().trim() + " " + + generator + .getBiomeSource() + .toString() + .replace("\n", "\n "); + var cl = ChatFormatting.LIGHT_PURPLE; + if (serverLevel.dimension().location().equals(Level.OVERWORLD.location())) + cl = ChatFormatting.WHITE; + else if (serverLevel.dimension().location().equals(Level.NETHER.location())) + cl = ChatFormatting.RED; + if (serverLevel.dimension().location().equals(Level.END.location())) + cl = ChatFormatting.YELLOW; + Component dimComponent = Component.literal(output) + .setStyle(Style.EMPTY.withBold(false).withColor(cl)); + result.append(dimComponent); + } + ctx.getSource().sendSuccess(() -> result, false); + return Command.SINGLE_SUCCESS; + } + + static int printUpdates(CommandContext ctx, boolean withUI) { + boolean hasOne = false; + MutableComponent header = Component.literal("Mod Updates:") + .setStyle(Style.EMPTY.withBold(false) + .withColor(ChatFormatting.WHITE)); + ctx.getSource().sendSuccess(() -> header, false); + + VersionChecker.forEachUpdate((mod, cur, updated) -> { + ModContainer nfo = FabricLoader.getInstance().getModContainer(mod).orElse(null); + MutableComponent result = Component.literal(" - ") + .setStyle(Style.EMPTY.withBold(false) + .withUnderlined(false) + .withColor(ChatFormatting.WHITE)); + if (nfo != null) + result.append(Component.literal(nfo.getMetadata().getName()) + .setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE))); + else + result.append(Component.literal(mod) + .setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE))); + result.append(Component.literal(": ") + .setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE))); + result.append(Component.literal(cur).setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE))); + result.append(Component.literal(" -> ") + .setStyle(Style.EMPTY.withBold(false).withColor(ChatFormatting.WHITE))); + if (nfo != null && nfo.getMetadata().getContact().get("homepage").isPresent()) { + var ce = new ClickEvent( + ClickEvent.Action.OPEN_URL, + nfo.getMetadata().getContact().get("homepage").get() + ); + + result.append(Component.literal(updated) + .setStyle(Style.EMPTY.withClickEvent(ce) + .withBold(false) + .withItalic(true) + .withColor(ChatFormatting.GREEN))); + result.append(Component.literal(" ") + .setStyle(Style.EMPTY.withClickEvent(ce).withBold(true).withItalic(false))); + + result = result.append(Component.literal("[CurseForge]") + .setStyle(Style.EMPTY.withClickEvent(ce) + .withBold(true) + .withColor(ChatFormatting.GREEN) + .withUnderlined(true))); + + } else { + result.append(Component.literal(updated) + .setStyle(Style.EMPTY.withBold(false) + .withItalic(true) + .withColor(ChatFormatting.WHITE))); + result.append(Component.literal(" ").setStyle(Style.EMPTY.withBold(true).withItalic(false))); + + } + MutableComponent finalResult = result; + ctx.getSource().sendSuccess(() -> finalResult, false); + }); + MutableComponent footer = Component.literal("\n") + .setStyle(Style.EMPTY.withBold(false) + .withUnderlined(true) + .withColor(ChatFormatting.WHITE)); + ctx.getSource().sendSuccess(() -> footer, false); + + if (withUI && BCLib.isClient() && Configs.CLIENT_CONFIG.showUpdateInfo() && !VersionChecker.isEmpty()) { + UpdatesScreen.showUpdateUI(); + } + return Command.SINGLE_SUCCESS; + } + +} diff --git a/src/main/java/org/betterx/bclib/commands/arguments/BCLibArguments.java b/src/main/java/org/betterx/bclib/commands/arguments/BCLibArguments.java new file mode 100644 index 00000000..1cb1fdae --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/arguments/BCLibArguments.java @@ -0,0 +1,29 @@ +package org.betterx.bclib.commands.arguments; + +import org.betterx.bclib.BCLib; + +import net.minecraft.commands.synchronization.SingletonArgumentInfo; + +import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; + +public class BCLibArguments { + public static void register() { + ArgumentTypeRegistry.registerArgumentType( + BCLib.makeID("template_placement"), + TemplatePlacementArgument.class, + SingletonArgumentInfo.contextFree(TemplatePlacementArgument::templatePlacement) + ); + + ArgumentTypeRegistry.registerArgumentType( + BCLib.makeID("float3"), + Float3ArgumentType.class, + new Float3ArgumentInfo() + ); + + ArgumentTypeRegistry.registerArgumentType( + BCLib.makeID("connector"), + ConnectorArgument.class, + SingletonArgumentInfo.contextFree(ConnectorArgument::id) + ); + } +} diff --git a/src/main/java/org/betterx/bclib/commands/arguments/ConnectorArgument.java b/src/main/java/org/betterx/bclib/commands/arguments/ConnectorArgument.java new file mode 100644 index 00000000..f7abf352 --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/arguments/ConnectorArgument.java @@ -0,0 +1,49 @@ +package org.betterx.bclib.commands.arguments; + +import org.betterx.bclib.commands.PlaceCommand; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.commands.arguments.ResourceLocationArgument; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.jetbrains.annotations.NotNull; + +public class ConnectorArgument extends ResourceLocationArgument { + private static final Collection EXAMPLES = Arrays.asList("-:building_entrance", "-:bottom", "-:street"); + + @Override + public Collection getExamples() { + return EXAMPLES; + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + ResourceLocation pool = null; + try { + pool = context.getArgument(PlaceCommand.POOL, ResourceKey.class).location(); + } catch (Throwable t) { + pool = new ResourceLocation("-", ""); + } + return SharedSuggestionProvider.suggest(getStrings( + pool.getNamespace(), + List.of("bottom", "building_entrance", "street") + ), builder); + } + + @NotNull + private List getStrings(String namespace, List input) { + return input.stream().map(s -> namespace + ":" + s).toList(); + } + + public static ConnectorArgument id() { + return new ConnectorArgument(); + } +} diff --git a/src/main/java/org/betterx/bclib/commands/arguments/Float3ArgumentInfo.java b/src/main/java/org/betterx/bclib/commands/arguments/Float3ArgumentInfo.java new file mode 100644 index 00000000..c79e7a03 --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/arguments/Float3ArgumentInfo.java @@ -0,0 +1,95 @@ +package org.betterx.bclib.commands.arguments; + +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.synchronization.ArgumentTypeInfo; +import net.minecraft.network.FriendlyByteBuf; + +import com.google.gson.JsonObject; + +public class Float3ArgumentInfo implements ArgumentTypeInfo { + private static byte MIN_FLAG = 1 << 1; + private static byte MAX_FLAG = 1 << 2; + private static byte IS_INT_FLAG = 1 << 0; + + private static byte createNumberFlags(boolean isInt, boolean hasMin, boolean hasMax) { + byte flagByte = 0; + if (isInt) flagByte |= IS_INT_FLAG; + if (hasMin) flagByte |= MIN_FLAG; + if (hasMax) flagByte |= MAX_FLAG; + + return flagByte; + } + + private static boolean numberHasMin(byte flag) { + return (flag & MIN_FLAG) != 0; + } + + private static boolean numberHasMax(byte flag) { + return (flag & MAX_FLAG) != 0; + } + + private static boolean numberIsInt(byte flag) { + return (flag & IS_INT_FLAG) != 0; + } + + public void serializeToNetwork( + Float3ArgumentInfo.Template template, + FriendlyByteBuf friendlyByteBuf + ) { + final boolean hasMin = template.min != -Double.MAX_VALUE; + final boolean hasMax = template.max != Double.MAX_VALUE; + friendlyByteBuf.writeByte(createNumberFlags(template.asInt, hasMin, hasMax)); + if (hasMin) friendlyByteBuf.writeDouble(template.min); + if (hasMax) friendlyByteBuf.writeDouble(template.max); + } + + public Float3ArgumentInfo.Template deserializeFromNetwork( + FriendlyByteBuf friendlyByteBuf + ) { + byte flag = friendlyByteBuf.readByte(); + boolean asInt = numberIsInt(flag); + double min = numberHasMin(flag) ? friendlyByteBuf.readDouble() : -Double.MAX_VALUE; + double max = numberHasMax(flag) ? friendlyByteBuf.readDouble() : Double.MAX_VALUE; + return new Float3ArgumentInfo.Template(asInt, min, max); + } + + public void serializeToJson(Float3ArgumentInfo.Template template, JsonObject jsonObject) { + if (!template.asInt) { + jsonObject.addProperty("asInt", template.asInt); + } + if (template.min != -Double.MAX_VALUE) { + jsonObject.addProperty("min", template.min); + } + if (template.max != Double.MAX_VALUE) { + jsonObject.addProperty("max", template.max); + } + + } + + public Float3ArgumentInfo.Template unpack(Float3ArgumentType type) { + return new Float3ArgumentInfo.Template(type.isInt(), type.getMinimum(), type.getMaximum()); + } + + public final class Template implements ArgumentTypeInfo.Template { + final double min; + final double max; + final boolean asInt; + + Template(boolean asInt, double min, double max) { + this.min = min; + this.max = max; + this.asInt = asInt; + + } + + public Float3ArgumentType instantiate(CommandBuildContext commandBuildContext) { + return this.asInt + ? Float3ArgumentType.int3((int) this.min, (int) this.max) + : Float3ArgumentType.float3(this.min, this.max); + } + + public ArgumentTypeInfo type() { + return Float3ArgumentInfo.this; + } + } +} diff --git a/src/main/java/org/betterx/bclib/commands/arguments/Float3ArgumentType.java b/src/main/java/org/betterx/bclib/commands/arguments/Float3ArgumentType.java new file mode 100644 index 00000000..f22f4cdb --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/arguments/Float3ArgumentType.java @@ -0,0 +1,198 @@ +package org.betterx.bclib.commands.arguments; + +import de.ambertation.wunderlib.math.Float3; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.commands.arguments.coordinates.Vec3Argument; +import net.minecraft.commands.arguments.coordinates.WorldCoordinate; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +public class Float3ArgumentType implements ArgumentType { + private static final Collection EXAMPLES = List.of("0 0 0"); + + private final double minimum; + private final double maximum; + private final boolean asInt; + + Float3ArgumentType(int minimum, int maximum) { + this(true, minimum, maximum); + } + + Float3ArgumentType(double minimum, double maximum) { + this(false, minimum, maximum); + } + + Float3ArgumentType(boolean asInt, double minimum, double maximum) { + this.asInt = asInt; + this.minimum = minimum; + this.maximum = maximum; + } + + public double getMinimum() { + return minimum; + } + + public double getMaximum() { + return maximum; + } + + public boolean isInt() { + return asInt; + } + + @Override + public Float3 parse(StringReader reader) throws CommandSyntaxException { + int i = reader.getCursor(); + double x = asInt ? parseInt(reader) : parseDouble(reader); + if (!reader.canRead() || reader.peek() != ' ') { + reader.setCursor(i); + throw Vec3Argument.ERROR_NOT_COMPLETE.createWithContext(reader); + } + reader.skip(); + double y = asInt ? parseInt(reader) : parseDouble(reader); + if (!reader.canRead() || reader.peek() != ' ') { + reader.setCursor(i); + throw Vec3Argument.ERROR_NOT_COMPLETE.createWithContext(reader); + } + reader.skip(); + double z = asInt ? parseInt(reader) : parseDouble(reader); + return Float3.of(x, y, z); + } + + private double parseDouble(StringReader reader) throws CommandSyntaxException { + if (!reader.canRead()) { + throw WorldCoordinate.ERROR_EXPECTED_DOUBLE.createWithContext(reader); + } else { + final int start = reader.getCursor(); + double result = reader.canRead() && reader.peek() != ' ' ? reader.readDouble() : 0.0; + + if (result < minimum) { + reader.setCursor(start); + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.doubleTooLow() + .createWithContext(reader, result, minimum); + } + if (result > maximum) { + reader.setCursor(start); + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.doubleTooHigh() + .createWithContext(reader, result, maximum); + } + + return result; + } + } + + private double parseInt(StringReader reader) throws CommandSyntaxException { + if (!reader.canRead()) { + throw WorldCoordinate.ERROR_EXPECTED_DOUBLE.createWithContext(reader); + } else { + final int start = reader.getCursor(); + int result = reader.canRead() && reader.peek() != ' ' ? reader.readInt() : 0; + + if (result < minimum) { + reader.setCursor(start); + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooLow() + .createWithContext(reader, result, minimum); + } + if (result > maximum) { + reader.setCursor(start); + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.integerTooHigh() + .createWithContext(reader, result, maximum); + } + + return result; + } + } + + public static Float3ArgumentType int3() { + return new Float3ArgumentType(-Double.MAX_VALUE, Double.MAX_VALUE); + } + + public static Float3ArgumentType int3(int min) { + return new Float3ArgumentType(min, Double.MAX_VALUE); + } + + public static Float3ArgumentType int3(int min, int max) { + return new Float3ArgumentType(min, max); + } + + public static Float3ArgumentType float3() { + return new Float3ArgumentType(-Double.MAX_VALUE, Double.MAX_VALUE); + } + + public static Float3ArgumentType float3(double min) { + return new Float3ArgumentType(min, Double.MAX_VALUE); + } + + public static Float3ArgumentType float3(double min, double max) { + return new Float3ArgumentType(min, max); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + final String remaining = builder.getRemaining(); + return SharedSuggestionProvider.suggestCoordinates( + remaining, + List.of(new SharedSuggestionProvider.TextCoordinates("8", "8", "8")), + builder, + Commands.createValidator(this::parse) + ); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } + + public static Float3 getFloat3(CommandContext commandContext, String string) { + return commandContext.getArgument(string, Float3.class); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Float3ArgumentType)) return false; + Float3ArgumentType that = (Float3ArgumentType) o; + return Double.compare(that.minimum, minimum) == 0 && Double.compare( + that.maximum, + maximum + ) == 0 && asInt == that.asInt; + } + + @Override + public int hashCode() { + return Objects.hash(minimum, maximum, asInt); + } + + @Override + public String toString() { + if (asInt) { + if (minimum == -Double.MAX_VALUE && maximum == Double.MAX_VALUE) { + return "int3()"; + } else if (maximum == Double.MAX_VALUE) { + return "int3(" + (int) minimum + ")"; + } else { + return "int3(" + (int) minimum + ", " + (int) maximum + ")"; + } + } else { + if (minimum == -Double.MAX_VALUE && maximum == Double.MAX_VALUE) { + return "float3()"; + } else if (maximum == Double.MAX_VALUE) { + return "float3(" + (int) minimum + ")"; + } else { + return "float3(" + (int) minimum + ", " + (int) maximum + ")"; + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/commands/arguments/PlacementDirections.java b/src/main/java/org/betterx/bclib/commands/arguments/PlacementDirections.java new file mode 100644 index 00000000..13447313 --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/arguments/PlacementDirections.java @@ -0,0 +1,74 @@ +package org.betterx.bclib.commands.arguments; + +import de.ambertation.wunderlib.math.Bounds; +import de.ambertation.wunderlib.math.Float3; + +import com.mojang.serialization.Codec; +import net.minecraft.core.BlockPos; +import net.minecraft.util.StringRepresentable; + +public enum PlacementDirections implements StringRepresentable { + NORTH_OF("northOf", Float3.NORTH, PlacementDirections::resetStartNorthSouth), + EAST_OF("eastOf", Float3.EAST, PlacementDirections::resetStartEastWest), + SOUTH_OF("southOf", Float3.SOUTH, PlacementDirections::resetStartNorthSouth), + WEST_OF("westOf", Float3.WEST, PlacementDirections::resetStartEastWest), + ABOVE("above", Float3.UP, PlacementDirections::resetStartEastWest), + BELOW("below", Float3.DOWN, PlacementDirections::resetStartEastWest), + AT("at", null, null); + + public static final Codec CODEC = StringRepresentable.fromEnum(PlacementDirections::values); + + interface ResetStart { + Float3 calculate(Bounds totalBounds, BlockPos lastStart, int offset); + } + + private static Float3 resetStartNorthSouth(Bounds totalBounds, BlockPos lastStart, int offset) { + return Float3.of(totalBounds.max.x + offset, lastStart.getY(), lastStart.getZ()); + } + + private static Float3 resetStartEastWest(Bounds totalBounds, BlockPos lastStart, int offset) { + return Float3.of(lastStart.getX(), lastStart.getY(), totalBounds.max.z + offset); + } + + private final String name; + public final Float3 dir; + private final ResetStart resetStart; + + PlacementDirections(String name, Float3 dir, ResetStart resetStart) { + this.name = name; + this.dir = dir; + this.resetStart = resetStart; + } + + public BlockPos getOffset() { + return dir == null || dir == Float3.ZERO ? null : dir.toBlockPos(); + } + + public int sizeInDirection(Bounds totalBounds) { + return (int) totalBounds.getSize().mul(dir).length(); + } + + public BlockPos advanceStart(Bounds placedBound, BlockPos lastStart) { + return advanceStart(placedBound, lastStart, 1); + } + + public BlockPos advanceStart(Bounds placedBound, BlockPos lastStart, int offset) { + return lastStart.offset(dir.mul(sizeInDirection(placedBound) + offset).toBlockPos()); + } + + public BlockPos resetStart(Bounds totalBounds, BlockPos lastStart) { + return resetStart(totalBounds, lastStart, 3); + } + + public BlockPos resetStart(Bounds totalBounds, BlockPos lastStart, int offset) { + if (resetStart == null) { + return lastStart; + } + return this.resetStart.calculate(totalBounds, lastStart, offset).toBlockPos(); + } + + @Override + public String getSerializedName() { + return name; + } +} diff --git a/src/main/java/org/betterx/bclib/commands/arguments/TemplatePlacementArgument.java b/src/main/java/org/betterx/bclib/commands/arguments/TemplatePlacementArgument.java new file mode 100644 index 00000000..220da0ef --- /dev/null +++ b/src/main/java/org/betterx/bclib/commands/arguments/TemplatePlacementArgument.java @@ -0,0 +1,20 @@ +package org.betterx.bclib.commands.arguments; + +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.StringRepresentableArgument; + +public class TemplatePlacementArgument + extends StringRepresentableArgument { + private TemplatePlacementArgument() { + super(PlacementDirections.CODEC, PlacementDirections::values); + } + + public static TemplatePlacementArgument templatePlacement() { + return new TemplatePlacementArgument(); + } + + public static PlacementDirections getPlacement(CommandContext commandContext, String string) { + return commandContext.getArgument(string, PlacementDirections.class); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/BCLWoodTypeWrapper.java b/src/main/java/org/betterx/bclib/complexmaterials/BCLWoodTypeWrapper.java new file mode 100644 index 00000000..0a12669d --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/BCLWoodTypeWrapper.java @@ -0,0 +1,108 @@ +package org.betterx.bclib.complexmaterials; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.state.properties.BlockSetType; +import net.minecraft.world.level.block.state.properties.WoodType; +import net.minecraft.world.level.material.MapColor; + +import net.fabricmc.fabric.api.object.builder.v1.block.type.BlockSetTypeRegistry; +import net.fabricmc.fabric.api.object.builder.v1.block.type.WoodTypeRegistry; + +import java.util.Objects; + +public final class BCLWoodTypeWrapper { + public final ResourceLocation id; + public final WoodType type; + public final MapColor color; + public final boolean flammable; + + protected BCLWoodTypeWrapper(ResourceLocation id, WoodType type, MapColor color, boolean flammable) { + this.id = id; + this.type = type; + this.color = color; + this.flammable = flammable; + } + + public static Builder create(String modID, String string) { + return new Builder(new ResourceLocation(modID, string)); + } + + public static Builder create(ResourceLocation id) { + return new Builder(id); + } + + public BlockSetType setType() { + return type.setType(); + } + + public ResourceLocation id() { + return id; + } + + public WoodType type() { + return type; + } + + public MapColor color() { + return color; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (BCLWoodTypeWrapper) obj; + return Objects.equals(this.id, that.id) && + Objects.equals(this.type, that.type) && + Objects.equals(this.color, that.color); + } + + @Override + public int hashCode() { + return Objects.hash(id, type, color); + } + + @Override + public String toString() { + return "BCLWoodTypeWrapper[" + + "id=" + id + ", " + + "type=" + type + ", " + + "color=" + color + ']'; + } + + + public static class Builder { + private final ResourceLocation id; + private BlockSetType setType; + private MapColor color; + private boolean flammable; + + public Builder(ResourceLocation id) { + this.id = id; + this.color = MapColor.WOOD; + this.flammable = true; + } + + public Builder setBlockSetType(BlockSetType setType) { + this.setType = setType; + return this; + } + + public Builder setColor(MapColor color) { + this.color = color; + return this; + } + + public Builder setFlammable(boolean flammable) { + this.flammable = flammable; + return this; + } + + public BCLWoodTypeWrapper build() { + if (setType == null) setType = BlockSetTypeRegistry.registerWood(id); + + final WoodType type = WoodTypeRegistry.register(id, setType); + return new BCLWoodTypeWrapper(id, type, color, flammable); + } + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/ComplexMaterial.java b/src/main/java/org/betterx/bclib/complexmaterials/ComplexMaterial.java new file mode 100644 index 00000000..c977c1be --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/ComplexMaterial.java @@ -0,0 +1,487 @@ +package org.betterx.bclib.complexmaterials; + +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.ItemEntry; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.entry.RecipeEntry; +import org.betterx.bclib.registry.BlockRegistry; +import org.betterx.bclib.registry.ItemRegistry; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import net.fabricmc.fabric.api.registry.FlammableBlockRegistry; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import org.jetbrains.annotations.Nullable; + +public abstract class ComplexMaterial { + private static final Map> RECIPE_ENTRIES = Maps.newHashMap(); + private static final Map> BLOCK_ENTRIES = Maps.newHashMap(); + private static final Map> ITEM_ENTRIES = Maps.newHashMap(); + private static final List MATERIALS = Lists.newArrayList(); + + private final List defaultRecipeEntries = Lists.newArrayList(); + private final List defaultBlockEntries = Lists.newArrayList(); + private final List defaultItemEntries = Lists.newArrayList(); + + private final Map> blockTags = Maps.newHashMap(); + private final Map> itemTags = Maps.newHashMap(); + private final Map blocks = Maps.newHashMap(); + private final Map items = Maps.newHashMap(); + + protected final String baseName; + protected final String modID; + protected final String receipGroupPrefix; + + public ComplexMaterial(String modID, String baseName, String receipGroupPrefix) { + this.baseName = baseName; + this.modID = modID; + this.receipGroupPrefix = receipGroupPrefix; + MATERIALS.add(this); + } + + /** + * Initialize and registers all content inside material, return material itself. + * + * @param blocksRegistry {@link BlockRegistry} instance to add blocks in; + * @param itemsRegistry {@link ItemRegistry} instance to add items in; + * @return {@link ComplexMaterial}. + */ + public ComplexMaterial init(BlockRegistry blocksRegistry, ItemRegistry itemsRegistry) { + initTags(); + + final BlockBehaviour.Properties blockSettings = getBlockSettings(); + final Item.Properties itemSettings = getItemSettings(itemsRegistry); + initDefault(blockSettings, itemSettings); + + getBlockEntries().forEach(entry -> { + Block block = entry.init(this, blockSettings, blocksRegistry); + blocks.put(entry.getSuffix(), block); + }); + + getItemEntries().forEach(entry -> { + Item item = entry.init(this, itemSettings, itemsRegistry); + items.put(entry.getSuffix(), item); + }); + + initDefaultRecipes(); + getRecipeEntries().forEach(entry -> { + entry.init(this); + }); + + initFlammable(FlammableBlockRegistry.getDefaultInstance()); + return this; + } + + /** + * Init default content for {@link ComplexMaterial} - blocks and items. + * + * @param blockSettings {@link BlockBehaviour.Properties} default block settings for this material; + * @param itemSettings {@link Item.Properties} default item settings for this material. + */ + + protected abstract void initDefault(BlockBehaviour.Properties blockSettings, Item.Properties itemSettings); + + /** + * Init custom tags for this {@link ComplexMaterial}, not required. + */ + protected void initTags() { + } + + /** + * Init default recipes for this {@link ComplexMaterial}, not required. + */ + protected void initDefaultRecipes() { + } + + /** + * Allows to add blocks into Fabric {@link FlammableBlockRegistry} for this {@link ComplexMaterial}, not required. + */ + protected void initFlammable(FlammableBlockRegistry registry) { + } + + /** + * Adds custom block tag for this {@link ComplexMaterial}, tag can be created with {@link TagManager} or you can use one of already created tags. + * + * @param tag {@link TagKey} for {@link Block} + */ + protected void addBlockTag(TagKey tag) { + String key = tag.location().getPath().replace(getBaseName() + "_", ""); + blockTags.put(key, tag); + } + + /** + * Adds custom item tag for this {@link ComplexMaterial}, tag can be created with {@link TagManager} or you can use one of already created tags. + * + * @param tag {@link TagKey} for {@link Item} + */ + protected void addItemTag(TagKey tag) { + String key = tag.location().getPath().replace(getBaseName() + "_", ""); + itemTags.put(key, tag); + } + + /** + * Get custom {@link Block} {@link TagKey} from this {@link ComplexMaterial}. + * + * @param key {@link String} tag name (path of its {@link ResourceLocation}), for inner tags created inside material its tag suffix. + * @return {@link TagKey} for {@link Block} or {@code null} if nothing is stored. + */ + @Nullable + public TagKey getBlockTag(String key) { + return blockTags.get(key); + } + + /** + * Get custom {@link Item} {@link TagKey} from this {@link ComplexMaterial}. + * + * @param key {@link String} tag name (path of its {@link ResourceLocation}), for inner tags created inside material its tag suffix. + * @return {@link TagKey} for {@link Item} or {@code null} if nothing is stored. + */ + @Nullable + public TagKey getItemTag(String key) { + return itemTags.get(key); + } + + /** + * Get initiated {@link Block} from this {@link ComplexMaterial}. + * + * @param key {@link String} block name suffix (example: "mod:custom_log" will have a "log" suffix if "custom" is a base name of this material) + * @return {@link Block} or {@code null} if nothing is stored. + */ + @Nullable + public Block getBlock(String key) { + return blocks.get(key); + } + + /** + * Get initiated {@link Block} from this {@link ComplexMaterial}. + * + * @param key {@link String} block name suffix (example: "mod:custom_log" will have a "log" suffix if "custom" is a base name of this material) + * @param runIfPresent {@link Consumer} to run if block is present. + * @return {@link Block} or {@code null} if nothing is stored. + */ + @Nullable + public Block ifBlockPresent(String key, Consumer runIfPresent) { + final Block block = blocks.get(key); + if (block != null) { + runIfPresent.accept(block); + } + return block; + } + + /** + * Get initiated {@link Block} from this {@link ComplexMaterial}. + * + * @param slot {@link MaterialSlot} The Block Entry + * @param runIfPresent {@link Consumer} to run if block is present. + * @param The {@link ComplexMaterial} this slot is usable for Type + * @return {@link Block} or {@code null} if nothing is stored. + */ + @Nullable + public Block ifBlockPresent(MaterialSlot slot, Consumer runIfPresent) { + final Block block = blocks.get(slot.suffix); + if (block != null) { + runIfPresent.accept(block); + } + return block; + } + + /** + * Get initiated {@link Block} from this {@link ComplexMaterial}. + * + * @param key {@link MaterialSlot} The Block Entry + * @param The {@link ComplexMaterial} this slot is usable for Type + * @return {@link Block} or {@code null} if nothing is stored. + */ + @Nullable + public Block getBlock(MaterialSlot key) { + return blocks.get(key.suffix); + } + + /** + * Get initiated {@link Block} from this {@link ComplexMaterial}. + * + * @param key {@link MaterialSlot} The Block Entry + * @param The {@link ComplexMaterial} this slot is usable for Type + * @return {@link Block} or {@code null} if nothing is stored. + */ + @Nullable + public BlockItem getBlockItem(MaterialSlot key) { + return getBlockItem(key.suffix); + } + + /** + * Get initiated {@link Block} from this {@link ComplexMaterial}. + * + * @param key {@link MaterialSlot} The Block Entry + * @return {@link Block} or {@code null} if nothing is stored. + */ + @Nullable + public BlockItem getBlockItem(String key) { + final Block bl = blocks.get(key); + return bl != null && bl.asItem() instanceof BlockItem bi ? bi : null; + } + + /** + * Get initiated {@link Item} from this {@link ComplexMaterial}. + * + * @param key {@link String} block name suffix (example: "mod:custom_apple" will have a "apple" suffix if "custom" is a base name of this material) + * @return {@link Item} or {@code null} if nothing is stored. + */ + @Nullable + public Item getItem(String key) { + return items.get(key); + } + + /** + * Get initiated {@link Item} from this {@link ComplexMaterial}. + * + * @param slot {@link MaterialSlot} The Item Entry + * @param The {@link ComplexMaterial} this slot is usable for Type + * @return {@link Item} or {@code null} if nothing is stored. + */ + @Nullable + public Item getItem(MaterialSlot slot) { + return items.get(slot.suffix); + } + + /** + * Get initiated {@link Item} from this {@link ComplexMaterial}. + * + * @param key {@link String} item name suffix (example: "mod:custom_apple" will have a "apple" suffix if "custom" is a base name of this material) + * @param runIfPresent {@link Consumer} to run if item is present. + * @return {@link Item} or {@code null} if nothing is stored. + */ + @Nullable + public Item ifItemPresent(String key, Consumer runIfPresent) { + final Item item = items.get(key); + if (item != null) { + runIfPresent.accept(item); + } + return item; + } + + /** + * Get initiated {@link Item} from this {@link ComplexMaterial}. + * + * @param slot {@link MaterialSlot} The Item Entry + * @param runIfPresent {@link Consumer} to run if item is present. + * @param The {@link ComplexMaterial} this slot is usable for Type + * @return {@link Item} or {@code null} if nothing is stored. + */ + @Nullable + public Item ifItemPresent(MaterialSlot slot, Consumer runIfPresent) { + final Item item = items.get(slot.suffix); + if (item != null) { + runIfPresent.accept(item); + } + return item; + } + + + /** + * Get default block settings for this material. + * + * @return {@link BlockBehaviour.Properties} + */ + protected abstract BlockBehaviour.Properties getBlockSettings(); + + /** + * Get default item settings for this material. + * + * @return {@link Item.Properties} + */ + protected Item.Properties getItemSettings(ItemRegistry registry) { + return registry.makeItemSettings(); + } + + private Collection getBlockEntries() { + List result = Lists.newArrayList(defaultBlockEntries); + List entries = BLOCK_ENTRIES.get(this.getMaterialID()); + if (entries != null) { + result.addAll(entries); + } + return result; + } + + private Collection getItemEntries() { + List result = Lists.newArrayList(defaultItemEntries); + List entries = ITEM_ENTRIES.get(this.getMaterialID()); + if (entries != null) { + result.addAll(entries); + } + return result; + } + + private Collection getRecipeEntries() { + List result = Lists.newArrayList(defaultRecipeEntries); + List entries = RECIPE_ENTRIES.get(this.getMaterialID()); + if (entries != null) { + result.addAll(entries); + } + return result; + } + + /** + * Get base name of this {@link ComplexMaterial}. + * + * @return {@link String} name + */ + public String getBaseName() { + return baseName; + } + + /** + * Get mod ID for this {@link ComplexMaterial}. + * + * @return {@link String} mod ID. + */ + public String getModID() { + return modID; + } + + /** + * Get a unique {@link ResourceLocation} for each material class. + * For example WoodenComplexMaterial will have a "bclib:Wooden_Complex_Material" {@link ResourceLocation}. + * This is used to add custom entries before mods init using Fabric "preLaunch" entry point. + * + * @return {@link ResourceLocation} for this material + * @see Fabric Documentation: Entrypoint + */ + public abstract ResourceLocation getMaterialID(); + + /** + * Get all initiated block from this {@link ComplexMaterial}. + * + * @return {@link Collection} of {@link Block}. + */ + public Collection getBlocks() { + return blocks.values(); + } + + /** + * Get all initiated items from this {@link ComplexMaterial}. + * + * @return {@link Collection} of {@link Item}. + */ + public Collection getItems() { + return items.values(); + } + + /** + * Adds a default {@link BlockEntry} to this {@link ComplexMaterial}. Used to initiate blocks later. + * + * @param entry {@link BlockEntry} + */ + protected void addBlockEntry(BlockEntry entry) { + defaultBlockEntries.add(entry); + } + + /** + * Replaces or Adds a default {@link BlockEntry} to this {@link ComplexMaterial}. Used to initiate blocks later. + *

+ * If this {@link ComplexMaterial} does already contain an entry for the {@link ResourceLocation}, the entry will + * be removed first. + * + * @param entry {@link BlockEntry} + */ + protected void replaceOrAddBlockEntry(BlockEntry entry) { + int pos = defaultBlockEntries.indexOf(entry); + if (pos >= 0) defaultBlockEntries.remove(entry); + + addBlockEntry(entry); + } + + /** + * Adds a default {@link ItemEntry} to this {@link ComplexMaterial}. Used to initiate items later. + * + * @param entry {@link ItemEntry} + */ + protected void addItemEntry(ItemEntry entry) { + defaultItemEntries.add(entry); + } + + /** + * Adds a default {@link RecipeEntry} to this {@link ComplexMaterial}. Used to initiate items later. + * + * @param entry {@link RecipeEntry} + */ + protected void addRecipeEntry(RecipeEntry entry) { + defaultRecipeEntries.add(entry); + } + + /** + * Adds a custom {@link BlockEntry} for specified {@link ComplexMaterial} using its {@link ResourceLocation}. + * Used to add custom entry for all instances of {@link ComplexMaterial}. + * Should be called only using Fabric "preLaunch" entry point. + * + * @param materialName {@link ResourceLocation} id of {@link ComplexMaterial}; + * @param entry {@link BlockEntry}. + * @see Fabric Documentation: Entrypoint + */ + public static void addBlockEntry(ResourceLocation materialName, BlockEntry entry) { + List entries = BLOCK_ENTRIES.get(materialName); + if (entries == null) { + entries = Lists.newArrayList(); + BLOCK_ENTRIES.put(materialName, entries); + } + entries.add(entry); + } + + /** + * Adds a custom {@link ItemEntry} for specified {@link ComplexMaterial} using its {@link ResourceLocation}. + * Used to add custom entry for all instances of {@link ComplexMaterial}. + * Should be called only using Fabric "preLaunch" entry point. + * + * @param materialName {@link ResourceLocation} id of {@link ComplexMaterial}; + * @param entry {@link ItemEntry}. + * @see Fabric Documentation: Entrypoint + */ + public static void addItemEntry(ResourceLocation materialName, ItemEntry entry) { + List entries = ITEM_ENTRIES.get(materialName); + if (entries == null) { + entries = Lists.newArrayList(); + ITEM_ENTRIES.put(materialName, entries); + } + entries.add(entry); + } + + /** + * Adds a custom {@link RecipeEntry} for specified {@link ComplexMaterial} using its {@link ResourceLocation}. + * Used to add custom entry for all instances of {@link ComplexMaterial}. + * Should be called only using Fabric "preLaunch" entry point. + * + * @param materialName {@link ResourceLocation} id of {@link ComplexMaterial}; + * @param entry {@link RecipeEntry}. + * @see Fabric Documentation: Entrypoint + */ + public static void addRecipeEntry(ResourceLocation materialName, RecipeEntry entry) { + List entries = RECIPE_ENTRIES.get(materialName); + if (entries == null) { + entries = Lists.newArrayList(); + RECIPE_ENTRIES.put(materialName, entries); + } + entries.add(entry); + } + + /** + * Get all instances of all materials. + * + * @return {@link Collection} of {@link ComplexMaterial}. + */ + public static Collection getAllMaterials() { + return MATERIALS; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/ComplexMaterialSet.java b/src/main/java/org/betterx/bclib/complexmaterials/ComplexMaterialSet.java new file mode 100644 index 00000000..3903a4fb --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/ComplexMaterialSet.java @@ -0,0 +1,49 @@ +package org.betterx.bclib.complexmaterials; + +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.entry.SlotMap; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.state.BlockBehaviour; + +public abstract class ComplexMaterialSet> extends ComplexMaterial { + SlotMap slots; + + protected ComplexMaterialSet(String modID, String baseName, String receipGroupPrefix) { + super(modID, baseName, receipGroupPrefix); + } + + protected abstract SlotMap createMaterialSlots(); + + + @Override + protected final void initDefault(BlockBehaviour.Properties blockSettings, Item.Properties itemSettings) { + this.slots = createMaterialSlots(); + + for (MaterialSlot slot : slots) { + slot.onInit((M) this); + slot.addBlockEntry((M) this, this::addBlockEntry); + } + for (MaterialSlot slot : slots) { + slot.addItemEntry((M) this, this::addItemEntry); + } + + this.initAdditional(blockSettings, itemSettings); + } + + protected void initAdditional(BlockBehaviour.Properties blockSettings, Item.Properties itemSettings) { + + } + + @Override + protected final void initDefaultRecipes() { + super.initDefaultRecipes(); + this.initAdditionalRecipes(); + } + + protected void initAdditionalRecipes() { + for (MaterialSlot slot : slots) { + slot.addRecipeEntry((M) this, this::addRecipeEntry); + } + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/StoneComplexMaterial.java b/src/main/java/org/betterx/bclib/complexmaterials/StoneComplexMaterial.java new file mode 100644 index 00000000..8257c465 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/StoneComplexMaterial.java @@ -0,0 +1,50 @@ +package org.betterx.bclib.complexmaterials; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.complexmaterials.entry.SlotMap; +import org.betterx.bclib.complexmaterials.set.stone.StoneSlots; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.material.MapColor; + +public class StoneComplexMaterial extends ComplexMaterialSet { + public static final ResourceLocation MATERIAL_ID = BCLib.makeID("stone_material"); + public final MapColor color; + public final Block sourceBlock; + + protected StoneComplexMaterial( + String modID, + String baseName, + String receipGroupPrefix, + Block sourceBlock, + MapColor color + ) { + super(modID, baseName, receipGroupPrefix); + this.color = color; + this.sourceBlock = sourceBlock; + } + + @Override + protected BlockBehaviour.Properties getBlockSettings() { + return BehaviourBuilders.createStone(color); + } + + @Override + public ResourceLocation getMaterialID() { + return MATERIAL_ID; + } + + @Override + protected SlotMap createMaterialSlots() { + return SlotMap.of( + StoneSlots.SOURCE, + StoneSlots.SLAB, + StoneSlots.STAIRS, + StoneSlots.WALL + ); + } + +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/WoodenComplexMaterial.java b/src/main/java/org/betterx/bclib/complexmaterials/WoodenComplexMaterial.java new file mode 100644 index 00000000..b9cdd2b5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/WoodenComplexMaterial.java @@ -0,0 +1,159 @@ +package org.betterx.bclib.complexmaterials; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.complexmaterials.entry.SlotMap; +import org.betterx.bclib.complexmaterials.set.wood.WoodSlots; +import org.betterx.bclib.items.boat.BoatTypeOverride; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.material.MapColor; + +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; +import net.fabricmc.fabric.api.registry.FlammableBlockRegistry; + +import java.util.function.Consumer; +import org.jetbrains.annotations.Nullable; + +public class WoodenComplexMaterial extends ComplexMaterialSet { + public static final ResourceLocation MATERIAL_ID = BCLib.makeID("wooden_material"); + + public static final String BLOCK_CRAFTING_TABLE = WoodSlots.CRAFTING_TABLE.suffix; + public static final String BLOCK_STRIPPED_BARK = WoodSlots.STRIPPED_BARK.suffix; + public static final String BLOCK_STRIPPED_LOG = WoodSlots.STRIPPED_LOG.suffix; + public static final String BLOCK_PRESSURE_PLATE = WoodSlots.PRESSURE_PLATE.suffix; + public static final String BLOCK_BOOKSHELF = WoodSlots.BOOKSHELF.suffix; + public static final String BLOCK_COMPOSTER = WoodSlots.COMPOSTER.suffix; + public static final String BLOCK_TRAPDOOR = WoodSlots.TRAPDOOR.suffix; + public static final String BLOCK_BARREL = WoodSlots.BARREL.suffix; + public static final String BLOCK_BUTTON = WoodSlots.BUTTON.suffix; + public static final String BLOCK_LADDER = WoodSlots.LADDER.suffix; + public static final String BLOCK_PLANKS = WoodSlots.PLANKS.suffix; + public static final String BLOCK_STAIRS = WoodSlots.STAIRS.suffix; + public static final String BLOCK_CHEST = WoodSlots.CHEST.suffix; + public static final String BLOCK_FENCE = WoodSlots.FENCE.suffix; + public static final String BLOCK_BARK = WoodSlots.BARK.suffix; + public static final String BLOCK_DOOR = WoodSlots.DOOR.suffix; + public static final String BLOCK_GATE = WoodSlots.GATE.suffix; + public static final String BLOCK_SIGN = WoodSlots.SIGN.suffix; + public static final String BLOCK_WALL_SIGN = WoodSlots.WALL_SIGN; + public static final String BLOCK_SLAB = WoodSlots.SLAB.suffix; + public static final String BLOCK_LOG = WoodSlots.LOG.suffix; + public static final String ITEM_BOAT = WoodSlots.BOAT.suffix; + public static final String ITEM_CHEST_BOAT = WoodSlots.CHEST_BOAT.suffix; + + public static final String TAG_LOGS = "logs"; + + public final MapColor planksColor; + public final MapColor woodColor; + @Nullable + protected BoatTypeOverride boatType; + + public final BCLWoodTypeWrapper woodType; + + public WoodenComplexMaterial( + String modID, + String baseName, + String receipGroupPrefix, + MapColor woodColor, + MapColor planksColor + ) { + this(modID, baseName, receipGroupPrefix, woodColor, planksColor, null); + } + + public WoodenComplexMaterial( + String modID, + String baseName, + String receipGroupPrefix, + MapColor woodColor, + MapColor planksColor, + BoatTypeOverride boatType + ) { + super(modID, baseName, receipGroupPrefix); + this.planksColor = planksColor; + this.woodColor = woodColor; + this.woodType = createWoodTypeBuilder().build(); + this.boatType = boatType; + } + + protected BCLWoodTypeWrapper.Builder createWoodTypeBuilder() { + return BCLWoodTypeWrapper.create(getModID(), getBaseName()).setColor(planksColor); + } + + @Override + protected FabricBlockSettings getBlockSettings() { + return FabricBlockSettings.copyOf(Blocks.OAK_PLANKS) + .mapColor(planksColor); + } + + @Override + public ResourceLocation getMaterialID() { + return MATERIAL_ID; + } + + @Override + protected void initTags() { + addBlockTag(TagManager.BLOCKS.makeTag(getModID(), getBaseName() + "_logs")); + addItemTag(TagManager.ITEMS.makeTag(getModID(), getBaseName() + "_logs")); + } + + @Override + protected SlotMap createMaterialSlots() { + return SlotMap.of( + WoodSlots.STRIPPED_LOG, + WoodSlots.STRIPPED_BARK, + WoodSlots.LOG, + WoodSlots.BARK, + WoodSlots.PLANKS, + WoodSlots.STAIRS, + WoodSlots.SLAB, + WoodSlots.FENCE, + WoodSlots.GATE, + WoodSlots.BUTTON, + WoodSlots.PRESSURE_PLATE, + WoodSlots.TRAPDOOR, + WoodSlots.DOOR, + WoodSlots.LADDER, + WoodSlots.SIGN, + WoodSlots.CHEST, + WoodSlots.BARREL, + WoodSlots.CRAFTING_TABLE, + WoodSlots.BOOKSHELF, + WoodSlots.COMPOSTER + ); + } + + @Override + protected void initFlammable(FlammableBlockRegistry registry) { + final Consumer addFlammableHardWood = (Block block) -> registry.add(block, 5, 5); + getBlocks().forEach(block -> { + registry.add(block, 5, 20); + }); + + ifBlockPresent(WoodSlots.LOG, addFlammableHardWood); + ifBlockPresent(WoodSlots.BARK, addFlammableHardWood); + ifBlockPresent(WoodSlots.STRIPPED_LOG, addFlammableHardWood); + ifBlockPresent(WoodSlots.STRIPPED_BARK, addFlammableHardWood); + } + + + public final void initBoatType() { + if (getBoatType() == null) { + boatType = supplyBoatType(); + } + } + + protected BoatTypeOverride supplyBoatType() { + return BoatTypeOverride.create( + getModID(), + getBaseName(), + getBlock(WoodSlots.PLANKS) + ); + } + + public BoatTypeOverride getBoatType() { + return boatType; + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/complexmaterials/entry/BlockEntry.java b/src/main/java/org/betterx/bclib/complexmaterials/entry/BlockEntry.java new file mode 100644 index 00000000..353fa0c8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/BlockEntry.java @@ -0,0 +1,81 @@ +package org.betterx.bclib.complexmaterials.entry; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.registry.BlockRegistry; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import java.util.function.BiFunction; + +public class BlockEntry extends ComplexMaterialEntry { + final BiFunction initFunction; + final boolean hasItem; + final boolean isPseudoEntry; + + TagKey[] blockTags; + TagKey[] itemTags; + + public BlockEntry(String suffix, BiFunction initFunction) { + this(suffix, true, false, initFunction); + } + + public BlockEntry( + String suffix, + boolean hasItem, + BiFunction initFunction + ) { + this(suffix, hasItem, false, initFunction); + } + + public BlockEntry( + String suffix, + boolean hasItem, + boolean isPseudoEntry, + BiFunction initFunction + ) { + super(suffix); + this.initFunction = initFunction; + this.hasItem = hasItem; + this.isPseudoEntry = isPseudoEntry; + } + + @SafeVarargs + public final BlockEntry setBlockTags(TagKey... blockTags) { + this.blockTags = blockTags; + return this; + } + + @SafeVarargs + public final BlockEntry setItemTags(TagKey... itemTags) { + this.itemTags = itemTags; + return this; + } + + public Block init(ComplexMaterial material, BlockBehaviour.Properties blockSettings, BlockRegistry registry) { + ResourceLocation location = getLocation(material.getModID(), material.getBaseName()); + Block block = initFunction.apply(material, blockSettings); + if (block == null) return null; + + if (!isPseudoEntry) { + if (hasItem) { + registry.register(location, block); + + } else { + registry.registerBlockOnly(location, block); + } + } + if (hasItem && itemTags != null) { + TagManager.ITEMS.add(block.asItem(), itemTags); + } + if (blockTags != null) { + TagManager.BLOCKS.add(block, blockTags); + } + + return block; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/entry/ComplexMaterialEntry.java b/src/main/java/org/betterx/bclib/complexmaterials/entry/ComplexMaterialEntry.java new file mode 100644 index 00000000..9dea2b60 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/ComplexMaterialEntry.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.complexmaterials.entry; + +import net.minecraft.resources.ResourceLocation; + +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +public abstract class ComplexMaterialEntry { + @NotNull + private final String suffix; + + protected ComplexMaterialEntry(String suffix) { + this.suffix = suffix; + } + + public String getName(String baseName) { + return baseName + "_" + suffix; + } + + public ResourceLocation getLocation(String modID, String baseName) { + return new ResourceLocation(modID, getName(baseName)); + } + + public String getSuffix() { + return suffix; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ComplexMaterialEntry that = (ComplexMaterialEntry) o; + return suffix.equals(that.suffix); + } + + @Override + public int hashCode() { + return Objects.hash(suffix); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/entry/ItemEntry.java b/src/main/java/org/betterx/bclib/complexmaterials/entry/ItemEntry.java new file mode 100644 index 00000000..37d91f27 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/ItemEntry.java @@ -0,0 +1,37 @@ +package org.betterx.bclib.complexmaterials.entry; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.registry.ItemRegistry; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + +import java.util.function.BiFunction; + +public class ItemEntry extends ComplexMaterialEntry { + final BiFunction initFunction; + + TagKey[] itemTags; + + public ItemEntry(String suffix, BiFunction initFunction) { + super(suffix); + this.initFunction = initFunction; + } + + public ItemEntry setItemTags(TagKey[] itemTags) { + this.itemTags = itemTags; + return this; + } + + public Item init(ComplexMaterial material, Item.Properties itemSettings, ItemRegistry registry) { + ResourceLocation location = getLocation(material.getModID(), material.getBaseName()); + Item item = initFunction.apply(material, itemSettings); + registry.register(location, item); + if (itemTags != null) { + TagManager.ITEMS.add(item, itemTags); + } + return item; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/entry/MaterialSlot.java b/src/main/java/org/betterx/bclib/complexmaterials/entry/MaterialSlot.java new file mode 100644 index 00000000..2e745ead --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/MaterialSlot.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.complexmaterials.entry; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; + +import java.util.function.Consumer; +import org.jetbrains.annotations.NotNull; + +public abstract class MaterialSlot { + @NotNull + public final String suffix; + + public MaterialSlot(@NotNull String suffix) { + this.suffix = suffix; + } + + public abstract void addBlockEntry(M parentMaterial, Consumer adder); + public abstract void addRecipeEntry(M parentMaterial, Consumer adder); + + public void addItemEntry(M parentMaterial, Consumer adder) { + } + + + public void onInit(M parentMaterial) { + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/entry/RecipeEntry.java b/src/main/java/org/betterx/bclib/complexmaterials/entry/RecipeEntry.java new file mode 100644 index 00000000..97b0f6cb --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/RecipeEntry.java @@ -0,0 +1,20 @@ +package org.betterx.bclib.complexmaterials.entry; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; + +import net.minecraft.resources.ResourceLocation; + +import java.util.function.BiConsumer; + +public class RecipeEntry extends ComplexMaterialEntry { + final BiConsumer initFunction; + + public RecipeEntry(String suffix, BiConsumer initFunction) { + super(suffix); + this.initFunction = initFunction; + } + + public void init(ComplexMaterial material) { + initFunction.accept(material, getLocation(material.getModID(), material.getBaseName())); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/entry/SimpleBlockOnlyMaterialSlot.java b/src/main/java/org/betterx/bclib/complexmaterials/entry/SimpleBlockOnlyMaterialSlot.java new file mode 100644 index 00000000..3820d41c --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/SimpleBlockOnlyMaterialSlot.java @@ -0,0 +1,60 @@ +package org.betterx.bclib.complexmaterials.entry; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import java.util.function.BiFunction; +import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class SimpleBlockOnlyMaterialSlot extends SimpleMaterialSlot { + public SimpleBlockOnlyMaterialSlot(@NotNull String suffix) { + super(suffix); + } + + @Override + @Nullable + protected BlockEntry getBlockEntry(M parentMaterial) { + final BlockEntry entry = new BlockEntry(suffix, false, (c, p) -> this.createBlock(parentMaterial, p)); + modifyBlockEntry(parentMaterial, entry); + return entry; + } + + public static SimpleBlockOnlyMaterialSlot createBlockOnly( + @NotNull String suffix, + BiFunction maker + ) { + return new SimpleBlockOnlyMaterialSlot(suffix) { + @Override + protected @NotNull Block createBlock(ComplexMaterial parentMaterial, BlockBehaviour.Properties settings) { + return maker.apply(parentMaterial, settings); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + + } + }; + } + + public static SimpleBlockOnlyMaterialSlot createBlockOnly( + @NotNull String suffix, + Supplier maker + ) { + return new SimpleBlockOnlyMaterialSlot(suffix) { + @Override + protected @NotNull Block createBlock(ComplexMaterial parentMaterial, BlockBehaviour.Properties settings) { + return maker.get(); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + + } + }; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/entry/SimpleMaterialSlot.java b/src/main/java/org/betterx/bclib/complexmaterials/entry/SimpleMaterialSlot.java new file mode 100644 index 00000000..c066982d --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/SimpleMaterialSlot.java @@ -0,0 +1,112 @@ +package org.betterx.bclib.complexmaterials.entry; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class SimpleMaterialSlot extends MaterialSlot { + public SimpleMaterialSlot(@NotNull String suffix) { + super(suffix); + } + + @Override + public void addBlockEntry(M parentMaterial, Consumer adder) { + adder.accept(getBlockEntry(parentMaterial)); + } + + @Nullable + protected BlockEntry getBlockEntry(M parentMaterial) { + final BlockEntry entry = new BlockEntry(suffix, (c, p) -> this.createBlock(parentMaterial, p)); + modifyBlockEntry(parentMaterial, entry); + return entry; + } + + @NotNull + protected abstract Block createBlock(M parentMaterial, BlockBehaviour.Properties settings); + + protected void modifyBlockEntry(M parentMaterial, @NotNull BlockEntry entry) { + } + + @Override + public void addRecipeEntry(M parentMaterial, Consumer adder) { + adder.accept(getRecipeEntry(parentMaterial)); + } + + protected @Nullable RecipeEntry getRecipeEntry(M parentMaterial) { + return new RecipeEntry(suffix, this::makeRecipe); + } + + protected abstract @Nullable void makeRecipe( + ComplexMaterial parentMaterial, + ResourceLocation id + ); + + @Override + public void addItemEntry(M parentMaterial, Consumer adder) { + ItemEntry item = getItemEntry(parentMaterial); + if (item != null) { + adder.accept(item); + } + } + + @Nullable + protected ItemEntry getItemEntry(M parentMaterial) { + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SimpleMaterialSlot)) return false; + SimpleMaterialSlot that = (SimpleMaterialSlot) o; + return suffix.equals(that.suffix); + } + + @Override + public int hashCode() { + return Objects.hash(suffix); + } + + public static SimpleMaterialSlot createBlockItem( + @NotNull String suffix, + BiFunction maker + ) { + return new SimpleMaterialSlot(suffix) { + @Override + protected @NotNull Block createBlock(ComplexMaterial parentMaterial, BlockBehaviour.Properties settings) { + return maker.apply(parentMaterial, settings); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + + } + }; + } + + public static SimpleMaterialSlot createBlockItem( + @NotNull String suffix, + Supplier maker + ) { + return new SimpleMaterialSlot(suffix) { + @Override + protected @NotNull Block createBlock(ComplexMaterial parentMaterial, BlockBehaviour.Properties settings) { + return maker.get(); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + + } + }; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/entry/SlotMap.java b/src/main/java/org/betterx/bclib/complexmaterials/entry/SlotMap.java new file mode 100644 index 00000000..0876b143 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/entry/SlotMap.java @@ -0,0 +1,49 @@ +package org.betterx.bclib.complexmaterials.entry; + +import org.betterx.bclib.complexmaterials.ComplexMaterialSet; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import org.jetbrains.annotations.NotNull; + +public class SlotMap>> implements Iterable> { + private final Map> map; + + public SlotMap() { + this.map = new LinkedHashMap<>(); + } + + public static >> SlotMap of(MaterialSlot... slots) { + final SlotMap map = new SlotMap<>(); + for (MaterialSlot slot : slots) { + map.add(slot); + } + return map; + } + + public SlotMap replace(MaterialSlot slot) { + return add(slot); + } + + public SlotMap add(MaterialSlot slot) { + map.put(slot.suffix, slot); + return this; + } + + public SlotMap remove(MaterialSlot slot) { + map.remove(slot.suffix); + return this; + } + + public SlotMap remove(String slot) { + map.remove(slot); + return this; + } + + @NotNull + @Override + public Iterator> iterator() { + return map.values().iterator(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/common/AbstractSlab.java b/src/main/java/org/betterx/bclib/complexmaterials/set/common/AbstractSlab.java new file mode 100644 index 00000000..f701ad0a --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/common/AbstractSlab.java @@ -0,0 +1,37 @@ +package org.betterx.bclib.complexmaterials.set.common; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; + +import org.jetbrains.annotations.Nullable; + +public abstract class AbstractSlab extends SimpleMaterialSlot { + public AbstractSlab() { + super("slab"); + } + + protected AbstractSlab(String prefix) { + super(prefix + "_slab"); + } + + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(6) + .setShape("###") + .addMaterial('#', parentMaterial.getBlock(getSourceBlockSlot())) + .setGroup("slab") + .setCategory(RecipeCategory.BUILDING_BLOCKS) + .build(); + } + + @Nullable + protected abstract MaterialSlot getSourceBlockSlot(); +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/common/AbstractStairs.java b/src/main/java/org/betterx/bclib/complexmaterials/set/common/AbstractStairs.java new file mode 100644 index 00000000..ac9e13dd --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/common/AbstractStairs.java @@ -0,0 +1,37 @@ +package org.betterx.bclib.complexmaterials.set.common; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; + +import org.jetbrains.annotations.Nullable; + +public abstract class AbstractStairs extends SimpleMaterialSlot { + public AbstractStairs() { + super("stairs"); + } + + protected AbstractStairs(String prefix) { + super(prefix + "_stairs"); + } + + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(4) + .setShape("# ", "## ", "###") + .addMaterial('#', parentMaterial.getBlock(getSourceBlockSlot())) + .setGroup("stairs") + .setCategory(RecipeCategory.BUILDING_BLOCKS) + .build(); + } + + @Nullable + protected abstract MaterialSlot getSourceBlockSlot(); +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/common/AbstractWall.java b/src/main/java/org/betterx/bclib/complexmaterials/set/common/AbstractWall.java new file mode 100644 index 00000000..66f73a39 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/common/AbstractWall.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.complexmaterials.set.common; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.complexmaterials.set.stone.StoneSlots; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.resources.ResourceLocation; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class AbstractWall extends SimpleMaterialSlot { + public AbstractWall() { + super("wall"); + } + + protected AbstractWall(@NotNull String postfix) { + super(postfix + "_wall"); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder.crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(6) + .setShape("###", "###") + .addMaterial('#', parentMaterial.getBlock(StoneSlots.SOURCE)) + .setGroup("wall") + .build(); + } + + @Nullable + protected abstract MaterialSlot getSourceBlockSlot(); +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/stone/CrackedBlock.java b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/CrackedBlock.java new file mode 100644 index 00000000..31119f52 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/CrackedBlock.java @@ -0,0 +1,32 @@ +package org.betterx.bclib.complexmaterials.set.stone; + +import org.betterx.bclib.blocks.BaseBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.StoneComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CrackedBlock extends SimpleMaterialSlot { + public CrackedBlock() { + super("cracked"); + } + + @Override + protected @NotNull Block createBlock(StoneComplexMaterial parentMaterial, BlockBehaviour.Properties settings) { + return new BaseBlock.Stone(settings); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder.smelting(id, parentMaterial.getBlock(suffix)) + .setPrimaryInputAndUnlock(parentMaterial.getBlock(StoneSlots.SOURCE)) + .build(false, false, false); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Slab.java b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Slab.java new file mode 100644 index 00000000..3fa56298 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Slab.java @@ -0,0 +1,57 @@ +package org.betterx.bclib.complexmaterials.set.stone; + +import org.betterx.bclib.blocks.BaseSlabBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.StoneComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.set.common.AbstractSlab; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Slab extends AbstractSlab { + private final MaterialSlot base; + + public Slab() { + super(); + this.base = StoneSlots.SOURCE; + } + + public Slab(MaterialSlot base) { + super(base.suffix); + this.base = base; + } + + @Override + protected @NotNull Block createBlock( + StoneComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseSlabBlock.Stone(parentMaterial.getBlock(getSourceBlockSlot())); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + super.makeRecipe(parentMaterial, id); + + BCLRecipeBuilder + .stonecutting( + new ResourceLocation(id.getNamespace(), "stonecutter_" + id.getPath()), + parentMaterial.getBlock(suffix) + ) + .setPrimaryInputAndUnlock(parentMaterial.getBlock(getSourceBlockSlot())) + .setOutputCount(2) + .setGroup("slab") + .build(); + } + + @Nullable + @Override + protected MaterialSlot getSourceBlockSlot() { + return base; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Source.java b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Source.java new file mode 100644 index 00000000..0ed134d5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Source.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.complexmaterials.set.stone; + +import org.betterx.bclib.complexmaterials.StoneComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.entry.RecipeEntry; + +import java.util.function.Consumer; + +public class Source extends MaterialSlot { + public Source() { + super("source"); + } + + + @Override + public void addBlockEntry(StoneComplexMaterial parentMaterial, Consumer adder) { + adder.accept(new BlockEntry(suffix, true, true, (c, p) -> parentMaterial.sourceBlock)); + } + + @Override + public void addRecipeEntry(StoneComplexMaterial parentMaterial, Consumer adder) { + + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Stairs.java b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Stairs.java new file mode 100644 index 00000000..fd1be0e5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Stairs.java @@ -0,0 +1,56 @@ +package org.betterx.bclib.complexmaterials.set.stone; + +import org.betterx.bclib.blocks.BaseStairsBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.StoneComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.set.common.AbstractStairs; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Stairs extends AbstractStairs { + private final MaterialSlot base; + + public Stairs() { + super(); + this.base = StoneSlots.SOURCE; + } + + public Stairs(MaterialSlot base) { + super(base.suffix); + this.base = base; + } + + @Override + protected @NotNull Block createBlock( + StoneComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseStairsBlock.Stone(parentMaterial.getBlock(getSourceBlockSlot())); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + super.makeRecipe(parentMaterial, id); + + BCLRecipeBuilder + .stonecutting( + new ResourceLocation(id.getNamespace(), "stonecutter_" + id.getPath()), + parentMaterial.getBlock(suffix) + ) + .setPrimaryInputAndUnlock(parentMaterial.getBlock(getSourceBlockSlot())) + .setOutputCount(1) + .setGroup("stairs") + .build(); + } + + @Override + protected @Nullable MaterialSlot getSourceBlockSlot() { + return base; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/stone/StoneSlots.java b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/StoneSlots.java new file mode 100644 index 00000000..6163e61e --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/StoneSlots.java @@ -0,0 +1,21 @@ +package org.betterx.bclib.complexmaterials.set.stone; + +import org.betterx.bclib.complexmaterials.StoneComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; + +public class StoneSlots { + public static final MaterialSlot SOURCE = new Source(); + public static final MaterialSlot SLAB = new Slab(); + public static final MaterialSlot STAIRS = new Stairs(); + public static final MaterialSlot WALL = new Wall(); + + public static final MaterialSlot CRACKED = new CrackedBlock(); + public static final MaterialSlot CRACKED_SLAB = new Slab(CRACKED); + public static final MaterialSlot CRACKED_STAIRS = new Stairs(CRACKED); + public static final MaterialSlot CRACKED_WALL = new Wall(CRACKED); + + public static final MaterialSlot WEATHERED = new WeatheredBlock(); + public static final MaterialSlot WEATHERED_SLAB = new Slab(WEATHERED); + public static final MaterialSlot WEATHERED_STAIRS = new Stairs(WEATHERED); + public static final MaterialSlot WEATHERED_WALL = new Wall(WEATHERED); +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Wall.java b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Wall.java new file mode 100644 index 00000000..719bdbb8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/Wall.java @@ -0,0 +1,54 @@ +package org.betterx.bclib.complexmaterials.set.stone; + +import org.betterx.bclib.blocks.BaseWallBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.StoneComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.set.common.AbstractWall; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Wall extends AbstractWall { + private final MaterialSlot base; + + public Wall() { + super(); + this.base = StoneSlots.SOURCE; + } + + public Wall(MaterialSlot base) { + super(base.suffix); + this.base = base; + } + + @Override + protected @NotNull Block createBlock(StoneComplexMaterial parentMaterial, BlockBehaviour.Properties settings) { + return new BaseWallBlock.Stone(parentMaterial.getBlock(getSourceBlockSlot())); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + super.makeRecipe(parentMaterial, id); + + BCLRecipeBuilder + .stonecutting( + new ResourceLocation(id.getNamespace(), "stonecutter_" + id.getPath()), + parentMaterial.getBlock(suffix) + ) + .setPrimaryInputAndUnlock(parentMaterial.getBlock(getSourceBlockSlot())) + .setOutputCount(1) + .setGroup("wall") + .build(); + } + + @Override + protected @Nullable MaterialSlot getSourceBlockSlot() { + return base; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/stone/WeatheredBlock.java b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/WeatheredBlock.java new file mode 100644 index 00000000..785c2bdc --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/stone/WeatheredBlock.java @@ -0,0 +1,47 @@ +package org.betterx.bclib.complexmaterials.set.stone; + +import org.betterx.bclib.blocks.BaseBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.StoneComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class WeatheredBlock extends SimpleMaterialSlot { + public WeatheredBlock() { + super("weathered"); + } + + @Override + protected @NotNull Block createBlock(StoneComplexMaterial parentMaterial, BlockBehaviour.Properties settings) { + return new BaseBlock.Stone(settings); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder.crafting( + new ResourceLocation(id.getNamespace(), id.getPath() + "_from_moss"), + parentMaterial.getBlock(suffix) + ) + .shapeless() + .addMaterial('#', parentMaterial.getBlock(StoneSlots.SOURCE)) + .addMaterial('+', Blocks.MOSS_BLOCK) + .build(); + + BCLRecipeBuilder.crafting( + new ResourceLocation(id.getNamespace(), id.getPath() + "_from_vine"), + parentMaterial.getBlock(suffix) + ) + .shapeless() + .addMaterial('#', parentMaterial.getBlock(StoneSlots.SOURCE)) + .addMaterial('+', Blocks.VINE) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/AbstractSaplingSlot.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/AbstractSaplingSlot.java new file mode 100644 index 00000000..415a5ba6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/AbstractSaplingSlot.java @@ -0,0 +1,52 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import java.util.function.BiFunction; +import java.util.function.Supplier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public abstract class AbstractSaplingSlot extends SimpleMaterialSlot { + protected static final String SAPLING_SUFFIX = "sapling"; + + protected AbstractSaplingSlot() { + super(SAPLING_SUFFIX); + } + + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + + } + + public static AbstractSaplingSlot create(BiFunction maker) { + return new AbstractSaplingSlot() { + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, + BlockBehaviour.Properties settings + ) { + return maker.apply(parentMaterial, settings); + } + }; + } + + public static AbstractSaplingSlot create(Supplier maker) { + return new AbstractSaplingSlot() { + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, + BlockBehaviour.Properties settings + ) { + return maker.get(); + } + }; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/BarStool.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/BarStool.java new file mode 100644 index 00000000..dc77e460 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/BarStool.java @@ -0,0 +1,51 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.furniture.block.BaseBarStool; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class BarStool extends SimpleMaterialSlot { + public BarStool() { + super("bar_stool"); + } + + public static void makeBarStoolRecipe(ResourceLocation id, Block barStool, Block planks) { + BCLRecipeBuilder.crafting(id, barStool) + .setShape("##", "II", "II") + .addMaterial('#', planks) + .addMaterial('I', Items.STICK) + .setGroup("bar_stool") + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseBarStool.Wood(parentMaterial.getBlock(WoodSlots.SLAB)); + } + + @Override + protected void modifyBlockEntry(WoodenComplexMaterial parentMaterial, @NotNull BlockEntry entry) { + entry.setBlockTags(BlockTags.MINEABLE_WITH_AXE); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BarStool.makeBarStoolRecipe(id, parentMaterial.getBlock(suffix), parentMaterial.getBlock(WoodSlots.SLAB)); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Bark.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Bark.java new file mode 100644 index 00000000..bb82fe17 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Bark.java @@ -0,0 +1,56 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseStripableBarkBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Bark extends SimpleMaterialSlot { + public Bark() { + super("bark"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseStripableBarkBlock.Wood( + parentMaterial.woodColor, + parentMaterial.getBlock(WoodSlots.STRIPPED_BARK), + parentMaterial.woodType.flammable + ); + } + + + @Override + protected void modifyBlockEntry(WoodenComplexMaterial parentMaterial, @NotNull BlockEntry entry) { + entry + .setBlockTags( + parentMaterial.getBlockTag(WoodenComplexMaterial.TAG_LOGS) + ) + .setItemTags( + parentMaterial.getItemTag(WoodenComplexMaterial.TAG_LOGS) + ); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial material, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, material.getBlock(suffix)) + .setShape("##", "##") + .addMaterial('#', material.getBlock(WoodSlots.LOG)) + .setOutputCount(3) + .setCategory(RecipeCategory.BUILDING_BLOCKS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Barrel.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Barrel.java new file mode 100644 index 00000000..d3b9cc6c --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Barrel.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseBarrelBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Barrel extends SimpleMaterialSlot { + public Barrel() { + super("barrel"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseBarrelBlock.Wood(parentMaterial.getBlock(WoodSlots.PLANKS)); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setShape("#S#", "# #", "#S#") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .addMaterial('S', parentMaterial.getBlock(WoodSlots.SLAB)) + .setGroup("barrel") + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Boat.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Boat.java new file mode 100644 index 00000000..012060d9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Boat.java @@ -0,0 +1,61 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.ItemEntry; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import java.util.function.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Boat extends SimpleMaterialSlot { + public Boat() { + super("boat"); + } + + @Override + public void addBlockEntry(WoodenComplexMaterial parentMaterial, Consumer adder) { + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + //this should never get called + return null; + } + + @Override + protected @Nullable ItemEntry getItemEntry(WoodenComplexMaterial parentMaterial) { + return new ItemEntry(suffix, (cmx, settings) -> parentMaterial.getBoatType().createItem(false)); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + makeBoatRecipe(id, parentMaterial.getBlock(WoodSlots.PLANKS), parentMaterial.getItem(suffix)); + } + + @Override + public void onInit(WoodenComplexMaterial parentMaterial) { + parentMaterial.initBoatType(); + } + + public static void makeBoatRecipe(ResourceLocation id, Block planks, Item boat) { + BCLRecipeBuilder + .crafting(id, boat) + .setShape("# #", "###") + .addMaterial('#', planks) + .setGroup("boat") + .setCategory(RecipeCategory.TRANSPORTATION) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Bookshelf.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Bookshelf.java new file mode 100644 index 00000000..eefb18ec --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Bookshelf.java @@ -0,0 +1,41 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseBookshelfBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Bookshelf extends SimpleMaterialSlot { + public Bookshelf() { + super("bookshelf"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseBookshelfBlock.Wood(parentMaterial.getBlock(WoodSlots.PLANKS)); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setShape("###", "PPP", "###") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .addMaterial('P', Items.BOOK) + .setGroup("bookshelf") + .setCategory(RecipeCategory.BUILDING_BLOCKS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Button.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Button.java new file mode 100644 index 00000000..b35c6812 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Button.java @@ -0,0 +1,42 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseButtonBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Button extends SimpleMaterialSlot { + public Button() { + super("button"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseButtonBlock.Wood( + parentMaterial.getBlock(WoodSlots.PLANKS), + parentMaterial.woodType.setType() + ); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .shapeless() + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .setGroup("button") + .setCategory(RecipeCategory.REDSTONE) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Chair.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Chair.java new file mode 100644 index 00000000..e654669b --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Chair.java @@ -0,0 +1,51 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.furniture.block.BaseChair; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Chair extends SimpleMaterialSlot { + public Chair() { + super("chair"); + } + + public static void makeChairRecipe(ResourceLocation id, Block chair, Block planks) { + BCLRecipeBuilder.crafting(id, chair) + .setShape("I ", "##", "II") + .addMaterial('#', planks) + .addMaterial('I', Items.STICK) + .setGroup("chair") + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } + + @Override + protected void modifyBlockEntry(WoodenComplexMaterial parentMaterial, @NotNull BlockEntry entry) { + entry.setBlockTags(BlockTags.MINEABLE_WITH_AXE); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseChair.Wood(parentMaterial.getBlock(WoodSlots.SLAB)); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + Chair.makeChairRecipe(id, parentMaterial.getBlock(suffix), parentMaterial.getBlock(WoodSlots.SLAB)); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Chest.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Chest.java new file mode 100644 index 00000000..4d5eb350 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Chest.java @@ -0,0 +1,39 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseChestBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Chest extends SimpleMaterialSlot { + public Chest() { + super("chest"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseChestBlock.Wood(parentMaterial.getBlock(WoodSlots.PLANKS)); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setShape("###", "# #", "###") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .setGroup("chest") + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/ChestBoat.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/ChestBoat.java new file mode 100644 index 00000000..c7135830 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/ChestBoat.java @@ -0,0 +1,68 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.ItemEntry; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; +import org.betterx.worlds.together.tag.v3.CommonItemTags; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.ItemTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import java.util.function.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ChestBoat extends SimpleMaterialSlot { + public ChestBoat() { + super("chest_boat"); + } + + @Override + public void addBlockEntry(WoodenComplexMaterial parentMaterial, Consumer adder) { + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + //this should never get called + return null; + } + + @Override + protected @Nullable ItemEntry getItemEntry(WoodenComplexMaterial parentMaterial) { + return new ItemEntry( + suffix, + (cmx, settings) -> parentMaterial.getBoatType().createItem(true) + ).setItemTags(new TagKey[]{ItemTags.CHEST_BOATS}); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + makeChestBoatRecipe(id, parentMaterial.getItem(WoodSlots.BOAT), parentMaterial.getItem(WoodSlots.CHEST_BOAT)); + } + + @Override + public void onInit(WoodenComplexMaterial parentMaterial) { + parentMaterial.initBoatType(); + } + + public static void makeChestBoatRecipe(ResourceLocation id, Item boat, Item chestBoat) { + BCLRecipeBuilder + .crafting(id, chestBoat) + .shapeless() + .addMaterial('C', CommonItemTags.CHEST) + .addMaterial('#', boat) + .setGroup("chest_boat") + .setCategory(RecipeCategory.TRANSPORTATION) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Composter.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Composter.java new file mode 100644 index 00000000..df946255 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Composter.java @@ -0,0 +1,38 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseComposterBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Composter extends SimpleMaterialSlot { + public Composter() { + super("composter"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseComposterBlock.Wood(parentMaterial.getBlock(WoodSlots.PLANKS)); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder.crafting(id, parentMaterial.getBlock(suffix)) + .setShape("# #", "# #", "###") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.SLAB)) + .setGroup("composter") + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/CraftingTable.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/CraftingTable.java new file mode 100644 index 00000000..e60942c4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/CraftingTable.java @@ -0,0 +1,39 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseCraftingTableBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class CraftingTable extends SimpleMaterialSlot { + public CraftingTable() { + super("crafting_table"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseCraftingTableBlock.Wood(parentMaterial.getBlock(WoodSlots.PLANKS)); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setShape("##", "##") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .setGroup("table") + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Door.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Door.java new file mode 100644 index 00000000..3e71f866 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Door.java @@ -0,0 +1,43 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseDoorBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Door extends SimpleMaterialSlot { + public Door() { + super("door"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseDoorBlock.Wood( + parentMaterial.getBlock(WoodSlots.PLANKS), + parentMaterial.woodType.setType() + ); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(3) + .setShape("##", "##", "##") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .setGroup("door") + .setCategory(RecipeCategory.REDSTONE) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Fence.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Fence.java new file mode 100644 index 00000000..e4d77454 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Fence.java @@ -0,0 +1,42 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseFenceBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Fence extends SimpleMaterialSlot { + public Fence() { + super("fence"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseFenceBlock.Wood(parentMaterial.getBlock(WoodSlots.PLANKS), parentMaterial.woodType.setType()); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(3) + .setShape("#I#", "#I#") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .addMaterial('I', Items.STICK) + .setGroup("fence") + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Gate.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Gate.java new file mode 100644 index 00000000..a82a95b0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Gate.java @@ -0,0 +1,44 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseGateBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Gate extends SimpleMaterialSlot { + public Gate() { + super("gate"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseGateBlock.Wood( + parentMaterial.getBlock(WoodSlots.PLANKS), + parentMaterial.woodType.type() + ); + } + + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder.crafting(id, parentMaterial.getBlock(suffix)) + .setShape("I#I", "I#I") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .addMaterial('I', Items.STICK) + .setGroup("gate") + .setCategory(RecipeCategory.REDSTONE) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/HangingSign.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/HangingSign.java new file mode 100644 index 00000000..54a62797 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/HangingSign.java @@ -0,0 +1,63 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.signs.BaseHangingSignBlock; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.entry.RecipeEntry; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.world.item.Items; + +import java.util.function.Consumer; +import org.jetbrains.annotations.NotNull; + +public class HangingSign extends MaterialSlot { + @NotNull + public static final String WALL_SUFFFIX = "wall_hanging_sign"; + + public HangingSign() { + super("hanging_sign"); + } + + @Override + public void addBlockEntry(WoodenComplexMaterial parentMaterial, Consumer adder) { + var signEntry = new BlockEntry( + suffix, + (complexMaterial, settings) -> new BaseHangingSignBlock.Wood(parentMaterial.woodType) + ); + + var wallSignEntry = new BlockEntry( + WALL_SUFFFIX, + false, + (complexMaterial, settings) -> { + if (complexMaterial.getBlock(suffix) instanceof BaseHangingSignBlock sign) { + return sign.getWallSignBlock(); + } + return null; + } + ); + + adder.accept(signEntry); + adder.accept(wallSignEntry); + } + + @Override + public void addRecipeEntry( + WoodenComplexMaterial parentMaterial, + Consumer adder + ) { + adder.accept(new RecipeEntry(suffix, (mat, id) -> + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(3) + .setShape("I I", "###", "###") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.STRIPPED_LOG)) + .addMaterial('I', Items.CHAIN) + .setGroup("sign") + .setCategory(RecipeCategory.DECORATIONS) + .build() + )); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Ladder.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Ladder.java new file mode 100644 index 00000000..a6328d88 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Ladder.java @@ -0,0 +1,41 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseLadderBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; + +public class Ladder extends SimpleMaterialSlot { + public Ladder() { + super("ladder"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseLadderBlock.Wood(parentMaterial.getBlock(WoodSlots.PLANKS)); + } + + @Override + protected void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(3) + .setShape("I I", "I#I", "I I") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .addMaterial('I', Items.STICK) + .setGroup("ladder") + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Log.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Log.java new file mode 100644 index 00000000..85a94fde --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Log.java @@ -0,0 +1,55 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseStripableLogBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Log extends SimpleMaterialSlot { + public Log() { + super("log"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseStripableLogBlock.Wood( + parentMaterial.woodColor, + parentMaterial.getBlock(WoodSlots.STRIPPED_LOG), + parentMaterial.woodType.flammable + ); + } + + @Override + protected void modifyBlockEntry(WoodenComplexMaterial parentMaterial, @NotNull BlockEntry entry) { + entry + .setBlockTags( + parentMaterial.getBlockTag(WoodenComplexMaterial.TAG_LOGS) + ) + .setItemTags( + parentMaterial.getItemTag(WoodenComplexMaterial.TAG_LOGS) + ); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial material, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, material.getBlock(suffix)) + .setShape("##", "##") + .addMaterial('#', material.getBlock(WoodSlots.BARK)) + .setOutputCount(3) + .setCategory(RecipeCategory.BUILDING_BLOCKS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Planks.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Planks.java new file mode 100644 index 00000000..7af3407c --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Planks.java @@ -0,0 +1,45 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BasePlanks; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Planks extends SimpleMaterialSlot { + public Planks() { + super("planks"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BasePlanks.Wood(settings); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder.crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(4) + .shapeless() + .addMaterial( + '#', + parentMaterial.getBlock(WoodSlots.LOG), + parentMaterial.getBlock(WoodSlots.BARK), + parentMaterial.getBlock(WoodSlots.STRIPPED_LOG), + parentMaterial.getBlock(WoodSlots.STRIPPED_BARK) + ) + .setGroup("planks") + .setCategory(RecipeCategory.BUILDING_BLOCKS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/PressurePlate.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/PressurePlate.java new file mode 100644 index 00000000..66463f5c --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/PressurePlate.java @@ -0,0 +1,42 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BasePressurePlateBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class PressurePlate extends SimpleMaterialSlot { + public PressurePlate() { + super("plate"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BasePressurePlateBlock.Wood( + parentMaterial.getBlock(WoodSlots.PLANKS), + parentMaterial.woodType.setType() + ); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setShape("##") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .setGroup("pressure_plate") + .setCategory(RecipeCategory.REDSTONE) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Sign.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Sign.java new file mode 100644 index 00000000..1f91fa9c --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Sign.java @@ -0,0 +1,62 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.signs.BaseSignBlock; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.entry.RecipeEntry; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.world.item.Items; + +import java.util.function.Consumer; +import org.jetbrains.annotations.NotNull; + +public class Sign extends MaterialSlot { + @NotNull + public static final String WALL_SUFFFIX = "wall_sign"; + + public Sign() { + super("sign"); + } + + @Override + public void addBlockEntry(WoodenComplexMaterial parentMaterial, Consumer adder) { + var signEntry = new BlockEntry( + suffix, + (complexMaterial, settings) -> new BaseSignBlock.Wood(parentMaterial.woodType) + ); + + var wallSignEntry = new BlockEntry( + WALL_SUFFFIX, + false, + (complexMaterial, settings) -> { + if (complexMaterial.getBlock(suffix) instanceof BaseSignBlock sign) { + return sign.getWallSignBlock(); + } + return null; + } + ); + adder.accept(signEntry); + adder.accept(wallSignEntry); + } + + @Override + public void addRecipeEntry( + WoodenComplexMaterial parentMaterial, + Consumer adder + ) { + adder.accept(new RecipeEntry(suffix, (mat, id) -> + BCLRecipeBuilder + .crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(3) + .setShape("###", "###", " I ") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .addMaterial('I', Items.STICK) + .setGroup("sign") + .setCategory(RecipeCategory.DECORATIONS) + .build() + )); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Slab.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Slab.java new file mode 100644 index 00000000..d6104c6a --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Slab.java @@ -0,0 +1,28 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseSlabBlock; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.set.common.AbstractSlab; + +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Slab extends AbstractSlab { + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseSlabBlock.Wood(parentMaterial.getBlock(WoodSlots.PLANKS), !parentMaterial.woodType.flammable); + } + + + @Override + protected @Nullable MaterialSlot getSourceBlockSlot() { + return WoodSlots.PLANKS; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Stairs.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Stairs.java new file mode 100644 index 00000000..b814fc3f --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Stairs.java @@ -0,0 +1,27 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseStairsBlock; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.set.common.AbstractStairs; + +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Stairs extends AbstractStairs { + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseStairsBlock.Wood(parentMaterial.getBlock(WoodSlots.PLANKS), !parentMaterial.woodType.flammable); + } + + @Override + protected @Nullable MaterialSlot getSourceBlockSlot() { + return WoodSlots.PLANKS; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/StrippedBark.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/StrippedBark.java new file mode 100644 index 00000000..a47a713f --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/StrippedBark.java @@ -0,0 +1,51 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseBarkBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class StrippedBark extends SimpleMaterialSlot { + public StrippedBark() { + super("stripped_bark"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseBarkBlock.Wood(settings, parentMaterial.woodType.flammable); + } + + @Override + protected void modifyBlockEntry(WoodenComplexMaterial parentMaterial, @NotNull BlockEntry entry) { + entry + .setBlockTags( + parentMaterial.getBlockTag(WoodenComplexMaterial.TAG_LOGS) + ) + .setItemTags( + parentMaterial.getItemTag(WoodenComplexMaterial.TAG_LOGS) + ); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial material, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, material.getBlock(suffix)) + .setShape("##", "##") + .addMaterial('#', material.getBlock(WoodSlots.STRIPPED_LOG)) + .setOutputCount(3) + .setCategory(RecipeCategory.BUILDING_BLOCKS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/StrippedLog.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/StrippedLog.java new file mode 100644 index 00000000..629af9a7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/StrippedLog.java @@ -0,0 +1,69 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseRotatedPillarBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class StrippedLog extends SimpleMaterialSlot { + protected StrippedLog() { + super("stripped_log"); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseRotatedPillarBlock.Wood(settings, parentMaterial.woodType.flammable); + } + + @Override + protected void modifyBlockEntry(WoodenComplexMaterial parentMaterial, @NotNull BlockEntry entry) { + if (parentMaterial.woodType.flammable) { + entry + .setBlockTags( + BlockTags.LOGS, + BlockTags.LOGS_THAT_BURN, + parentMaterial.getBlockTag(WoodenComplexMaterial.TAG_LOGS) + ) + .setItemTags( + ItemTags.LOGS, + ItemTags.LOGS_THAT_BURN, + parentMaterial.getItemTag(WoodenComplexMaterial.TAG_LOGS) + ); + } else { + entry + .setBlockTags( + BlockTags.LOGS, + parentMaterial.getBlockTag(WoodenComplexMaterial.TAG_LOGS) + ) + .setItemTags( + ItemTags.LOGS, + parentMaterial.getItemTag(WoodenComplexMaterial.TAG_LOGS) + ); + } + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial material, ResourceLocation id) { + BCLRecipeBuilder + .crafting(id, material.getBlock(suffix)) + .setShape("##", "##") + .addMaterial('#', material.getBlock(WoodSlots.STRIPPED_BARK)) + .setOutputCount(3) + .setCategory(RecipeCategory.BUILDING_BLOCKS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Taburet.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Taburet.java new file mode 100644 index 00000000..1e602e04 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Taburet.java @@ -0,0 +1,51 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.BlockEntry; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.furniture.block.BaseTaburet; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Taburet extends SimpleMaterialSlot { + public Taburet() { + super("taburet"); + } + + public static void makeTaburetRecipe(ResourceLocation id, Block taburet, Block planks) { + BCLRecipeBuilder.crafting(id, taburet) + .setShape("##", "II") + .addMaterial('#', planks) + .addMaterial('I', Items.STICK) + .setGroup("taburet") + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } + + @Override + protected void modifyBlockEntry(WoodenComplexMaterial parentMaterial, @NotNull BlockEntry entry) { + entry.setBlockTags(BlockTags.MINEABLE_WITH_AXE); + } + + @Override + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseTaburet.Wood(parentMaterial.getBlock(WoodSlots.SLAB)); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + Taburet.makeTaburetRecipe(id, parentMaterial.getBlock(suffix), parentMaterial.getBlock(WoodSlots.SLAB)); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Trapdoor.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Trapdoor.java new file mode 100644 index 00000000..6d2ccaf9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Trapdoor.java @@ -0,0 +1,42 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseTrapdoorBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.SimpleMaterialSlot; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Trapdoor extends SimpleMaterialSlot { + public Trapdoor() { + super("trapdoor"); + } + + protected @NotNull Block createBlock( + WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings + ) { + return new BaseTrapdoorBlock.Wood( + parentMaterial.getBlock(WoodSlots.PLANKS), + parentMaterial.woodType.setType(), + parentMaterial.woodType.flammable + ); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder.crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(2) + .setShape("###", "###") + .addMaterial('#', parentMaterial.getBlock(WoodSlots.PLANKS)) + .setGroup("trapdoor") + .setCategory(RecipeCategory.REDSTONE) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Wall.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Wall.java new file mode 100644 index 00000000..16087b10 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/Wall.java @@ -0,0 +1,38 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.blocks.BaseWallBlock; +import org.betterx.bclib.complexmaterials.ComplexMaterial; +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; +import org.betterx.bclib.complexmaterials.set.common.AbstractWall; +import org.betterx.bclib.recipes.BCLRecipeBuilder; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class Wall extends AbstractWall { + @Override + protected @NotNull Block createBlock(WoodenComplexMaterial parentMaterial, BlockBehaviour.Properties settings) { + return new BaseWallBlock.Wood(parentMaterial.getBlock(getSourceBlockSlot())); + } + + @Override + protected @Nullable void makeRecipe(ComplexMaterial parentMaterial, ResourceLocation id) { + BCLRecipeBuilder.crafting(id, parentMaterial.getBlock(suffix)) + .setOutputCount(6) + .setShape("* *", "|||") + .addMaterial('*', parentMaterial.getBlock(WoodSlots.PLANKS)) + .addMaterial('|', parentMaterial.getBlock(WoodSlots.FENCE)) + .setGroup("wall") + .build(); + } + + @Override + protected @Nullable MaterialSlot getSourceBlockSlot() { + return WoodSlots.PLANKS; + } +} diff --git a/src/main/java/org/betterx/bclib/complexmaterials/set/wood/WoodSlots.java b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/WoodSlots.java new file mode 100644 index 00000000..121ef114 --- /dev/null +++ b/src/main/java/org/betterx/bclib/complexmaterials/set/wood/WoodSlots.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.complexmaterials.set.wood; + +import org.betterx.bclib.complexmaterials.WoodenComplexMaterial; +import org.betterx.bclib.complexmaterials.entry.MaterialSlot; + +public class WoodSlots { + public static final MaterialSlot STRIPPED_LOG = new StrippedLog(); + public static final MaterialSlot STRIPPED_BARK = new StrippedBark(); + public static final MaterialSlot LOG = new Log(); + public static final MaterialSlot BARK = new Bark(); + public static final MaterialSlot PLANKS = new Planks(); + public static final MaterialSlot STAIRS = new Stairs(); + public static final MaterialSlot SLAB = new Slab(); + public static final MaterialSlot FENCE = new Fence(); + public static final MaterialSlot GATE = new Gate(); + public static final MaterialSlot BUTTON = new Button(); + public static final MaterialSlot PRESSURE_PLATE = new PressurePlate(); + public static final MaterialSlot TRAPDOOR = new Trapdoor(); + public static final MaterialSlot DOOR = new Door(); + public static final MaterialSlot LADDER = new Ladder(); + public static final Sign SIGN = new Sign(); + public static final HangingSign HANGING_SIGN = new HangingSign(); + public static final MaterialSlot CHEST = new Chest(); + public static final MaterialSlot BARREL = new Barrel(); + public static final MaterialSlot CRAFTING_TABLE = new CraftingTable(); + public static final MaterialSlot BOOKSHELF = new Bookshelf(); + public static final MaterialSlot COMPOSTER = new Composter(); + public static final MaterialSlot BOAT = new Boat(); + public static final MaterialSlot CHEST_BOAT = new ChestBoat(); + + public static final String WALL_SIGN = Sign.WALL_SUFFFIX; + public static final String WALL_HANGING_SIGN = HangingSign.WALL_SUFFFIX; + public static final String SAPLING = AbstractSaplingSlot.SAPLING_SUFFIX; + public static final MaterialSlot TABURET = new Taburet(); + public static final MaterialSlot CHAIR = new Chair(); + public static final MaterialSlot BAR_STOOL = new BarStool(); + public static final MaterialSlot WALL = new Wall(); +} + + diff --git a/src/main/java/org/betterx/bclib/config/BiomesConfig.java b/src/main/java/org/betterx/bclib/config/BiomesConfig.java new file mode 100644 index 00000000..ea73ba70 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/BiomesConfig.java @@ -0,0 +1,111 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; + +import java.util.*; + +public class BiomesConfig extends PathConfig { + + private Map> BIOME_INCLUDE_LIST = null; + private Map> BIOME_EXCLUDE_LIST = null; + + + public static final BiomeAPI.BiomeType[] endTypes = { + BiomeAPI.BiomeType.END_LAND, + BiomeAPI.BiomeType.END_VOID, + BiomeAPI.BiomeType.END_CENTER, + BiomeAPI.BiomeType.END_BARRENS + }; + + public static final BiomeAPI.BiomeType[] netherTypes = { + BiomeAPI.BiomeType.NETHER + }; + + private static final BiomeAPI.BiomeType[] includeTypes = all(); + private static final BiomeAPI.BiomeType[] excludeTypes = {BiomeAPI.BiomeType.NETHER, BiomeAPI.BiomeType.END}; + + public BiomesConfig() { + super(BCLib.MOD_ID, "biomes", true); + for (var type : includeTypes) { + keeper.registerEntry( + new ConfigKey(type.getName(), "force_include"), + new ConfigKeeper.StringArrayEntry(Collections.EMPTY_LIST) + ); + } + for (var type : excludeTypes) { + keeper.registerEntry( + new ConfigKey(type.getName(), "force_exclude"), + new ConfigKeeper.StringArrayEntry(Collections.EMPTY_LIST) + ); + } + } + + private static BiomeAPI.BiomeType[] all() { + BiomeAPI.BiomeType[] res = new BiomeAPI.BiomeType[endTypes.length + netherTypes.length]; + System.arraycopy(netherTypes, 0, res, 0, netherTypes.length); + System.arraycopy(endTypes, 0, res, netherTypes.length, endTypes.length); + return res; + } + + private List getBiomeIncludeList(BiomeAPI.BiomeType type) { + var entry = getEntry( + "force_include", + type.getName(), + ConfigKeeper.StringArrayEntry.class + ); + if (entry == null) + return List.of(); + return entry.getValue(); + } + + private List getBiomeExcludeList(BiomeAPI.BiomeType type) { + var entry = getEntry( + "force_exclude", + type.getName(), + ConfigKeeper.StringArrayEntry.class + ); + if (entry == null) + return List.of(); + return entry.getValue(); + } + + public List getIncludeMatching(BiomeAPI.BiomeType type) { + return getBiomeIncludeMap().entrySet() + .stream() + .filter(e -> e.getKey().is(type)) + .map(e -> e.getValue()) + .flatMap(Collection::stream) + .toList(); + } + + public List getExcludeMatching(BiomeAPI.BiomeType type) { + return getBiomeExcludeMap().entrySet() + .stream() + .filter(e -> e.getKey().is(type)) + .map(e -> e.getValue()) + .flatMap(Collection::stream) + .toList(); + } + + + public Map> getBiomeIncludeMap() { + if (BIOME_INCLUDE_LIST == null) { + BIOME_INCLUDE_LIST = new HashMap<>(); + for (BiomeAPI.BiomeType type : includeTypes) { + BIOME_INCLUDE_LIST.put(type, getBiomeIncludeList(type)); + } + } + return BIOME_INCLUDE_LIST; + } + + public Map> getBiomeExcludeMap() { + if (BIOME_EXCLUDE_LIST == null) { + BIOME_EXCLUDE_LIST = new HashMap<>(); + for (BiomeAPI.BiomeType type : excludeTypes) { + BIOME_EXCLUDE_LIST.put(type, getBiomeExcludeList(type)); + } + } + return BIOME_EXCLUDE_LIST; + } +} diff --git a/src/main/java/org/betterx/bclib/config/CachedConfig.java b/src/main/java/org/betterx/bclib/config/CachedConfig.java new file mode 100644 index 00000000..07604a0e --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/CachedConfig.java @@ -0,0 +1,58 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.networking.VersionChecker; + +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Base64; + +public class CachedConfig extends NamedPathConfig { + + @ConfigUI(hide = true) + public static final ConfigToken LAST_CHECK_DATE = ConfigToken.String( + "never", + "last", + "version" + ); + + @ConfigUI(hide = true) + public static final ConfigToken LAST_JSON = ConfigToken.String( + "", + "cached", + "version" + ); + + public CachedConfig() { + super(BCLib.MOD_ID, "cache", false, false); + } + + public String lastVersionJson() { + byte[] decodedBytes = Base64.getUrlDecoder().decode(get(LAST_JSON)); + return new String(decodedBytes, StandardCharsets.UTF_8); + } + + public void setLastVersionJson(String json) { + set(LAST_JSON, Base64.getUrlEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8))); + } + + public Instant lastCheckDate() { + String d = get(LAST_CHECK_DATE); + if (d.trim().toLowerCase().equals("never")) { + return Instant.now().minus(VersionChecker.WAIT_FOR_DAYS + 1, ChronoUnit.DAYS); + } + return Instant.parse(d); + } + + public void setLastCheckDate() { + set(LAST_CHECK_DATE, Instant.now().toString()); + } + + @Override + public void saveChanges() { + synchronized (this) { + super.saveChanges(); + } + } +} diff --git a/src/main/java/org/betterx/bclib/config/CategoryConfig.java b/src/main/java/org/betterx/bclib/config/CategoryConfig.java new file mode 100644 index 00000000..5fe67b2b --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/CategoryConfig.java @@ -0,0 +1,10 @@ +package org.betterx.bclib.config; + +public class CategoryConfig extends IdConfig { + + public CategoryConfig(String modID, String group) { + super(modID, group, (id, category) -> { + return new ConfigKey(id.getPath(), id.getNamespace(), category); + }); + } +} diff --git a/src/main/java/org/betterx/bclib/config/ClientConfig.java b/src/main/java/org/betterx/bclib/config/ClientConfig.java new file mode 100644 index 00000000..1d7dc5ea --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ClientConfig.java @@ -0,0 +1,214 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSync; + +public class ClientConfig extends NamedPathConfig { + + @ConfigUI(hide = true) + public static final ConfigToken DID_SHOW_WELCOME = ConfigToken.Boolean( + false, + "didShowWelcome", + "version" + ); + public static final ConfigToken CHECK_VERSIONS = ConfigToken.Boolean( + true, + "check", + "version" + ); + @ConfigUI(leftPadding = 8) + public static final ConfigToken SHOW_UPDATE_INFO = ConfigToken.Boolean( + true, + "showUpdateInfo", + "ui" + ); + + @ConfigUI(leftPadding = 8) + public static final ConfigToken PREFER_MODRINTH_FOR_UPDATES = ConfigToken.Boolean( + false, + "useModrinthForUpdates", + "ui" + ); + + @ConfigUI(hide = true) + public static final ConfigToken FORCE_BETTERX_PRESET = ConfigToken.Boolean( + true, + "forceBetterXPreset", + "ui" + ); + @ConfigUI(topPadding = 12) + public static final ConfigToken SUPPRESS_EXPERIMENTAL_DIALOG = ConfigToken.Boolean( + false, + "suppressExperimentalDialogOnLoad", + "ui" + ); + + + @ConfigUI(hide = true) + public static final ConfigToken NO_DONOR = ConfigToken.Boolean( + true, + "notTheDonorType", + "ui" + ); + + + @ConfigUI(topPadding = 12) + public static final ConfigToken ENABLED = ConfigToken.Boolean(true, "enabled", AutoSync.SYNC_CATEGORY); + + @ConfigUI(leftPadding = 8) + public static final DependendConfigToken ACCEPT_CONFIGS = DependendConfigToken.Boolean( + true, + "acceptConfigs", + AutoSync.SYNC_CATEGORY, + (config) -> config.get( + ENABLED) + ); + @ConfigUI(leftPadding = 8) + public static final DependendConfigToken ACCEPT_FILES = DependendConfigToken.Boolean( + true, + "acceptFiles", + AutoSync.SYNC_CATEGORY, + (config) -> config.get( + ENABLED) + ); + @ConfigUI(leftPadding = 8) + public static final DependendConfigToken ACCEPT_MODS = DependendConfigToken.Boolean( + true, + "acceptMods", + AutoSync.SYNC_CATEGORY, + (config) -> config.get(ENABLED) + ); + @ConfigUI(leftPadding = 8) + public static final DependendConfigToken DISPLAY_MOD_INFO = DependendConfigToken.Boolean( + true, + "displayModInfo", + AutoSync.SYNC_CATEGORY, + (config) -> config.get(ENABLED) + ); + + @ConfigUI(leftPadding = 8) + public static final ConfigToken DEBUG_HASHES = ConfigToken.Boolean( + false, + "debugHashes", + AutoSync.SYNC_CATEGORY + ); + + @ConfigUI(topPadding = 12) + public static final ConfigToken CUSTOM_FOG_RENDERING = ConfigToken.Boolean( + true, + "customFogRendering", + "rendering" + ); + @ConfigUI(leftPadding = 8) + public static final ConfigToken NETHER_THICK_FOG = DependendConfigToken.Boolean( + true, + "netherThickFog", + "rendering", + (config) -> config.get(CUSTOM_FOG_RENDERING) + ); + + @ConfigUI(leftPadding = 8, minValue = 0, maxValue = 2) + public static final ConfigToken FOG_DENSITY = DependendConfigToken.Float( + 1.0f, + "FogDensity", + "rendering", + (config) -> config.get(CUSTOM_FOG_RENDERING) + ); + + @ConfigUI(topPadding = 12) + public static final ConfigToken SURVIES_ON_HINT = ConfigToken.Boolean( + true, + "survives_on_hint", + Configs.MAIN_INFO_CATEGORY + ); + + + public ClientConfig() { + super(BCLib.MOD_ID, "client", false); + } + + public boolean shouldPrintDebugHashes() { + return get(DEBUG_HASHES); + } + + public boolean isAllowingAutoSync() { + return get(ENABLED); + } + + public boolean isAcceptingMods() { + return get(ACCEPT_MODS) /*&& isAllowingAutoSync()*/; + } + + public boolean isAcceptingConfigs() { + return get(ACCEPT_CONFIGS) /*&& isAllowingAutoSync()*/; + } + + public boolean isAcceptingFiles() { + return get(ACCEPT_FILES) /*&& isAllowingAutoSync()*/; + } + + public boolean isShowingModInfo() { + return get(DISPLAY_MOD_INFO) /*&& isAllowingAutoSync()*/; + } + + public boolean suppressExperimentalDialog() { + return get(SUPPRESS_EXPERIMENTAL_DIALOG); + } + + public void setSuppressExperimentalDialog(boolean newValue) { + set(ClientConfig.SUPPRESS_EXPERIMENTAL_DIALOG, newValue); + } + + public boolean netherThickFog() { + return get(NETHER_THICK_FOG); + } + + public boolean renderCustomFog() { + return get(CUSTOM_FOG_RENDERING); + } + + public boolean showUpdateInfo() { + return get(SHOW_UPDATE_INFO); + } + + public boolean isDonor() { + return !get(NO_DONOR); + } + + public float fogDensity() { + return get(FOG_DENSITY); + } + + public boolean checkVersions() { + return get(ClientConfig.CHECK_VERSIONS); + } + + + public void setCheckVersions(boolean newValue) { + set(ClientConfig.CHECK_VERSIONS, newValue); + } + + public boolean didShowWelcomeScreen() { + return get(ClientConfig.DID_SHOW_WELCOME); + } + + public void setDidShowWelcomeScreen() { + set(ClientConfig.DID_SHOW_WELCOME, true); + } + + public boolean forceBetterXPreset() { + return get(FORCE_BETTERX_PRESET); + } + + public void setForceBetterXPreset(boolean v) { + set(FORCE_BETTERX_PRESET, v); + } + + public boolean survivesOnHint() { + return get(ClientConfig.SURVIES_ON_HINT); + } + + public boolean preferModrinthForUpdates() { + return get(ClientConfig.PREFER_MODRINTH_FOR_UPDATES); + } +} diff --git a/src/main/java/org/betterx/bclib/config/Config.java b/src/main/java/org/betterx/bclib/config/Config.java new file mode 100644 index 00000000..05ceb3a0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/Config.java @@ -0,0 +1,240 @@ +package org.betterx.bclib.config; + +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.api.v2.dataexchange.handler.autosync.AutoSyncID; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.FileContentWrapper; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.Nullable; + +public abstract class Config { + protected final static Map AUTO_SYNC_CONFIGS = new HashMap<>(); + public static final String CONFIG_SYNC_PREFIX = "CONFIG_"; + protected final ConfigKeeper keeper; + protected final boolean autoSync; + public final String configID; + + protected abstract void registerEntries(); + + protected Config(String modID, String group) { + this(modID, group, true, false); + } + + protected Config(String modID, String group, boolean autoSync) { + this(modID, group, autoSync, false); + } + + protected Config(String modID, String group, boolean autoSync, boolean diffContent) { + configID = modID + "." + group; + this.keeper = new ConfigKeeper(modID, group); + this.registerEntries(); + this.autoSync = autoSync; + + if (autoSync) { + final String uid = CONFIG_SYNC_PREFIX + configID; + final AutoSyncID aid = new AutoSyncID(BCLib.MOD_ID, uid); + if (diffContent) + DataExchangeAPI.addAutoSyncFile(aid.modID, aid.uniqueID, keeper.getConfigFile(), this::compareForSync); + else + DataExchangeAPI.addAutoSyncFile(aid.modID, aid.uniqueID, keeper.getConfigFile()); + + AUTO_SYNC_CONFIGS.put(aid, this); + + BCLib.LOGGER.info("Added Config " + configID + " to auto sync (" + (diffContent + ? "content diff" + : "file hash") + ")"); + } + } + + private boolean compareForSync(SyncFileHash clientHash, SyncFileHash serverHash, FileContentWrapper content) { + //identical hashes => nothing to do + if (clientHash.equals(serverHash)) { + return false; + } + + return keeper.compareAndUpdateForSync(content); + } + + public void saveChanges() { + this.keeper.save(); + } + + public static void reloadSyncedConfig(AutoSyncID aid, File file) { + Config cfg = AUTO_SYNC_CONFIGS.get(aid); + if (cfg != null) { + cfg.reload(); + } + } + + public void reload() { + this.keeper.reload(); + BCLib.LOGGER.info("Did Reload " + keeper.getConfigFile()); + } + + @Nullable + public > E getEntry(ConfigKey key, Class type) { + return this.keeper.getEntry(key, type); + } + + @Nullable + public > T getDefault(ConfigKey key, Class type) { + ConfigKeeper.Entry entry = keeper.getEntry(key, type); + return entry != null ? entry.getDefault() : null; + } + + protected String getString(ConfigKey key, String defaultValue) { + String str = keeper.getValue(key, ConfigKeeper.StringEntry.class); + if (str == null) { + ConfigKeeper.StringEntry entry = keeper.registerEntry(key, new ConfigKeeper.StringEntry(defaultValue)); + return entry.getValue(); + } + return str != null ? str : defaultValue; + } + + protected String getString(ConfigKey key) { + String str = keeper.getValue(key, ConfigKeeper.StringEntry.class); + return str != null ? str : ""; + } + + protected boolean setString(ConfigKey key, String value) { + try { + ConfigKeeper.StringEntry entry = keeper.getEntry(key, ConfigKeeper.StringEntry.class); + if (entry == null) return false; + entry.setValue(value); + return true; + } catch (NullPointerException ex) { + BCLib.LOGGER.catching(ex); + } + return false; + } + + protected int getInt(ConfigKey key, int defaultValue) { + Integer val = keeper.getValue(key, ConfigKeeper.IntegerEntry.class); + if (val == null) { + ConfigKeeper.IntegerEntry entry = keeper.registerEntry(key, new ConfigKeeper.IntegerEntry(defaultValue)); + return entry.getValue(); + } + return val != null ? val : defaultValue; + } + + protected int getInt(ConfigKey key) { + Integer val = keeper.getValue(key, ConfigKeeper.IntegerEntry.class); + return val != null ? val : 0; + } + + protected boolean setInt(ConfigKey key, int value) { + try { + ConfigKeeper.IntegerEntry entry = keeper.getEntry(key, ConfigKeeper.IntegerEntry.class); + if (entry == null) return false; + entry.setValue(value); + return true; + } catch (NullPointerException ex) { + BCLib.LOGGER.catching(ex); + } + return false; + } + + protected , RE extends ConfigKeeper.RangeEntry> boolean setRanged( + ConfigKey key, + T value, + Class type + ) { + try { + ConfigKeeper.RangeEntry entry = keeper.getEntry(key, type); + if (entry == null) return false; + entry.setValue(value); + return true; + } catch (NullPointerException | ClassCastException ex) { + BCLib.LOGGER.catching(ex); + } + return false; + } + + protected float getFloat(ConfigKey key, float defaultValue) { + Float val = keeper.getValue(key, ConfigKeeper.FloatEntry.class); + if (val == null) { + ConfigKeeper.FloatEntry entry = keeper.registerEntry(key, new ConfigKeeper.FloatEntry(defaultValue)); + return entry.getValue(); + } + return val; + } + + protected float getFloat(ConfigKey key) { + Float val = keeper.getValue(key, ConfigKeeper.FloatEntry.class); + return val != null ? val : 0.0F; + } + + protected boolean setFloat(ConfigKey key, float value) { + try { + ConfigKeeper.FloatEntry entry = keeper.getEntry(key, ConfigKeeper.FloatEntry.class); + if (entry == null) return false; + entry.setValue(value); + return true; + } catch (NullPointerException ex) { + BCLib.LOGGER.catching(ex); + } + return false; + } + + protected boolean getBoolean(ConfigKey key, boolean defaultValue) { + Boolean val = keeper.getValue(key, ConfigKeeper.BooleanEntry.class); + if (val == null) { + ConfigKeeper.BooleanEntry entry = keeper.registerEntry(key, new ConfigKeeper.BooleanEntry(defaultValue)); + return entry.getValue(); + } + return val; + } + + protected boolean getBoolean(ConfigKey key) { + Boolean val = keeper.getValue(key, ConfigKeeper.BooleanEntry.class); + return val != null ? val : false; + } + + protected boolean setBoolean(ConfigKey key, boolean value) { + try { + ConfigKeeper.BooleanEntry entry = keeper.getEntry(key, ConfigKeeper.BooleanEntry.class); + + if (entry == null) return false; + entry.setValue(value); + return true; + } catch (NullPointerException ex) { + BCLib.LOGGER.catching(ex); + } + return false; + } + + protected List getStringArray(ConfigKey key, List defaultValue) { + List str = keeper.getValue(key, ConfigKeeper.StringArrayEntry.class); + if (str == null) { + ConfigKeeper.StringArrayEntry entry = keeper.registerEntry( + key, + new ConfigKeeper.StringArrayEntry(defaultValue) + ); + return entry.getValue(); + } + return str != null ? str : defaultValue; + } + + protected List getStringArray(ConfigKey key) { + List str = keeper.getValue(key, ConfigKeeper.StringArrayEntry.class); + return str != null ? str : new ArrayList<>(0); + } + + protected boolean setStringArray(ConfigKey key, List value) { + try { + ConfigKeeper.StringArrayEntry entry = keeper.getEntry(key, ConfigKeeper.StringArrayEntry.class); + if (entry == null) return false; + entry.setValue(value); + return true; + } catch (NullPointerException ex) { + BCLib.LOGGER.catching(ex); + } + return false; + } +} diff --git a/src/main/java/org/betterx/bclib/config/ConfigKeeper.java b/src/main/java/org/betterx/bclib/config/ConfigKeeper.java new file mode 100644 index 00000000..6571b1f7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ConfigKeeper.java @@ -0,0 +1,464 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.FileContentWrapper; +import org.betterx.bclib.util.JsonFactory; +import org.betterx.bclib.util.Pair; + +import net.minecraft.util.GsonHelper; + +import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.OutputStream; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; +import org.jetbrains.annotations.Nullable; + +public final class ConfigKeeper { + private final Map> configEntries = Maps.newHashMap(); + private final ConfigWriter writer; + + private JsonObject configObject; + private boolean changed = false; + + public ConfigKeeper(String modID, String group) { + this.writer = new ConfigWriter(modID, group); + this.configObject = writer.load(); + } + + public File getConfigFile() { + return this.writer.getConfigFile(); + } + + boolean compareAndUpdateForSync(FileContentWrapper content) { + ByteArrayInputStream inputStream = content.getInputStream(); + final JsonObject other = JsonFactory.getJsonObject(inputStream); + + boolean changed = this.compareAndUpdateForSync(other); + if (changed) { + OutputStream outStream = content.getEmptyOutputStream(); + JsonFactory.storeJson(outStream, this.configObject); + content.syncWithOutputStream(); + } + return changed; + } + + boolean compareAndUpdateForSync(JsonObject other) { + return compareAndUpdateForSync(this.configObject, other); + } + + private static Pair> find(JsonObject json, Pair key) { + String kk = key.first + key.second; + for (var entry : json.entrySet()) { + final Pair otherKey = ConfigKey.realKey(entry.getKey()); + if (kk.equals(entry)) return new Pair<>(entry.getValue(), otherKey); + } + +// for (var entry : json.entrySet()) { +// final Pair otherKey = ConfigKey.realKey(entry.getKey()); +// if (otherKey.first.equals(key.first)) return new Pair<>(entry.getValue(), otherKey); +// } + + return null; + } + + /** + * Called for content based auto-sync. + * + * @param me - When called in AutoSync this represents the content of the client. + * @param other - When called in AutoSync, this represents the content of the server + * @return {@code true} if content was changed + */ + static boolean compareAndUpdateForSync(JsonObject me, JsonObject other) { + boolean changed = false; + for (var otherEntry : other.entrySet()) { + final Pair otherKey = ConfigKey.realKey(otherEntry.getKey()); + final JsonElement otherValue = otherEntry.getValue(); + + Pair> temp = find(me, otherKey); + //we already have an entry + if (temp != null) { + final Pair myKey = temp.second; + final JsonElement myValue = temp.first; + + if ((otherValue.isJsonNull() && !myValue.isJsonNull()) || (otherValue.isJsonPrimitive() && !myValue.isJsonPrimitive()) || (otherValue.isJsonObject() && !myValue.isJsonObject()) || (otherValue.isJsonArray() && !myValue.isJsonArray())) { + //types are different => replace with "server"-version in other + changed = true; + me.add(myKey.first + myKey.second, otherValue); + } else if (otherValue.isJsonPrimitive() || otherValue.isJsonArray() || otherValue.isJsonNull()) { + if (!otherValue.equals(myValue)) { + changed = true; + me.add(myKey.first + myKey.second, otherValue); + } + } else if (otherValue.isJsonObject()) { + changed |= compareAndUpdateForSync(myValue.getAsJsonObject(), otherValue.getAsJsonObject()); + } + } else { //no entry, just copy the value from other + if (!otherValue.isJsonNull()) { + changed = true; + temp = find(me, otherKey); + me.add(otherKey.first + otherKey.second, otherValue); + } + } + } + + return changed; + } + + + public void save() { + if (!changed) return; + this.writer.save(); + this.changed = false; + } + + void reload() { + this.configObject = this.writer.reload(); + this.configEntries.clear(); + this.changed = false; + } + + private > void initializeEntry(ConfigKey key, E entry) { + if (configObject == null) { + return; + } + String[] path = key.getPath(); + JsonObject obj = configObject; + + if (!key.isRoot()) { + for (String group : path) { + JsonElement element = obj.get(group); + if (element == null || !element.isJsonObject()) { + element = new JsonObject(); + obj.add(group, element); + } + obj = element.getAsJsonObject(); + } + } + + String paramKey = key.getEntry(); + if (entry.hasDefaultInName()) { + paramKey += " [default: " + entry.getDefault() + "]"; + } + + this.changed |= entry.setLocation(obj, paramKey); + } + + private > void storeValue(E entry, T value) { + if (configObject == null) { + return; + } + T val = entry.getValue(); + if (value.equals(val)) return; + entry.toJson(value); + this.changed = true; + } + + private > T getValue(E entry) { + if (!entry.hasLocation()) { + return entry.getDefault(); + } + return entry.fromJson(); + } + + @Nullable + public > E getEntry(ConfigKey key, Class type) { + Entry entry = this.configEntries.get(key); + if (type.isInstance(entry)) { + return type.cast(entry); + } + return null; + } + + @Nullable + public > T getValue(ConfigKey key, Class type) { + Entry entry = this.getEntry(key, type); + if (entry == null) { + return null; + } + return entry.getValue(); + } + + public > E registerEntry(ConfigKey key, E entry) { + entry.setWriter(value -> this.storeValue(entry, value)); + entry.setReader(() -> { + return this.getValue(entry); + }); + this.initializeEntry(key, entry); + this.configEntries.put(key, entry); + return entry; + } + + public static class BooleanEntry extends Entry { + + public BooleanEntry(Boolean defaultValue) { + super(defaultValue); + } + + @Override + public Boolean fromJson() { + return GsonHelper.getAsBoolean(location, key, defaultValue); + } + + @Override + public void toJson(Boolean value) { + this.location.addProperty(key, value); + } + } + + public static class FloatEntry extends Entry { + + public FloatEntry(Float defaultValue) { + super(defaultValue); + } + + @Override + public Float fromJson() { + return GsonHelper.getAsFloat(location, key, defaultValue); + } + + @Override + public void toJson(Float value) { + this.location.addProperty(key, value); + } + } + + public static class FloatRange extends RangeEntry { + + public FloatRange(Float defaultValue, float minVal, float maxVal) { + super(defaultValue, minVal, maxVal); + } + + @Override + public Float fromJson() { + return GsonHelper.getAsFloat(location, key, defaultValue); + } + + @Override + public void toJson(Float value) { + this.location.addProperty(key, value); + } + } + + public static class IntegerEntry extends Entry { + + public IntegerEntry(Integer defaultValue) { + super(defaultValue); + } + + @Override + public Integer getDefault() { + return this.defaultValue; + } + + @Override + public Integer fromJson() { + return GsonHelper.getAsInt(location, key, defaultValue); + } + + @Override + public void toJson(Integer value) { + this.location.addProperty(key, value); + } + } + + public static class IntegerRange extends RangeEntry { + + public IntegerRange(Integer defaultValue, int minVal, int maxVal) { + super(defaultValue, minVal, maxVal); + } + + @Override + public Integer fromJson() { + return GsonHelper.getAsInt(location, key, defaultValue); + } + + @Override + public void toJson(Integer value) { + this.location.addProperty(key, value); + } + } + + public static class StringEntry extends Entry { + + public StringEntry(String defaultValue) { + super(defaultValue); + } + + @Override + public String fromJson() { + return GsonHelper.getAsString(location, key, defaultValue); + } + + @Override + public void toJson(String value) { + this.location.addProperty(key, value); + } + } + + public static abstract class ArrayEntry extends Entry> { + public ArrayEntry(List defaultValue) { + super(defaultValue); + } + + protected abstract T getValue(JsonElement element); + protected abstract void add(JsonArray array, T element); + + private JsonArray toArray(List input) { + final JsonArray array = new JsonArray(); + input.forEach(s -> add(array, s)); + return array; + } + + @Override + public List fromJson() { + final JsonArray resArray = GsonHelper.getAsJsonArray(location, key, toArray(defaultValue)); + final List res = new ArrayList<>(resArray.size()); + resArray.forEach(e -> res.add(getValue(e))); + + return res; + } + + @Override + public void toJson(List value) { + this.location.add(key, toArray(value)); + } + } + + public static class StringArrayEntry extends ArrayEntry { + public StringArrayEntry(List defaultValue) { + super(defaultValue); + } + + @Override + protected String getValue(JsonElement el) { + return el.getAsString(); + } + + protected void add(JsonArray array, String el) { + array.add(el); + } + + @Override + protected boolean hasDefaultInName() { + return false; + } + } + + public static class EnumEntry> extends Entry { + private final Type type; + + public EnumEntry(T defaultValue) { + super(defaultValue); + TypeToken token = new TypeToken() { + private static final long serialVersionUID = 1L; + }; + this.type = token.getType(); + } + + @Override + public T getDefault() { + return this.defaultValue; + } + + @Override + public T fromJson() { + return JsonFactory.GSON.fromJson(location.get(key), type); + } + + @Override + public void toJson(T value) { + location.addProperty(key, JsonFactory.GSON.toJson(value, type)); + } + } + + public static abstract class RangeEntry> extends Entry { + private final T min, max; + + public RangeEntry(T defaultValue, T minVal, T maxVal) { + super(defaultValue); + this.min = minVal; + this.max = maxVal; + } + + @Override + public void setValue(T value) { + super.setValue(value.compareTo(min) < 0 ? min : value.compareTo(max) > 0 ? max : value); + } + + public T minValue() { + return this.min; + } + + public T maxValue() { + return this.max; + } + } + + public static abstract class Entry { + protected final T defaultValue; + protected Consumer writer; + protected Supplier reader; + protected JsonObject location; + protected String key; + + public abstract T fromJson(); + + public abstract void toJson(T value); + + public Entry(T defaultValue) { + this.defaultValue = defaultValue; + } + + protected void setWriter(Consumer writer) { + this.writer = writer; + } + + protected void setReader(Supplier reader) { + this.reader = reader; + } + + protected boolean setLocation(JsonObject location, String key) { + this.location = location; + this.key = key; + if (!location.has(key)) { + this.toJson(defaultValue); + return true; + } + return false; + } + + protected boolean hasLocation() { + return this.location != null && this.key != null; + } + + public T getValue() { + return this.reader.get(); + } + + public void setValue(T value) { + this.writer.accept(value); + } + + public T getDefault() { + return this.defaultValue; + } + + public void setDefault() { + this.setValue(defaultValue); + } + + protected boolean hasDefaultInName() { + return true; + } + } +} diff --git a/src/main/java/org/betterx/bclib/config/ConfigKey.java b/src/main/java/org/betterx/bclib/config/ConfigKey.java new file mode 100644 index 00000000..53c16304 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ConfigKey.java @@ -0,0 +1,97 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.util.Pair; + +import net.minecraft.resources.ResourceLocation; + +import java.util.Arrays; +import org.jetbrains.annotations.NotNull; + +public class ConfigKey { + private final String[] path; + private final String entry; + private final boolean root; + + public ConfigKey(String entry, String... path) { + this.validate(entry); + this.path = path; + this.entry = entry; + this.root = path.length == 0 || (path.length == 1 && path[0].isEmpty()); + } + + public ConfigKey(String entry, ResourceLocation path) { + this(entry, path.getNamespace(), path.getPath()); + } + + public String[] getPath() { + return path; + } + + public String getEntry() { + return entry; + } + + public boolean isRoot() { + return root; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(path); + result = prime * result + entry.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ConfigKey)) { + return false; + } + ConfigKey other = (ConfigKey) obj; + if (other.path.length != path.length) { + return false; + } + for (int i = 0; i < path.length; i++) { + if (!path[i].equals(other.path[i])) { + return false; + } + } + return entry.equals(other.entry); + } + + @Override + public String toString() { + if (root) { + return String.format("[root]:%s", entry); + } + String p = path[0]; + for (int i = 1; i < path.length; i++) { + p += "." + path[i]; + } + return String.format("%s:%s", p, entry); + } + + private void validate(String entry) { + if (entry == null) { + throw new NullPointerException("Config key must be not null!"); + } + if (entry.isEmpty()) { + throw new IndexOutOfBoundsException("Config key must be not empty!"); + } + } + + public static Pair realKey(@NotNull String key) { + String[] parts = key.split("\\[default:", 2); + if (parts.length == 1) { + return new Pair(parts[0].trim(), ""); + } else if (parts.length == 2) { + return new Pair(parts[0].trim(), " " + ("[default:" + parts[1]).trim()); + } + return new Pair(key, ""); + } +} diff --git a/src/main/java/org/betterx/bclib/config/ConfigUI.java b/src/main/java/org/betterx/bclib/config/ConfigUI.java new file mode 100644 index 00000000..d2fc94ff --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ConfigUI.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface ConfigUI { + /** + * When {@code true}, this option will not generate UI-Elements. + */ + boolean hide() default false; + + /** + * When a Widget is generated for this option, it will be indented by this Value + */ + int leftPadding() default 0; + + /** + * When a Widget is generated for this option, it will be indented by this Value + */ + int topPadding() default 0; + + /** + * When a Slider is generated, this will be the minimum Value + */ + int minValue() default 0; + + /** + * When a Slider is generated, this will be the maximu Value + */ + int maxValue() default 0; +} diff --git a/src/main/java/org/betterx/bclib/config/ConfigWriter.java b/src/main/java/org/betterx/bclib/config/ConfigWriter.java new file mode 100644 index 00000000..951f0d3c --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ConfigWriter.java @@ -0,0 +1,73 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.util.JsonFactory; + +import net.fabricmc.loader.api.FabricLoader; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.io.File; +import java.nio.file.Path; + +public class ConfigWriter { + private final static Path GAME_CONFIG_DIR = FabricLoader.getInstance().getConfigDir(); + + private final File configFile; + private JsonObject configObject; + + public ConfigWriter(String modID, String configFile) { + this.configFile = new File(GAME_CONFIG_DIR.resolve(modID).toFile(), configFile + ".json"); + File parent = this.configFile.getParentFile(); + if (!parent.exists()) { + parent.mkdirs(); + } + this.load(); + } + + File getConfigFile() { + return this.configFile; + } + + public JsonObject getConfig() { + return configObject; + } + + public void save() { + if (configObject == null) { + return; + } + save(configFile, configObject); + } + + JsonObject reload() { + configObject = load(configFile); + return configObject; + } + + public JsonObject load() { + if (configObject == null) { + configObject = load(configFile); + } + return configObject; + } + + public void save(JsonElement config) { + this.configObject = config.getAsJsonObject(); + save(configFile, config); + } + + public static JsonObject load(File configFile) { + return JsonFactory.getJsonObject(configFile); + } + + public static void save(File configFile, JsonElement config) { + JsonFactory.storeJson(configFile, config); + } + + public static String scrubFileName(String input) { + input = input.replaceAll("[/\\ ]+", "_"); + input = input.replaceAll("[,:&\"\\|\\<\\>\\?\\*]", "_"); + return input; + } +} diff --git a/src/main/java/org/betterx/bclib/config/Configs.java b/src/main/java/org/betterx/bclib/config/Configs.java new file mode 100644 index 00000000..913a7437 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/Configs.java @@ -0,0 +1,26 @@ +package org.betterx.bclib.config; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public class Configs { + // Client and Server-Config must be the first entries. They are not part of the Auto-Sync process + // But will be needed by other Auto-Sync Config-Files + @Environment(EnvType.CLIENT) + public static final ClientConfig CLIENT_CONFIG = new ClientConfig(); + public static final ServerConfig SERVER_CONFIG = new ServerConfig(); + + public static final GeneratorConfig GENERATOR_CONFIG = new GeneratorConfig(); + public static final MainConfig MAIN_CONFIG = new MainConfig(); + public static final CachedConfig CACHED_CONFIG = new CachedConfig(); + public static final BiomesConfig BIOMES_CONFIG = new BiomesConfig(); + + public static final String MAIN_PATCH_CATEGORY = "patches"; + public static final String MAIN_INFO_CATEGORY = "infos"; + + public static void save() { + MAIN_CONFIG.saveChanges(); + GENERATOR_CONFIG.saveChanges(); + BIOMES_CONFIG.saveChanges(); + } +} diff --git a/src/main/java/org/betterx/bclib/config/EntryConfig.java b/src/main/java/org/betterx/bclib/config/EntryConfig.java new file mode 100644 index 00000000..44276cfc --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/EntryConfig.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.config; + +public class EntryConfig extends IdConfig { + public EntryConfig(String modID, String group) { + super(modID, group, (id, entry) -> { + return new ConfigKey(entry, id); + }); + } +} diff --git a/src/main/java/org/betterx/bclib/config/GeneratorConfig.java b/src/main/java/org/betterx/bclib/config/GeneratorConfig.java new file mode 100644 index 00000000..250542b9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/GeneratorConfig.java @@ -0,0 +1,10 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.BCLib; + +public class GeneratorConfig extends NamedPathConfig { + public GeneratorConfig() { + super(BCLib.MOD_ID, "generator", true); + } + +} diff --git a/src/main/java/org/betterx/bclib/config/IdConfig.java b/src/main/java/org/betterx/bclib/config/IdConfig.java new file mode 100644 index 00000000..c51afd5a --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/IdConfig.java @@ -0,0 +1,89 @@ +package org.betterx.bclib.config; + +import net.minecraft.resources.ResourceLocation; + +import java.util.function.BiFunction; +import org.jetbrains.annotations.Nullable; + +public class IdConfig extends Config { + protected final BiFunction keyFactory; + + public IdConfig(String modID, String group, BiFunction keyFactory) { + super(modID, group); + this.keyFactory = keyFactory; + } + + @Override + protected void registerEntries() { + } + + protected ConfigKey createKey(ResourceLocation id, String key) { + return this.keyFactory.apply(id, key); + } + + @Nullable + public > E getEntry(ResourceLocation id, String key, Class type) { + return this.getEntry(createKey(id, key), type); + } + + @Nullable + public > T getDefault(ResourceLocation id, String key, Class type) { + return this.getDefault(createKey(id, key), type); + } + + public String getString(ResourceLocation id, String key, String defaultValue) { + return this.getString(createKey(id, key), defaultValue); + } + + public String getString(ResourceLocation id, String key) { + return this.getString(createKey(id, key)); + } + + public boolean setString(ResourceLocation id, String key, String value) { + return this.setString(createKey(id, key), value); + } + + public int getInt(ResourceLocation id, String key, int defaultValue) { + return this.getInt(createKey(id, key), defaultValue); + } + + public int getInt(ResourceLocation id, String key) { + return this.getInt(createKey(id, key)); + } + + public boolean setInt(ResourceLocation id, String key, int value) { + return this.setInt(createKey(id, key), value); + } + + public boolean setRangedInt(ResourceLocation id, String key, int value) { + return this.setRanged(createKey(id, key), value, ConfigKeeper.IntegerRange.class); + } + + public boolean setRangedFloat(ResourceLocation id, String key, float value) { + return this.setRanged(createKey(id, key), value, ConfigKeeper.FloatRange.class); + } + + public float getFloat(ResourceLocation id, String key, float defaultValue) { + return this.getFloat(createKey(id, key), defaultValue); + } + + public float getFloat(ResourceLocation id, String key) { + return this.getFloat(createKey(id, key)); + } + + public boolean setFloat(ResourceLocation id, String key, float value) { + return this.setFloat(createKey(id, key), value); + } + + public boolean getBoolean(ResourceLocation id, String key, boolean defaultValue) { + return this.getBoolean(createKey(id, key), defaultValue); + } + + public boolean getBoolean(ResourceLocation id, String key) { + return this.getBoolean(createKey(id, key)); + } + + public boolean setBoolean(ResourceLocation id, String key, boolean value) { + return this.setBoolean(createKey(id, key), value); + } +} diff --git a/src/main/java/org/betterx/bclib/config/MainConfig.java b/src/main/java/org/betterx/bclib/config/MainConfig.java new file mode 100644 index 00000000..7748a27b --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/MainConfig.java @@ -0,0 +1,46 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.BCLib; + +public class MainConfig extends NamedPathConfig { + + + public static final ConfigToken APPLY_PATCHES = ConfigToken.Boolean( + true, + "applyPatches", + Configs.MAIN_PATCH_CATEGORY + ); + + @ConfigUI(leftPadding = 8) + public static final ConfigToken REPAIR_BIOMES = DependendConfigToken.Boolean( + false, + "repairBiomesOnLoad", + Configs.MAIN_PATCH_CATEGORY, + (config) -> config.get( + APPLY_PATCHES) + ); + + @ConfigUI(topPadding = 8) + public static final ConfigToken VERBOSE_LOGGING = ConfigToken.Boolean( + true, + "verbose", + Configs.MAIN_INFO_CATEGORY + ); + + + public MainConfig() { + super(BCLib.MOD_ID, "main", true, true); + } + + public boolean applyPatches() { + return get(APPLY_PATCHES); + } + + public boolean repairBiomes() { + return get(REPAIR_BIOMES); + } + + public boolean verboseLogging() { + return get(VERBOSE_LOGGING); + } +} diff --git a/src/main/java/org/betterx/bclib/config/NamedPathConfig.java b/src/main/java/org/betterx/bclib/config/NamedPathConfig.java new file mode 100644 index 00000000..ee04e162 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/NamedPathConfig.java @@ -0,0 +1,300 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.BCLib; + +import net.minecraft.resources.ResourceLocation; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Predicate; + +public class NamedPathConfig extends PathConfig { + public static class ConfigTokenDescription { + public final ConfigToken token; + public final String internalName; + public final Boolean hidden; + public final int leftPadding; + public final int topPadding; + public final int minRange; + public final int maxRange; + + @SuppressWarnings("unchecked") + ConfigTokenDescription(Field fl) throws IllegalAccessException { + token = (ConfigToken) fl.get(null); + internalName = fl.getName(); + + ConfigUI ui = fl.getAnnotation(ConfigUI.class); + if (ui != null) { + this.hidden = ui.hide(); + leftPadding = ui.leftPadding(); + topPadding = ui.topPadding(); + minRange = ui.minValue(); + maxRange = ui.maxValue(); + } else { + this.hidden = false; + this.leftPadding = 0; + topPadding = 0; + minRange = 0; + maxRange = 0; + } + + } + + public String getPath() { + StringBuilder path = new StringBuilder(); + for (String p : token.getPath()) { + path.append(".") + .append(p); + + } + path.append(".").append(token.getEntry()); + return path.toString(); + } + } + + public static class DependendConfigToken extends ConfigToken { + protected final Predicate dependenciesTrue; + + protected DependendConfigToken( + Class type, + T defaultValue, + String entry, + ResourceLocation path, + Predicate dependenciesTrue + ) { + this(type, defaultValue, entry, new String[]{path.getNamespace(), path.getPath()}, dependenciesTrue); + } + + protected DependendConfigToken( + Class type, + T defaultValue, + String entry, + String path, + Predicate dependenciesTrue + ) { + super(type, defaultValue, entry, path); + this.dependenciesTrue = dependenciesTrue; + } + + protected DependendConfigToken( + Class type, + T defaultValue, + String entry, + String[] path, + Predicate dependenciesTrue + ) { + super(type, defaultValue, entry, path); + this.dependenciesTrue = dependenciesTrue; + } + + public boolean dependenciesTrue(NamedPathConfig config) { + return dependenciesTrue.test(config); + } + + public static DependendConfigToken Boolean( + boolean defaultValue, + String entry, + String path, + Predicate dependenciesTrue + ) { + return new DependendConfigToken( + ConfigKeeper.BooleanEntry.class, + defaultValue, + entry, + path, + dependenciesTrue + ); + } + + public static DependendConfigToken Float( + float defaultValue, + String entry, + String path, + Predicate dependenciesTrue + ) { + return new DependendConfigToken( + ConfigKeeper.FloatEntry.class, + defaultValue, + entry, + path, + dependenciesTrue + ); + } + } + + public static class ConfigToken extends ConfigKey { + public final T defaultValue; + public final Class type; + + protected ConfigToken(Class type, T defaultValue, String entry, ResourceLocation path) { + this(type, defaultValue, entry, path.getNamespace(), path.getPath()); + } + + @SuppressWarnings("unchecked") + protected ConfigToken(Class type, T defaultValue, String entry, String... path) { + super(entry, path); + this.defaultValue = defaultValue; + + this.type = type; + } + + public boolean dependenciesTrue(NamedPathConfig config) { + return true; + } + + public static ConfigToken Boolean(boolean defaultValue, String entry, String path) { + return new ConfigToken(ConfigKeeper.BooleanEntry.class, defaultValue, entry, path); + } + + public static ConfigToken Int(int defaultValue, String entry, String path) { + return new ConfigToken(ConfigKeeper.IntegerEntry.class, defaultValue, entry, path); + } + + public static ConfigToken Float(float defaultValue, String entry, String path) { + return new ConfigToken(ConfigKeeper.FloatEntry.class, defaultValue, entry, path); + } + + public static ConfigToken String(String defaultValue, String entry, String path) { + return new ConfigToken(ConfigKeeper.StringEntry.class, defaultValue, entry, path); + } + + public static ConfigToken> StringArray(List defaultValue, String entry, String path) { + return new ConfigToken>(ConfigKeeper.StringArrayEntry.class, defaultValue, entry, path); + } + } + + public NamedPathConfig(String modID, String group, boolean autoSync, boolean diffContent) { + super(modID, group, autoSync, diffContent); + onInit(); + } + + public NamedPathConfig(String modID, String group, boolean autoSync) { + super(modID, group, autoSync); + onInit(); + } + + public NamedPathConfig(String modID, String group) { + super(modID, group); + onInit(); + } + + public List> getAllOptions() { + List> res = new LinkedList<>(); + for (Field fl : this.getClass().getDeclaredFields()) { + int modifiers = fl.getModifiers(); + if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && ConfigToken.class.isAssignableFrom(fl.getType())) { + try { + res.add(new ConfigTokenDescription<>(fl)); + } catch (IllegalAccessException e) { + BCLib.LOGGER.error("Could not access " + fl); + } + } + } + return res; + } + + protected void onInit() { + getAllOptions().forEach(e -> get(e.token)); + this.saveChanges(); + } + + /** + * The value without any check of {@link DependendConfigToken} + *

+ * In most cases you probably want to use {@link #get(ConfigToken)}, we use this Method if we + * present the actual value of the Settings from the Config File without any additional processing. + * + * @param what The Option you want to get + * @param The Type of the Option + * @return The Value of the Option (without checking the {@link DependendConfigToken}): + */ + public T getRaw(ConfigToken what) { + return _get(what, true); + } + + /** + * The value of an Option + * + * @param what he Option you want to get + * @param The Type of the Option + * @return The Value of the Option. If this option is a {@link DependendConfigToken}, the returned value + * may not be the value from the config File. For Example, on a {@link Boolean}-Type the result is always false + * if {@link DependendConfigToken#dependenciesTrue} returns {@code false}. + */ + public T get(ConfigToken what) { + return _get(what, false); + } + + @SuppressWarnings("unchecked") + private T _get(ConfigToken what, boolean raw) { + if (ConfigKeeper.BooleanEntry.class.isAssignableFrom(what.type)) { + return (T) _getBoolean((ConfigToken) what, raw); + } + if (ConfigKeeper.IntegerEntry.class.isAssignableFrom(what.type)) { + return (T) _getInt((ConfigToken) what); + } + if (ConfigKeeper.FloatEntry.class.isAssignableFrom(what.type)) { + return (T) _getFloat((ConfigToken) what); + } + if (ConfigKeeper.StringEntry.class.isAssignableFrom(what.type)) { + return (T) _getString((ConfigToken) what); + } + if (ConfigKeeper.StringArrayEntry.class.isAssignableFrom(what.type)) { + return (T) _getStringArray((ConfigToken>) what); + } + return this._get(what); + } + + private T _get(ConfigToken what) { + BCLib.LOGGER.error(what + " has unsupported Type."); + return what.defaultValue; + } + + public void set(ConfigToken what, boolean value) { + this.setBoolean(what, value); + } + + private Boolean _getBoolean(ConfigToken what, boolean raw) { + if (!raw && !what.dependenciesTrue(this)) { + return false; + } + + return this.getBoolean(what, what.defaultValue); + } + + public void set(ConfigToken what, int value) { + this.setInt(what, value); + } + + private Integer _getInt(ConfigToken what) { + return this.getInt(what, what.defaultValue); + } + + public void set(ConfigToken what, float value) { + this.setFloat(what, value); + } + + private Float _getFloat(ConfigToken what) { + return this.getFloat(what, what.defaultValue); + } + + public void set(ConfigToken what, String value) { + this.setString(what, value); + } + + private String _getString(ConfigToken what) { + return this.getString(what, what.defaultValue); + } + + public void set(ConfigToken> what, List value) { + this.setStringArray(what, value); + } + + private List _getStringArray(ConfigToken> what) { + return this.getStringArray(what, what.defaultValue); + } + + +} diff --git a/src/main/java/org/betterx/bclib/config/PathConfig.java b/src/main/java/org/betterx/bclib/config/PathConfig.java new file mode 100644 index 00000000..2d1240f8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/PathConfig.java @@ -0,0 +1,166 @@ +package org.betterx.bclib.config; + +import java.util.List; +import org.jetbrains.annotations.Nullable; + +public class PathConfig extends Config { + public PathConfig(String modID, String group, boolean autoSync, boolean diffContent) { + super(modID, group, autoSync, diffContent); + } + + public PathConfig(String modID, String group, boolean autoSync) { + super(modID, group, autoSync); + } + + public PathConfig(String modID, String group) { + super(modID, group); + } + + @Override + protected void registerEntries() { + } + + protected static ConfigKey createKey(String category, String key) { + return new ConfigKey(key, category.split("\\.")); + } + + protected static ConfigKey createKey(String key) { + return createKey("", key); + } + + @Nullable + public > E getEntry(String category, String key, Class type) { + return this.getEntry(createKey(category, key), type); + } + + @Nullable + public > T getDefault(String category, String key, Class type) { + return this.getDefault(createKey(category, key), type); + } + + public String getString(String category, String key, String defaultValue) { + return this.getString(createKey(category, key), defaultValue); + } + + public String getString(String category, String key) { + return this.getString(createKey(category, key)); + } + + public boolean setString(String category, String key, String value) { + return this.setString(createKey(category, key), value); + } + + public int getInt(String category, String key, int defaultValue) { + return this.getInt(createKey(category, key), defaultValue); + } + + public int getInt(String category, String key) { + return this.getInt(createKey(category, key)); + } + + public boolean setInt(String category, String key, int value) { + return this.setInt(createKey(category, key), value); + } + + public boolean setRangedInt(String category, String key, int value) { + return this.setRanged(createKey(category, key), value, ConfigKeeper.IntegerRange.class); + } + + public boolean setRangedFloat(String category, String key, float value) { + return this.setRanged(createKey(category, key), value, ConfigKeeper.FloatRange.class); + } + + public float getFloat(String category, String key, float defaultValue) { + return this.getFloat(createKey(category, key), defaultValue); + } + + public float getFloat(String category, String key) { + return this.getFloat(createKey(category, key)); + } + + public boolean setFloat(String category, String key, float value) { + return this.setFloat(createKey(category, key), value); + } + + public boolean getBoolean(String category, String key, boolean defaultValue) { + return this.getBoolean(createKey(category, key), defaultValue); + } + + public boolean getBoolean(String category, String key) { + return this.getBoolean(createKey(category, key)); + } + + public boolean setBoolean(String category, String key, boolean value) { + return this.setBoolean(createKey(category, key), value); + } + + public List getStringArray(String category, String key, List defaultValue) { + return this.getStringArray(createKey(category, key), defaultValue); + } + + public List getStringArray(String category, String key) { + return this.getStringArray(createKey(category, key)); + } + + public boolean setStringArray(String category, String key, List value) { + return this.setStringArray(createKey(category, key), value); + } + + // From Root + + public String getStringRoot(String key, String defaultValue) { + return this.getString(createKey(key), defaultValue); + } + + public String getStringRoot(String key) { + return this.getString(createKey(key)); + } + + public boolean setStringRoot(String key, String value) { + return this.setString(createKey(key), value); + } + + public int getIntRoot(String key, int defaultValue) { + return this.getInt(createKey(key), defaultValue); + } + + public int getIntRoot(String key) { + return this.getInt(createKey(key)); + } + + public boolean setIntRoot(String key, int value) { + return this.setInt(createKey(key), value); + } + + public boolean setRangedIntRoot(String key, int value) { + return this.setRanged(createKey(key), value, ConfigKeeper.IntegerRange.class); + } + + public boolean setRangedFloatRoot(String key, float value) { + return this.setRanged(createKey(key), value, ConfigKeeper.FloatRange.class); + } + + public float getFloatRoot(String key, float defaultValue) { + return this.getFloat(createKey(key), defaultValue); + } + + public float getFloatRoot(String key) { + return this.getFloat(createKey(key)); + } + + public boolean setFloatRoot(String key, float value) { + return this.setFloat(createKey(key), value); + } + + public boolean getBooleanRoot(String key, boolean defaultValue) { + return this.getBoolean(createKey(key), defaultValue); + } + + public boolean getBooleanRoot(String key) { + return this.getBoolean(createKey(key)); + } + + public boolean setBooleanRoot(String key, boolean value) { + return this.setBoolean(createKey(key), value); + } +} diff --git a/src/main/java/org/betterx/bclib/config/ServerConfig.java b/src/main/java/org/betterx/bclib/config/ServerConfig.java new file mode 100644 index 00000000..86ebdae8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/config/ServerConfig.java @@ -0,0 +1,96 @@ +package org.betterx.bclib.config; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.dataexchange.handler.autosync.AutoSync; + +import java.util.ArrayList; +import java.util.List; + +public class ServerConfig extends NamedPathConfig { + public static final ConfigToken ENABLED = ConfigToken.Boolean(true, "enabled", AutoSync.SYNC_CATEGORY); + public static final DependendConfigToken OFFER_CONFIGS = DependendConfigToken.Boolean( + true, + "offerConfigs", + AutoSync.SYNC_CATEGORY, + (config) -> config.get(ENABLED) + ); + public static final DependendConfigToken OFFER_FILES = DependendConfigToken.Boolean( + true, + "offerFiles", + AutoSync.SYNC_CATEGORY, + (config) -> config.get( + ENABLED) + ); + public static final DependendConfigToken OFFER_MODS = DependendConfigToken.Boolean( + true, + "offerMods", + AutoSync.SYNC_CATEGORY, + (config) -> config.get( + ENABLED) + ); + public static final DependendConfigToken OFFER_ALL_MODS = DependendConfigToken.Boolean( + false, + "offerAllMods", + AutoSync.SYNC_CATEGORY, + (config) -> config.get( + OFFER_MODS) + ); + public static final DependendConfigToken SEND_ALL_MOD_INFO = DependendConfigToken.Boolean( + false, + "sendAllModInfo", + AutoSync.SYNC_CATEGORY, + (config) -> config.get( + ENABLED) + ); + + + public static final ConfigToken> ADDITIONAL_MODS = ConfigToken.StringArray( + new ArrayList<>(0), + "additionalMods", + AutoSync.SYNC_CATEGORY + ); + public static final ConfigToken> EXCLUDED_MODS = ConfigToken.StringArray( + new ArrayList<>(0), + "excludeMods", + AutoSync.SYNC_CATEGORY + ); + public static final ConfigToken FORCE_BETTERX_PRESET = ConfigToken.Boolean( + true, + "forceBetterXPreset", + AutoSync.SYNC_CATEGORY + ); + + + public ServerConfig() { + super(BCLib.MOD_ID, "server", false); + } + + public boolean isAllowingAutoSync() { + return get(ENABLED); + } + + public boolean isOfferingConfigs() { + return get(OFFER_CONFIGS) /*&& isAllowingAutoSync()*/; + } + + public boolean isOfferingFiles() { + return get(OFFER_FILES) /*&& isAllowingAutoSync()*/; + } + + public boolean isOfferingMods() { + return get(OFFER_MODS) /*&& isAllowingAutoSync()*/; + } + + public boolean isOfferingAllMods() { + return get(OFFER_ALL_MODS) /*&& isAllowingAutoSync()*/; + } + + public boolean isOfferingInfosForMods() { + return get(SEND_ALL_MOD_INFO) /*&& isAllowingAutoSync()*/; + } + + public boolean forceBetterXPreset() { + return get(FORCE_BETTERX_PRESET); + } + +} diff --git a/src/main/java/org/betterx/bclib/creativetab/BCLCreativeTab.java b/src/main/java/org/betterx/bclib/creativetab/BCLCreativeTab.java new file mode 100644 index 00000000..a85db19c --- /dev/null +++ b/src/main/java/org/betterx/bclib/creativetab/BCLCreativeTab.java @@ -0,0 +1,94 @@ +package org.betterx.bclib.creativetab; + +import org.betterx.bclib.behaviours.interfaces.BehaviourLeaves; +import org.betterx.bclib.behaviours.interfaces.BehaviourPlantLike; + +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; + +import java.util.LinkedList; +import java.util.List; +import org.jetbrains.annotations.NotNull; + +public class BCLCreativeTab { + public static CreativeTabPredicate NATURE = item -> item instanceof BlockItem bi + && (bi.getBlock() instanceof BehaviourPlantLike + || bi.getBlock() instanceof BehaviourLeaves); + + public static CreativeTabPredicate BLOCKS = item -> item instanceof BlockItem; + + public interface CreativeTabPredicate { + boolean contains(Item item); + } + + public final ResourceLocation id; + public final ItemLike icon; + public final Component title; + public final CreativeTabPredicate predicate; + + public final ResourceKey key; + + public BCLCreativeTab(ResourceLocation id, ItemLike icon, Component title, CreativeTabPredicate predicate) { + this.id = id; + this.icon = icon; + this.title = title; + this.predicate = predicate; + this.key = ResourceKey.create( + Registries.CREATIVE_MODE_TAB, + id + ); + this.items = new LinkedList<>(); + } + + protected final List items; + + void addItem(Item item) { + items.add(item); + } + + public static class Builder { + private final String name; + private final ResourceLocation id; + private ItemLike icon; + private CreativeTabPredicate predicate = item -> true; + private Component title; + private final BCLCreativeTabManager manager; + + public Builder(@NotNull BCLCreativeTabManager manager, @NotNull String name) { + this.name = name; + this.manager = manager; + this.id = new ResourceLocation(manager.modID, name + "_tab"); + this.title = Component.translatable("itemGroup." + manager.modID + "." + name); + } + + public Builder setIcon(ItemLike icon) { + this.icon = icon; + return this; + } + + public Builder setPredicate(CreativeTabPredicate predicate) { + this.predicate = predicate; + return this; + } + + public Builder setTitle(Component title) { + this.title = title; + return this; + } + + public BCLCreativeTabManager build() { + if (icon == null) + throw new IllegalStateException("Icon must be set"); + BCLCreativeTab res = new BCLCreativeTab(id, icon, title, predicate); + manager.tabs.add(res); + + return manager; + } + } +} diff --git a/src/main/java/org/betterx/bclib/creativetab/BCLCreativeTabManager.java b/src/main/java/org/betterx/bclib/creativetab/BCLCreativeTabManager.java new file mode 100644 index 00000000..c5448c37 --- /dev/null +++ b/src/main/java/org/betterx/bclib/creativetab/BCLCreativeTabManager.java @@ -0,0 +1,79 @@ +package org.betterx.bclib.creativetab; + +import org.betterx.bclib.registry.BaseRegistry; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ItemLike; + +import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; + +import java.util.LinkedList; +import java.util.List; + +public class BCLCreativeTabManager { + public final String modID; + protected final List tabs = new LinkedList<>(); + + public static BCLCreativeTabManager create(String modID) { + return new BCLCreativeTabManager(modID); + } + + protected BCLCreativeTabManager(String modID) { + this.modID = modID; + } + + public BCLCreativeTab.Builder createTab(String name) { + return new BCLCreativeTab.Builder(this, name); + } + + public BCLCreativeTab.Builder createBlockTab(ItemLike icon) { + return new BCLCreativeTab.Builder(this, "blocks").setIcon(icon).setPredicate(BCLCreativeTab.BLOCKS); + } + + public BCLCreativeTab.Builder createItemsTab(ItemLike icon) { + return new BCLCreativeTab.Builder(this, "items").setIcon(icon); + } + + public BCLCreativeTabManager processBCLRegistry() { + process(BaseRegistry.getModItems(modID)); + process(BaseRegistry.getModBlockItems(modID)); + return this; + } + + public BCLCreativeTabManager process(List items) { + for (Item item : items) { + for (BCLCreativeTab tab : tabs) { + if (tab.predicate.contains(item)) { + tab.addItem(item); + break; + } + } + } + + return this; + } + + public void register() { + for (BCLCreativeTab tab : tabs) { + var tabItem = FabricItemGroup + .builder() + .icon(() -> new ItemStack(tab.icon)) + .title(tab.title) + .displayItems((featureFlagSet, output) -> { + output.acceptAll(tab.items.stream().map(ItemStack::new).toList()); + //tab.items.clear(); + }).build(); + + Registry.register( + BuiltInRegistries.CREATIVE_MODE_TAB, + tab.key, + tabItem + ); + } + + //this.tabs.clear(); + } +} diff --git a/src/main/java/org/betterx/bclib/entity/BCLEntityWrapper.java b/src/main/java/org/betterx/bclib/entity/BCLEntityWrapper.java new file mode 100644 index 00000000..f8025dbb --- /dev/null +++ b/src/main/java/org/betterx/bclib/entity/BCLEntityWrapper.java @@ -0,0 +1,7 @@ +package org.betterx.bclib.entity; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; + +public record BCLEntityWrapper(EntityType type, boolean canSpawn) { +} diff --git a/src/main/java/org/betterx/bclib/entity/DespawnableAnimal.java b/src/main/java/org/betterx/bclib/entity/DespawnableAnimal.java new file mode 100644 index 00000000..4a39b451 --- /dev/null +++ b/src/main/java/org/betterx/bclib/entity/DespawnableAnimal.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.entity; + +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.level.Level; + +public abstract class DespawnableAnimal extends Animal { + protected DespawnableAnimal(EntityType entityType, Level level) { + super(entityType, level); + } + + @Override + public boolean removeWhenFarAway(double d) { + return !this.hasCustomName(); + } +} diff --git a/src/main/java/org/betterx/bclib/furniture/block/AbstractChair.java b/src/main/java/org/betterx/bclib/furniture/block/AbstractChair.java new file mode 100644 index 00000000..b0e9d255 --- /dev/null +++ b/src/main/java/org/betterx/bclib/furniture/block/AbstractChair.java @@ -0,0 +1,152 @@ +package org.betterx.bclib.furniture.block; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.blocks.BaseBlockNotFull; +import org.betterx.bclib.furniture.entity.EntityChair; +import org.betterx.bclib.registry.BaseBlockEntities; +import org.betterx.bclib.util.BlocksHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; + +import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; + +import java.util.List; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public abstract class AbstractChair extends BaseBlockNotFull { + public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; + private final float height; + + public AbstractChair(Block block, int height) { + super(FabricBlockSettings.copyOf(block).noOcclusion()); + this.height = (height - 3F) / 16F; + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(FACING); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) { + return this.defaultBlockState().setValue(FACING, ctx.getHorizontalDirection().getOpposite()); + } + + @Override + public InteractionResult use( + BlockState state, + Level world, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hit + ) { + if (world.isClientSide) { + return InteractionResult.FAIL; + } else { + if (player.isPassenger() || player.isSpectator()) + return InteractionResult.FAIL; + + + Optional active = getEntity(world, pos); + EntityChair entity; + + if (active.isEmpty()) { + entity = createEntity(state, world, pos); + } else { + entity = active.get(); + if (entity.isVehicle()) + return InteractionResult.FAIL; + } + + if (entity != null) { + float yaw = state.getValue(FACING).getOpposite().toYRot(); + player.startRiding(entity, true); + player.setYBodyRot(yaw); + player.setYHeadRot(yaw); + return InteractionResult.SUCCESS; + } + + return InteractionResult.FAIL; + } + } + + @Nullable + private EntityChair createEntity(BlockState state, Level world, BlockPos pos) { + BCLib.LOGGER.info("Creating Chair at " + pos + ", " + state); + EntityChair entity; + double px = pos.getX() + 0.5; + double py = pos.getY() + height; + double pz = pos.getZ() + 0.5; + float yaw = state.getValue(FACING).getOpposite().toYRot(); + + entity = BaseBlockEntities.CHAIR.create(world); + entity.moveTo(px, py, pz, yaw, 0); + entity.setNoGravity(true); + entity.setSilent(true); + entity.setInvisible(true); + entity.setYHeadRot(yaw); + entity.setYBodyRot(yaw); + if (!world.addFreshEntity(entity)) { + entity = null; + } + return entity; + } + + private Optional getEntity(Level level, BlockPos pos) { + List list = level.getEntitiesOfClass( + EntityChair.class, + new AABB(pos), + entity -> true + ); + if (list.isEmpty()) return Optional.empty(); + return Optional.of(list.get(0)); + } + + @Override + public BlockState rotate(BlockState state, Rotation rotation) { + return BlocksHelper.rotateHorizontal(state, rotation, FACING); + } + + @Override + public BlockState mirror(BlockState state, Mirror mirror) { + return BlocksHelper.mirrorHorizontal(state, mirror, FACING); + } + + @Override + public void onPlace(BlockState blockState, Level level, BlockPos blockPos, BlockState blockState2, boolean bl) { + super.onPlace(blockState, level, blockPos, blockState2, bl); + BCLib.LOGGER.info("Created at " + blockPos + ", " + blockState + ", " + blockState2); + if (blockState.hasProperty(BaseChair.TOP)) { + if (blockState.getValue(BaseChair.TOP)) + return; + } + createEntity(blockState, level, blockPos); + } + + @Override + public void onRemove(BlockState blockState, Level level, BlockPos blockPos, BlockState blockState2, boolean bl) { + super.onRemove(blockState, level, blockPos, blockState2, bl); +// Optional e = getEntity(level, blockPos); +// +// if (e.isPresent()) { +// BCLib.LOGGER.info("Discarding Chair at " + blockPos); +// e.get().discard(); +// } + } +} diff --git a/src/main/java/org/betterx/bclib/furniture/block/BaseBarStool.java b/src/main/java/org/betterx/bclib/furniture/block/BaseBarStool.java new file mode 100644 index 00000000..ef4c90ed --- /dev/null +++ b/src/main/java/org/betterx/bclib/furniture/block/BaseBarStool.java @@ -0,0 +1,48 @@ +package org.betterx.bclib.furniture.block; + +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +public abstract class BaseBarStool extends AbstractChair { + private static final VoxelShape SHAPE = Block.box(4, 0, 4, 12, 16, 12); + + public BaseBarStool(Block block) { + super(block, 15); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPE; + } + + public static class Wood extends BaseBarStool implements BehaviourWood { + public Wood(Block block) { + super(block); + } + } + + public static class Stone extends BaseBarStool implements BehaviourStone { + public Stone(Block block) { + super(block); + } + } + + public static class Metal extends BaseBarStool implements BehaviourMetal { + public Metal(Block block) { + super(block); + } + } + + public static BaseBarStool from(Block source) { + return BehaviourHelper.from(source, Wood::new, Stone::new, Metal::new); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/furniture/block/BaseChair.java b/src/main/java/org/betterx/bclib/furniture/block/BaseChair.java new file mode 100644 index 00000000..46104541 --- /dev/null +++ b/src/main/java/org/betterx/bclib/furniture/block/BaseChair.java @@ -0,0 +1,143 @@ +package org.betterx.bclib.furniture.block; + +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; +import org.betterx.bclib.util.BlocksHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; + +import java.util.Collections; +import java.util.List; + +public abstract class BaseChair extends AbstractChair { + private static final VoxelShape SHAPE_BOTTOM = box(3, 0, 3, 13, 16, 13); + private static final VoxelShape SHAPE_TOP = box(3, 0, 3, 13, 6, 13); + private static final VoxelShape COLLIDER = box(3, 0, 3, 13, 10, 13); + public static final BooleanProperty TOP = BooleanProperty.create("top"); + + public BaseChair(Block block) { + super(block, 10); + this.registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.NORTH).setValue(TOP, false)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { + stateManager.add(FACING, TOP); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return state.getValue(TOP) ? SHAPE_TOP : SHAPE_BOTTOM; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return state.getValue(TOP) ? Shapes.empty() : COLLIDER; + } + + @Override + public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { + if (state.getValue(TOP)) + return true; + BlockState up = world.getBlockState(pos.above()); + return up.isAir() || (up.getBlock() == this && up.getValue(TOP)); + } + + @Override + public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) { + if (!world.isClientSide()) + BlocksHelper.setWithUpdate(world, pos.above(), state.setValue(TOP, true)); + } + + @Override + public BlockState updateShape( + BlockState state, + Direction facing, + BlockState neighborState, + LevelAccessor world, + BlockPos pos, + BlockPos neighborPos + ) { + if (state.getValue(TOP)) { + return world.getBlockState(pos.below()).getBlock() == this ? state : Blocks.AIR.defaultBlockState(); + } else { + return world.getBlockState(pos.above()).getBlock() == this ? state : Blocks.AIR.defaultBlockState(); + } + } + + @Override + public List getDrops(BlockState state, LootParams.Builder builder) { + if (!state.getValue(TOP)) + return Collections.singletonList(new ItemStack(this.asItem())); + else + return Collections.emptyList(); + } + + @Override + public InteractionResult use( + BlockState state, + Level world, + BlockPos pos, + Player player, + InteractionHand hand, + BlockHitResult hit + ) { + if (state.getValue(TOP)) { + pos = pos.below(); + state = world.getBlockState(pos); + } + return super.use(state, world, pos, player, hand, hit); + } + + @Override + public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) { + if (player.isCreative() && state.getValue(TOP) && world.getBlockState(pos.below()).getBlock() == this) { + world.setBlockAndUpdate(pos.below(), Blocks.AIR.defaultBlockState()); + } + super.playerWillDestroy(world, pos, state, player); + } + + public static class Wood extends BaseChair implements BehaviourWood { + public Wood(Block block) { + super(block); + } + } + + public static class Stone extends BaseChair implements BehaviourStone { + public Stone(Block block) { + super(block); + } + } + + public static class Metal extends BaseChair implements BehaviourMetal { + public Metal(Block block) { + super(block); + } + } + + public static BaseChair from(Block source) { + return BehaviourHelper.from(source, Wood::new, Stone::new, Metal::new); + } +} diff --git a/src/main/java/org/betterx/bclib/furniture/block/BaseTaburet.java b/src/main/java/org/betterx/bclib/furniture/block/BaseTaburet.java new file mode 100644 index 00000000..d662cc58 --- /dev/null +++ b/src/main/java/org/betterx/bclib/furniture/block/BaseTaburet.java @@ -0,0 +1,48 @@ +package org.betterx.bclib.furniture.block; + +import org.betterx.bclib.behaviours.BehaviourHelper; +import org.betterx.bclib.behaviours.interfaces.BehaviourMetal; +import org.betterx.bclib.behaviours.interfaces.BehaviourStone; +import org.betterx.bclib.behaviours.interfaces.BehaviourWood; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +public abstract class BaseTaburet extends AbstractChair { + private static final VoxelShape SHAPE = Block.box(2, 0, 2, 14, 10, 14); + + public BaseTaburet(Block block) { + super(block, 9); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { + return SHAPE; + } + + public static class Wood extends BaseTaburet implements BehaviourWood { + public Wood(Block block) { + super(block); + } + } + + public static class Stone extends BaseTaburet implements BehaviourStone { + public Stone(Block block) { + super(block); + } + } + + public static class Metal extends BaseTaburet implements BehaviourMetal { + public Metal(Block block) { + super(block); + } + } + + public static BaseTaburet from(Block source) { + return BehaviourHelper.from(source, Wood::new, Stone::new, Metal::new); + } +} diff --git a/src/main/java/org/betterx/bclib/furniture/entity/EntityChair.java b/src/main/java/org/betterx/bclib/furniture/entity/EntityChair.java new file mode 100644 index 00000000..30c2964a --- /dev/null +++ b/src/main/java/org/betterx/bclib/furniture/entity/EntityChair.java @@ -0,0 +1,147 @@ +package org.betterx.bclib.furniture.entity; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.furniture.block.AbstractChair; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntitySelector; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.attributes.AttributeSupplier; +import net.minecraft.world.entity.animal.WaterAnimal; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; + +import java.util.List; +import org.jetbrains.annotations.Nullable; + +public class EntityChair extends Entity { + public EntityChair(EntityType type, Level world) { + super(type, world); + } + + @Override + protected void defineSynchedData() { + + } + + protected int getMaxPassengers() { + return 1; + } + + @Override + public void tick() { + if (this.level().getBlockState(this.blockPosition()).getBlock() instanceof AbstractChair) + localTick(); + else { + BCLib.LOGGER.info("Chair Block was deleted -> ejecting"); + this.ejectPassengers(); + this.discard(); + } + } + + protected void localTick() { + super.tick(); + List pushableEntities = this.level().getEntities( + this, + this.getBoundingBox().inflate(0.7f, -0.01f, 0.7f), + EntitySelector.pushableBy(this) + ); + + if (!pushableEntities.isEmpty()) { + boolean free = !this.level().isClientSide && !(this.getControllingPassenger() instanceof Player); + for (int j = 0; j < pushableEntities.size(); ++j) { + Entity entity = pushableEntities.get(j); + if (entity.hasPassenger(this)) continue; + if (free + && this.getPassengers().size() < this.getMaxPassengers() + && !entity.isPassenger() + //&& entity.getBbWidth() < this.getBbWidth() + && entity instanceof LivingEntity + && !(entity instanceof WaterAnimal) + && !(entity instanceof Player) + ) { + entity.startRiding(this); + continue; + } + this.push(entity); + } + } + } + + @Override + protected void readAdditionalSaveData(CompoundTag compoundTag) { + + } + + @Override + protected void addAdditionalSaveData(CompoundTag compoundTag) { + + } + + @Override + public boolean isAlive() { + return !this.isRemoved(); + } + + @Override + public Packet getAddEntityPacket() { + return new ClientboundAddEntityPacket(this); + } + + @Override + protected boolean canAddPassenger(Entity entity) { + return this.getPassengers().size() < this.getMaxPassengers(); + } + + @Nullable + @Override + public LivingEntity getControllingPassenger() { + for (Entity e : getPassengers()) { + if (e instanceof LivingEntity le) return le; + } + return null; + } + + @Override + public void push(Entity entity) { + //Do nothing. Should not be pushable + } + + @Override + public double getPassengersRidingOffset() { + return 0.0; + } + + @Override + public double getMyRidingOffset() { + return 0.0; + } + + @Override + public InteractionResult interact(Player player, InteractionHand interactionHand) { + if (player.isSecondaryUseActive()) { + return InteractionResult.PASS; + } + + if (!this.level().isClientSide) { + return player.startRiding(this) ? InteractionResult.CONSUME : InteractionResult.PASS; + } + return InteractionResult.SUCCESS; + } + + public static AttributeSupplier getAttributeContainer() { + return AttributeSupplier.builder().build(); + } + + @Override + public boolean isPickable() { + return !this.isRemoved(); + } +} diff --git a/src/main/java/org/betterx/bclib/furniture/renderer/RenderChair.java b/src/main/java/org/betterx/bclib/furniture/renderer/RenderChair.java new file mode 100644 index 00000000..1ebe6ceb --- /dev/null +++ b/src/main/java/org/betterx/bclib/furniture/renderer/RenderChair.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.furniture.renderer; + +import org.betterx.bclib.furniture.entity.EntityChair; + +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(value = EnvType.CLIENT) +public class RenderChair extends EntityRenderer { + private static final ResourceLocation TEXTURE = new ResourceLocation("minecraft:textures/block/stone.png"); + + public RenderChair(EntityRendererProvider.Context context) { + super(context); + } + + @Override + public ResourceLocation getTextureLocation(EntityChair entity) { + return TEXTURE; + } +} diff --git a/src/main/java/org/betterx/bclib/integration/ModIntegration.java b/src/main/java/org/betterx/bclib/integration/ModIntegration.java new file mode 100644 index 00000000..37c81b64 --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/ModIntegration.java @@ -0,0 +1,203 @@ +package org.betterx.bclib.integration; + +import org.betterx.bclib.BCLib; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.Holder; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import net.fabricmc.loader.api.FabricLoader; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public abstract class ModIntegration { + private final String modID; + + public void init() { + } + + public ModIntegration(String modID) { + this.modID = modID; + } + + public ResourceLocation getID(String name) { + return new ResourceLocation(modID, name); + } + + public ResourceKey getFeatureKey(String name) { + return ResourceKey.create(Registries.PLACED_FEATURE, getID(name)); + } + + public Block getBlock(String name) { + return BuiltInRegistries.BLOCK.get(getID(name)); + } + + public Item getItem(String name) { + return BuiltInRegistries.ITEM.get(getID(name)); + } + + public BlockState getDefaultState(String name) { + return getBlock(name).defaultBlockState(); + } + + public ResourceKey getKey(String name) { + return ResourceKey.create(Registries.BIOME, getID(name)); + } + + public boolean modIsInstalled() { + return FabricLoader.getInstance().isModLoaded(modID); + } + + + public ConfiguredFeature getConfiguredFeature(String name) { + //TODO: 1.19.3 find how to change this without having features before a world gets loaded + return null; //BuiltInRegistries.CONFIGURED_FEATURE.get(getID(name)); + } + + public Holder getBiome(String name) { + //TODO: 1.19.3 find how to change this without having features before a world gets loaded + return null; //BuiltInRegistries.BIOME.getHolder(getKey(name)).orElseThrow(); + } + + public Class getClass(String path) { + Class cl = null; + try { + cl = Class.forName(path); + } catch (ClassNotFoundException e) { + BCLib.LOGGER.error(e.getMessage()); + if (BCLib.isDevEnvironment()) { + e.printStackTrace(); + } + } + return cl; + } + + @SuppressWarnings("unchecked") + public T getStaticFieldValue(Class cl, String name) { + if (cl != null) { + try { + Field field = cl.getDeclaredField(name); + if (field != null) { + return (T) field.get(null); + } + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } + return null; + } + + public Object getFieldValue(Class cl, String name, Object classInstance) { + if (cl != null) { + try { + Field field = cl.getDeclaredField(name); + if (field != null) { + return field.get(classInstance); + } + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } + return null; + } + + public Method getMethod(Class cl, String functionName, Class... args) { + if (cl != null) { + try { + return cl.getMethod(functionName, args); + } catch (NoSuchMethodException | SecurityException e) { + BCLib.LOGGER.error(e.getMessage()); + if (BCLib.isDevEnvironment()) { + e.printStackTrace(); + } + } + } + return null; + } + + public Object executeMethod(Object instance, Method method, Object... args) { + if (method != null) { + try { + return method.invoke(instance, args); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + BCLib.LOGGER.error(e.getMessage()); + if (BCLib.isDevEnvironment()) { + e.printStackTrace(); + } + } + } + return null; + } + + public Object getAndExecuteStatic(Class cl, String functionName, Object... args) { + if (cl != null) { + Class[] classes = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + classes[i] = args[i].getClass(); + } + Method method = getMethod(cl, functionName, classes); + return executeMethod(null, method, args); + } + return null; + } + + @SuppressWarnings("unchecked") + public T getAndExecuteRuntime( + Class cl, + Object instance, + String functionName, + Object... args + ) { + if (instance != null) { + Class[] classes = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + classes[i] = args[i].getClass(); + } + Method method = getMethod(cl, functionName, classes); + return (T) executeMethod(instance, method, args); + } + return null; + } + + public Object newInstance(Class cl, Object... args) { + if (cl != null) { + for (Constructor constructor : cl.getConstructors()) { + if (constructor.getParameterCount() == args.length) { + try { + return constructor.newInstance(args); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | + InvocationTargetException e) { + BCLib.LOGGER.error(e.getMessage()); + if (BCLib.isDevEnvironment()) { + e.printStackTrace(); + } + } + } + } + } + return null; + } + + public TagKey getItemTag(String name) { + ResourceLocation id = getID(name); + return TagManager.ITEMS.makeTag(id); + } + + public TagKey getBlockTag(String name) { + ResourceLocation id = getID(name); + return TagManager.BLOCKS.makeTag(id); + } +} diff --git a/src/main/java/org/betterx/bclib/integration/emi/EMIAbstractAlloyingRecipe.java b/src/main/java/org/betterx/bclib/integration/emi/EMIAbstractAlloyingRecipe.java new file mode 100644 index 00000000..8d75c0cf --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/emi/EMIAbstractAlloyingRecipe.java @@ -0,0 +1,117 @@ +package org.betterx.bclib.integration.emi; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.Recipe; + +import dev.emi.emi.api.recipe.EmiRecipe; +import dev.emi.emi.api.recipe.EmiRecipeCategory; +import dev.emi.emi.api.render.EmiTexture; +import dev.emi.emi.api.stack.EmiIngredient; +import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.api.widget.WidgetHolder; + +import java.util.List; + +public abstract class EMIAbstractAlloyingRecipe> implements EmiRecipe { + private final ResourceLocation id; + private final List input; + private final List output; + protected final T recipe; + + private final int fuelMultiplier; + private final boolean infiniBurn; + + public EMIAbstractAlloyingRecipe(T recipe, ResourceLocation id, int fuelMultiplier, boolean infiniBurn) { + this.recipe = recipe; + this.id = id; + this.input = List.of( + EmiIngredient.of(recipe.getIngredients().get(0)), + recipe.getIngredients().size() > 1 + ? EmiIngredient.of(recipe.getIngredients().get(1)) + : EmiIngredient.of(Ingredient.EMPTY) + ); + + this.output = List.of(EmiStack.of(recipe.getResultItem(Minecraft.getInstance().level.registryAccess()))); + this.fuelMultiplier = fuelMultiplier; + this.infiniBurn = infiniBurn; + } + + protected abstract int getSmeltTime(); + protected abstract float getExperience(); + + + @Override + public EmiRecipeCategory getCategory() { + return EMIPlugin.END_ALLOYING_CATEGORY; + } + + @Override + public ResourceLocation getId() { + return id; + } + + @Override + public List getInputs() { + return input; + } + + @Override + public List getOutputs() { + return output; + } + + protected int getXOffset() { + return 22; + } + + @Override + public int getDisplayWidth() { + return 82 + getXOffset(); + } + + @Override + public int getDisplayHeight() { + return 38; + } + + @Override + public void addWidgets(WidgetHolder widgets) { + // Add an arrow texture to indicate processing + widgets.addFillingArrow(24 + getXOffset(), 5, 50 * getSmeltTime()) + .tooltip((mx, my) -> List.of(ClientTooltipComponent.create(Component.translatable( + "emi.cooking.time", + new Object[]{(float) getSmeltTime() / 20.0F} + ).getVisualOrderText()))); + + if (this.infiniBurn) { + widgets.addTexture(EmiTexture.FULL_FLAME, 1, 24); + } else { + widgets.addTexture(EmiTexture.EMPTY_FLAME, 1, 24); + widgets.addAnimatedTexture(EmiTexture.FULL_FLAME, 1, 24, 4000 / this.fuelMultiplier, false, true, true); + } + + // Adds an input slot on the left + widgets.addSlot(input.get(0), 0, 4); + widgets.addSlot((input.size() > 1) ? input.get(1) : null, 20, 4); + widgets.addText( + Component.translatable("emi.cooking.experience", getExperience()).getVisualOrderText(), + 24 + getXOffset(), 28, 0xFFFFFFFF, true + ); + + // Adds an output slot on the right + // Note that output slots need to call `recipeContext` to inform EMI about their recipe context + // This includes being able to resolve recipe trees, favorite stacks with recipe context, and more + widgets.addSlot(output.get(0), 56 + getXOffset(), 0).large(true).recipeContext(this); + } + + @Override + public boolean supportsRecipeTree() { + return true; + } +} + diff --git a/src/main/java/org/betterx/bclib/integration/emi/EMIAlloyingRecipe.java b/src/main/java/org/betterx/bclib/integration/emi/EMIAlloyingRecipe.java new file mode 100644 index 00000000..43a957e8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/emi/EMIAlloyingRecipe.java @@ -0,0 +1,32 @@ +package org.betterx.bclib.integration.emi; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.recipes.AlloyingRecipe; + +import net.minecraft.world.Container; +import net.minecraft.world.item.crafting.RecipeManager; + +import dev.emi.emi.api.EmiRegistry; + +public class EMIAlloyingRecipe extends EMIAbstractAlloyingRecipe { + public EMIAlloyingRecipe(AlloyingRecipe recipe) { + super(recipe, recipe.getId(), 1, false); + } + + @Override + protected int getSmeltTime() { + return recipe.getSmeltTime(); + } + + @Override + protected float getExperience() { + return recipe.getExperience(); + } + + static void addAllRecipes(EmiRegistry emiRegistry, RecipeManager manager) { + EMIPlugin.addAllRecipes( + emiRegistry, manager, BCLib.LOGGER, + AlloyingRecipe.TYPE, EMIAlloyingRecipe::new + ); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/integration/emi/EMIAnvilRecipe.java b/src/main/java/org/betterx/bclib/integration/emi/EMIAnvilRecipe.java new file mode 100644 index 00000000..f76dda4b --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/emi/EMIAnvilRecipe.java @@ -0,0 +1,111 @@ +package org.betterx.bclib.integration.emi; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.recipes.AnvilRecipe; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.RecipeManager; + +import dev.emi.emi.api.EmiRegistry; +import dev.emi.emi.api.recipe.EmiRecipe; +import dev.emi.emi.api.recipe.EmiRecipeCategory; +import dev.emi.emi.api.render.EmiTexture; +import dev.emi.emi.api.stack.EmiIngredient; +import dev.emi.emi.api.stack.EmiStack; +import dev.emi.emi.api.widget.WidgetHolder; + +import java.util.List; +import java.util.stream.StreamSupport; +import org.jetbrains.annotations.Nullable; + +public class EMIAnvilRecipe implements EmiRecipe { + private final ResourceLocation id; + private final List input; + private final List output; + private final EmiRecipeCategory category; + + public EMIAnvilRecipe(AnvilRecipe recipe, Item hammer) { + this.id = new ResourceLocation( + "emi", + recipe.getId().getNamespace() + "/" + recipe.getId().getPath() + "/anvil/" + hammer.getDescriptionId() + ); + this.input = List.of( + EmiIngredient.of(recipe.getMainIngredient(), recipe.getInputCount()), + EmiIngredient.of(Ingredient.of(hammer)) + ); + this.output = List.of(EmiStack.of(recipe.getResultItem(Minecraft.getInstance().level.registryAccess()))); + this.category = EMIPlugin.getAnvilCategoryForLevel(recipe.getAnvilLevel()); + } + + static void addAllRecipes(EmiRegistry emiRegistry, RecipeManager manager) { + Iterable> hammers = AnvilRecipe.getAllHammers(); + EMIPlugin.addAllRecipes( + emiRegistry, manager, BCLib.LOGGER, + AnvilRecipe.TYPE, + recipe -> StreamSupport.stream(hammers.spliterator(), false) + .map(Holder::value) + .filter(recipe::canUse) + .toList(), + EMIAnvilRecipe::new + ); + } + + @Override + public EmiRecipeCategory getCategory() { + return category; + } + + @Override + public @Nullable ResourceLocation getId() { + return id; + } + + @Override + public List getInputs() { + return input; + } + + @Override + public List getOutputs() { + return output; + } + + @Override + public int getDisplayWidth() { + return 104; + } + + @Override + public int getDisplayHeight() { + return 26; + } + + @Override + public void addWidgets(WidgetHolder widgetHolder) { + // Add an arrow texture to indicate processing + widgetHolder.addTexture(EmiTexture.EMPTY_ARROW, 46, 5); + + // Adds an input slot on the left + widgetHolder.addSlot(input.get(0), 0, 4); + widgetHolder.addSlot(input.get(1), 20, 4).catalyst(true); + + // Adds an output slot on the right + // Note that output slots need to call `recipeContext` to inform EMI about their recipe context + // This includes being able to resolve recipe trees, favorite stacks with recipe context, and more + widgetHolder.addSlot(output.get(0), 78, 0).large(true).recipeContext(this); + } + + @Override + public List getCatalysts() { + return List.of(input.get(1)); + } + + @Override + public boolean supportsRecipeTree() { + return true; + } +} diff --git a/src/main/java/org/betterx/bclib/integration/emi/EMIAnvilRecipeCategory.java b/src/main/java/org/betterx/bclib/integration/emi/EMIAnvilRecipeCategory.java new file mode 100644 index 00000000..08399b1e --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/emi/EMIAnvilRecipeCategory.java @@ -0,0 +1,183 @@ +package org.betterx.bclib.integration.emi; + +import org.betterx.bclib.blocks.LeveledAnvilBlock; +import org.betterx.bclib.util.RomanNumeral; + +import com.mojang.blaze3d.vertex.Tesselator; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.FormattedCharSequence; + +import com.google.common.collect.Lists; +import dev.emi.emi.api.recipe.EmiRecipe; +import dev.emi.emi.api.recipe.EmiRecipeCategory; +import dev.emi.emi.api.render.EmiRenderable; +import dev.emi.emi.api.render.EmiTexture; +import org.joml.Matrix4f; + +import java.util.Comparator; +import java.util.List; + +public class EMIAnvilRecipeCategory extends EmiRecipeCategory { + private final int anvilLevel; + private final List titleLines; + + + public EMIAnvilRecipeCategory(ResourceLocation id, EmiRenderable icon, int anvilLevel) { + super(id, icon); + this.anvilLevel = anvilLevel; + titleLines = LeveledAnvilBlock.getNamesForLevel(anvilLevel); + } + + public EMIAnvilRecipeCategory(ResourceLocation id, EmiRenderable icon, EmiRenderable simplified, int anvilLevel) { + super(id, icon, simplified); + this.anvilLevel = anvilLevel; + titleLines = LeveledAnvilBlock.getNamesForLevel(anvilLevel); + } + + public EMIAnvilRecipeCategory( + ResourceLocation id, + EmiRenderable icon, EmiTexture simplified, + Comparator sorter, + int anvilLevel + ) { + super(id, icon, simplified, sorter); + this.anvilLevel = anvilLevel; + titleLines = LeveledAnvilBlock.getNamesForLevel(anvilLevel); + + + } + + @Override + public void renderSimplified(GuiGraphics guiGraphics, int x, int y, float delta) { + super.renderSimplified(guiGraphics, x, y, delta); + final Font font = Minecraft.getInstance().font; + final String content = RomanNumeral.toRoman(anvilLevel); + + final MultiBufferSource.BufferSource bufferSource = MultiBufferSource + .immediate(Tesselator.getInstance().getBuilder()); + final int xx = x + 19 - 2 - font.width(content); + final int yy = y + 6 + 3; + final Matrix4f matrix = guiGraphics.pose().last().pose(); + + font.drawInBatch( + content, + xx - 1, + yy - 1, + 0xFF000000, + false, + matrix, + bufferSource, + Font.DisplayMode.NORMAL, + 0, + 0xF000F0 + ); + font.drawInBatch( + content, + xx, + yy - 1, + 0xFF000000, + false, + matrix, + bufferSource, + Font.DisplayMode.NORMAL, + 0, + 0xF000F0 + ); + font.drawInBatch( + content, + xx + 1, + yy - 1, + 0xFF000000, + false, + matrix, + bufferSource, + Font.DisplayMode.NORMAL, + 0, + 0xF000F0 + ); + font.drawInBatch( + content, + xx - 1, + yy, + 0xFF000000, + false, + matrix, + bufferSource, + Font.DisplayMode.NORMAL, + 0, + 0xF000F0 + ); + font.drawInBatch( + content, + xx + 1, + yy, + 0xFF000000, + false, + matrix, + bufferSource, + Font.DisplayMode.NORMAL, + 0, + 0xF000F0 + ); + font.drawInBatch( + content, + xx - 1, + yy + 1, + 0xFF000000, + false, + matrix, + bufferSource, + Font.DisplayMode.NORMAL, + 0, + 0xF000F0 + ); + font.drawInBatch( + content, + xx + 1, + yy + 1, + 0xFF000000, + false, + matrix, + bufferSource, + Font.DisplayMode.NORMAL, + 0, + 0xF000F0 + ); + font.drawInBatch( + content, + xx, + yy + 1, + 0xFF000000, + false, + matrix, + bufferSource, + Font.DisplayMode.NORMAL, + 0, + 0xF000F0 + ); + + font.drawInBatch(content, xx, yy, 0xFFFFFFFF, true, matrix, bufferSource, Font.DisplayMode.NORMAL, 0, 0xF000F0); + bufferSource.endBatch(); + } + + @Override + public List getTooltip() { + List list = super.getTooltip(); + if (!titleLines.isEmpty()) { + List newList = Lists.newArrayList(); + for (var line : titleLines) + newList.add(ClientTooltipComponent.create(line)); + + if (list.size() > 0) list.remove(0); + newList.addAll(list); + return newList; + } + + return list; + } +} diff --git a/src/main/java/org/betterx/bclib/integration/emi/EMIPlugin.java b/src/main/java/org/betterx/bclib/integration/emi/EMIPlugin.java new file mode 100644 index 00000000..d276d5fe --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/emi/EMIPlugin.java @@ -0,0 +1,170 @@ +package org.betterx.bclib.integration.emi; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.blocks.LeveledAnvilBlock; +import org.betterx.bclib.interfaces.AlloyingRecipeWorkstation; +import org.betterx.worlds.together.util.Logger; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.block.Blocks; + +import dev.emi.emi.api.EmiPlugin; +import dev.emi.emi.api.EmiRegistry; +import dev.emi.emi.api.recipe.EmiRecipe; +import dev.emi.emi.api.recipe.EmiRecipeCategory; +import dev.emi.emi.api.render.EmiTexture; +import dev.emi.emi.api.stack.EmiStack; + +import java.util.Comparator; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class EMIPlugin implements EmiPlugin { + private static boolean didInit = false; + private static int maxAnvilLevel = 1; + public static final ResourceLocation BCL_SIMPLIFIED_SPRITES = BCLib.makeID( + "textures/gui/widgets.png" + ); + + public static EmiStack END_ALLOYING_WORKSTATION; + public static EmiRecipeCategory END_ALLOYING_CATEGORY; + + public static EmiRecipeCategory[] ANVIL_CATEGORIES; + public static EmiStack[] ANVIL_WORKSTATIONS; + + public static EmiTexture getSprite(int u, int v) { + return new EmiTexture(BCL_SIMPLIFIED_SPRITES, u, v, 16, 16, 16, 16, 32, 32); + } + + public void lazyInit() { + if (!didInit) { + didInit = true; + lazyInitAlloyingCategory(); + lazyInitAnvilCategories(); + } + } + + private void lazyInitAlloyingCategory() { + var workstations = AlloyingRecipeWorkstation.getWorkstationIcon(); + if (!workstations.is(Blocks.BARRIER.asItem())) { + END_ALLOYING_WORKSTATION = EmiStack.of(workstations); + + END_ALLOYING_CATEGORY = new EmiRecipeCategory( + BCLib.makeID("alloying"), + END_ALLOYING_WORKSTATION, + getSprite(16, 0) + ); + } + } + + + private void lazyInitAnvilCategories() { + if (ANVIL_CATEGORIES == null) { + maxAnvilLevel = Math.max(1, LeveledAnvilBlock + .getAnvils() + .stream() + .map(LeveledAnvilBlock::getAnvilCraftingLevel) + .reduce(0, Math::max) + ); + + ANVIL_CATEGORIES = new EmiRecipeCategory[maxAnvilLevel + 1]; + ANVIL_WORKSTATIONS = new EmiStack[maxAnvilLevel + 1]; + + for (int anvilLevel = 0; anvilLevel <= maxAnvilLevel; anvilLevel++) { + int finalAnvilLevel = anvilLevel; + ANVIL_WORKSTATIONS[anvilLevel] = EmiStack.of(LeveledAnvilBlock + .getAnvils() + .stream() + .filter(b -> LeveledAnvilBlock.canHandle(b, finalAnvilLevel)) + .sorted(Comparator.comparingInt(LeveledAnvilBlock::getAnvilCraftingLevel)) + .findFirst().orElse(Blocks.BARRIER) + ); + ANVIL_CATEGORIES[anvilLevel] = new EMIAnvilRecipeCategory( + BCLib.makeID("anvil_" + anvilLevel), + ANVIL_WORKSTATIONS[anvilLevel], + getSprite(0, 0), + anvilLevel + ); + + if (anvilLevel > 0 && ANVIL_WORKSTATIONS[anvilLevel].isEqual(ANVIL_WORKSTATIONS[anvilLevel - 1])) { + ANVIL_WORKSTATIONS[anvilLevel - 1] = ANVIL_WORKSTATIONS[anvilLevel]; + ANVIL_CATEGORIES[anvilLevel - 1] = ANVIL_CATEGORIES[anvilLevel]; + } + } + } + } + + + @Override + public void register(EmiRegistry emiRegistry) { + lazyInit(); + final RecipeManager manager = emiRegistry.getRecipeManager(); + + if (END_ALLOYING_CATEGORY != null && END_ALLOYING_WORKSTATION != null) { + emiRegistry.addCategory(END_ALLOYING_CATEGORY); + emiRegistry.addWorkstation(END_ALLOYING_CATEGORY, END_ALLOYING_WORKSTATION); + + EMIAlloyingRecipe.addAllRecipes(emiRegistry, manager); + } + + if (ANVIL_CATEGORIES != null && ANVIL_WORKSTATIONS != null && ANVIL_CATEGORIES.length > 0) { + for (int i = 0; i <= maxAnvilLevel; i++) { + emiRegistry.addCategory(ANVIL_CATEGORIES[i]); + emiRegistry.addWorkstation(ANVIL_CATEGORIES[i], ANVIL_WORKSTATIONS[i]); + } + EMIAnvilRecipe.addAllRecipes(emiRegistry, manager); + } + } + + public static , E extends EmiRecipe> void addAllRecipes( + EmiRegistry emiRegistry, + RecipeManager manager, + Logger logger, + RecipeType recipeType, + Function createRecipe + ) { + addAllRecipes( + emiRegistry, + manager, + logger, + recipeType, + (_ignored) -> null, + (recipe, _ignored) -> createRecipe.apply(recipe) + ); + } + + public static , E extends EmiRecipe, V> void addAllRecipes( + EmiRegistry emiRegistry, + RecipeManager manager, + Logger logger, + RecipeType recipeType, + Function> variantSupplier, + BiFunction createRecipe + ) { + for (T recipe : manager.getAllRecipesFor(recipeType)) { + List variants = variantSupplier.apply(recipe); + if (variants == null) { + emiRegistry.addRecipe(createRecipe.apply(recipe, null)); + } else { + for (V variantData : variants) { + try { + emiRegistry.addRecipe(createRecipe.apply(recipe, variantData)); + } catch (Exception e) { + logger.error("Exception when parsing vanilla recipe " + recipe.getId(), e); + } + } + } + } + } + + + static EmiRecipeCategory getAnvilCategoryForLevel(int anvilLevel) { + anvilLevel = Math.max(0, Math.min(ANVIL_CATEGORIES.length - 1, anvilLevel)); + return ANVIL_CATEGORIES[anvilLevel]; + } +} diff --git a/src/main/java/org/betterx/bclib/integration/modmenu/ModMenu.java b/src/main/java/org/betterx/bclib/integration/modmenu/ModMenu.java new file mode 100644 index 00000000..414862ad --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/modmenu/ModMenu.java @@ -0,0 +1,30 @@ +package org.betterx.bclib.integration.modmenu; + +import net.minecraft.client.gui.screens.Screen; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * Integration, to provide a custom Screen for ModMenu. + *

+ * This integration allows you to use ModMenu without adding a dependency to your project. If the + * Mod is installed on the Client. + *

+ * You can add a screen for your mod by calling {@link #addModMenuScreen(String, Function)} + */ +public class ModMenu { + static final Map> screen = new HashMap<>(); + + /** + * registers a ModMenu entrypoint for another Mod. For Example {@code addModMenuScreen("myMod", (parent)->new Screen(parent));} + * + * @param modID The ID of your Mod + * @param scr a function that takes a parent {@link Screen} and provides the main Screen you want + * to show with ModMenu for your Mod. + */ + public static void addModMenuScreen(String modID, Function scr) { + screen.put(modID, scr); + } +} diff --git a/src/main/java/org/betterx/bclib/integration/modmenu/ModMenuEntryPoint.java b/src/main/java/org/betterx/bclib/integration/modmenu/ModMenuEntryPoint.java new file mode 100644 index 00000000..7a10c0e1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/integration/modmenu/ModMenuEntryPoint.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.integration.modmenu; + +import org.betterx.bclib.client.gui.modmenu.MainScreen; + +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + + +/** + * Internal class to hook into ModMenu, you should not need to use this class. If you want to register a + * ModMenu Screen for a Mod using BCLib, use {@link ModMenu#addModMenuScreen(String, Function)} + */ +public class ModMenuEntryPoint implements ModMenuApi { + @Override + public Map> getProvidedConfigScreenFactories() { + Map> copy = new HashMap<>(); + for (var entry : ModMenu.screen.entrySet()) { + copy.put(entry.getKey(), (parent) -> entry.getValue().apply(parent)); + } + return copy; + } + + @Override + public ConfigScreenFactory getModConfigScreenFactory() { + return (parent) -> new MainScreen(parent); + } + + +} diff --git a/src/main/java/org/betterx/bclib/interfaces/AirSelectionItem.java b/src/main/java/org/betterx/bclib/interfaces/AirSelectionItem.java new file mode 100644 index 00000000..a83fcb62 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/AirSelectionItem.java @@ -0,0 +1,60 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.ClipBlockStateContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +public interface AirSelectionItem { + default boolean renderAirSelection() { + return true; + } + + default int airSelectionColor() { + return 0xBFF6FA70; + } + + default BlockHitResult getAirSelectionHit(Level level, Player player) { + if (renderAirSelection()) { + final var vec = new Vec3(0, 0, 1) + .xRot(-player.getXRot() * Mth.DEG_TO_RAD) + .yRot(-player.getYHeadRot() * Mth.DEG_TO_RAD); + + return level.isBlockInLine(new ClipBlockStateContext( + player.getEyePosition(), + player.getEyePosition().add(vec.scale(4.9)), + BlockBehaviour.BlockStateBase::isAir + )); + } + return null; + } + + default InteractionResultHolder useOnAir(Level level, Player player, InteractionHand interactionHand) { + final BlockHitResult hit = getAirSelectionHit(level, player); + + if (hit != null) { + var result = this.useOn(new UseOnContext(player, interactionHand, hit)); + + if (result == InteractionResult.SUCCESS) + return InteractionResultHolder.success(player.getItemInHand(interactionHand)); + else if (result == InteractionResult.FAIL) + return InteractionResultHolder.fail(player.getItemInHand(interactionHand)); + else if (result == InteractionResult.PASS) + return InteractionResultHolder.pass(player.getItemInHand(interactionHand)); + else if (result == InteractionResult.CONSUME) + return InteractionResultHolder.consume(player.getItemInHand(interactionHand)); + } + + return InteractionResultHolder.pass(player.getItemInHand(interactionHand)); + } + + InteractionResult useOn(UseOnContext useOnContext); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/AlloyingRecipeWorkstation.java b/src/main/java/org/betterx/bclib/interfaces/AlloyingRecipeWorkstation.java new file mode 100644 index 00000000..5d4c65a8 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/AlloyingRecipeWorkstation.java @@ -0,0 +1,23 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; + +import java.util.List; + +public interface AlloyingRecipeWorkstation { + static List getWorkstations() { + return BuiltInRegistries.BLOCK + .stream() + .filter(b -> b instanceof AlloyingRecipeWorkstation) + .toList(); + } + + static ItemStack getWorkstationIcon() { + var workstations = AlloyingRecipeWorkstation.getWorkstations(); + if (workstations.isEmpty()) return new ItemStack(Blocks.BARRIER); + return new ItemStack(workstations.get(0)); + } +} diff --git a/src/main/java/org/betterx/bclib/interfaces/AnvilScreenHandlerExtended.java b/src/main/java/org/betterx/bclib/interfaces/AnvilScreenHandlerExtended.java new file mode 100644 index 00000000..e00a2a32 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/AnvilScreenHandlerExtended.java @@ -0,0 +1,36 @@ +package org.betterx.bclib.interfaces; + + +import org.betterx.bclib.recipes.AnvilRecipe; + +import java.util.List; + +public interface AnvilScreenHandlerExtended { + void bcl_updateCurrentRecipe(AnvilRecipe recipe); + + AnvilRecipe bcl_getCurrentRecipe(); + + List bcl_getRecipes(); + + default void be_nextRecipe() { + List recipes = bcl_getRecipes(); + if (recipes.size() < 2) return; + AnvilRecipe current = bcl_getCurrentRecipe(); + int i = recipes.indexOf(current) + 1; + if (i >= recipes.size()) { + i = 0; + } + bcl_updateCurrentRecipe(recipes.get(i)); + } + + default void be_previousRecipe() { + List recipes = bcl_getRecipes(); + if (recipes.size() < 2) return; + AnvilRecipe current = bcl_getCurrentRecipe(); + int i = recipes.indexOf(current) - 1; + if (i <= 0) { + i = recipes.size() - 1; + } + bcl_updateCurrentRecipe(recipes.get(i)); + } +} diff --git a/src/main/java/org/betterx/bclib/interfaces/BCLPlacementContext.java b/src/main/java/org/betterx/bclib/interfaces/BCLPlacementContext.java new file mode 100644 index 00000000..3656f479 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/BCLPlacementContext.java @@ -0,0 +1,11 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; + +public interface BCLPlacementContext { + Rotation bcl_getRotation(); + void bcl_setRotation(Rotation bcl_rotation); + Mirror bcl_getMirror(); + void bcl_setMirror(Mirror bcl_mirror); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/BlockModelProvider.java b/src/main/java/org/betterx/bclib/interfaces/BlockModelProvider.java new file mode 100644 index 00000000..dd7a76cd --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/BlockModelProvider.java @@ -0,0 +1,54 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.state.BlockState; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.Nullable; + +public interface BlockModelProvider extends ItemModelProvider { + @Environment(EnvType.CLIENT) + default @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { + Optional pattern = PatternsHelper.createBlockSimple(resourceLocation); + return ModelsHelper.fromPattern(pattern); + } + + @Environment(EnvType.CLIENT) + default UnbakedModel getModelVariant( + ResourceLocation stateId, + BlockState blockState, + Map modelCache + ) { + ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); + registerBlockModel(stateId, modelId, blockState, modelCache); + return ModelsHelper.createBlockSimple(modelId); + } + + @Environment(EnvType.CLIENT) + default void registerBlockModel( + ResourceLocation stateId, + ResourceLocation modelId, + BlockState blockState, + Map modelCache + ) { + if (!modelCache.containsKey(modelId)) { + BlockModel model = getBlockModel(stateId, blockState); + if (model != null) { + model.name = modelId.toString(); + modelCache.put(modelId, model); + } else { + BCLib.LOGGER.warning("Error loading model: {}", modelId); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/interfaces/ClientLevelAccess.java b/src/main/java/org/betterx/bclib/interfaces/ClientLevelAccess.java new file mode 100644 index 00000000..9e790cdb --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/ClientLevelAccess.java @@ -0,0 +1,21 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.client.particle.Particle; +import net.minecraft.core.particles.ParticleOptions; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import org.jetbrains.annotations.Nullable; + +@Environment(EnvType.CLIENT) +public interface ClientLevelAccess { + @Nullable + LevelRendererAccess bcl_getLevelRenderer(); + @Nullable + Particle bcl_addParticle( + ParticleOptions particleOptions, + double x, double y, double z, + double vx, double vy, double vz + ); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/CustomColorProvider.java b/src/main/java/org/betterx/bclib/interfaces/CustomColorProvider.java new file mode 100644 index 00000000..5fff479f --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/CustomColorProvider.java @@ -0,0 +1,10 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.client.color.block.BlockColor; +import net.minecraft.client.color.item.ItemColor; + +public interface CustomColorProvider { + BlockColor getProvider(); + + ItemColor getItemProvider(); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/CustomItemProvider.java b/src/main/java/org/betterx/bclib/interfaces/CustomItemProvider.java new file mode 100644 index 00000000..0c2e261f --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/CustomItemProvider.java @@ -0,0 +1,14 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; + +public interface CustomItemProvider { + /** + * Used to replace default Block Item when block is registered. + * + * @return {@link BlockItem} + */ + BlockItem getCustomItem(ResourceLocation blockID, Item.Properties settings); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/Fuel.java b/src/main/java/org/betterx/bclib/interfaces/Fuel.java new file mode 100644 index 00000000..0809f99b --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/Fuel.java @@ -0,0 +1,5 @@ +package org.betterx.bclib.interfaces; + +public interface Fuel { + int getFuelTime(); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/ItemModelProvider.java b/src/main/java/org/betterx/bclib/interfaces/ItemModelProvider.java new file mode 100644 index 00000000..a1dc638a --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/ItemModelProvider.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.client.models.ModelsHelper; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public interface ItemModelProvider { + @Environment(EnvType.CLIENT) + default BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createItemModel(resourceLocation); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/interfaces/LevelRendererAccess.java b/src/main/java/org/betterx/bclib/interfaces/LevelRendererAccess.java new file mode 100644 index 00000000..316b2d57 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/LevelRendererAccess.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.interfaces; + + +import net.minecraft.client.particle.Particle; +import net.minecraft.core.particles.ParticleOptions; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import org.jetbrains.annotations.Nullable; + +@Environment(EnvType.CLIENT) +public interface LevelRendererAccess { + @Nullable Particle bcl_addParticle( + ParticleOptions particleOptions, + double x, double y, double z, + double vx, double vy, double vz + ); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/LootPoolAccessor.java b/src/main/java/org/betterx/bclib/interfaces/LootPoolAccessor.java new file mode 100644 index 00000000..01e2838d --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/LootPoolAccessor.java @@ -0,0 +1,10 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; + +import java.util.List; + +public interface LootPoolAccessor { + LootPool bcl_mergeEntries(List newEntries); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/NumericProvider.java b/src/main/java/org/betterx/bclib/interfaces/NumericProvider.java new file mode 100644 index 00000000..1f5da299 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/NumericProvider.java @@ -0,0 +1,26 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.mixin.common.SurfaceRulesContextAccessor; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.Lifecycle; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; + +import java.util.function.Function; + +public interface NumericProvider { + ResourceKey>> NUMERIC_PROVIDER_REGISTRY = ResourceKey.createRegistryKey( + BCLib.makeID("worldgen/numeric_provider")); + Registry> NUMERIC_PROVIDER = new MappedRegistry<>( + NUMERIC_PROVIDER_REGISTRY, + Lifecycle.experimental() + ); + Codec CODEC = NUMERIC_PROVIDER.byNameCodec() + .dispatch(NumericProvider::pcodec, Function.identity()); + int getNumber(SurfaceRulesContextAccessor context); + + Codec pcodec(); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/PatchBiFunction.java b/src/main/java/org/betterx/bclib/interfaces/PatchBiFunction.java new file mode 100644 index 00000000..b9b654c1 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/PatchBiFunction.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.api.v2.datafixer.MigrationProfile; +import org.betterx.bclib.api.v2.datafixer.PatchDidiFailException; + +@FunctionalInterface +public interface PatchBiFunction { + R apply(U t, V v, MigrationProfile profile) throws PatchDidiFailException; +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/interfaces/PatchFunction.java b/src/main/java/org/betterx/bclib/interfaces/PatchFunction.java new file mode 100644 index 00000000..c6e292f0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/PatchFunction.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.api.v2.datafixer.MigrationProfile; +import org.betterx.bclib.api.v2.datafixer.PatchDidiFailException; + +@FunctionalInterface +public interface PatchFunction { + R apply(T t, MigrationProfile profile) throws PatchDidiFailException; +} diff --git a/src/main/java/org/betterx/bclib/interfaces/PostInitable.java b/src/main/java/org/betterx/bclib/interfaces/PostInitable.java new file mode 100644 index 00000000..751fe877 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/PostInitable.java @@ -0,0 +1,5 @@ +package org.betterx.bclib.interfaces; + +public interface PostInitable { + void postInit(); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/RenderLayerProvider.java b/src/main/java/org/betterx/bclib/interfaces/RenderLayerProvider.java new file mode 100644 index 00000000..5fc1f2d7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/RenderLayerProvider.java @@ -0,0 +1,7 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.client.render.BCLRenderLayer; + +public interface RenderLayerProvider { + BCLRenderLayer getRenderLayer(); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/SpawnRule.java b/src/main/java/org/betterx/bclib/interfaces/SpawnRule.java new file mode 100644 index 00000000..1cfcdf78 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SpawnRule.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.core.BlockPos; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.level.LevelAccessor; + +@FunctionalInterface +public interface SpawnRule { + boolean canSpawn( + EntityType type, + LevelAccessor world, + MobSpawnType spawnReason, + BlockPos pos, + RandomSource random + ); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/SurfaceMaterialProvider.java b/src/main/java/org/betterx/bclib/interfaces/SurfaceMaterialProvider.java new file mode 100644 index 00000000..aee8f19e --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SurfaceMaterialProvider.java @@ -0,0 +1,58 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.api.v2.levelgen.surface.SurfaceRuleBuilder; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.world.level.block.state.BlockState; + +public interface SurfaceMaterialProvider { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance + .group( + BlockState.CODEC.fieldOf("top").forGetter(o -> o.getTopMaterial()), + BlockState.CODEC.fieldOf("under").forGetter(o -> o.getUnderMaterial()), + BlockState.CODEC.fieldOf("alt").forGetter(o -> o.getAltTopMaterial()), + Codec.BOOL.fieldOf("floor_rule").forGetter(o -> o.generateFloorRule()) + ).apply(instance, SurfaceMaterialProvider::create)); + + public static SurfaceMaterialProvider create( + BlockState top, + BlockState under, + BlockState alt, + boolean genFloorRule + ) { + return new SurfaceMaterialProvider() { + @Override + public BlockState getTopMaterial() { + return top; + } + + @Override + public BlockState getUnderMaterial() { + return under; + } + + @Override + public BlockState getAltTopMaterial() { + return alt; + } + + @Override + public boolean generateFloorRule() { + return genFloorRule; + } + + @Override + public SurfaceRuleBuilder surface() { + return null; + } + }; + } + + BlockState getTopMaterial(); + BlockState getUnderMaterial(); + BlockState getAltTopMaterial(); + + boolean generateFloorRule(); + SurfaceRuleBuilder surface(); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/SurvivesOn.java b/src/main/java/org/betterx/bclib/interfaces/SurvivesOn.java new file mode 100644 index 00000000..121ae451 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SurvivesOn.java @@ -0,0 +1,10 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.world.level.block.state.BlockState; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface SurvivesOn { + boolean isSurvivable(BlockState state); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/SurvivesOnBlocks.java b/src/main/java/org/betterx/bclib/interfaces/SurvivesOnBlocks.java new file mode 100644 index 00000000..f11fbb60 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SurvivesOnBlocks.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public interface SurvivesOnBlocks extends SurvivesOnSpecialGround { + List getSurvivableBlocks(); + + @Override + default String getSurvivableBlocksString() { + return getSurvivableBlocks() + .stream() + .filter(block -> block != Blocks.AIR && block != null) + .map(block -> { + ItemStack stack = new ItemStack(block); + if (stack.hasCustomHoverName()) return stack.getHoverName().getString(); + else return block.getName().getString(); + }) + .sorted(Comparator.naturalOrder()) + .collect(Collectors.joining(", ")); + } + + @Override + default boolean isSurvivable(BlockState state) { + return getSurvivableBlocks().contains(state.getBlock()); + } +} diff --git a/src/main/java/org/betterx/bclib/interfaces/SurvivesOnSpecialGround.java b/src/main/java/org/betterx/bclib/interfaces/SurvivesOnSpecialGround.java new file mode 100644 index 00000000..63096b09 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SurvivesOnSpecialGround.java @@ -0,0 +1,84 @@ +package org.betterx.bclib.interfaces; + +import org.betterx.bclib.config.Configs; + +import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.state.BlockState; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Lists; + +import java.util.List; + +public interface SurvivesOnSpecialGround extends SurvivesOn { + String getSurvivableBlocksString(); + + default String prefixComponent() { + return "tooltip.bclib.place_on"; + } + + @Environment(EnvType.CLIENT) + static List splitLines(String input) { + final int MAX_LEN = 45; + List lines = Lists.newArrayList(); + + while (input.length() > MAX_LEN) { + int idx = input.lastIndexOf(",", MAX_LEN); + if (idx >= 0) { + lines.add(input.substring(0, idx + 1).trim()); + input = input.substring(idx + 1).trim(); + } else { + break; + } + } + lines.add(input.trim()); + + return lines; + } + + @Environment(EnvType.CLIENT) + static void appendHoverText(SurvivesOnSpecialGround surv, List list) { + if (!Configs.CLIENT_CONFIG.survivesOnHint()) return; + final int MAX_LINES = 7; + final String description = surv.getSurvivableBlocksString(); + List lines = splitLines(description); + if (lines.size() == 1) { + list.add(Component.translatable(surv.prefixComponent(), lines.get(0)).withStyle(ChatFormatting.GREEN)); + } else if (lines.size() > 1) { + list.add(Component.translatable(surv.prefixComponent(), "").withStyle(ChatFormatting.GREEN)); + for (int i = 0; i < Math.min(lines.size(), MAX_LINES); i++) { + String line = lines.get(i); + if (i == MAX_LINES - 1 && i < lines.size() - 1) line += " ..."; + list.add(Component.literal(" " + line).withStyle(ChatFormatting.GREEN)); + } + } + } + + @Environment(EnvType.CLIENT) + public static void appendHoverTextUnderwater(List list) { + list.add(Component.translatable("tooltip.bclib.place_underwater") + .withStyle(ChatFormatting.GREEN)); + } + + @Environment(EnvType.CLIENT) + public static void appendHoverTextUnderwaterInDepth(List list, int depth) { + list.add(Component.translatable("tooltip.bclib.place_underwater_depth", depth) + .withStyle(ChatFormatting.GREEN)); + } + + default boolean canSurviveOnTop(LevelReader world, BlockPos pos) { + return isSurvivable(world.getBlockState(pos.below())); + } + + default boolean canSurviveOnBottom(LevelReader world, BlockPos pos) { + return isSurvivable(world.getBlockState(pos.above())); + } + default boolean isTerrain(BlockState state) { + return isSurvivable(state); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/interfaces/SurvivesOnTags.java b/src/main/java/org/betterx/bclib/interfaces/SurvivesOnTags.java new file mode 100644 index 00000000..2170b14f --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SurvivesOnTags.java @@ -0,0 +1,39 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public interface SurvivesOnTags extends SurvivesOnSpecialGround { + List> getSurvivableTags(); + + @Override + default String getSurvivableBlocksString() { + return getSurvivableTags() + .stream() + .map(tag -> BuiltInRegistries.BLOCK.getTag(tag)) + .filter(named -> named.isPresent()) + .map(named -> named.get()) + .flatMap(named -> named.stream()) + .filter(block -> block != Blocks.AIR && block != null) + .map(block -> { + ItemStack stack = new ItemStack(block.value()); + if (stack.hasCustomHoverName()) return stack.getHoverName().getString(); + else return block.value().getName().getString(); + }) + .sorted(Comparator.naturalOrder()) + .collect(Collectors.joining(", ")); + } + + @Override + default boolean isSurvivable(BlockState state) { + return getSurvivableTags().stream().anyMatch(tag -> state.is(tag)); + } +} diff --git a/src/main/java/org/betterx/bclib/interfaces/SurvivesOnWater.java b/src/main/java/org/betterx/bclib/interfaces/SurvivesOnWater.java new file mode 100644 index 00000000..47a79f5e --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/SurvivesOnWater.java @@ -0,0 +1,22 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.List; + +public interface SurvivesOnWater extends SurvivesOnBlocks { + List BLOCKS = List.of(Blocks.WATER); + + @Override + default List getSurvivableBlocks() { + return BLOCKS; + } + + default boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { + return world.getBlockState(pos).isAir() && isTerrain(world.getBlockState(pos.below())); + } +} diff --git a/src/main/java/org/betterx/bclib/interfaces/TagProvider.java b/src/main/java/org/betterx/bclib/interfaces/TagProvider.java new file mode 100644 index 00000000..ee754cbd --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/TagProvider.java @@ -0,0 +1,11 @@ +package org.betterx.bclib.interfaces; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.List; + +public interface TagProvider { + void addTags(List> blockTags, List> itemTags); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/TriConsumer.java b/src/main/java/org/betterx/bclib/interfaces/TriConsumer.java new file mode 100644 index 00000000..a06dcdcd --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/TriConsumer.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.interfaces; + +@FunctionalInterface +public interface TriConsumer { + void accept(A a, B b, C c); +} diff --git a/src/main/java/org/betterx/bclib/interfaces/UnknownReceipBookCategory.java b/src/main/java/org/betterx/bclib/interfaces/UnknownReceipBookCategory.java new file mode 100644 index 00000000..46052df4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/UnknownReceipBookCategory.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.interfaces; + +public interface UnknownReceipBookCategory { +} diff --git a/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableAxe.java b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableAxe.java new file mode 100644 index 00000000..6c978913 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableAxe.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.interfaces.tools; + +public interface AddMineableAxe extends HasMinableBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableHammer.java b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableHammer.java new file mode 100644 index 00000000..8c3fd0b4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableHammer.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.interfaces.tools; + +public interface AddMineableHammer extends HasMinableBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableHoe.java b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableHoe.java new file mode 100644 index 00000000..784f0993 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableHoe.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.interfaces.tools; + +public interface AddMineableHoe extends HasMinableBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/interfaces/tools/AddMineablePickaxe.java b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineablePickaxe.java new file mode 100644 index 00000000..2a789a65 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineablePickaxe.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.interfaces.tools; + +public interface AddMineablePickaxe extends HasMinableBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableShears.java b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableShears.java new file mode 100644 index 00000000..09ff4290 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableShears.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.interfaces.tools; + +public interface AddMineableShears extends HasMinableBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableShovel.java b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableShovel.java new file mode 100644 index 00000000..3b848be5 --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableShovel.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.interfaces.tools; + +public interface AddMineableShovel extends HasMinableBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableSword.java b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableSword.java new file mode 100644 index 00000000..a71e0fca --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/tools/AddMineableSword.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.interfaces.tools; + +public interface AddMineableSword extends HasMinableBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/interfaces/tools/HasMinableBehaviour.java b/src/main/java/org/betterx/bclib/interfaces/tools/HasMinableBehaviour.java new file mode 100644 index 00000000..88b2bd4c --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/tools/HasMinableBehaviour.java @@ -0,0 +1,8 @@ +package org.betterx.bclib.interfaces.tools; + + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public interface HasMinableBehaviour { +} diff --git a/src/main/java/org/betterx/bclib/interfaces/tools/PreventMineableAdd.java b/src/main/java/org/betterx/bclib/interfaces/tools/PreventMineableAdd.java new file mode 100644 index 00000000..5c75262f --- /dev/null +++ b/src/main/java/org/betterx/bclib/interfaces/tools/PreventMineableAdd.java @@ -0,0 +1,4 @@ +package org.betterx.bclib.interfaces.tools; + +public interface PreventMineableAdd { +} diff --git a/src/main/java/org/betterx/bclib/items/BaseAnvilItem.java b/src/main/java/org/betterx/bclib/items/BaseAnvilItem.java new file mode 100644 index 00000000..b9f576df --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/BaseAnvilItem.java @@ -0,0 +1,79 @@ +package org.betterx.bclib.items; + +import org.betterx.bclib.blocks.BaseAnvilBlock; +import org.betterx.bclib.interfaces.BlockModelProvider; +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.IntegerProperty; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.List; +import java.util.Locale; +import org.jetbrains.annotations.Nullable; + +public class BaseAnvilItem extends BlockItem implements ItemModelProvider { + public final static String DESTRUCTION = "destruction"; + + public BaseAnvilItem(Block block, Properties properties) { + super(block, properties); + } + + @Override + protected BlockState getPlacementState(BlockPlaceContext blockPlaceContext) { + BlockState blockState = super.getPlacementState(blockPlaceContext); + ItemStack stack = blockPlaceContext.getItemInHand(); + int destruction = stack.getOrCreateTag().getInt(DESTRUCTION); + if (blockState != null) { + BaseAnvilBlock block = (BaseAnvilBlock) blockState.getBlock(); + IntegerProperty durabilityProp = block.getDurabilityProp(); + if (destruction == 0) { + blockState = blockState.setValue(durabilityProp, 0).setValue(BaseAnvilBlock.DESTRUCTION, 0); + } else { + int destructionValue = destruction / block.getMaxDurability(); + int durabilityValue = destruction - destructionValue * block.getMaxDurability(); + blockState = blockState.setValue(durabilityProp, durabilityValue) + .setValue(BaseAnvilBlock.DESTRUCTION, destructionValue); + } + } + return blockState; + } + + @Override + @Environment(EnvType.CLIENT) + public void appendHoverText( + ItemStack itemStack, + @Nullable Level level, + List list, + TooltipFlag tooltipFlag + ) { + int destruction = itemStack.getOrCreateTag().getInt(DESTRUCTION); + if (destruction > 0) { + BaseAnvilBlock block = (BaseAnvilBlock) ((BaseAnvilItem) itemStack.getItem()).getBlock(); + int maxValue = block.getMaxDurability() * 3; + float damage = maxValue - destruction; + String percents = String.format(Locale.ROOT, "%.0f%%", damage); + list.add(Component.translatable("message.bclib.anvil_damage").append(": " + percents)); + } + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + Block anvilBlock = getBlock(); + ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(anvilBlock); + return ((BlockModelProvider) anvilBlock).getBlockModel(blockId, anvilBlock.defaultBlockState()); + } +} diff --git a/src/main/java/org/betterx/bclib/items/BaseArmorItem.java b/src/main/java/org/betterx/bclib/items/BaseArmorItem.java new file mode 100644 index 00000000..6e97b952 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/BaseArmorItem.java @@ -0,0 +1,64 @@ +package org.betterx.bclib.items; + +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.ai.attributes.Attribute; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.item.ArmorItem; +import net.minecraft.world.item.ArmorMaterial; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import java.util.UUID; + +public class BaseArmorItem extends ArmorItem implements ItemModelProvider { + + protected static final UUID[] ARMOR_MODIFIER_UUID_PER_SLOT = new UUID[]{ + UUID.fromString("845DB27C-C624-495F-8C9F-6020A9A58B6B"), + UUID.fromString("D8499B04-0E66-4726-AB29-64469D734E0D"), + UUID.fromString("9F3D476D-C118-4544-8365-64846904B48E"), + UUID.fromString("2AD3F246-FEE1-4E67-B886-69FD380BB150") + }; + + protected final Multimap defaultModifiers; + + public BaseArmorItem(ArmorMaterial material, Type type, Properties settings) { + super(material, type, settings); + this.defaultModifiers = HashMultimap.create(); + UUID uuid = ARMOR_MODIFIER_UUID_PER_SLOT[type.getSlot().getIndex()]; + addAttributeModifier( + Attributes.ARMOR, + new AttributeModifier(uuid, "Armor modifier", getDefense(), AttributeModifier.Operation.ADDITION) + ); + addAttributeModifier( + Attributes.ARMOR_TOUGHNESS, + new AttributeModifier(uuid, "Armor toughness", getToughness(), AttributeModifier.Operation.ADDITION) + ); + if (knockbackResistance > 0.0F) { + addAttributeModifier( + Attributes.KNOCKBACK_RESISTANCE, + new AttributeModifier( + uuid, + "Armor knockback resistance", + knockbackResistance, + AttributeModifier.Operation.ADDITION + ) + ); + } + } + + @Override + public Multimap getDefaultAttributeModifiers(EquipmentSlot equipmentSlot) { + return equipmentSlot == type.getSlot() ? defaultModifiers : super.getDefaultAttributeModifiers(equipmentSlot); + } + + protected void addAttributeModifier(Attribute attribute, AttributeModifier modifier) { + if (defaultModifiers.containsKey(attribute)) { + defaultModifiers.removeAll(attribute); + } + defaultModifiers.put(attribute, modifier); + } +} diff --git a/src/main/java/org/betterx/bclib/items/BaseAttribute.java b/src/main/java/org/betterx/bclib/items/BaseAttribute.java new file mode 100644 index 00000000..2ff7cd72 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/BaseAttribute.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.items; + +import net.minecraft.world.entity.ai.attributes.Attribute; + +public class BaseAttribute extends Attribute { + public BaseAttribute(String description, double value) { + super(description, value); + } +} diff --git a/src/main/java/org/betterx/bclib/items/BaseBucketItem.java b/src/main/java/org/betterx/bclib/items/BaseBucketItem.java new file mode 100644 index 00000000..283273c9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/BaseBucketItem.java @@ -0,0 +1,15 @@ +package org.betterx.bclib.items; + +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.sounds.SoundEvents; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.MobBucketItem; +import net.minecraft.world.level.material.Fluids; + +public class BaseBucketItem extends MobBucketItem implements ItemModelProvider { + public BaseBucketItem(EntityType type, Item.Properties settings) { + super(type, Fluids.WATER, SoundEvents.BUCKET_EMPTY_FISH, settings.stacksTo(1)); + } +} diff --git a/src/main/java/org/betterx/bclib/items/BaseDiscItem.java b/src/main/java/org/betterx/bclib/items/BaseDiscItem.java new file mode 100644 index 00000000..747de8a0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/BaseDiscItem.java @@ -0,0 +1,16 @@ +package org.betterx.bclib.items; + +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.RecordItem; + +public class BaseDiscItem { + public static RecordItem create( + int comparatorOutput, + SoundEvent sound, + Item.Properties settings, + int lengthInSeconds + ) { + return new RecordItem(comparatorOutput, sound, settings, lengthInSeconds); + } +} diff --git a/src/main/java/org/betterx/bclib/items/BaseDrinkItem.java b/src/main/java/org/betterx/bclib/items/BaseDrinkItem.java new file mode 100644 index 00000000..85b3ebeb --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/BaseDrinkItem.java @@ -0,0 +1,59 @@ +package org.betterx.bclib.items; + +import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.stats.Stats; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemUtils; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.UseAnim; +import net.minecraft.world.level.Level; + +public class BaseDrinkItem extends ModelProviderItem { + public BaseDrinkItem(Properties settings) { + super(settings); + } + + @Override + public int getUseDuration(ItemStack stack) { + return 32; + } + + @Override + public UseAnim getUseAnimation(ItemStack stack) { + return UseAnim.DRINK; + } + + @Override + public InteractionResultHolder use(Level world, Player user, InteractionHand hand) { + return ItemUtils.startUsingInstantly(world, user, hand); + } + + @Override + public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity user) { + if (this.isEdible()) { + int count = stack.getCount(); + user.eat(level, stack); + stack.setCount(count); + } + + if (user instanceof ServerPlayer serverPlayerEntity) { + CriteriaTriggers.CONSUME_ITEM.trigger(serverPlayerEntity, stack); + serverPlayerEntity.awardStat(Stats.ITEM_USED.get(this)); + } + + if (user instanceof Player && !((Player) user).getAbilities().instabuild) { + stack.shrink(1); + } + + if (!level.isClientSide) { + user.removeAllEffects(); + } + + return stack.isEmpty() ? new ItemStack(Items.GLASS_BOTTLE) : stack; + } +} diff --git a/src/main/java/org/betterx/bclib/items/BaseSpawnEggItem.java b/src/main/java/org/betterx/bclib/items/BaseSpawnEggItem.java new file mode 100644 index 00000000..493785d2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/BaseSpawnEggItem.java @@ -0,0 +1,30 @@ +package org.betterx.bclib.items; + +import org.betterx.bclib.client.models.BasePatterns; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.client.models.PatternsHelper; +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.item.SpawnEggItem; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.Optional; + +public class BaseSpawnEggItem extends SpawnEggItem implements ItemModelProvider { + public BaseSpawnEggItem(EntityType type, int primaryColor, int secondaryColor, Properties settings) { + super(type, primaryColor, secondaryColor, settings); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_SPAWN_EGG, resourceLocation); + return ModelsHelper.fromPattern(pattern); + } +} diff --git a/src/main/java/org/betterx/bclib/items/DebugDataItem.java b/src/main/java/org/betterx/bclib/items/DebugDataItem.java new file mode 100644 index 00000000..a5c13cbf --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/DebugDataItem.java @@ -0,0 +1,398 @@ +package org.betterx.bclib.items; + +import de.ambertation.wunderlib.math.Bounds; +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.commands.PlaceCommand; +import org.betterx.bclib.interfaces.AirSelectionItem; +import org.betterx.bclib.interfaces.ItemModelProvider; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.ui.ColorUtil; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.commands.arguments.blocks.BlockStateParser; +import net.minecraft.core.BlockPos; +import net.minecraft.core.FrontAndTop; +import net.minecraft.core.Vec3i; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.worldgen.Pools; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.JigsawBlock; +import net.minecraft.world.level.block.entity.*; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.HashSet; +import java.util.Set; + +public class DebugDataItem extends Item implements ItemModelProvider, AirSelectionItem { + + public static final ResourceLocation DEFAULT_ICON = new ResourceLocation("stick"); + + public static InteractionResult fillStructureEntityBounds( + UseOnContext useOnContext, + BlockEntity entity, + BlockStatePredicate predicate, + BlockState newState, + boolean floodFill + ) { + if (entity instanceof StructureBlockEntity e) { + if (floodFill) { + floodFillStructureEntityBounds(useOnContext, e, predicate, newState); + } else { + fillStructureEntityBounds(useOnContext, e, predicate, newState); + } + return InteractionResult.SUCCESS; + } + return InteractionResult.FAIL; + } + + public static void fillStructureEntityBounds( + UseOnContext useOnContext, + StructureBlockEntity entity, + BlockStatePredicate predicate, + BlockState newState + ) { + final var level = useOnContext.getLevel(); + final Vec3i size = entity.getStructureSize(); + final BlockPos pos = useOnContext.getClickedPos().offset(entity.getStructurePos()); + + for (int x = 0; x < size.getX(); x++) { + for (int y = 0; y < size.getY(); y++) { + for (int z = 0; z < size.getZ(); z++) { + var blockPos = pos.offset(x, y, z); + var state = level.getBlockState(blockPos); + if (predicate.test(state)) { + level.setBlock( + blockPos, + newState, + BlocksHelper.SET_SILENT + ); + } + } + } + } + } + + public static void floodFillStructureEntityBounds( + UseOnContext useOnContext, + StructureBlockEntity entity, + BlockStatePredicate predicate, + BlockState newState + ) { + final Bounds bounds = Bounds.of( + useOnContext.getClickedPos().offset(entity.getStructurePos()), + entity.getStructureSize() + ); + + floodFillStructureEntityBounds( + useOnContext.getLevel(), + bounds, + bounds.max.toBlockPos(), + entity, + predicate, + newState, + new HashSet<>() + ); + } + + private static void floodFillStructureEntityBounds( + Level level, + Bounds bounds, + BlockPos pos, + StructureBlockEntity entity, + BlockStatePredicate predicate, + BlockState newState, + Set visited + ) { + if (!bounds.isInside(pos)) return; + if (visited.contains(pos)) return; + visited.add(pos); + + if (predicate.test(level.getBlockState(pos))) { + level.setBlock(pos, newState, BlocksHelper.SET_SILENT); + + floodFillStructureEntityBounds(level, bounds, pos.above(), entity, predicate, newState, visited); + floodFillStructureEntityBounds(level, bounds, pos.below(), entity, predicate, newState, visited); + floodFillStructureEntityBounds(level, bounds, pos.north(), entity, predicate, newState, visited); + floodFillStructureEntityBounds(level, bounds, pos.east(), entity, predicate, newState, visited); + floodFillStructureEntityBounds(level, bounds, pos.south(), entity, predicate, newState, visited); + floodFillStructureEntityBounds(level, bounds, pos.west(), entity, predicate, newState, visited); + } + } + + public interface DebugInteraction { + InteractionResult use(UseOnContext useOnContext); + } + + public interface DebugEntityInteraction extends DebugInteraction { + @Override + default InteractionResult use(UseOnContext useOnContext) { + var entity = useOnContext.getLevel().getBlockEntity(useOnContext.getClickedPos()); + if (entity != null) { + return use(useOnContext.getPlayer(), entity, useOnContext); + } + return InteractionResult.FAIL; + } + + InteractionResult use(Player player, BlockEntity entity, UseOnContext useOnContext); + } + + protected final DebugInteraction interaction; + protected final ResourceLocation icon; + public final boolean placeInAir; + + public DebugDataItem(DebugEntityInteraction interaction, boolean placeInAir, ResourceLocation icon) { + this((DebugInteraction) interaction, placeInAir, icon); + } + + public DebugDataItem(DebugInteraction interaction, boolean placeInAir, ResourceLocation icon) { + super(new Item.Properties().fireResistant().stacksTo(1)); + + this.interaction = interaction; + this.icon = (icon == null ? DEFAULT_ICON : icon); + this.placeInAir = placeInAir; + } + + + public boolean renderAirSelection() { + return placeInAir; + } + + @Override + public boolean isFoil(ItemStack itemStack) { + return true; + } + + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createItemModel(icon); + } + + @Override + public InteractionResult useOn(UseOnContext useOnContext) { + if (!useOnContext.getPlayer().canUseGameMasterBlocks()) { + return InteractionResult.FAIL; + } + return interaction.use(useOnContext); + } + + public static void message(Player player, String text) { + message(player, text, ColorUtil.GRAY); + } + + public static void message(Player player, String text, int color) { + message(player, Component.literal(text).withStyle(Style.EMPTY.withColor(color))); + } + + public static void message(Player player, Component component) { + if (player instanceof ServerPlayer sp) { + sp.sendSystemMessage(component, true); + } + } + + @Override + public boolean canAttackBlock(BlockState blockState, Level level, BlockPos blockPos, Player player) { + return true; + } + + @Override + public InteractionResultHolder use(Level level, Player player, InteractionHand interactionHand) { + return AirSelectionItem.super.useOnAir(level, player, interactionHand); + } + + public static DebugDataItem forLootTable(ResourceLocation table, Item icon) { + ResourceLocation iconId = BuiltInRegistries.ITEM.getKey(icon); + return new DebugDataItem( + (player, entity, ctx) -> { + CompoundTag tag = entity.saveWithoutMetadata(); + tag.remove(RandomizableContainerBlockEntity.LOOT_TABLE_SEED_TAG); + tag.remove("Items"); + + tag.putString(RandomizableContainerBlockEntity.LOOT_TABLE_TAG, table.toString()); + + entity.load(tag); + message(player, "Did set Loot Table to " + table.toString()); + return InteractionResult.SUCCESS; + }, + false, + iconId + ); + } + + public static DebugDataItem forSpawner(CompoundTag tag, Item icon) { + ResourceLocation iconId = BuiltInRegistries.ITEM.getKey(icon); + return new DebugDataItem( + (player, entity, ctx) -> { + if (entity instanceof SpawnerBlockEntity) { + entity.load(tag); + message(player, "Did set Data to " + tag.toString()); + return InteractionResult.SUCCESS; + } + return InteractionResult.FAIL; + }, + false, + iconId + ); + } + + public static DebugDataItem forSteetJigSaw( + String modID, + ResourceKey pool, + Item icon + ) { + return forJigsaw( + pool == null ? Pools.EMPTY : pool, + new ResourceLocation(modID, "street"), + JigsawBlockEntity.JointType.ALIGNED, + null, + null, + icon + ); + } + + public static DebugDataItem forHouseEntranceJigSaw( + String modID, + ResourceKey pool, + Item icon + ) { + return forJigsaw( + pool == null ? Pools.EMPTY : pool, + pool == null + ? new ResourceLocation(modID, "building_entrance") + : new ResourceLocation(modID, "street_entrance"), + pool == null + ? new ResourceLocation("street_entrance") + : new ResourceLocation(modID, "building_entrance"), + JigsawBlockEntity.JointType.ALIGNED, + null, + null, + icon + ); + } + + public static DebugDataItem forDecorationJigSaw( + String modID, + ResourceKey pool, + Item icon + ) { + return forJigsaw( + pool == null ? Pools.EMPTY : pool, + pool == null ? new ResourceLocation(modID, "side") : new ResourceLocation(modID, "side_street"), + pool == null ? new ResourceLocation("side_street") : new ResourceLocation(modID, "side"), + JigsawBlockEntity.JointType.ALIGNED, + null, + null, + icon + ); + } + + public static DebugDataItem forStreetDecorationJigSaw( + String modID, + ResourceKey pool, + Item icon + ) { + return forJigsaw( + pool == null ? Pools.EMPTY : pool, + pool == null ? new ResourceLocation(modID, "bottom") : new ResourceLocation(modID, "bottom_street"), + pool == null ? new ResourceLocation("bottom_street") : new ResourceLocation(modID, "bottom"), + JigsawBlockEntity.JointType.ROLLABLE, + null, + pool == null ? FrontAndTop.DOWN_WEST : FrontAndTop.UP_WEST, + icon + ); + } + + public static DebugDataItem forJigsaw( + ResourceKey pool, + ResourceLocation connector, + JigsawBlockEntity.JointType type, + BlockState finalState, + FrontAndTop forceOrientation, + Item icon + ) { + return forJigsaw(pool, connector, connector, type, finalState, forceOrientation, icon); + } + + public static DebugDataItem forJigsaw( + ResourceKey pool, + ResourceLocation name, + ResourceLocation target, + JigsawBlockEntity.JointType type, + BlockState finalState, + FrontAndTop forceOrientation, + Item icon + ) { + ResourceLocation iconId = BuiltInRegistries.ITEM.getKey(icon); + return new DebugDataItem( + (ctx) -> { + final var player = ctx.getPlayer(); + final var level = ctx.getLevel(); + final var pos = ctx.getClickedPos(); + var state = level.getBlockState(pos); + var entity = level.getBlockEntity(pos); + var targetState = finalState; + if (!(entity instanceof JigsawBlockEntity)) { + if (targetState == null) { + + targetState = state.isAir() ? Blocks.STRUCTURE_VOID.defaultBlockState() : state; + } + + state = Blocks.JIGSAW.defaultBlockState(); + level.setBlock(pos, state, BlocksHelper.SET_SILENT); + entity = level.getBlockEntity(pos); + + message(player, "Created JigSaw at " + pos.toString()); + } + + if (entity instanceof JigsawBlockEntity e) { + if (forceOrientation == null) { + state = PlaceCommand.setJigsawOrientation( + JigsawBlockEntity.JointType.ROLLABLE != type, + player, pos, state + ); + } else { + state = state.setValue(JigsawBlock.ORIENTATION, forceOrientation); + } + level.setBlock(pos, state, BlocksHelper.SET_SILENT); + + if (pool != null) e.setName(name); + if (pool != null) e.setTarget(target); + if (pool != null) e.setPool(pool); + if (targetState != null) e.setFinalState(BlockStateParser.serialize(targetState)); + e.setJoint(type); + + + message(player, "Did update Jigsaw at " + pos.toString()); + + return InteractionResult.SUCCESS; + } + return InteractionResult.FAIL; + }, + true, + iconId + ); + } + + public interface BlockStatePredicate { + boolean test(BlockState state); + } +} diff --git a/src/main/java/org/betterx/bclib/items/ModelProviderItem.java b/src/main/java/org/betterx/bclib/items/ModelProviderItem.java new file mode 100644 index 00000000..bb207265 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/ModelProviderItem.java @@ -0,0 +1,23 @@ +package org.betterx.bclib.items; + +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public class ModelProviderItem extends Item implements ItemModelProvider { + public ModelProviderItem(Properties settings) { + super(settings); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createItemModel(resourceLocation); + } +} diff --git a/src/main/java/org/betterx/bclib/items/boat/BaseBoatItem.java b/src/main/java/org/betterx/bclib/items/boat/BaseBoatItem.java new file mode 100644 index 00000000..32ac4066 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/boat/BaseBoatItem.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.items.boat; + +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.world.entity.vehicle.Boat; +import net.minecraft.world.item.BoatItem; + +public class BaseBoatItem extends BoatItem implements CustomBoatTypeOverride, ItemModelProvider { + BoatTypeOverride customType; + + public BaseBoatItem(boolean bl, BoatTypeOverride type, Properties properties) { + super(bl, Boat.Type.OAK, properties); + setCustomType(type); + } + + @Override + public void setCustomType(BoatTypeOverride type) { + customType = type; + } + + @Override + public BoatTypeOverride bcl_getCustomType() { + return customType; + } +} diff --git a/src/main/java/org/betterx/bclib/items/boat/BoatTypeOverride.java b/src/main/java/org/betterx/bclib/items/boat/BoatTypeOverride.java new file mode 100644 index 00000000..c5498198 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/boat/BoatTypeOverride.java @@ -0,0 +1,195 @@ +package org.betterx.bclib.items.boat; + +import org.betterx.bclib.BCLib; + +import net.minecraft.client.model.*; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.vehicle.Boat; +import net.minecraft.world.item.BoatItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public final class BoatTypeOverride { + private static final String DEFAULT_LAYER = "main"; + + private static final List values = new ArrayList<>(8); + private final String name; + private final Block planks; + private final int ordinal; + public final ResourceLocation id; + public final ResourceLocation boatTexture; + public final ResourceLocation chestBoatTexture; + public final ModelLayerLocation boatModelName; + public final ModelLayerLocation chestBoatModelName; + @Environment(value = EnvType.CLIENT) + private ListModel boatModel, chestBoatModel; + private BoatItem boat, chestBoat; + public final boolean isRaft; + + BoatTypeOverride(String modID, String name, Block planks) { + this(modID, name, planks, false); + } + + BoatTypeOverride(String modID, String name, Block planks, boolean isRaft) { + this.id = new ResourceLocation(modID, name); + this.name = name; + this.planks = planks; + int nr = Objects.hash(name); + if (nr >= 0 && nr <= 1000) nr += 1000; + while (byId(nr) != null) { + BCLib.LOGGER.warning("Boat Type Ordinal " + nr + " is already used, searching for another one"); + nr++; + if (nr >= 0 && nr <= 1000) nr += 1000; + } + this.ordinal = nr; + this.isRaft = isRaft; + if (BCLib.isClient()) { + this.boatModelName = createBoatModelName(id.getNamespace(), id.getPath()); + this.chestBoatModelName = createChestBoatModelName(id.getNamespace(), id.getPath()); + this.boatTexture = getTextureLocation(modID, name, false); + this.chestBoatTexture = getTextureLocation(modID, name, true); + } else { + this.boatModelName = null; + this.chestBoatModelName = null; + this.boatTexture = null; + this.chestBoatTexture = null; + } + + values.add(this); + } + + @Environment(value = EnvType.CLIENT) + public ListModel getBoatModel(boolean chest) { + return chest ? chestBoatModel : boatModel; + } + + @Environment(value = EnvType.CLIENT) + public void createBoatModels(EntityRendererProvider.Context context) { + if (BCLib.isClient() && boatModel == null) { + if (isRaft) { + boatModel = new RaftModel(context.bakeLayer(boatModelName)); + chestBoatModel = new ChestRaftModel(context.bakeLayer(chestBoatModelName)); + } else { + boatModel = new BoatModel(context.bakeLayer(boatModelName)); + chestBoatModel = new ChestBoatModel(context.bakeLayer(chestBoatModelName)); + } + } + } + + public Block getPlanks() { + return planks; + } + + public void setBoatItem(BoatItem item) { + this.boat = item; + } + + public BoatItem getBoatItem() { + return boat; + } + + public void setChestBoatItem(BoatItem item) { + this.chestBoat = item; + } + + public BoatItem getChestBoatItem() { + return chestBoat; + } + + public static Stream values() { + return values.stream(); + } + + private static ModelLayerLocation createBoatModelName(String modID, String name) { + return new ModelLayerLocation(new ResourceLocation(modID, "boat/" + name), DEFAULT_LAYER); + } + + private static ModelLayerLocation createChestBoatModelName(String modID, String name) { + return new ModelLayerLocation(new ResourceLocation(modID, "chest_boat/" + name), DEFAULT_LAYER); + } + + private static ResourceLocation getTextureLocation(String modID, String name, boolean chest) { + if (chest) { + return new ResourceLocation(modID, "textures/entity/chest_boat/" + name + ".png"); + } + return new ResourceLocation(modID, "textures/entity/boat/" + name + ".png"); + } + + public static BoatTypeOverride create(String modID, String name, Block planks) { + return create(modID, name, planks, false); + } + + public static BoatTypeOverride create(String modID, String name, Block planks, boolean isRaft) { + BoatTypeOverride t = new BoatTypeOverride(modID, name, planks, isRaft); + + return t; + } + + public BoatItem createItem(boolean hasChest) { + return createItem(hasChest, new Item.Properties().stacksTo(1)); + } + + public BoatItem createItem(boolean hasChest, Item.Properties itemSettings) { + BoatItem item = new BaseBoatItem(hasChest, this, itemSettings); + + if (hasChest) this.setChestBoatItem(item); + else this.setBoatItem(item); + + return item; + } + + public static BoatTypeOverride byId(int i) { + for (BoatTypeOverride t : values) { + if (t.ordinal == i) return t; + } + return null; + } + + public static BoatTypeOverride byName(String string) { + for (BoatTypeOverride t : values) { + if (!t.name().equals(string)) continue; + return t; + } + return null; + } + + public String name() { + return name; + } + + public int ordinal() { + return ordinal; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (BoatTypeOverride) obj; + return Objects.equals(this.name, that.name) && + this.ordinal == that.ordinal; + } + + @Override + public int hashCode() { + return Objects.hash(name, ordinal); + } + + @Override + public String toString() { + return "BoatTypeOverride[" + + "name=" + name + ", " + + "ordinal=" + ordinal + ']'; + } + +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/items/boat/CustomBoatTypeOverride.java b/src/main/java/org/betterx/bclib/items/boat/CustomBoatTypeOverride.java new file mode 100644 index 00000000..4d1ddd99 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/boat/CustomBoatTypeOverride.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.items.boat; + +public interface CustomBoatTypeOverride { + void setCustomType(BoatTypeOverride type); + BoatTypeOverride bcl_getCustomType(); +} diff --git a/src/main/java/org/betterx/bclib/items/complex/EquipmentDescription.java b/src/main/java/org/betterx/bclib/items/complex/EquipmentDescription.java new file mode 100644 index 00000000..efb23730 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/complex/EquipmentDescription.java @@ -0,0 +1,109 @@ +package org.betterx.bclib.items.complex; + +import org.betterx.bclib.items.BaseArmorItem; +import org.betterx.bclib.recipes.BCLRecipeBuilder; +import org.betterx.bclib.recipes.CraftingRecipeBuilder; +import org.betterx.bclib.registry.ItemRegistry; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.*; +import net.minecraft.world.level.ItemLike; + +import java.util.function.Function; +import org.jetbrains.annotations.Nullable; + +public class EquipmentDescription { + private final Function creator; + private I item; + public final org.betterx.bclib.items.complex.EquipmentSlot slot; + + public EquipmentDescription(org.betterx.bclib.items.complex.EquipmentSlot slot, Function creator) { + this.creator = creator; + this.slot = slot; + } + + public void init( + ResourceLocation id, + ItemRegistry itemsRegistry, + Tier material, + ItemLike stick, + @Nullable EquipmentSet sourceSet + ) { + item = creator.apply(material); + itemsRegistry.registerTool(id, item); + + addRecipe(id, item, material, stick, sourceSet); + } + + public void addRecipe( + ResourceLocation id, + Item tool, + Tier material, + ItemLike stick, + @Nullable EquipmentSet sourceSet + ) { + if (material == null) return; + var repair = material.getRepairIngredient(); + if (repair == null) return; + var repairItems = repair.getItems(); + if (repairItems == null || repairItems.length == 0) return; + final ItemLike ingot = repairItems[0].getItem(); + + if (material instanceof SmithingSet smit && smit.getSmithingTemplateItem() != null && sourceSet != null) { + var builder = BCLRecipeBuilder + .smithing(id, tool) + .setTemplate(smit.getSmithingTemplateItem()) + .setPrimaryInput(sourceSet.getSlot(this.slot)) + .setAdditionAndUnlock(ingot) + .setCategory(slot.category()); + + builder.build(); + } else { + var builder = BCLRecipeBuilder.crafting(id, tool) + .addMaterial('#', ingot) + .setCategory(RecipeCategory.TOOLS); + + if (buildRecipe(tool, stick, builder)) return; + builder + .setGroup(id.getPath()) + .build(); + } + + } + + protected boolean buildRecipe(Item tool, ItemLike stick, CraftingRecipeBuilder builder) { + if (tool instanceof ShearsItem) { + builder.setShape(" #", "# "); + } else if (tool instanceof BaseArmorItem bai) { + if (bai.getType().getSlot() == EquipmentSlot.FEET) { + builder.setShape("# #", "# #"); + } else if (bai.getType().getSlot() == EquipmentSlot.HEAD) { + builder.setShape("###", "# #"); + } else if (bai.getType().getSlot() == EquipmentSlot.CHEST) { + builder.setShape("# #", "###", "###"); + } else if (bai.getType().getSlot() == EquipmentSlot.LEGS) { + builder.setShape("###", "# #", "# #"); + } else return true; + } else { + builder.addMaterial('I', stick); + if (tool instanceof PickaxeItem) { + builder.setShape("###", " I ", " I "); + } else if (tool instanceof AxeItem) { + builder.setShape("##", "#I", " I"); + } else if (tool instanceof HoeItem) { + builder.setShape("##", " I", " I"); + } else if (tool instanceof ShovelItem) { + builder.setShape("#", "I", "I"); + } else if (tool instanceof SwordItem) { + builder.setShape("#", "#", "I"); + } else return true; + } + return false; + } + + public I getItem() { + return item; + } +} diff --git a/src/main/java/org/betterx/bclib/items/complex/EquipmentSet.java b/src/main/java/org/betterx/bclib/items/complex/EquipmentSet.java new file mode 100644 index 00000000..2a983b88 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/complex/EquipmentSet.java @@ -0,0 +1,248 @@ +package org.betterx.bclib.items.complex; + +import org.betterx.bclib.registry.ItemRegistry; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Tier; +import net.minecraft.world.level.ItemLike; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import org.jetbrains.annotations.NotNull; + +public abstract class EquipmentSet { + public static class AttackDamage { + public static SetValues WOOD_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, 3) + .add(EquipmentSet.SHOVEL_SLOT, 1.5f) + .add(EquipmentSet.PICKAXE_SLOT, 1) + .add(EquipmentSet.AXE_SLOT, 6) + .add(EquipmentSet.HOE_SLOT, 0); + + public static SetValues STONE_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, 3) + .add(EquipmentSet.SHOVEL_SLOT, 1.5f) + .add(EquipmentSet.PICKAXE_SLOT, 1) + .add(EquipmentSet.AXE_SLOT, 7) + .add(EquipmentSet.HOE_SLOT, -1); + + public static SetValues GOLDEN_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, 3) + .add(EquipmentSet.SHOVEL_SLOT, 1.5f) + .add(EquipmentSet.PICKAXE_SLOT, 1) + .add(EquipmentSet.AXE_SLOT, 6) + .add(EquipmentSet.HOE_SLOT, 0); + public static SetValues IRON_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, 3) + .add(EquipmentSet.SHOVEL_SLOT, 1.5f) + .add(EquipmentSet.PICKAXE_SLOT, 1) + .add(EquipmentSet.AXE_SLOT, 6) + .add(EquipmentSet.HOE_SLOT, -2); + + public static SetValues DIAMOND_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, 3) + .add(EquipmentSet.SHOVEL_SLOT, 1.5f) + .add(EquipmentSet.PICKAXE_SLOT, 1) + .add(EquipmentSet.AXE_SLOT, 5) + .add(EquipmentSet.HOE_SLOT, -3); + + public static SetValues NETHERITE_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, 3) + .add(EquipmentSet.SHOVEL_SLOT, 1.5f) + .add(EquipmentSet.PICKAXE_SLOT, 1) + .add(EquipmentSet.AXE_SLOT, 5) + .add(EquipmentSet.HOE_SLOT, -4); + } + + public static class AttackSpeed { + public static SetValues WOOD_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, -2.4f) + .add(EquipmentSet.SHOVEL_SLOT, -3.0f) + .add(EquipmentSet.PICKAXE_SLOT, -2.8f) + .add(EquipmentSet.AXE_SLOT, -3.2f) + .add(EquipmentSet.HOE_SLOT, -3.0f); + + public static SetValues STONE_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, -2.4f) + .add(EquipmentSet.SHOVEL_SLOT, -3.0f) + .add(EquipmentSet.PICKAXE_SLOT, -2.8f) + .add(EquipmentSet.AXE_SLOT, -3.2f) + .add(EquipmentSet.HOE_SLOT, -2.0f); + + public static SetValues GOLDEN_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, -2.4f) + .add(EquipmentSet.SHOVEL_SLOT, -3.0f) + .add(EquipmentSet.PICKAXE_SLOT, -2.8f) + .add(EquipmentSet.AXE_SLOT, -3.0f) + .add(EquipmentSet.HOE_SLOT, -3.0f); + public static SetValues IRON_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, -2.4f) + .add(EquipmentSet.SHOVEL_SLOT, -3.0f) + .add(EquipmentSet.PICKAXE_SLOT, -2.8f) + .add(EquipmentSet.AXE_SLOT, -3.1f) + .add(EquipmentSet.HOE_SLOT, -1.0f); + + public static SetValues DIAMOND_LEVEL = EquipmentSet.SetValues + .create() + .add(EquipmentSet.SWORD_SLOT, -2.4f) + .add(EquipmentSet.SHOVEL_SLOT, -3.0f) + .add(EquipmentSet.PICKAXE_SLOT, -2.8f) + .add(EquipmentSet.AXE_SLOT, -3.0f) + .add(EquipmentSet.HOE_SLOT, 0.0f); + + public static SetValues NETHERITE_LEVEL = DIAMOND_LEVEL; + } + + public interface ItemDescriptorCreator { + EquipmentDescription build(EquipmentSlot slot, Item base, Function creator); + } + + public interface DescriptorCreator { + EquipmentDescription build(EquipmentSlot slot, Function creator); + } + + public interface ItemCreator { + I build(Tier t, float attackDamage, float attackSpeed); + } + + public static class SetValues { + private final Map values; + + private SetValues() { + values = new HashMap<>(); + } + + public static SetValues create() { + return new SetValues(); + } + + public static SetValues copy(SetValues source, float offset) { + SetValues v = create(); + for (var e : source.values.entrySet()) + v.add(e.getKey(), e.getValue() + offset); + return v; + } + + public SetValues add(EquipmentSlot slot, float value) { + values.put(slot, value); + return this; + } + + public SetValues offset(EquipmentSlot slot, float offset) { + values.put(slot, get(slot) + offset); + return this; + } + + public float get(EquipmentSlot slot) { + return values.getOrDefault(slot, 0.0f); + } + } + + public final Tier material; + public final String baseName; + public final String modID; + public final ItemLike stick; + + public static final EquipmentSlot PICKAXE_SLOT = new EquipmentSlot("pickaxe", RecipeCategory.TOOLS); + public static final EquipmentSlot AXE_SLOT = new EquipmentSlot("axe", RecipeCategory.TOOLS); + public static final EquipmentSlot SHOVEL_SLOT = new EquipmentSlot("shovel", RecipeCategory.TOOLS); + public static final EquipmentSlot SWORD_SLOT = new EquipmentSlot("sword", RecipeCategory.COMBAT); + public static final EquipmentSlot HOE_SLOT = new EquipmentSlot("hoe", RecipeCategory.TOOLS); + public static final EquipmentSlot SHEARS_SLOT = new EquipmentSlot("shears", RecipeCategory.TOOLS); + public static final EquipmentSlot HELMET_SLOT = new EquipmentSlot("helmet", RecipeCategory.COMBAT); + public static final EquipmentSlot CHESTPLATE_SLOT = new EquipmentSlot("chestplate", RecipeCategory.COMBAT); + public static final EquipmentSlot LEGGINGS_SLOT = new EquipmentSlot("leggings", RecipeCategory.COMBAT); + public static final EquipmentSlot BOOTS_SLOT = new EquipmentSlot("boots", RecipeCategory.COMBAT); + + public final SetValues attackDamage; + public final SetValues attackSpeed; + + private final Map> descriptions = new HashMap<>(); + protected final EquipmentSet sourceSet; + + public EquipmentSet( + Tier material, + String modID, + String baseName, + ItemLike stick, + SetValues attackDamage, + SetValues attackSpeed + ) { + this(material, modID, baseName, stick, null, attackDamage, attackSpeed); + } + + public EquipmentSet( + Tier material, + String modID, + String baseName, + ItemLike stick, + EquipmentSet sourceSet, + SetValues attackDamage, + SetValues attackSpeed + ) { + this.material = material; + this.baseName = baseName; + this.modID = modID; + this.stick = stick; + this.attackDamage = attackDamage; + this.attackSpeed = attackSpeed; + this.sourceSet = sourceSet; + } + + protected void add(EquipmentSlot slot, EquipmentDescription desc) { + descriptions.put(slot, desc); + } + + protected void add( + EquipmentSlot slot, + EquipmentSet baseSet, + ItemDescriptorCreator descriptor, + ItemCreator item + ) { + EquipmentDescription desc = descriptor.build( + slot, + baseSet.getSlot(slot), + (tier) -> item.build(tier, this.attackDamage.get(slot), this.attackSpeed.get(slot)) + ); + descriptions.put(slot, desc); + } + + protected void add(EquipmentSlot slot, DescriptorCreator descriptor, ItemCreator item) { + EquipmentDescription desc = descriptor.build( + slot, + (tier) -> item.build(tier, this.attackDamage.get(slot), this.attackSpeed.get(slot)) + ); + descriptions.put(slot, desc); + } + + + public EquipmentSet init(ItemRegistry itemsRegistry) { + for (var desc : descriptions.entrySet()) { + desc.getValue() + .init(buildID(desc), itemsRegistry, material, stick, sourceSet); + } + return this; + } + + @NotNull + protected ResourceLocation buildID(Map.Entry> desc) { + return new ResourceLocation(modID, baseName + "_" + desc.getKey().name()); + } + + public I getSlot(EquipmentSlot slot) { + return (I) descriptions.get(slot).getItem(); + } +} diff --git a/src/main/java/org/betterx/bclib/items/complex/EquipmentSlot.java b/src/main/java/org/betterx/bclib/items/complex/EquipmentSlot.java new file mode 100644 index 00000000..e43b31e6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/complex/EquipmentSlot.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.items.complex; + +import net.minecraft.data.recipes.RecipeCategory; + +public record EquipmentSlot(String name, RecipeCategory category) { +} diff --git a/src/main/java/org/betterx/bclib/items/complex/SmithingSet.java b/src/main/java/org/betterx/bclib/items/complex/SmithingSet.java new file mode 100644 index 00000000..629c98f9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/complex/SmithingSet.java @@ -0,0 +1,7 @@ +package org.betterx.bclib.items.complex; + +import net.minecraft.world.item.SmithingTemplateItem; + +public interface SmithingSet { + SmithingTemplateItem getSmithingTemplateItem(); +} diff --git a/src/main/java/org/betterx/bclib/items/elytra/BCLElytraItem.java b/src/main/java/org/betterx/bclib/items/elytra/BCLElytraItem.java new file mode 100644 index 00000000..762c9a55 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/elytra/BCLElytraItem.java @@ -0,0 +1,31 @@ +package org.betterx.bclib.items.elytra; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.gameevent.GameEvent; + +import net.fabricmc.fabric.api.entity.event.v1.FabricElytraItem; + +public interface BCLElytraItem extends FabricElytraItem { + ResourceLocation getModelTexture(); + + double getMovementFactor(); + + + default void doVanillaElytraTick(LivingEntity entity, ItemStack chestStack) { + vanillaElytraTick(entity, chestStack); + } + + static void vanillaElytraTick(LivingEntity entity, ItemStack chestStack) { + int nextRoll = entity.getFallFlyingTicks() + 1; + + if (!entity.level().isClientSide && nextRoll % 10 == 0) { + if ((nextRoll / 10) % 2 == 0) { + chestStack.hurtAndBreak(1, entity, (e) -> BCLElytraUtils.onBreak.accept(e, chestStack)); + } + + entity.gameEvent(GameEvent.ELYTRA_GLIDE); + } + } +} diff --git a/src/main/java/org/betterx/bclib/items/elytra/BCLElytraUtils.java b/src/main/java/org/betterx/bclib/items/elytra/BCLElytraUtils.java new file mode 100644 index 00000000..ee20dfed --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/elytra/BCLElytraUtils.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.items.elytra; + +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class BCLElytraUtils { + @FunctionalInterface + public interface SlotProvider { + ItemStack getElytra(LivingEntity entity, Function slotGetter); + } + + public static SlotProvider slotProvider = null; + public static BiConsumer onBreak = + (entity, chestStack) -> entity.broadcastBreakEvent(EquipmentSlot.CHEST); +} diff --git a/src/main/java/org/betterx/bclib/items/tool/BaseAxeItem.java b/src/main/java/org/betterx/bclib/items/tool/BaseAxeItem.java new file mode 100644 index 00000000..90807831 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/tool/BaseAxeItem.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.items.tool; + +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.AxeItem; +import net.minecraft.world.item.Tier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public class BaseAxeItem extends AxeItem implements ItemModelProvider { + public BaseAxeItem(Tier material, float attackDamage, float attackSpeed, Properties settings) { + super(material, attackDamage, attackSpeed, settings); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createHandheldItem(resourceLocation); + } +} diff --git a/src/main/java/org/betterx/bclib/items/tool/BaseHoeItem.java b/src/main/java/org/betterx/bclib/items/tool/BaseHoeItem.java new file mode 100644 index 00000000..f316166a --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/tool/BaseHoeItem.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.items.tool; + +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.HoeItem; +import net.minecraft.world.item.Tier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public class BaseHoeItem extends HoeItem implements ItemModelProvider { + public BaseHoeItem(Tier material, int attackDamage, float attackSpeed, Properties settings) { + super(material, attackDamage, attackSpeed, settings); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createHandheldItem(resourceLocation); + } +} diff --git a/src/main/java/org/betterx/bclib/items/tool/BasePickaxeItem.java b/src/main/java/org/betterx/bclib/items/tool/BasePickaxeItem.java new file mode 100644 index 00000000..1ed0e38d --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/tool/BasePickaxeItem.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.items.tool; + +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.PickaxeItem; +import net.minecraft.world.item.Tier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public class BasePickaxeItem extends PickaxeItem implements ItemModelProvider { + public BasePickaxeItem(Tier material, int attackDamage, float attackSpeed, Properties settings) { + super(material, attackDamage, attackSpeed, settings); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createHandheldItem(resourceLocation); + } +} diff --git a/src/main/java/org/betterx/bclib/items/tool/BaseShearsItem.java b/src/main/java/org/betterx/bclib/items/tool/BaseShearsItem.java new file mode 100644 index 00000000..84c4a602 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/tool/BaseShearsItem.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.items.tool; + + +import org.betterx.worlds.together.tag.v3.CommonItemTags; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.ShearsItem; + +import net.fabricmc.fabric.api.mininglevel.v1.FabricMineableTags; + +public class BaseShearsItem extends ShearsItem { + public BaseShearsItem(Properties properties) { + super(properties); + } + + public static boolean isShear(ItemStack tool) { + return tool.is(Items.SHEARS) | tool.is(CommonItemTags.SHEARS) || TagManager.isToolWithMineableTag( + tool, + FabricMineableTags.SHEARS_MINEABLE + ); + } + + public static boolean isShear(ItemStack itemStack, Item item) { + if (item == Items.SHEARS) { + return itemStack.is(item) | itemStack.is(CommonItemTags.SHEARS); + } else { + return itemStack.is(item); + } + } +} diff --git a/src/main/java/org/betterx/bclib/items/tool/BaseShovelItem.java b/src/main/java/org/betterx/bclib/items/tool/BaseShovelItem.java new file mode 100644 index 00000000..4dd0e746 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/tool/BaseShovelItem.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.items.tool; + +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ShovelItem; +import net.minecraft.world.item.Tier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public class BaseShovelItem extends ShovelItem implements ItemModelProvider { + public BaseShovelItem(Tier material, float attackDamage, float attackSpeed, Properties settings) { + super(material, attackDamage, attackSpeed, settings); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createHandheldItem(resourceLocation); + } +} diff --git a/src/main/java/org/betterx/bclib/items/tool/BaseSwordItem.java b/src/main/java/org/betterx/bclib/items/tool/BaseSwordItem.java new file mode 100644 index 00000000..960c9389 --- /dev/null +++ b/src/main/java/org/betterx/bclib/items/tool/BaseSwordItem.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.items.tool; + +import org.betterx.bclib.client.models.ModelsHelper; +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.SwordItem; +import net.minecraft.world.item.Tier; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +public class BaseSwordItem extends SwordItem implements ItemModelProvider { + public BaseSwordItem(Tier material, int attackDamage, float attackSpeed, Properties settings) { + super(material, attackDamage, attackSpeed, settings); + } + + @Override + @Environment(EnvType.CLIENT) + public BlockModel getItemModel(ResourceLocation resourceLocation) { + return ModelsHelper.createHandheldItem(resourceLocation); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/AnvilScreenMixin.java b/src/main/java/org/betterx/bclib/mixin/client/AnvilScreenMixin.java new file mode 100644 index 00000000..e53e90e9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/AnvilScreenMixin.java @@ -0,0 +1,124 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.interfaces.AnvilScreenHandlerExtended; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.events.ContainerEventHandler; +import net.minecraft.client.gui.screens.inventory.AnvilScreen; +import net.minecraft.client.gui.screens.inventory.ItemCombinerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.AnvilMenu; +import net.minecraft.world.item.ItemStack; + +import com.google.common.collect.Lists; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(AnvilScreen.class) +@Implements(@Interface(iface = ContainerEventHandler.class, prefix = "bcl$")) +public class AnvilScreenMixin extends ItemCombinerScreen { + + @Shadow + private EditBox name; + + @Shadow + @Final + private static ResourceLocation ANVIL_LOCATION; + private final List be_buttons = Lists.newArrayList(); + + public AnvilScreenMixin(AnvilMenu handler, Inventory playerInventory, Component title, ResourceLocation texture) { + super(handler, playerInventory, title, texture); + } + + @Override + public void renderErrorIcon(GuiGraphics guiGraphics, int i, int j) { + if (this.bcl_hasRecipeError()) { + guiGraphics.blit(ANVIL_LOCATION, i + 65, j + 46, this.imageWidth, 0, 28, 21); + } + } + + private boolean bcl_hasRecipeError() { + //TODO: 1.19.4 check error conditions + return false; + } + + @Inject(method = "subInit", at = @At("TAIL")) + protected void be_subInit(CallbackInfo info) { + int x = (width - imageWidth) / 2; + int y = (height - imageHeight) / 2; + be_buttons.clear(); + be_buttons.add(Button.builder(Component.literal("<"), b -> be_previousRecipe()) + .bounds(x + 8, y + 45, 15, 20) + .build()); + be_buttons.add(Button.builder(Component.literal(">"), b -> be_nextRecipe()) + .bounds(x + 154, y + 45, 15, 20) + .build()); + + be_buttons.forEach(button -> addWidget(button)); + } + + @Inject(method = "renderFg", at = @At("TAIL")) + protected void be_renderForeground( + GuiGraphics guiGraphics, + int mouseX, + int mouseY, + float delta, + CallbackInfo info + ) { + be_buttons.forEach(button -> { + button.render(guiGraphics, mouseX, mouseY, delta); + }); + } + + @Inject(method = "slotChanged", at = @At("HEAD"), cancellable = true) + public void be_onSlotUpdate(AbstractContainerMenu handler, int slotId, ItemStack stack, CallbackInfo info) { + AnvilScreenHandlerExtended anvilHandler = (AnvilScreenHandlerExtended) handler; + if (anvilHandler.bcl_getCurrentRecipe() != null) { + if (anvilHandler.bcl_getRecipes().size() > 1) { + be_buttons.forEach(button -> button.visible = true); + } else { + be_buttons.forEach(button -> button.visible = false); + } + name.setValue(""); + info.cancel(); + } else { + be_buttons.forEach(button -> button.visible = false); + } + } + + private void be_nextRecipe() { + ((AnvilScreenHandlerExtended) menu).be_nextRecipe(); + } + + private void be_previousRecipe() { + ((AnvilScreenHandlerExtended) menu).be_previousRecipe(); + } + + + @Intrinsic(displace = true) + //@Override + public boolean bcl$mouseClicked(double mouseX, double mouseY, int button) { + if (minecraft != null) { + for (AbstractWidget elem : be_buttons) { + if (elem.visible && elem.mouseClicked(mouseX, mouseY, button)) { + if (minecraft.gameMode != null) { + int i = be_buttons.indexOf(elem); + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, i); + return true; + } + } + } + } + return super.mouseClicked(mouseX, mouseY, button); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/AtlasSetMixin.java b/src/main/java/org/betterx/bclib/mixin/client/AtlasSetMixin.java new file mode 100644 index 00000000..700152a4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/AtlasSetMixin.java @@ -0,0 +1,27 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.client.textures.AtlasSetManager; + +import net.minecraft.client.renderer.texture.atlas.SpriteResourceLoader; +import net.minecraft.client.renderer.texture.atlas.SpriteSource; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import java.util.List; + +@Mixin(SpriteResourceLoader.class) +public class AtlasSetMixin { + @ModifyVariable(method = "load", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/atlas/SpriteResourceLoader;(Ljava/util/List;)V")) + private static List bcl_load( + List list, + ResourceManager resourceManager, + ResourceLocation type + ) { + AtlasSetManager.onLoadResources(type, list); + return list; + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/BlockMixin.java b/src/main/java/org/betterx/bclib/mixin/client/BlockMixin.java new file mode 100644 index 00000000..dc07b070 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/BlockMixin.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.interfaces.SurvivesOnSpecialGround; + +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; +import org.jetbrains.annotations.Nullable; + +@Mixin(Block.class) +public class BlockMixin { + @Inject(method = "appendHoverText", at = @At("HEAD")) + void bclib_appendSurvivalBlock( + ItemStack itemStack, + @Nullable BlockGetter blockGetter, + List list, + TooltipFlag tooltipFlag, + CallbackInfo ci + ) { + if (this instanceof SurvivesOnSpecialGround surv) { + SurvivesOnSpecialGround.appendHoverText(surv, list); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/ClientLevelMixin.java b/src/main/java/org/betterx/bclib/mixin/client/ClientLevelMixin.java new file mode 100644 index 00000000..75e08f49 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/ClientLevelMixin.java @@ -0,0 +1,53 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.interfaces.ClientLevelAccess; +import org.betterx.bclib.interfaces.LevelRendererAccess; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.core.particles.ParticleOptions; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import org.jetbrains.annotations.Nullable; + +@Mixin(ClientLevel.class) +@Environment(EnvType.CLIENT) +public class ClientLevelMixin implements ClientLevelAccess { + @Shadow + @Final + private LevelRenderer levelRenderer; + + @Override + @Nullable + public LevelRendererAccess bcl_getLevelRenderer() { + if (this.levelRenderer instanceof LevelRendererAccess a) { + return a; + } + return null; + } + + @Override + @Nullable + public Particle bcl_addParticle( + ParticleOptions particleOptions, + double x, + double y, + double z, + double vx, + double vy, + double vz + ) { + var renderer = this.bcl_getLevelRenderer(); + if (renderer != null) { + return renderer.bcl_addParticle(particleOptions, x, y, z, vx, vy, vz); + } + return null; + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/ClientPacketListenerMixin.java b/src/main/java/org/betterx/bclib/mixin/client/ClientPacketListenerMixin.java new file mode 100644 index 00000000..effe328c --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/ClientPacketListenerMixin.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI; + +import net.minecraft.client.multiplayer.ClientPacketListener; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPacketListener.class) +public class ClientPacketListenerMixin { + + @Inject(method = "handleLogin", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/telemetry/WorldSessionTelemetryManager;onPlayerInfoReceived(Lnet/minecraft/world/level/GameType;Z)V")) + public void bclib_onStart(CallbackInfo ci) { + DataExchangeAPI.sendOnEnter(); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/ClientRecipeBookMixin.java b/src/main/java/org/betterx/bclib/mixin/client/ClientRecipeBookMixin.java new file mode 100644 index 00000000..ea8c32d6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/ClientRecipeBookMixin.java @@ -0,0 +1,22 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.interfaces.UnknownReceipBookCategory; + +import net.minecraft.client.ClientRecipeBook; +import net.minecraft.client.RecipeBookCategories; +import net.minecraft.world.item.crafting.Recipe; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ClientRecipeBook.class) +public abstract class ClientRecipeBookMixin { + @Inject(method = "getCategory", at = @At("HEAD"), cancellable = true) + private static void be_getGroupForRecipe(Recipe recipe, CallbackInfoReturnable info) { + if (recipe instanceof UnknownReceipBookCategory) { + info.setReturnValue(RecipeBookCategories.UNKNOWN); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/FogRendererMixin.java b/src/main/java/org/betterx/bclib/mixin/client/FogRendererMixin.java new file mode 100644 index 00000000..cd4e63ee --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/FogRendererMixin.java @@ -0,0 +1,73 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.client.render.CustomFogRenderer; +import org.betterx.bclib.util.BackgroundInfo; + +import net.minecraft.client.Camera; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.FogRenderer; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.material.FogType; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(FogRenderer.class) +public class FogRendererMixin { + @Shadow + private static float fogRed; + @Shadow + private static float fogGreen; + @Shadow + private static float fogBlue; + + @Inject(method = "setupColor", at = @At("RETURN")) + private static void bclib_onRender( + Camera camera, + float tickDelta, + ClientLevel world, + int i, + float f, + CallbackInfo info + ) { + FogType fogType = camera.getFluidInCamera(); + if (fogType != FogType.WATER && world.dimension().equals(Level.END)) { + Entity entity = camera.getEntity(); + boolean skip = false; + if (entity instanceof LivingEntity) { + MobEffectInstance effect = ((LivingEntity) entity).getEffect(MobEffects.NIGHT_VISION); + skip = effect != null && effect.getDuration() > 0; + } + if (!skip) { + fogRed *= 4; + fogGreen *= 4; + fogBlue *= 4; + } + } + + BackgroundInfo.fogColorRed = fogRed; + BackgroundInfo.fogColorGreen = fogGreen; + BackgroundInfo.fogColorBlue = fogBlue; + } + + @Inject(method = "setupFog", at = @At("HEAD"), cancellable = true) + private static void bclib_fogDensity( + Camera camera, + FogRenderer.FogMode fogMode, + float viewDistance, + boolean thickFog, + float g, + CallbackInfo ci + ) { + if (CustomFogRenderer.applyFogDensity(camera, viewDistance, thickFog)) { + ci.cancel(); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/LevelRendererMixin.java b/src/main/java/org/betterx/bclib/mixin/client/LevelRendererMixin.java new file mode 100644 index 00000000..02b5516d --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/LevelRendererMixin.java @@ -0,0 +1,122 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.interfaces.AirSelectionItem; +import org.betterx.bclib.interfaces.LevelRendererAccess; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.particle.Particle; +import net.minecraft.client.renderer.*; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.util.FastColor; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import org.joml.Matrix4f; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import org.jetbrains.annotations.Nullable; + +@Mixin(LevelRenderer.class) +@Environment(EnvType.CLIENT) +public abstract class LevelRendererMixin implements LevelRendererAccess { + @Final + @Shadow + private Minecraft minecraft; + @Shadow + @Final + private RenderBuffers renderBuffers; + + public Particle bcl_addParticle( + ParticleOptions particleOptions, + double x, double y, double z, + double vx, double vy, double vz + ) { + return this.addParticleInternal(particleOptions, false, x, y, z, vx, vy, vz); + } + + @Shadow + protected static void renderShape( + PoseStack poseStack, + VertexConsumer vertexConsumer, + VoxelShape voxelShape, + double x, double y, double z, + float r, float g, float b, float a + ) { + } + + @Shadow + @Nullable + protected abstract Particle addParticleInternal( + ParticleOptions particleOptions, + boolean bl, + double d, + double e, + double f, + double g, + double h, + double i + ); + + @Inject(method = "renderLevel", at = @At( + value = "INVOKE", + target = "Lcom/mojang/blaze3d/systems/RenderSystem;getModelViewStack()Lcom/mojang/blaze3d/vertex/PoseStack;", + shift = At.Shift.BEFORE + )) + public void bcl_renderLevel( + PoseStack poseStack, + float f, + long l, + boolean bl, + Camera camera, + GameRenderer gameRenderer, + LightTexture lightTexture, + Matrix4f matrix4f, + CallbackInfo info + ) { + if (BCLib.isDevEnvironment() && minecraft.hitResult instanceof BlockHitResult blockHitResult) { + //will render a block outline when empty blocks are targeted + ItemStack item = minecraft.player.getMainHandItem(); + if (item != null + && (item.getItem() instanceof AirSelectionItem airSelect) + && airSelect.renderAirSelection() + && blockHitResult.getType() == HitResult.Type.MISS + ) { + final BlockPos pos = blockHitResult.getBlockPos(); + final BlockState state = Blocks.DIRT.defaultBlockState(); + final int color = airSelect.airSelectionColor(); + final MultiBufferSource.BufferSource bufferSource = this.renderBuffers.bufferSource(); + final VertexConsumer consumer = bufferSource.getBuffer(RenderType.lines()); + final Vec3 camPos = camera.getPosition(); + + this.renderShape( + poseStack, consumer, + state.getShape(minecraft.level, pos, CollisionContext.of(camera.getEntity())), + pos.getX() - camPos.x(), pos.getY() - camPos.y(), pos.getZ() - camPos.z(), + FastColor.ARGB32.red(color) / (float) 0xff, + FastColor.ARGB32.green(color) / (float) 0xff, + FastColor.ARGB32.blue(color) / (float) 0xff, + FastColor.ARGB32.alpha(color) / (float) 0xff + ); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/MinecraftMixin.java b/src/main/java/org/betterx/bclib/mixin/client/MinecraftMixin.java new file mode 100644 index 00000000..ab3f828b --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/MinecraftMixin.java @@ -0,0 +1,48 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.interfaces.CustomColorProvider; +import org.betterx.bclib.networking.VersionCheckerClient; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.color.item.ItemColors; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.main.GameConfig; +import net.minecraft.core.registries.BuiltInRegistries; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import org.jetbrains.annotations.Nullable; + +@Mixin(Minecraft.class) +public abstract class MinecraftMixin { + @Final + @Shadow + private BlockColors blockColors; + + @Final + @Shadow + private ItemColors itemColors; + + + @Shadow + @Nullable + public Screen screen; + + @Inject(method = "*", at = @At("TAIL")) + private void bclib_onMCInit(GameConfig args, CallbackInfo info) { + BuiltInRegistries.BLOCK.forEach(block -> { + if (block instanceof CustomColorProvider provider) { + blockColors.register(provider.getProvider(), block); + itemColors.register(provider.getItemProvider(), block.asItem()); + } + }); + + VersionCheckerClient.presentUpdateScreen(screen); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/ModelManagerMixin.java b/src/main/java/org/betterx/bclib/mixin/client/ModelManagerMixin.java new file mode 100644 index 00000000..78d75184 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/ModelManagerMixin.java @@ -0,0 +1,32 @@ +package org.betterx.bclib.mixin.client; + +import org.betterx.bclib.client.BCLibClient; + +import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.server.packs.resources.PreparableReloadListener; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.profiling.ProfilerFiller; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@Mixin(ModelManager.class) +public class ModelManagerMixin { + @Inject(method = "reload", at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/util/profiling/ProfilerFiller;startTick()V")) + private void bclib_loadCustomModels( + PreparableReloadListener.PreparationBarrier preparationBarrier, + ResourceManager resourceManager, + ProfilerFiller profilerFiller, + ProfilerFiller profilerFiller2, + Executor executor, + Executor executor2, + CallbackInfoReturnable> cir + ) { + BCLibClient.lazyModelbakery().loadCustomModels(resourceManager); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/client/boat/BoatRendererMixin.java b/src/main/java/org/betterx/bclib/mixin/client/boat/BoatRendererMixin.java new file mode 100644 index 00000000..62da3630 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/client/boat/BoatRendererMixin.java @@ -0,0 +1,42 @@ +package org.betterx.bclib.mixin.client.boat; + + +import org.betterx.bclib.items.boat.BoatTypeOverride; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.BoatRenderer; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.world.entity.vehicle.Boat; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(BoatRenderer.class) +public abstract class BoatRendererMixin extends EntityRenderer { + protected BoatRendererMixin(EntityRendererProvider.Context context) { + super(context); + } + + @Inject(method = "", at = @At("TAIL")) + private void bcl_init(EntityRendererProvider.Context context, boolean bl, CallbackInfo ci) { + BoatTypeOverride.values().forEach(type -> type.createBoatModels(context)); + } + + @Inject(method = "render(Lnet/minecraft/world/entity/vehicle/Boat;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At("HEAD"), cancellable = true) + void bcl_render( + Boat boat, + float f, float g, + PoseStack poseStack, MultiBufferSource multiBufferSource, + int i, + CallbackInfo ci + ) { + if (org.betterx.bclib.client.render.BoatRenderer.render(boat, f, g, poseStack, multiBufferSource, i)) { + super.render(boat, f, g, poseStack, multiBufferSource, i); + ci.cancel(); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/AnvilBlockMixin.java b/src/main/java/org/betterx/bclib/mixin/common/AnvilBlockMixin.java new file mode 100644 index 00000000..7a85d149 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/AnvilBlockMixin.java @@ -0,0 +1,22 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.blocks.BaseAnvilBlock; + +import net.minecraft.world.level.block.AnvilBlock; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(AnvilBlock.class) +public class AnvilBlockMixin { + @Inject(method = "damage", at = @At("HEAD"), cancellable = true) + private static void bclib_anvilDamage(BlockState state, CallbackInfoReturnable info) { + if (state.getBlock() instanceof BaseAnvilBlock) { + BaseAnvilBlock anvil = (BaseAnvilBlock) state.getBlock(); + info.setReturnValue(anvil.damageAnvilFall(state)); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/AnvilMenuMixin.java b/src/main/java/org/betterx/bclib/mixin/common/AnvilMenuMixin.java new file mode 100644 index 00000000..d7ef0c97 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/AnvilMenuMixin.java @@ -0,0 +1,180 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.blocks.BaseAnvilBlock; +import org.betterx.bclib.blocks.LeveledAnvilBlock; +import org.betterx.bclib.interfaces.AnvilScreenHandlerExtended; +import org.betterx.bclib.recipes.AnvilRecipe; + +import net.minecraft.core.BlockPos; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.*; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.LevelEvent; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.jetbrains.annotations.Nullable; + +@Mixin(AnvilMenu.class) +public abstract class AnvilMenuMixin extends ItemCombinerMenu implements AnvilScreenHandlerExtended { + private List bcl_recipes = Collections.emptyList(); + private AnvilRecipe bcl_currentRecipe; + private DataSlot anvilLevel; + + @Shadow + private int repairItemCountCost; + + @Final + @Shadow + private DataSlot cost; + + public AnvilMenuMixin( + @Nullable MenuType menuType, + int i, + Inventory inventory, + ContainerLevelAccess containerLevelAccess + ) { + super(menuType, i, inventory, containerLevelAccess); + } + + @Inject(method = "(ILnet/minecraft/world/entity/player/Inventory;Lnet/minecraft/world/inventory/ContainerLevelAccess;)V", at = @At("TAIL")) + public void be_initAnvilLevel(int syncId, Inventory inventory, ContainerLevelAccess context, CallbackInfo info) { + this.anvilLevel = addDataSlot(DataSlot.standalone()); + if (context != ContainerLevelAccess.NULL) { + int level = context.evaluate((world, blockPos) -> { + Block anvilBlock = world.getBlockState(blockPos).getBlock(); + return LeveledAnvilBlock.getAnvilCraftingLevel(anvilBlock); + }, 0); + anvilLevel.set(level); + } else { + anvilLevel.set(0); + } + } + + @Shadow + public abstract void createResult(); + + @Inject(method = "mayPickup", at = @At("HEAD"), cancellable = true) + protected void bcl_canTakeOutput(Player player, boolean present, CallbackInfoReturnable info) { + if (bcl_currentRecipe != null) { + info.setReturnValue(bcl_currentRecipe.checkHammerDurability(inputSlots, player)); + } + } + + @Inject(method = "method_24922", at = @At(value = "HEAD"), cancellable = true) + private static void bcl_onDamageAnvil(Player player, Level level, BlockPos blockPos, CallbackInfo ci) { + BlockState blockState = level.getBlockState(blockPos); + if (!player.getAbilities().instabuild + && blockState.getBlock() instanceof BaseAnvilBlock anvil + && player.getRandom().nextDouble() < 0.12) { + BlockState damaged = anvil.damageAnvilUse(blockState, player.getRandom()); + BaseAnvilBlock.destroyWhenNull(level, blockPos, damaged); + ci.cancel(); + } + } + + + @Inject(method = "onTake", at = @At("HEAD"), cancellable = true) + protected void bcl_onTakeAnvilOutput(Player player, ItemStack stack, CallbackInfo info) { + if (bcl_currentRecipe != null) { + final int ingredientSlot = AnvilRecipe.getIngredientSlot(inputSlots); + + inputSlots.getItem(ingredientSlot).shrink(bcl_currentRecipe.getInputCount()); + stack = bcl_currentRecipe.craft(inputSlots, player); + slotsChanged(inputSlots); + access.execute((level, blockPos) -> { + final BlockState anvilState = level.getBlockState(blockPos); + final Block anvilBlock = anvilState.getBlock(); + if (anvilBlock instanceof BaseAnvilBlock anvil) { + if (!player.getAbilities().instabuild + && anvilState.is(BlockTags.ANVIL) + && player.getRandom().nextDouble() < 0.1) { + BlockState damagedState = anvil.damageAnvilUse(anvilState, player.getRandom()); + BaseAnvilBlock.destroyWhenNull(level, blockPos, damagedState); + } else { + level.levelEvent(LevelEvent.SOUND_ANVIL_USED, blockPos, 0); + } + } + }); + info.cancel(); + } + } + + @Inject(method = "createResult", at = @At("HEAD"), cancellable = true) + public void bcl_updateOutput(CallbackInfo info) { + RecipeManager recipeManager = this.player.level().getRecipeManager(); + bcl_recipes = recipeManager.getRecipesFor(AnvilRecipe.TYPE, inputSlots, player.level()); + if (bcl_recipes.size() > 0) { + int anvilLevel = this.anvilLevel.get(); + bcl_recipes = bcl_recipes.stream() + .filter(recipe -> anvilLevel >= recipe.getAnvilLevel()) + .collect(Collectors.toList()); + if (bcl_recipes.size() > 0) { + if (bcl_currentRecipe == null || !bcl_recipes.contains(bcl_currentRecipe)) { + bcl_currentRecipe = bcl_recipes.get(0); + } + bcl_updateResult(); + info.cancel(); + } else { + bcl_currentRecipe = null; + } + } + } + + @Inject(method = "setItemName", at = @At("HEAD"), cancellable = true) + public void bcl_setNewItemName(String string, CallbackInfoReturnable cir) { + if (bcl_currentRecipe != null) { + cir.setReturnValue(false); + cir.cancel(); + } + } + + @Override + public boolean clickMenuButton(Player player, int id) { + if (id == 0) { + be_previousRecipe(); + return true; + } else if (id == 1) { + be_nextRecipe(); + return true; + } + return super.clickMenuButton(player, id); + } + + private void bcl_updateResult() { + if (bcl_currentRecipe == null) return; + resultSlots.setItem(0, bcl_currentRecipe.assemble(inputSlots, this.player.level().registryAccess())); + broadcastChanges(); + } + + @Override + public void bcl_updateCurrentRecipe(AnvilRecipe recipe) { + this.bcl_currentRecipe = recipe; + bcl_updateResult(); + } + + @Override + public AnvilRecipe bcl_getCurrentRecipe() { + return bcl_currentRecipe; + } + + @Override + public List bcl_getRecipes() { + return bcl_recipes; + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/BiomeGenerationSettingsAccessor.java b/src/main/java/org/betterx/bclib/mixin/common/BiomeGenerationSettingsAccessor.java new file mode 100644 index 00000000..15c80ed4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/BiomeGenerationSettingsAccessor.java @@ -0,0 +1,39 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.core.HolderSet; +import net.minecraft.world.level.biome.BiomeGenerationSettings; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +@Mixin(BiomeGenerationSettings.class) +public interface BiomeGenerationSettingsAccessor { + @Accessor("features") + List> bclib_getFeatures(); + + @Accessor("features") + @Mutable + void bclib_setFeatures(List> value); + + @Accessor("featureSet") + void bclib_setFeatureSet(Supplier> featureSet); + + @Accessor("flowerFeatures") + void bclib_setFlowerFeatures(Supplier>> flowerFeatures); + + @Accessor("carvers") + Map>> bclib_getCarvers(); + + @Accessor("carvers") + void bclib_setCarvers(Map>> features); +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/BiomeMixin.java b/src/main/java/org/betterx/bclib/mixin/common/BiomeMixin.java new file mode 100644 index 00000000..bcade6fd --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/BiomeMixin.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.world.level.biome.Biome; + +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(Biome.class) +public class BiomeMixin { +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/BoneMealItemMixin.java b/src/main/java/org/betterx/bclib/mixin/common/BoneMealItemMixin.java new file mode 100644 index 00000000..44cb4963 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/BoneMealItemMixin.java @@ -0,0 +1,55 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.api.v3.bonemeal.BonemealAPI; +import org.betterx.bclib.blocks.FeatureSaplingBlock; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.item.BoneMealItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BonemealableBlock; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(BoneMealItem.class) +public class BoneMealItemMixin { + @Inject(method = "useOn", at = @At("HEAD"), cancellable = true) + private void bclib_onUse(UseOnContext context, CallbackInfoReturnable info) { + Level level = context.getLevel(); + final BlockPos blockPos = context.getClickedPos(); + + if (context.getPlayer().isCreative()) { + if (BonemealAPI.INSTANCE.runSpreaders(context.getItemInHand(), level, blockPos, true)) { + info.setReturnValue(InteractionResult.sidedSuccess(level.isClientSide)); + } + + final BlockState blockState = level.getBlockState(blockPos); + if (blockState.getBlock() instanceof BonemealableBlock bblock + && level instanceof ServerLevel server + && blockState.getBlock() instanceof FeatureSaplingBlock + ) { + bblock.performBonemeal(server, context.getLevel().getRandom(), blockPos, blockState); + info.setReturnValue(InteractionResult.sidedSuccess(level.isClientSide)); + } + } + } + + @Inject(method = "growCrop", at = @At("HEAD"), cancellable = true) + private static void bcl_growCrop( + ItemStack itemStack, + Level level, + BlockPos blockPos, + CallbackInfoReturnable cir + ) { + if (BonemealAPI.INSTANCE.runSpreaders(itemStack, level, blockPos, false)) { + cir.setReturnValue(true); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorMixin.java b/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorMixin.java new file mode 100644 index 00000000..4dd5655d --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/ChunkGeneratorMixin.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ChunkGenerator.class) +public class ChunkGeneratorMixin { + private int bclib_featureIteratorSeed; + + + @ModifyArg(method = "applyBiomeDecoration", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/WorldgenRandom;setFeatureSeed(JII)V")) + private long bclib_updateFeatureSeed(long seed) { + return Long.rotateRight(seed, bclib_featureIteratorSeed++); + } + + @Inject(method = "applyBiomeDecoration", at = @At("HEAD")) + private void bclib_obBiomeGenerate( + WorldGenLevel worldGenLevel, + ChunkAccess chunkAccess, + StructureManager structureFeatureManager, + CallbackInfo ci + ) { + bclib_featureIteratorSeed = 0; + } +} diff --git a/src/main/java/ru/bclib/mixin/common/ComposterBlockAccessor.java b/src/main/java/org/betterx/bclib/mixin/common/ComposterBlockAccessor.java similarity index 58% rename from src/main/java/ru/bclib/mixin/common/ComposterBlockAccessor.java rename to src/main/java/org/betterx/bclib/mixin/common/ComposterBlockAccessor.java index 81a2ba6c..48ffe51f 100644 --- a/src/main/java/ru/bclib/mixin/common/ComposterBlockAccessor.java +++ b/src/main/java/org/betterx/bclib/mixin/common/ComposterBlockAccessor.java @@ -1,15 +1,15 @@ -package ru.bclib.mixin.common; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -import net.minecraft.world.level.ItemLike; -import net.minecraft.world.level.block.ComposterBlock; - -@Mixin(ComposterBlock.class) -public interface ComposterBlockAccessor { - @Invoker - static void callAdd(float levelIncreaseChance, ItemLike item) { - throw new AssertionError("@Invoker dummy body called"); - } -} +package org.betterx.bclib.mixin.common; + +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.ComposterBlock; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(ComposterBlock.class) +public interface ComposterBlockAccessor { + @Invoker + static void callAdd(float levelIncreaseChance, ItemLike item) { + throw new AssertionError("@Invoker dummy body called"); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/CraftingMenuMixin.java b/src/main/java/org/betterx/bclib/mixin/common/CraftingMenuMixin.java new file mode 100644 index 00000000..6854d7b0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/CraftingMenuMixin.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ContainerLevelAccess; +import net.minecraft.world.inventory.CraftingMenu; +import net.minecraft.world.level.block.CraftingTableBlock; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(CraftingMenu.class) +public abstract class CraftingMenuMixin { + @Final + @Shadow + private ContainerLevelAccess access; + + @Inject(method = "stillValid", at = @At("HEAD"), cancellable = true) + private void bclib_stillValid(Player player, CallbackInfoReturnable info) { + if (access.evaluate((world, pos) -> { + BlockState state = world.getBlockState(pos); + return state.getBlock() instanceof CraftingTableBlock || state.is(CommonBlockTags.WORKBENCHES); + }, true)) { + info.setReturnValue(true); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/DiggerItemMixin.java b/src/main/java/org/betterx/bclib/mixin/common/DiggerItemMixin.java new file mode 100644 index 00000000..1fa10a78 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/DiggerItemMixin.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.api.v2.DiggerItemSpeed; + +import net.minecraft.world.item.DiggerItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; + +@Mixin(DiggerItem.class) +public class DiggerItemMixin { + @Inject(method = "getDestroySpeed", at = @At(value = "RETURN"), cancellable = true) + void bn_getDestroySpeed(ItemStack stack, BlockState state, CallbackInfoReturnable cir) { + final Optional newSpeed = DiggerItemSpeed.getModifiedSpeed(stack, state, cir.getReturnValue()); + if (newSpeed.isPresent()) { + cir.setReturnValue(newSpeed.get()); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/EnchantingTableBlockMixin.java b/src/main/java/org/betterx/bclib/mixin/common/EnchantingTableBlockMixin.java new file mode 100644 index 00000000..cc72f506 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/EnchantingTableBlockMixin.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EnchantmentTableBlock; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(EnchantmentTableBlock.class) +public abstract class EnchantingTableBlockMixin extends Block { + public EnchantingTableBlockMixin(Properties settings) { + super(settings); + } + + @Inject(method = "isValidBookShelf(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/core/BlockPos;)Z", at = @At("HEAD"), cancellable = true) + private static void bclib_isBookshelf( + Level level, + BlockPos tablePos, + BlockPos delta, + CallbackInfoReturnable info + ) { + if (level.getBlockState(tablePos.offset(delta)).is(CommonBlockTags.BOOKSHELVES) + && level.isEmptyBlock(tablePos.offset(delta.getX() / 2, delta.getY(), delta.getZ() / 2))) { + info.setReturnValue(true); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/ItemStackMixin.java b/src/main/java/org/betterx/bclib/mixin/common/ItemStackMixin.java new file mode 100644 index 00000000..19f1b440 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/ItemStackMixin.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.util.MethodReplace; + +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.function.Function; + +@Mixin(ItemStack.class) +public class ItemStackMixin { + @Inject(method = "is(Lnet/minecraft/world/item/Item;)Z", at = @At("HEAD"), cancellable = true) + private void bclib_replaceFunction(Item item, CallbackInfoReturnable info) { + Function replacement = MethodReplace.getItemReplace(item); + if (replacement != null) { + info.setReturnValue(replacement.apply(ItemStack.class.cast(this))); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/LayerLightSectionStorageMixin.java b/src/main/java/org/betterx/bclib/mixin/common/LayerLightSectionStorageMixin.java new file mode 100644 index 00000000..e2948d5b --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/LayerLightSectionStorageMixin.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.chunk.DataLayer; +import net.minecraft.world.level.lighting.LayerLightSectionStorage; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(LayerLightSectionStorage.class) +public class LayerLightSectionStorageMixin { + @Shadow + protected DataLayer getDataLayer(long sectionPos, boolean cached) { + return null; + } + + @Inject(method = "getStoredLevel", at = @At(value = "HEAD"), cancellable = true) + private void bclib_lightFix(long blockPos, CallbackInfoReturnable info) { + try { + long pos = SectionPos.blockToSection(blockPos); + DataLayer dataLayer = this.getDataLayer(pos, true); + info.setReturnValue(dataLayer.get( + SectionPos.sectionRelative(BlockPos.getX(blockPos)), + SectionPos.sectionRelative(BlockPos.getY(blockPos)), + SectionPos.sectionRelative(BlockPos.getZ(blockPos)) + )); + } catch (Exception e) { + info.setReturnValue(0); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/LootPoolMixin.java b/src/main/java/org/betterx/bclib/mixin/common/LootPoolMixin.java new file mode 100644 index 00000000..f39390a3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/LootPoolMixin.java @@ -0,0 +1,59 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.interfaces.LootPoolAccessor; + +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import net.minecraft.world.level.storage.loot.functions.LootItemFunction; +import net.minecraft.world.level.storage.loot.predicates.LootItemCondition; +import net.minecraft.world.level.storage.loot.providers.number.NumberProvider; + +import com.google.common.collect.Lists; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Predicate; + +@Mixin(LootPool.class) +public class LootPoolMixin implements LootPoolAccessor { + @Shadow + @Final + public LootPoolEntryContainer[] entries; + @Shadow + @Final + public LootItemCondition[] conditions; + @Shadow + @Final + private Predicate compositeCondition; + @Shadow + @Final + public LootItemFunction[] functions; + @Shadow + @Final + private BiFunction compositeFunction; + @Shadow + @Final + public NumberProvider rolls; + @Shadow + @Final + public NumberProvider bonusRolls; + + @Override + public LootPool bcl_mergeEntries(List newEntries) { + final List merged = Lists.newArrayList(entries); + merged.addAll(newEntries); + + return new LootPool( + merged.toArray(new LootPoolEntryContainer[0]), + this.conditions, + this.functions, + this.rolls, + this.bonusRolls + ); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/MobSpawnSettingsAccessor.java b/src/main/java/org/betterx/bclib/mixin/common/MobSpawnSettingsAccessor.java new file mode 100644 index 00000000..7207fcff --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/MobSpawnSettingsAccessor.java @@ -0,0 +1,22 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.util.random.WeightedRandomList; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.biome.MobSpawnSettings; +import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(MobSpawnSettings.class) +public interface MobSpawnSettingsAccessor { + @Accessor("spawners") + Map> bcl_getSpawners(); + + @Accessor("spawners") + @Mutable + void bcl_setSpawners(Map> spawners); +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/MultiPackResourceManagerMixin.java b/src/main/java/org/betterx/bclib/mixin/common/MultiPackResourceManagerMixin.java new file mode 100644 index 00000000..f6ed11c6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/MultiPackResourceManagerMixin.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.MultiPackResourceManager; +import net.minecraft.server.packs.resources.Resource; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; + +@Mixin(MultiPackResourceManager.class) +public class MultiPackResourceManagerMixin { + private static final String[] BCLIB_MISSING_RESOURCES = new String[]{ + "dimension/the_end.json", + "dimension/the_nether.json", + "dimension_type/the_end.json", + "dimension_type/the_nether.json" + }; + + @Inject(method = "getResource", at = @At("HEAD"), cancellable = true) + private void bclib_hasResource(ResourceLocation resourceLocation, CallbackInfoReturnable> info) { + if (resourceLocation.getNamespace().equals("minecraft")) { + for (String key : BCLIB_MISSING_RESOURCES) { + if (resourceLocation.getPath().equals(key)) { +// info.setReturnValue(Optional.empty()); +// info.cancel(); + return; + } + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/PistonBaseBlockMixin.java b/src/main/java/org/betterx/bclib/mixin/common/PistonBaseBlockMixin.java new file mode 100644 index 00000000..1401045b --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/PistonBaseBlockMixin.java @@ -0,0 +1,33 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.piston.PistonBaseBlock; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PistonBaseBlock.class) +public class PistonBaseBlockMixin { + @Inject(method = "isPushable", at = @At("HEAD"), cancellable = true) + private static void bclib_isPushable( + BlockState blockState, + Level level, + BlockPos blockPos, + Direction direction, + boolean bl, + Direction direction2, + CallbackInfoReturnable cir + ) { + if (blockState.is(CommonBlockTags.IMMOBILE)) { + cir.setReturnValue(false); + cir.cancel(); + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/PlacementContextMixin.java b/src/main/java/org/betterx/bclib/mixin/common/PlacementContextMixin.java new file mode 100644 index 00000000..d6bc7828 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/PlacementContextMixin.java @@ -0,0 +1,34 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.levelgen.placement.PlacementContext; + +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(PlacementContext.class) +public class PlacementContextMixin implements org.betterx.bclib.interfaces.BCLPlacementContext { + private Rotation bcl_rotation = Rotation.NONE; + private Mirror bcl_mirror = Mirror.NONE; + + + @Override + public Rotation bcl_getRotation() { + return bcl_rotation; + } + + @Override + public void bcl_setRotation(Rotation bcl_rotation) { + this.bcl_rotation = bcl_rotation; + } + + @Override + public Mirror bcl_getMirror() { + return bcl_mirror; + } + + @Override + public void bcl_setMirror(Mirror bcl_mirror) { + this.bcl_mirror = bcl_mirror; + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/PoiTypeMixin.java b/src/main/java/org/betterx/bclib/mixin/common/PoiTypeMixin.java new file mode 100644 index 00000000..6a081e01 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/PoiTypeMixin.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.api.v2.poi.PoiTypeExtension; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.ai.village.poi.PoiType; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PoiType.class) +public class PoiTypeMixin implements PoiTypeExtension { + private TagKey bcl_tag = null; + + + @Inject(method = "is", cancellable = true, at = @At("HEAD")) + void bcl_is(BlockState blockState, CallbackInfoReturnable cir) { + if (bcl_tag != null && blockState.is(bcl_tag)) { + cir.setReturnValue(true); + } + } + + public void bcl_setTag(TagKey tag) { + bcl_tag = tag; + } + + public TagKey bcl_getTag() { + return bcl_tag; + } + +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/PortalShapeMixin.java b/src/main/java/org/betterx/bclib/mixin/common/PortalShapeMixin.java new file mode 100644 index 00000000..6d3957f7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/PortalShapeMixin.java @@ -0,0 +1,50 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockBehaviour.StatePredicate; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.portal.PortalShape; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(PortalShape.class) +public class PortalShapeMixin { + @Redirect(method = "getDistanceUntilEdgeAboveFrame", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$StatePredicate;test(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z")) + private boolean be_getDistanceUntilEdgeAboveFrame( + StatePredicate statePredicate, + BlockState blockState, + BlockGetter blockGetter, + BlockPos blockPos + ) { + return be_FRAME(statePredicate, blockState, blockGetter, blockPos); + } + + @Redirect(method = "hasTopFrame", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$StatePredicate;test(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z")) + private boolean be_hasTopFrame( + StatePredicate statePredicate, + BlockState blockState, + BlockGetter blockGetter, + BlockPos blockPos + ) { + return be_FRAME(statePredicate, blockState, blockGetter, blockPos); + } + + @Redirect(method = "getDistanceUntilTop", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockBehaviour$StatePredicate;test(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z")) + private boolean be_getDistanceUntilTop( + StatePredicate statePredicate, + BlockState blockState, + BlockGetter blockGetter, + BlockPos blockPos + ) { + return be_FRAME(statePredicate, blockState, blockGetter, blockPos); + } + + private static boolean be_FRAME(StatePredicate FRAME, BlockState state, BlockGetter getter, BlockPos pos) { + return state.is(CommonBlockTags.NETHER_PORTAL_FRAME) || FRAME.test(state, getter, pos); + } +} diff --git a/src/main/java/ru/bclib/mixin/common/PotionBrewingAccessor.java b/src/main/java/org/betterx/bclib/mixin/common/PotionBrewingAccessor.java similarity index 61% rename from src/main/java/ru/bclib/mixin/common/PotionBrewingAccessor.java rename to src/main/java/org/betterx/bclib/mixin/common/PotionBrewingAccessor.java index b0c8e3f4..34d5f054 100644 --- a/src/main/java/ru/bclib/mixin/common/PotionBrewingAccessor.java +++ b/src/main/java/org/betterx/bclib/mixin/common/PotionBrewingAccessor.java @@ -1,16 +1,16 @@ -package ru.bclib.mixin.common; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Invoker; - -import net.minecraft.world.item.Item; -import net.minecraft.world.item.alchemy.Potion; -import net.minecraft.world.item.alchemy.PotionBrewing; - -@Mixin(PotionBrewing.class) -public interface PotionBrewingAccessor { - @Invoker - static void callAddMix(Potion input, Item item, Potion output) { - throw new AssertionError("@Invoker dummy body called"); - } -} +package org.betterx.bclib.mixin.common; + +import net.minecraft.world.item.Item; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.alchemy.PotionBrewing; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(PotionBrewing.class) +public interface PotionBrewingAccessor { + @Invoker + static void callAddMix(Potion input, Item item, Potion output) { + throw new AssertionError("@Invoker dummy body called"); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/RecipeManagerMixin.java b/src/main/java/org/betterx/bclib/mixin/common/RecipeManagerMixin.java new file mode 100644 index 00000000..9474601e --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/RecipeManagerMixin.java @@ -0,0 +1,67 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.recipes.BCLRecipeManager; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.Container; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.Level; + +import com.google.gson.JsonElement; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Map; +import java.util.Optional; + +@Mixin(RecipeManager.class) +public abstract class RecipeManagerMixin { + @Shadow + protected abstract > Map byType(RecipeType recipeType); + + @Inject(method = "apply(Ljava/util/Map;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", at = @At("HEAD")) + public void bcl_interceptApply( + Map map, + ResourceManager resourceManager, + ProfilerFiller profiler, + CallbackInfo info + ) { + BCLRecipeManager.removeDisabledRecipes(resourceManager, map); + } + + @Inject(method = "getRecipeFor(Lnet/minecraft/world/item/crafting/RecipeType;Lnet/minecraft/world/Container;Lnet/minecraft/world/level/Level;)Ljava/util/Optional;", at = @At("HEAD"), cancellable = true) + > void bcl_sort( + RecipeType recipeType, + C container, + Level level, + CallbackInfoReturnable> cir + ) { + var inter = this.byType(recipeType); + var all = inter.values().stream().filter((recipe) -> recipe.matches(container, level)).sorted((a, b) -> { + if (a.getId().getNamespace().equals(b.getId().getNamespace())) { + return a.getId().getPath().compareTo(b.getId().getPath()); + } + if (a.getId().getNamespace().equals("minecraft") && !b.getId().getNamespace().equals("minecraft")) { + return 1; + } else if (!a.getId().getNamespace().equals("minecraft") && b.getId().getNamespace().equals("minecraft")) { + return -1; + } else { + return a.getId().getNamespace().compareTo(b.getId().getNamespace()); + } + }).toList(); + + if (all.size() > 1) { + cir.setReturnValue(Optional.of(all.get(0))); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/mixin/common/RecipeMixin.java b/src/main/java/org/betterx/bclib/mixin/common/RecipeMixin.java new file mode 100644 index 00000000..1df41cdd --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/RecipeMixin.java @@ -0,0 +1,34 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.PotionItem; +import net.minecraft.world.item.alchemy.PotionUtils; +import net.minecraft.world.item.alchemy.Potions; +import net.minecraft.world.item.crafting.Recipe; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Recipe.class) +public interface RecipeMixin { + //Water Bottles are potions and they do not return an empty bottle in crafting Recipes + //This mixin will fix that behaviour + + @Inject(method = "getRemainingItems", at = @At("RETURN")) + default void bcl_getRemainingItems(C container, CallbackInfoReturnable> cir) { + NonNullList remaining = cir.getReturnValue(); + + for (int i = 0; i < remaining.size(); ++i) { + ItemStack stack = container.getItem(i); + if (stack.getItem() instanceof PotionItem pi) { + if (PotionUtils.getPotion(stack) == Potions.WATER) + remaining.set(i, new ItemStack(Items.GLASS_BOTTLE)); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/RegistryDataLoaderMixin.java b/src/main/java/org/betterx/bclib/mixin/common/RegistryDataLoaderMixin.java new file mode 100644 index 00000000..0cf4f571 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/RegistryDataLoaderMixin.java @@ -0,0 +1,45 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeData; + +import net.minecraft.resources.RegistryDataLoader; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.ArrayList; +import java.util.List; + +@Mixin(value = RegistryDataLoader.class, priority = 500) +public class RegistryDataLoaderMixin { + @Accessor("WORLDGEN_REGISTRIES") + @Mutable + static void wt_set_WORLDGEN_REGISTRIES(List> list) { + //SHADOWED + } + + @Inject(method = "", at = @At("TAIL")) + private static void bcl_init(CallbackInfo ci) { + //we need this to ensure, that the BCL-Biome Registry is loaded at the correct time + List> enhanced = new ArrayList(RegistryDataLoader.WORLDGEN_REGISTRIES.size() + 1); + enhanced.add(new RegistryDataLoader.RegistryData<>( + BCLBiomeRegistry.BCL_BIOMES_REGISTRY, BiomeData.CODEC + )); + enhanced.addAll(RegistryDataLoader.WORLDGEN_REGISTRIES); + wt_set_WORLDGEN_REGISTRIES(enhanced); + } + +// // Fabric force changes the directory path for all modded registries to be prefixed with the mod id. +// // We do not want this for our BCL-Biome/Surface Rule Registry, so we remove the prefix here. +// @Inject(method = "registryDirPath", at = @At("RETURN"), cancellable = true) +// private static void bcl_prependDirectoryWithNamespace(ResourceLocation id, CallbackInfoReturnable info) { +// if (id.getNamespace().equals(WorldsTogether.MOD_ID) || id.getNamespace().equals(BCLib.MOD_ID)) { +// info.setReturnValue(info.getReturnValue()); +// } +// } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/ServerLevelMixin.java b/src/main/java/org/betterx/bclib/mixin/common/ServerLevelMixin.java new file mode 100644 index 00000000..d08ef26f --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/ServerLevelMixin.java @@ -0,0 +1,97 @@ +package org.betterx.bclib.mixin.common; + +import org.betterx.bclib.api.v2.LifeCycleAPI; +import org.betterx.worlds.together.world.BiomeSourceWithNoiseRelatedSettings; +import org.betterx.worlds.together.world.BiomeSourceWithSeed; + +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +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.util.profiling.ProfilerFiller; +import net.minecraft.world.RandomSequences; +import net.minecraft.world.level.Level; +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.storage.LevelStorageSource.LevelStorageAccess; +import net.minecraft.world.level.storage.ServerLevelData; +import net.minecraft.world.level.storage.WritableLevelData; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +@Mixin(ServerLevel.class) +public abstract class ServerLevelMixin extends Level { + private static String bclib_lastWorld = null; + + protected ServerLevelMixin( + WritableLevelData writableLevelData, + ResourceKey resourceKey, + RegistryAccess registryAccess, + Holder holder, + Supplier supplier, + boolean bl, + boolean bl2, + long l, + int i + ) { + super(writableLevelData, resourceKey, registryAccess, holder, supplier, bl, bl2, l, i); + } + + + @Inject(method = "*", at = @At("TAIL")) + private void bclib_onServerWorldInit( + MinecraftServer minecraftServer, + Executor executor, + LevelStorageAccess levelStorageAccess, + ServerLevelData serverLevelData, + ResourceKey resourceKey, + LevelStem levelStem, + ChunkProgressListener chunkProgressListener, + boolean bl, + long l, + List list, + boolean bl2, + RandomSequences randomSequences, + CallbackInfo ci + ) { + ServerLevel level = ServerLevel.class.cast(this); + LifeCycleAPI._runLevelLoad( + level, + minecraftServer, + executor, + levelStorageAccess, + serverLevelData, + resourceKey, + chunkProgressListener, + bl, + l, + list, + bl2 + ); + + if (levelStem.generator().getBiomeSource() instanceof BiomeSourceWithSeed source) { + source.setSeed(level.getSeed()); + } + + if (levelStem.generator().getBiomeSource() instanceof BiomeSourceWithNoiseRelatedSettings bcl + && levelStem.generator() instanceof NoiseBasedChunkGenerator noiseGenerator) { + bcl.onLoadGeneratorSettings(noiseGenerator.generatorSettings().value()); + } + + if (bclib_lastWorld != null && bclib_lastWorld.equals(levelStorageAccess.getLevelId())) { + return; + } + + bclib_lastWorld = levelStorageAccess.getLevelId(); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/ShovelItemAccessor.java b/src/main/java/org/betterx/bclib/mixin/common/ShovelItemAccessor.java new file mode 100644 index 00000000..6f9bd859 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/ShovelItemAccessor.java @@ -0,0 +1,18 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.world.item.ShovelItem; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(ShovelItem.class) +public interface ShovelItemAccessor { + @Accessor("FLATTENABLES") + static Map bclib_getFlattenables() { + throw new AssertionError("@Accessor dummy body called"); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/SurfaceRulesContextAccessor.java b/src/main/java/org/betterx/bclib/mixin/common/SurfaceRulesContextAccessor.java new file mode 100644 index 00000000..0b7ffcfd --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/SurfaceRulesContextAccessor.java @@ -0,0 +1,52 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.core.Holder; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.levelgen.NoiseChunk; +import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.levelgen.SurfaceRules; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.function.Supplier; + +@Mixin(SurfaceRules.Context.class) +public interface SurfaceRulesContextAccessor { + @Accessor("blockX") + int getBlockX(); + + @Accessor("blockY") + int getBlockY(); + + @Accessor("blockZ") + int getBlockZ(); + + @Accessor("surfaceDepth") + int getSurfaceDepth(); + + @Accessor("biome") + Supplier> getBiome(); + + @Accessor("chunk") + ChunkAccess getChunk(); + + @Accessor("noiseChunk") + NoiseChunk getNoiseChunk(); + + @Accessor("stoneDepthAbove") + int getStoneDepthAbove(); + + @Accessor("stoneDepthBelow") + int getStoneDepthBelow(); + + @Accessor("lastUpdateY") + long getLastUpdateY(); + + @Accessor("lastUpdateXZ") + long getLastUpdateXZ(); + + @Accessor("randomState") + RandomState getRandomState(); +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/WorldGenRegionMixin.java b/src/main/java/org/betterx/bclib/mixin/common/WorldGenRegionMixin.java new file mode 100644 index 00000000..59ee6f81 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/WorldGenRegionMixin.java @@ -0,0 +1,27 @@ +package org.betterx.bclib.mixin.common; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.WorldGenRegion; +import net.minecraft.world.level.chunk.ChunkAccess; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(WorldGenRegion.class) +public class WorldGenRegionMixin { + @Final + @Shadow + private ChunkAccess center; + + @Inject(method = "ensureCanWrite", at = @At("HEAD"), cancellable = true) + private void be_alterBlockCheck(BlockPos blockPos, CallbackInfoReturnable info) { + int x = blockPos.getX() >> 4; + int z = blockPos.getZ() >> 4; + WorldGenRegion region = (WorldGenRegion) (Object) this; + info.setReturnValue(Math.abs(x - center.getPos().x) < 2 && Math.abs(z - center.getPos().z) < 2); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/boat/BoatItemMixin.java b/src/main/java/org/betterx/bclib/mixin/common/boat/BoatItemMixin.java new file mode 100644 index 00000000..e349d1b4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/boat/BoatItemMixin.java @@ -0,0 +1,23 @@ +package org.betterx.bclib.mixin.common.boat; + +import org.betterx.bclib.items.boat.CustomBoatTypeOverride; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.BoatItem; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(BoatItem.class) +public class BoatItemMixin { + @ModifyArg(method = "use", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;noCollision(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;)Z")) + Entity bcl_suse(Entity boat) { + if (this instanceof CustomBoatTypeOverride self) { + if (boat instanceof CustomBoatTypeOverride newBoat) { + newBoat.setCustomType(self.bcl_getCustomType()); + } + } + return boat; + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/boat/BoatMixin.java b/src/main/java/org/betterx/bclib/mixin/common/boat/BoatMixin.java new file mode 100644 index 00000000..9a4bda53 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/boat/BoatMixin.java @@ -0,0 +1,117 @@ +package org.betterx.bclib.mixin.common.boat; + +import org.betterx.bclib.items.boat.BoatTypeOverride; +import org.betterx.bclib.items.boat.CustomBoatTypeOverride; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.vehicle.Boat; +import net.minecraft.world.item.BoatItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Boat.class) +public abstract class BoatMixin extends Entity implements CustomBoatTypeOverride { + private BoatTypeOverride bcl_type = null; + @Shadow + @Final + private static EntityDataAccessor DATA_ID_TYPE; + + public BoatMixin(EntityType entityType, Level level) { + super(entityType, level); + } + + public void setCustomType(BoatTypeOverride type) { + bcl_type = type; + if (type == null) + this.entityData.set(DATA_ID_TYPE, Boat.Type.OAK.ordinal()); + else + this.entityData.set(DATA_ID_TYPE, bcl_type.ordinal()); + } + + public BoatTypeOverride bcl_getCustomType() { + bcl_type = BoatTypeOverride.byId(this.entityData.get(DATA_ID_TYPE)); + return bcl_type; + } + + @Inject(method = "setVariant", at = @At("HEAD"), cancellable = true) + void bcl_setType(Boat.Type type, CallbackInfo ci) { + if (bcl_type != null) { + this.entityData.set(DATA_ID_TYPE, bcl_type.ordinal()); + ci.cancel(); + } + } + + @Inject(method = "getVariant", at = @At("HEAD"), cancellable = true) + void bcl_getBoatType(CallbackInfoReturnable cir) { + BoatTypeOverride type = BoatTypeOverride.byId(this.entityData.get(DATA_ID_TYPE)); + if (type != null) { + bcl_type = type; + cir.setReturnValue(Boat.Type.OAK); + } + } + + + @Inject(method = "addAdditionalSaveData", at = @At("HEAD")) + void bcl_addAdditionalSaveData(CompoundTag compoundTag, CallbackInfo ci) { + BoatTypeOverride type = this.bcl_getCustomType(); + if (type != null) { + compoundTag.putString("cType", type.name()); + } + } + + @Inject(method = "readAdditionalSaveData", at = @At("HEAD")) + void bcl_readAdditionalSaveData(CompoundTag compoundTag, CallbackInfo ci) { + if (compoundTag.contains("cType")) { + this.setCustomType(BoatTypeOverride.byName(compoundTag.getString("cType"))); + } else { + this.setCustomType(null); + } + } + + @Inject(method = "getDropItem", at = @At("HEAD"), cancellable = true) + void bcl_getDropItem(CallbackInfoReturnable cir) { + BoatTypeOverride type = this.bcl_getCustomType(); + if (type != null) { + BoatItem boat = type.getBoatItem(); + if (boat != null) { + cir.setReturnValue(boat); + } + } + } + + @Inject(method = "checkFallDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/vehicle/Boat;kill()V", shift = At.Shift.AFTER), cancellable = true) + void bcl_checkFallDamage(double d, boolean bl, BlockState blockState, BlockPos blockPos, CallbackInfo ci) { + BoatTypeOverride type = this.bcl_getCustomType(); + if (type != null) { + if (this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) { + for (int i = 0; i < 3; ++i) { + this.spawnAtLocation(type.getPlanks()); + } + for (int i = 0; i < 2; ++i) { + this.spawnAtLocation(Items.STICK); + } + + this.resetFallDistance(); + ci.cancel(); + } + + + } + } + +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/boat/ChestBoatMixin.java b/src/main/java/org/betterx/bclib/mixin/common/boat/ChestBoatMixin.java new file mode 100644 index 00000000..bdb8c552 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/boat/ChestBoatMixin.java @@ -0,0 +1,36 @@ +package org.betterx.bclib.mixin.common.boat; + +import org.betterx.bclib.items.boat.BoatTypeOverride; +import org.betterx.bclib.items.boat.CustomBoatTypeOverride; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.vehicle.ChestBoat; +import net.minecraft.world.item.BoatItem; +import net.minecraft.world.item.Item; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ChestBoat.class) +public abstract class ChestBoatMixin { + @Shadow + public abstract InteractionResult interact(Player player, InteractionHand interactionHand); + + @Inject(method = "getDropItem", at = @At("HEAD"), cancellable = true) + void bcl_getDropItem(CallbackInfoReturnable cir) { + if (this instanceof CustomBoatTypeOverride cbto) { + BoatTypeOverride type = cbto.bcl_getCustomType(); + if (type != null) { + BoatItem boat = type.getChestBoatItem(); + if (boat != null) { + cir.setReturnValue(boat); + } + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/elytra/LivingEntityMixin.java b/src/main/java/org/betterx/bclib/mixin/common/elytra/LivingEntityMixin.java new file mode 100644 index 00000000..5a2e5825 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/elytra/LivingEntityMixin.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.mixin.common.elytra; + +import org.betterx.bclib.items.elytra.BCLElytraItem; +import org.betterx.bclib.items.elytra.BCLElytraUtils; + +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.Slice; + +@Mixin(value = LivingEntity.class, priority = 199) +public abstract class LivingEntityMixin { + @Shadow + public abstract ItemStack getItemBySlot(EquipmentSlot equipmentSlot); + + @ModifyArg( + method = "travel", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;isFallFlying()Z"), + to = @At(value = "INVOKE:LAST", target = "Lnet/minecraft/world/entity/LivingEntity;setSharedFlag(IZ)V") + ), + at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;setDeltaMovement(Lnet/minecraft/world/phys/Vec3;)V") + ) + public Vec3 be_travel(Vec3 moveDelta) { + ItemStack itemStack; + if (BCLElytraUtils.slotProvider == null) itemStack = getItemBySlot(EquipmentSlot.CHEST); + else itemStack = BCLElytraUtils.slotProvider.getElytra((LivingEntity) (Object) this, this::getItemBySlot); + if (itemStack != null && itemStack.getItem() instanceof BCLElytraItem elytra) { + double movementFactor = elytra.getMovementFactor(); + moveDelta = moveDelta.multiply(movementFactor, 1.0D, movementFactor); + } + return moveDelta; + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/shears/BeehiveBlockMixin.java b/src/main/java/org/betterx/bclib/mixin/common/shears/BeehiveBlockMixin.java new file mode 100644 index 00000000..2e06db29 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/shears/BeehiveBlockMixin.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.mixin.common.shears; + +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.bclib.util.MethodReplace; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BeehiveBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(BeehiveBlock.class) +public class BeehiveBlockMixin { + @Inject(method = "use(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/InteractionHand;Lnet/minecraft/world/phys/BlockHitResult;)Lnet/minecraft/world/InteractionResult;", at = @At("HEAD")) + private void bclib_isShears( + BlockState blockState, + Level level, + BlockPos blockPos, + Player player, + InteractionHand interactionHand, + BlockHitResult blockHitResult, + CallbackInfoReturnable info + ) { + MethodReplace.addItemReplace(Items.SHEARS, BaseShearsItem::isShear); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/shears/DiggingEnchantmentMixin.java b/src/main/java/org/betterx/bclib/mixin/common/shears/DiggingEnchantmentMixin.java new file mode 100644 index 00000000..779f8dfb --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/shears/DiggingEnchantmentMixin.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.mixin.common.shears; + +import org.betterx.bclib.items.tool.BaseShearsItem; + +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.DiggingEnchantment; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(DiggingEnchantment.class) +public class DiggingEnchantmentMixin { + @Inject(method = "canEnchant(Lnet/minecraft/world/item/ItemStack;)Z", at = @At("HEAD"), cancellable = true) + private void bclib_isShears(ItemStack itemStack, CallbackInfoReturnable info) { + if (BaseShearsItem.isShear(itemStack)) info.setReturnValue(true); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/shears/ItemPredicateBuilderMixin.java b/src/main/java/org/betterx/bclib/mixin/common/shears/ItemPredicateBuilderMixin.java new file mode 100644 index 00000000..c9eff80e --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/shears/ItemPredicateBuilderMixin.java @@ -0,0 +1,34 @@ +package org.betterx.bclib.mixin.common.shears; + +import org.betterx.worlds.together.tag.v3.CommonItemTags; + +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Set; +import org.jetbrains.annotations.Nullable; + +@Mixin(ItemPredicate.class) +public abstract class ItemPredicateBuilderMixin { + @Shadow + @Final + private @Nullable Set items; + + @Inject(method = "matches", at = @At("HEAD"), cancellable = true) + void bclib_isShears(ItemStack itemStack, CallbackInfoReturnable info) { + if (this.items != null && this.items.size() == 1 && this.items.contains(Items.SHEARS)) { + if (itemStack.is(CommonItemTags.SHEARS)) { + info.setReturnValue(true); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/shears/MushroomCowMixin.java b/src/main/java/org/betterx/bclib/mixin/common/shears/MushroomCowMixin.java new file mode 100644 index 00000000..0bd0580d --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/shears/MushroomCowMixin.java @@ -0,0 +1,27 @@ +package org.betterx.bclib.mixin.common.shears; + +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.bclib.util.MethodReplace; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.animal.MushroomCow; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Items; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(MushroomCow.class) +public class MushroomCowMixin { + @Inject(method = "mobInteract(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/InteractionHand;)Lnet/minecraft/world/InteractionResult;", at = @At("HEAD")) + private void bclib_isShears( + Player player, + InteractionHand interactionHand, + CallbackInfoReturnable info + ) { + MethodReplace.addItemReplace(Items.SHEARS, BaseShearsItem::isShear); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/shears/PumpkinBlockMixin.java b/src/main/java/org/betterx/bclib/mixin/common/shears/PumpkinBlockMixin.java new file mode 100644 index 00000000..41c1c43f --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/shears/PumpkinBlockMixin.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.mixin.common.shears; + +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.bclib.util.MethodReplace; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.PumpkinBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(PumpkinBlock.class) +public abstract class PumpkinBlockMixin { + @Inject(method = "use", at = @At("HEAD")) + private void bclib_isShears( + BlockState blockState, + Level level, + BlockPos blockPos, + Player player, + InteractionHand interactionHand, + BlockHitResult blockHitResult, + CallbackInfoReturnable info + ) { + MethodReplace.addItemReplace(Items.SHEARS, BaseShearsItem::isShear); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/shears/SheepMixin.java b/src/main/java/org/betterx/bclib/mixin/common/shears/SheepMixin.java new file mode 100644 index 00000000..7727382e --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/shears/SheepMixin.java @@ -0,0 +1,27 @@ +package org.betterx.bclib.mixin.common.shears; + +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.bclib.util.MethodReplace; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.animal.Sheep; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Items; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Sheep.class) +public class SheepMixin { + @Inject(method = "mobInteract(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/InteractionHand;)Lnet/minecraft/world/InteractionResult;", at = @At("HEAD")) + private void bclib_isShears( + Player player, + InteractionHand interactionHand, + CallbackInfoReturnable info + ) { + MethodReplace.addItemReplace(Items.SHEARS, BaseShearsItem::isShear); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/shears/SnowGolemMixin.java b/src/main/java/org/betterx/bclib/mixin/common/shears/SnowGolemMixin.java new file mode 100644 index 00000000..afdd2c75 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/shears/SnowGolemMixin.java @@ -0,0 +1,27 @@ +package org.betterx.bclib.mixin.common.shears; + +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.bclib.util.MethodReplace; + +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.animal.SnowGolem; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Items; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(SnowGolem.class) +public class SnowGolemMixin { + @Inject(method = "mobInteract(Lnet/minecraft/world/entity/player/Player;Lnet/minecraft/world/InteractionHand;)Lnet/minecraft/world/InteractionResult;", at = @At("HEAD")) + private void bclib_isShears( + Player player, + InteractionHand interactionHand, + CallbackInfoReturnable info + ) { + MethodReplace.addItemReplace(Items.SHEARS, BaseShearsItem::isShear); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/shears/TripWireBlockMixin.java b/src/main/java/org/betterx/bclib/mixin/common/shears/TripWireBlockMixin.java new file mode 100644 index 00000000..1ada450b --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/shears/TripWireBlockMixin.java @@ -0,0 +1,30 @@ +package org.betterx.bclib.mixin.common.shears; + +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.bclib.util.MethodReplace; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.TripWireBlock; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(TripWireBlock.class) +public class TripWireBlockMixin { + @Inject(method = "playerWillDestroy(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/entity/player/Player;)V", at = @At("HEAD")) + private void bclib_isShears( + Level level, + BlockPos blockPos, + BlockState blockState, + Player player, + CallbackInfo info + ) { + MethodReplace.addItemReplace(Items.SHEARS, BaseShearsItem::isShear); + } +} diff --git a/src/main/java/org/betterx/bclib/mixin/common/signs/BlockEntityTypeMixin.java b/src/main/java/org/betterx/bclib/mixin/common/signs/BlockEntityTypeMixin.java new file mode 100644 index 00000000..848eaba9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/mixin/common/signs/BlockEntityTypeMixin.java @@ -0,0 +1,34 @@ +package org.betterx.bclib.mixin.common.signs; + +import org.betterx.bclib.blocks.signs.BaseHangingSignBlock; +import org.betterx.bclib.blocks.signs.BaseSignBlock; +import org.betterx.bclib.blocks.signs.BaseWallHangingSignBlock; +import org.betterx.bclib.blocks.signs.BaseWallSignBlock; + +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(BlockEntityType.class) +public class BlockEntityTypeMixin { + @Inject(method = "isValid", at = @At("HEAD"), cancellable = true) + void bcl_isValid(BlockState blockState, CallbackInfoReturnable cir) { + final BlockEntityType self = (BlockEntityType) (Object) this; + if (self == BlockEntityType.SIGN) { + final Block block = blockState.getBlock(); + if ((block instanceof BaseSignBlock) || (block instanceof BaseWallSignBlock)) { + cir.setReturnValue(true); + } + } else if (self == BlockEntityType.HANGING_SIGN) { + final Block block = blockState.getBlock(); + if ((block instanceof BaseHangingSignBlock) || (block instanceof BaseWallHangingSignBlock)) { + cir.setReturnValue(true); + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/models/RecordItemModelProvider.java b/src/main/java/org/betterx/bclib/models/RecordItemModelProvider.java new file mode 100644 index 00000000..34d33e53 --- /dev/null +++ b/src/main/java/org/betterx/bclib/models/RecordItemModelProvider.java @@ -0,0 +1,48 @@ +package org.betterx.bclib.models; + +import org.betterx.bclib.interfaces.ItemModelProvider; + +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.RecordItem; + +import java.util.HashMap; +import java.util.Map; +import org.jetbrains.annotations.ApiStatus; + +/** + * This was just added because the constructor of a RecordItem from 1.19 to 1.19.1 changed + * and we only need to add the {@link ItemModelProvider} interface. + *

+ * In order to keep cross version compat for bclib we choose to use reflection to instanciate + * RecordItem, but we need an additional registry that will provide the {@link ItemModelProvider} + * for RecordItems. + *

+ * This class (and and all according changes in + * {@link org.betterx.bclib.client.models.CustomModelBakery#loadCustomModels(ResourceManager)} can + * be abandoned when we drop support for 1.19. + */ +@ApiStatus.Internal +public class RecordItemModelProvider { + @ApiStatus.Internal + public static final ItemModelProvider DEFAULT_PROVIDER = new ItemModelProvider() { + }; + private static final Map PROIVDER_MAPPPING = new HashMap<>(); + + @ApiStatus.Internal + public static void add(RecordItem record) { + if (record != null) { + PROIVDER_MAPPPING.put(record, DEFAULT_PROVIDER); + } + } + + @ApiStatus.Internal + public static boolean has(Item i) { + return PROIVDER_MAPPPING.containsKey(i); + } + + @ApiStatus.Internal + public static ItemModelProvider get(Item i) { + return PROIVDER_MAPPPING.get(i); + } +} diff --git a/src/main/java/org/betterx/bclib/networking/VersionChecker.java b/src/main/java/org/betterx/bclib/networking/VersionChecker.java new file mode 100644 index 00000000..209a29dc --- /dev/null +++ b/src/main/java/org/betterx/bclib/networking/VersionChecker.java @@ -0,0 +1,157 @@ +package org.betterx.bclib.networking; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.Configs; +import org.betterx.worlds.together.util.ModUtil; + +import net.fabricmc.loader.api.FabricLoader; + +import com.google.gson.Gson; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.LinkedList; +import java.util.List; + +public class VersionChecker implements Runnable { + @FunctionalInterface + public interface UpdateInfoProvider { + void send(String modID, String currentVersion, String newVersion); + } + + private static final List KNOWN_MODS = new LinkedList<>(); + private static final List NEW_VERSIONS = new LinkedList<>(); + + public static class ModVersion { + String n; + String v; + + @Override + public String toString() { + return n + ":" + v; + } + } + + public static class Versions { + String mc; + String loader; + List mods; + + @Override + public String toString() { + return "Versions{" + + "mc='" + mc + '\'' + + ", loader='" + loader + '\'' + + ", mods=" + mods + + '}'; + } + } + + public static final int WAIT_FOR_DAYS = 5; + private static final String BASE_URL = "https://wunderreich.ambertation.de/api/v1/versions/"; + private static Thread versionChecker; + + public static void startCheck(boolean isClient) { + if (versionChecker == null && isClient) { + if (Configs.CLIENT_CONFIG.checkVersions() && Configs.CLIENT_CONFIG.didShowWelcomeScreen()) { + versionChecker = new Thread(isClient ? new VersionCheckerClient() : new VersionChecker()); + versionChecker.start(); + } + } + } + + public static void registerMod(String modID) { + KNOWN_MODS.add(modID); + } + + boolean needRecheck() { + Instant lastCheck = Configs.CACHED_CONFIG.lastCheckDate().plus(WAIT_FOR_DAYS, ChronoUnit.DAYS); + Instant now = Instant.now(); + + return now.isAfter(lastCheck); + } + + @Override + public void run() { + Gson gson = new Gson(); + if (needRecheck()) { + String minecraftVersion = ModUtil.getModVersion("minecraft").replace(".", "_"); + BCLib.LOGGER.info("Check Versions for minecraft=" + minecraftVersion); + + try { + String fileName = "mc_fabric_" + URLEncoder.encode( + minecraftVersion, + StandardCharsets.ISO_8859_1.toString() + ) + ".json"; + + URL url = new URL(BASE_URL + fileName); + try (InputStreamReader reader = new InputStreamReader(url.openStream())) { + Versions json = gson.fromJson(reader, Versions.class); + String str = gson.getAdapter(Versions.class).toJson(json); + Configs.CACHED_CONFIG.setLastVersionJson(str); + Configs.CACHED_CONFIG.setLastCheckDate(); + Configs.CACHED_CONFIG.saveChanges(); + + processVersions(json); + } + } catch (UnsupportedEncodingException e) { + BCLib.LOGGER.error("Failed to encode URL during VersionCheck", e); + return; + } catch (MalformedURLException e) { + BCLib.LOGGER.error("Invalid URL during VersionCheck", e); + return; + } catch (IOException e) { + BCLib.LOGGER.error("I/O Error during VersionCheck", e); + return; + } + } else { + String str = Configs.CACHED_CONFIG.lastVersionJson(); + if (str != null && str.trim().length() > 0) { + Versions json = gson.fromJson(str, Versions.class); + processVersions(json); + } + } + } + + private void processVersions(Versions json) { + if (json != null) { + BCLib.LOGGER.info("Received Version Info for minecraft=" + json.mc + ", loader=" + json.loader); + if (json.mods != null) { + for (ModVersion mod : json.mods) { + if (!KNOWN_MODS.contains(mod.n)) { + if (FabricLoader.getInstance().getModContainer(mod.n).isPresent()) + registerMod(mod.n); + } + if (mod.n != null && mod.v != null && KNOWN_MODS.contains(mod.n)) { + String installedVersion = ModUtil.getModVersion(mod.n); + boolean isNew = ModUtil.isLargerVersion(mod.v, installedVersion) + && !installedVersion.equals("0.0.0"); + BCLib.LOGGER.info(" - " + mod.n + ":" + mod.v + (isNew ? " (update available)" : "")); + if (isNew) + NEW_VERSIONS.add(mod); + } + } + } + } else { + BCLib.LOGGER.warning("No valid Version Info"); + } + } + + public static boolean isEmpty() { + return NEW_VERSIONS.isEmpty(); + } + + public static void forEachUpdate(UpdateInfoProvider consumer) { + for (ModVersion v : NEW_VERSIONS) { + String currrent = ModUtil.getModVersion(v.n); + consumer.send(v.n, currrent, v.v); + } + } +} diff --git a/src/main/java/org/betterx/bclib/networking/VersionCheckerClient.java b/src/main/java/org/betterx/bclib/networking/VersionCheckerClient.java new file mode 100644 index 00000000..f6baae73 --- /dev/null +++ b/src/main/java/org/betterx/bclib/networking/VersionCheckerClient.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.networking; + +import org.betterx.bclib.client.gui.screens.UpdatesScreen; +import org.betterx.bclib.client.gui.screens.WelcomeScreen; +import org.betterx.bclib.config.Configs; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +@Environment(EnvType.CLIENT) +public class VersionCheckerClient extends VersionChecker { + + public static void presentUpdateScreen(Screen parent) { + if (!Configs.CLIENT_CONFIG.didShowWelcomeScreen()) { + Minecraft.getInstance().setScreen(new WelcomeScreen(parent)); + + } else if (Configs.CLIENT_CONFIG.showUpdateInfo() && !VersionChecker.isEmpty()) { + Minecraft.getInstance().setScreen(new UpdatesScreen(parent)); + } + } +} diff --git a/src/main/java/org/betterx/bclib/noise/Noises.java b/src/main/java/org/betterx/bclib/noise/Noises.java new file mode 100644 index 00000000..a1ff546b --- /dev/null +++ b/src/main/java/org/betterx/bclib/noise/Noises.java @@ -0,0 +1,43 @@ +package org.betterx.bclib.noise; + +import org.betterx.bclib.BCLib; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.synth.NormalNoise; + +import java.util.HashMap; +import java.util.Map; + +public class Noises { + private static final Map, NormalNoise> noiseIntances = new HashMap<>(); + public static final ResourceKey ROUGHNESS_NOISE = createKey(BCLib.makeID( + "roughness_noise")); + + public static ResourceKey createKey(ResourceLocation loc) { + return ResourceKey.create(Registries.NOISE, loc); + } + + public static NormalNoise createNoise( + Registry registry, + RandomSource randomSource, + ResourceKey resourceKey + ) { + Holder holder = registry.getHolderOrThrow(resourceKey); + return NormalNoise.create(randomSource, holder.value()); + } + + public static NormalNoise getOrCreateNoise( + RegistryAccess registryAccess, + RandomSource randomSource, + ResourceKey noise + ) { + final Registry registry = registryAccess.registryOrThrow(Registries.NOISE); + return noiseIntances.computeIfAbsent(noise, (key) -> createNoise(registry, randomSource, noise)); + } +} diff --git a/src/main/java/org/betterx/bclib/noise/OpenSimplexNoise.java b/src/main/java/org/betterx/bclib/noise/OpenSimplexNoise.java new file mode 100644 index 00000000..5c2f9956 --- /dev/null +++ b/src/main/java/org/betterx/bclib/noise/OpenSimplexNoise.java @@ -0,0 +1,2528 @@ +package org.betterx.bclib.noise; + +/* + * OpenSimplex Noise in Java. + * by Kurt Spencer + * + * v1.1 (October 5, 2014) + * - Added 2D and 4D implementations. + * - Proper gradient sets for all dimensions, from a + * dimensionally-generalizable scheme with an actual + * rhyme and reason behind it. + * - Removed default permutation array in favor of + * default seed. + * - Changed seed-based constructor to be independent + * of any particular randomization library, so results + * will be the same when ported to other languages. + */ + +public class OpenSimplexNoise { + private static final double STRETCH_CONSTANT_2D = -0.211324865405187; // (1/Math.sqrt(2+1)-1)/2; + private static final double SQUISH_CONSTANT_2D = 0.366025403784439; // (Math.sqrt(2+1)-1)/2; + private static final double STRETCH_CONSTANT_3D = -1.0 / 6; // (1/Math.sqrt(3+1)-1)/3; + private static final double SQUISH_CONSTANT_3D = 1.0 / 3; // (Math.sqrt(3+1)-1)/3; + private static final double STRETCH_CONSTANT_4D = -0.138196601125011; // (1/Math.sqrt(4+1)-1)/4; + private static final double SQUISH_CONSTANT_4D = 0.309016994374947; // (Math.sqrt(4+1)-1)/4; + + private static final double NORM_CONSTANT_2D = 47; + private static final double NORM_CONSTANT_3D = 103; + private static final double NORM_CONSTANT_4D = 30; + + private static final long DEFAULT_SEED = 0; + + private final short[] perm; + private final short[] permGradIndex3D; + + public OpenSimplexNoise() { + this(DEFAULT_SEED); + } + + public OpenSimplexNoise(short[] perm) { + this.perm = perm; + permGradIndex3D = new short[256]; + + for (int i = 0; i < 256; i++) { + // Since 3D has 24 gradients, simple bitmask won't work, so + // precompute modulo array. + permGradIndex3D[i] = (short) ((perm[i] % (gradients3D.length / 3)) * 3); + } + } + + // Initializes the class using a permutation array generated from a 64-bit + // seed. + // Generates a proper permutation (i.e. doesn't merely perform N successive + // pair swaps on a base array) + // Uses a simple 64-bit LCG. + public OpenSimplexNoise(long seed) { + perm = new short[256]; + permGradIndex3D = new short[256]; + short[] source = new short[256]; + for (short i = 0; i < 256; i++) { + source[i] = i; + } + seed = seed * 6364136223846793005l + 1442695040888963407l; + seed = seed * 6364136223846793005l + 1442695040888963407l; + seed = seed * 6364136223846793005l + 1442695040888963407l; + for (int i = 255; i >= 0; i--) { + seed = seed * 6364136223846793005l + 1442695040888963407l; + int r = (int) ((seed + 31) % (i + 1)); + if (r < 0) r += (i + 1); + perm[i] = source[r]; + permGradIndex3D[i] = (short) ((perm[i] % (gradients3D.length / 3)) * 3); + source[r] = source[i]; + } + } + + // 2D OpenSimplex Noise. + public double eval(double x, double y) { + + // Place input coordinates onto grid. + double stretchOffset = (x + y) * STRETCH_CONSTANT_2D; + double xs = x + stretchOffset; + double ys = y + stretchOffset; + + // Floor to get grid coordinates of rhombus (stretched square) + // super-cell origin. + int xsb = fastFloor(xs); + int ysb = fastFloor(ys); + + // Skew out to get actual coordinates of rhombus origin. We'll need + // these later. + double squishOffset = (xsb + ysb) * SQUISH_CONSTANT_2D; + double xb = xsb + squishOffset; + double yb = ysb + squishOffset; + + // Compute grid coordinates relative to rhombus origin. + double xins = xs - xsb; + double yins = ys - ysb; + + // Sum those together to get a value that determines which region we're + // in. + double inSum = xins + yins; + + // Positions relative to origin point. + double dx0 = x - xb; + double dy0 = y - yb; + + // We'll be defining these inside the next block and using them + // afterwards. + double dx_ext, dy_ext; + int xsv_ext, ysv_ext; + + double value = 0; + + // Contribution (1,0) + double dx1 = dx0 - 1 - SQUISH_CONSTANT_2D; + double dy1 = dy0 - 0 - SQUISH_CONSTANT_2D; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, dx1, dy1); + } + + // Contribution (0,1) + double dx2 = dx0 - 0 - SQUISH_CONSTANT_2D; + double dy2 = dy0 - 1 - SQUISH_CONSTANT_2D; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, dx2, dy2); + } + + if (inSum <= 1) { // We're inside the triangle (2-Simplex) at (0,0) + double zins = 1 - inSum; + if (zins > xins || zins > yins) { // (0,0) is one of the closest two + // triangular vertices + if (xins > yins) { + xsv_ext = xsb + 1; + ysv_ext = ysb - 1; + dx_ext = dx0 - 1; + dy_ext = dy0 + 1; + } else { + xsv_ext = xsb - 1; + ysv_ext = ysb + 1; + dx_ext = dx0 + 1; + dy_ext = dy0 - 1; + } + } else { // (1,0) and (0,1) are the closest two vertices. + xsv_ext = xsb + 1; + ysv_ext = ysb + 1; + dx_ext = dx0 - 1 - 2 * SQUISH_CONSTANT_2D; + dy_ext = dy0 - 1 - 2 * SQUISH_CONSTANT_2D; + } + } else { // We're inside the triangle (2-Simplex) at (1,1) + double zins = 2 - inSum; + if (zins < xins || zins < yins) { // (0,0) is one of the closest two + // triangular vertices + if (xins > yins) { + xsv_ext = xsb + 2; + ysv_ext = ysb + 0; + dx_ext = dx0 - 2 - 2 * SQUISH_CONSTANT_2D; + dy_ext = dy0 + 0 - 2 * SQUISH_CONSTANT_2D; + } else { + xsv_ext = xsb + 0; + ysv_ext = ysb + 2; + dx_ext = dx0 + 0 - 2 * SQUISH_CONSTANT_2D; + dy_ext = dy0 - 2 - 2 * SQUISH_CONSTANT_2D; + } + } else { // (1,0) and (0,1) are the closest two vertices. + dx_ext = dx0; + dy_ext = dy0; + xsv_ext = xsb; + ysv_ext = ysb; + } + xsb += 1; + ysb += 1; + dx0 = dx0 - 1 - 2 * SQUISH_CONSTANT_2D; + dy0 = dy0 - 1 - 2 * SQUISH_CONSTANT_2D; + } + + // Contribution (0,0) or (1,1) + double attn0 = 2 - dx0 * dx0 - dy0 * dy0; + if (attn0 > 0) { + attn0 *= attn0; + value += attn0 * attn0 * extrapolate(xsb, ysb, dx0, dy0); + } + + // Extra Vertex + double attn_ext = 2 - dx_ext * dx_ext - dy_ext * dy_ext; + if (attn_ext > 0) { + attn_ext *= attn_ext; + value += attn_ext * attn_ext * extrapolate(xsv_ext, ysv_ext, dx_ext, dy_ext); + } + + return value / NORM_CONSTANT_2D; + } + + // 3D OpenSimplex Noise. + public double eval(double x, double y, double z) { + + // Place input coordinates on simplectic honeycomb. + double stretchOffset = (x + y + z) * STRETCH_CONSTANT_3D; + double xs = x + stretchOffset; + double ys = y + stretchOffset; + double zs = z + stretchOffset; + + // Floor to get simplectic honeycomb coordinates of rhombohedron + // (stretched cube) super-cell origin. + int xsb = fastFloor(xs); + int ysb = fastFloor(ys); + int zsb = fastFloor(zs); + + // Skew out to get actual coordinates of rhombohedron origin. We'll need + // these later. + double squishOffset = (xsb + ysb + zsb) * SQUISH_CONSTANT_3D; + double xb = xsb + squishOffset; + double yb = ysb + squishOffset; + double zb = zsb + squishOffset; + + // Compute simplectic honeycomb coordinates relative to rhombohedral + // origin. + double xins = xs - xsb; + double yins = ys - ysb; + double zins = zs - zsb; + + // Sum those together to get a value that determines which region we're + // in. + double inSum = xins + yins + zins; + + // Positions relative to origin point. + double dx0 = x - xb; + double dy0 = y - yb; + double dz0 = z - zb; + + // We'll be defining these inside the next block and using them + // afterwards. + double dx_ext0, dy_ext0, dz_ext0; + double dx_ext1, dy_ext1, dz_ext1; + int xsv_ext0, ysv_ext0, zsv_ext0; + int xsv_ext1, ysv_ext1, zsv_ext1; + + double value = 0; + if (inSum <= 1) { // We're inside the tetrahedron (3-Simplex) at (0,0,0) + + // Determine which two of (0,0,1), (0,1,0), (1,0,0) are closest. + byte aPoint = 0x01; + double aScore = xins; + byte bPoint = 0x02; + double bScore = yins; + if (aScore >= bScore && zins > bScore) { + bScore = zins; + bPoint = 0x04; + } else if (aScore < bScore && zins > aScore) { + aScore = zins; + aPoint = 0x04; + } + + // Now we determine the two lattice points not part of the + // tetrahedron that may contribute. + // This depends on the closest two tetrahedral vertices, including + // (0,0,0) + double wins = 1 - inSum; + if (wins > aScore || wins > bScore) { // (0,0,0) is one of the + // closest two tetrahedral + // vertices. + byte c = (bScore > aScore ? bPoint : aPoint); // Our other + // closest + // vertex is the + // closest out + // of a and b. + + if ((c & 0x01) == 0) { + xsv_ext0 = xsb - 1; + xsv_ext1 = xsb; + dx_ext0 = dx0 + 1; + dx_ext1 = dx0; + } else { + xsv_ext0 = xsv_ext1 = xsb + 1; + dx_ext0 = dx_ext1 = dx0 - 1; + } + + if ((c & 0x02) == 0) { + ysv_ext0 = ysv_ext1 = ysb; + dy_ext0 = dy_ext1 = dy0; + if ((c & 0x01) == 0) { + ysv_ext1 -= 1; + dy_ext1 += 1; + } else { + ysv_ext0 -= 1; + dy_ext0 += 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy_ext1 = dy0 - 1; + } + + if ((c & 0x04) == 0) { + zsv_ext0 = zsb; + zsv_ext1 = zsb - 1; + dz_ext0 = dz0; + dz_ext1 = dz0 + 1; + } else { + zsv_ext0 = zsv_ext1 = zsb + 1; + dz_ext0 = dz_ext1 = dz0 - 1; + } + } else { // (0,0,0) is not one of the closest two tetrahedral + // vertices. + byte c = (byte) (aPoint | bPoint); // Our two extra vertices are + // determined by the closest + // two. + + if ((c & 0x01) == 0) { + xsv_ext0 = xsb; + xsv_ext1 = xsb - 1; + dx_ext0 = dx0 - 2 * SQUISH_CONSTANT_3D; + dx_ext1 = dx0 + 1 - SQUISH_CONSTANT_3D; + } else { + xsv_ext0 = xsv_ext1 = xsb + 1; + dx_ext0 = dx0 - 1 - 2 * SQUISH_CONSTANT_3D; + dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_3D; + } + + if ((c & 0x02) == 0) { + ysv_ext0 = ysb; + ysv_ext1 = ysb - 1; + dy_ext0 = dy0 - 2 * SQUISH_CONSTANT_3D; + dy_ext1 = dy0 + 1 - SQUISH_CONSTANT_3D; + } else { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy0 - 1 - 2 * SQUISH_CONSTANT_3D; + dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_3D; + } + + if ((c & 0x04) == 0) { + zsv_ext0 = zsb; + zsv_ext1 = zsb - 1; + dz_ext0 = dz0 - 2 * SQUISH_CONSTANT_3D; + dz_ext1 = dz0 + 1 - SQUISH_CONSTANT_3D; + } else { + zsv_ext0 = zsv_ext1 = zsb + 1; + dz_ext0 = dz0 - 1 - 2 * SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_3D; + } + } + + // Contribution (0,0,0) + double attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0; + if (attn0 > 0) { + attn0 *= attn0; + value += attn0 * attn0 * extrapolate(xsb + 0, ysb + 0, zsb + 0, dx0, dy0, dz0); + } + + // Contribution (1,0,0) + double dx1 = dx0 - 1 - SQUISH_CONSTANT_3D; + double dy1 = dy0 - 0 - SQUISH_CONSTANT_3D; + double dz1 = dz0 - 0 - SQUISH_CONSTANT_3D; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, dx1, dy1, dz1); + } + + // Contribution (0,1,0) + double dx2 = dx0 - 0 - SQUISH_CONSTANT_3D; + double dy2 = dy0 - 1 - SQUISH_CONSTANT_3D; + double dz2 = dz1; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, dx2, dy2, dz2); + } + + // Contribution (0,0,1) + double dx3 = dx2; + double dy3 = dy1; + double dz3 = dz0 - 1 - SQUISH_CONSTANT_3D; + double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3; + if (attn3 > 0) { + attn3 *= attn3; + value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, dx3, dy3, dz3); + } + } else if (inSum >= 2) { // We're inside the tetrahedron (3-Simplex) at + // (1,1,1) + + // Determine which two tetrahedral vertices are the closest, out of + // (1,1,0), (1,0,1), (0,1,1) but not (1,1,1). + byte aPoint = 0x06; + double aScore = xins; + byte bPoint = 0x05; + double bScore = yins; + if (aScore <= bScore && zins < bScore) { + bScore = zins; + bPoint = 0x03; + } else if (aScore > bScore && zins < aScore) { + aScore = zins; + aPoint = 0x03; + } + + // Now we determine the two lattice points not part of the + // tetrahedron that may contribute. + // This depends on the closest two tetrahedral vertices, including + // (1,1,1) + double wins = 3 - inSum; + if (wins < aScore || wins < bScore) { // (1,1,1) is one of the + // closest two tetrahedral + // vertices. + byte c = (bScore < aScore ? bPoint : aPoint); // Our other + // closest + // vertex is the + // closest out + // of a and b. + + if ((c & 0x01) != 0) { + xsv_ext0 = xsb + 2; + xsv_ext1 = xsb + 1; + dx_ext0 = dx0 - 2 - 3 * SQUISH_CONSTANT_3D; + dx_ext1 = dx0 - 1 - 3 * SQUISH_CONSTANT_3D; + } else { + xsv_ext0 = xsv_ext1 = xsb; + dx_ext0 = dx_ext1 = dx0 - 3 * SQUISH_CONSTANT_3D; + } + + if ((c & 0x02) != 0) { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy_ext1 = dy0 - 1 - 3 * SQUISH_CONSTANT_3D; + if ((c & 0x01) != 0) { + ysv_ext1 += 1; + dy_ext1 -= 1; + } else { + ysv_ext0 += 1; + dy_ext0 -= 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysb; + dy_ext0 = dy_ext1 = dy0 - 3 * SQUISH_CONSTANT_3D; + } + + if ((c & 0x04) != 0) { + zsv_ext0 = zsb + 1; + zsv_ext1 = zsb + 2; + dz_ext0 = dz0 - 1 - 3 * SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 2 - 3 * SQUISH_CONSTANT_3D; + } else { + zsv_ext0 = zsv_ext1 = zsb; + dz_ext0 = dz_ext1 = dz0 - 3 * SQUISH_CONSTANT_3D; + } + } else { // (1,1,1) is not one of the closest two tetrahedral + // vertices. + byte c = (byte) (aPoint & bPoint); // Our two extra vertices are + // determined by the closest + // two. + + if ((c & 0x01) != 0) { + xsv_ext0 = xsb + 1; + xsv_ext1 = xsb + 2; + dx_ext0 = dx0 - 1 - SQUISH_CONSTANT_3D; + dx_ext1 = dx0 - 2 - 2 * SQUISH_CONSTANT_3D; + } else { + xsv_ext0 = xsv_ext1 = xsb; + dx_ext0 = dx0 - SQUISH_CONSTANT_3D; + dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D; + } + + if ((c & 0x02) != 0) { + ysv_ext0 = ysb + 1; + ysv_ext1 = ysb + 2; + dy_ext0 = dy0 - 1 - SQUISH_CONSTANT_3D; + dy_ext1 = dy0 - 2 - 2 * SQUISH_CONSTANT_3D; + } else { + ysv_ext0 = ysv_ext1 = ysb; + dy_ext0 = dy0 - SQUISH_CONSTANT_3D; + dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D; + } + + if ((c & 0x04) != 0) { + zsv_ext0 = zsb + 1; + zsv_ext1 = zsb + 2; + dz_ext0 = dz0 - 1 - SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 2 - 2 * SQUISH_CONSTANT_3D; + } else { + zsv_ext0 = zsv_ext1 = zsb; + dz_ext0 = dz0 - SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D; + } + } + + // Contribution (1,1,0) + double dx3 = dx0 - 1 - 2 * SQUISH_CONSTANT_3D; + double dy3 = dy0 - 1 - 2 * SQUISH_CONSTANT_3D; + double dz3 = dz0 - 0 - 2 * SQUISH_CONSTANT_3D; + double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3; + if (attn3 > 0) { + attn3 *= attn3; + value += attn3 * attn3 * extrapolate(xsb + 1, ysb + 1, zsb + 0, dx3, dy3, dz3); + } + + // Contribution (1,0,1) + double dx2 = dx3; + double dy2 = dy0 - 0 - 2 * SQUISH_CONSTANT_3D; + double dz2 = dz0 - 1 - 2 * SQUISH_CONSTANT_3D; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(xsb + 1, ysb + 0, zsb + 1, dx2, dy2, dz2); + } + + // Contribution (0,1,1) + double dx1 = dx0 - 0 - 2 * SQUISH_CONSTANT_3D; + double dy1 = dy3; + double dz1 = dz2; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(xsb + 0, ysb + 1, zsb + 1, dx1, dy1, dz1); + } + + // Contribution (1,1,1) + dx0 = dx0 - 1 - 3 * SQUISH_CONSTANT_3D; + dy0 = dy0 - 1 - 3 * SQUISH_CONSTANT_3D; + dz0 = dz0 - 1 - 3 * SQUISH_CONSTANT_3D; + double attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0; + if (attn0 > 0) { + attn0 *= attn0; + value += attn0 * attn0 * extrapolate(xsb + 1, ysb + 1, zsb + 1, dx0, dy0, dz0); + } + } else { // We're inside the octahedron (Rectified 3-Simplex) in + // between. + double aScore; + byte aPoint; + boolean aIsFurtherSide; + double bScore; + byte bPoint; + boolean bIsFurtherSide; + + // Decide between point (0,0,1) and (1,1,0) as closest + double p1 = xins + yins; + if (p1 > 1) { + aScore = p1 - 1; + aPoint = 0x03; + aIsFurtherSide = true; + } else { + aScore = 1 - p1; + aPoint = 0x04; + aIsFurtherSide = false; + } + + // Decide between point (0,1,0) and (1,0,1) as closest + double p2 = xins + zins; + if (p2 > 1) { + bScore = p2 - 1; + bPoint = 0x05; + bIsFurtherSide = true; + } else { + bScore = 1 - p2; + bPoint = 0x02; + bIsFurtherSide = false; + } + + // The closest out of the two (1,0,0) and (0,1,1) will replace the + // furthest out of the two decided above, if closer. + double p3 = yins + zins; + if (p3 > 1) { + double score = p3 - 1; + if (aScore <= bScore && aScore < score) { + aScore = score; + aPoint = 0x06; + aIsFurtherSide = true; + } else if (aScore > bScore && bScore < score) { + bScore = score; + bPoint = 0x06; + bIsFurtherSide = true; + } + } else { + double score = 1 - p3; + if (aScore <= bScore && aScore < score) { + aScore = score; + aPoint = 0x01; + aIsFurtherSide = false; + } else if (aScore > bScore && bScore < score) { + bScore = score; + bPoint = 0x01; + bIsFurtherSide = false; + } + } + + // Where each of the two closest points are determines how the extra + // two vertices are calculated. + if (aIsFurtherSide == bIsFurtherSide) { + if (aIsFurtherSide) { // Both closest points on (1,1,1) side + + // One of the two extra points is (1,1,1) + dx_ext0 = dx0 - 1 - 3 * SQUISH_CONSTANT_3D; + dy_ext0 = dy0 - 1 - 3 * SQUISH_CONSTANT_3D; + dz_ext0 = dz0 - 1 - 3 * SQUISH_CONSTANT_3D; + xsv_ext0 = xsb + 1; + ysv_ext0 = ysb + 1; + zsv_ext0 = zsb + 1; + + // Other extra point is based on the shared axis. + byte c = (byte) (aPoint & bPoint); + if ((c & 0x01) != 0) { + dx_ext1 = dx0 - 2 - 2 * SQUISH_CONSTANT_3D; + dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D; + xsv_ext1 = xsb + 2; + ysv_ext1 = ysb; + zsv_ext1 = zsb; + } else if ((c & 0x02) != 0) { + dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D; + dy_ext1 = dy0 - 2 - 2 * SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D; + xsv_ext1 = xsb; + ysv_ext1 = ysb + 2; + zsv_ext1 = zsb; + } else { + dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D; + dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 2 - 2 * SQUISH_CONSTANT_3D; + xsv_ext1 = xsb; + ysv_ext1 = ysb; + zsv_ext1 = zsb + 2; + } + } else {// Both closest points on (0,0,0) side + + // One of the two extra points is (0,0,0) + dx_ext0 = dx0; + dy_ext0 = dy0; + dz_ext0 = dz0; + xsv_ext0 = xsb; + ysv_ext0 = ysb; + zsv_ext0 = zsb; + + // Other extra point is based on the omitted axis. + byte c = (byte) (aPoint | bPoint); + if ((c & 0x01) == 0) { + dx_ext1 = dx0 + 1 - SQUISH_CONSTANT_3D; + dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_3D; + xsv_ext1 = xsb - 1; + ysv_ext1 = ysb + 1; + zsv_ext1 = zsb + 1; + } else if ((c & 0x02) == 0) { + dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_3D; + dy_ext1 = dy0 + 1 - SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_3D; + xsv_ext1 = xsb + 1; + ysv_ext1 = ysb - 1; + zsv_ext1 = zsb + 1; + } else { + dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_3D; + dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_3D; + dz_ext1 = dz0 + 1 - SQUISH_CONSTANT_3D; + xsv_ext1 = xsb + 1; + ysv_ext1 = ysb + 1; + zsv_ext1 = zsb - 1; + } + } + } else { // One point on (0,0,0) side, one point on (1,1,1) side + byte c1, c2; + if (aIsFurtherSide) { + c1 = aPoint; + c2 = bPoint; + } else { + c1 = bPoint; + c2 = aPoint; + } + + // One contribution is a permutation of (1,1,-1) + if ((c1 & 0x01) == 0) { + dx_ext0 = dx0 + 1 - SQUISH_CONSTANT_3D; + dy_ext0 = dy0 - 1 - SQUISH_CONSTANT_3D; + dz_ext0 = dz0 - 1 - SQUISH_CONSTANT_3D; + xsv_ext0 = xsb - 1; + ysv_ext0 = ysb + 1; + zsv_ext0 = zsb + 1; + } else if ((c1 & 0x02) == 0) { + dx_ext0 = dx0 - 1 - SQUISH_CONSTANT_3D; + dy_ext0 = dy0 + 1 - SQUISH_CONSTANT_3D; + dz_ext0 = dz0 - 1 - SQUISH_CONSTANT_3D; + xsv_ext0 = xsb + 1; + ysv_ext0 = ysb - 1; + zsv_ext0 = zsb + 1; + } else { + dx_ext0 = dx0 - 1 - SQUISH_CONSTANT_3D; + dy_ext0 = dy0 - 1 - SQUISH_CONSTANT_3D; + dz_ext0 = dz0 + 1 - SQUISH_CONSTANT_3D; + xsv_ext0 = xsb + 1; + ysv_ext0 = ysb + 1; + zsv_ext0 = zsb - 1; + } + + // One contribution is a permutation of (0,0,2) + dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D; + dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D; + dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D; + xsv_ext1 = xsb; + ysv_ext1 = ysb; + zsv_ext1 = zsb; + if ((c2 & 0x01) != 0) { + dx_ext1 -= 2; + xsv_ext1 += 2; + } else if ((c2 & 0x02) != 0) { + dy_ext1 -= 2; + ysv_ext1 += 2; + } else { + dz_ext1 -= 2; + zsv_ext1 += 2; + } + } + + // Contribution (1,0,0) + double dx1 = dx0 - 1 - SQUISH_CONSTANT_3D; + double dy1 = dy0 - 0 - SQUISH_CONSTANT_3D; + double dz1 = dz0 - 0 - SQUISH_CONSTANT_3D; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, dx1, dy1, dz1); + } + + // Contribution (0,1,0) + double dx2 = dx0 - 0 - SQUISH_CONSTANT_3D; + double dy2 = dy0 - 1 - SQUISH_CONSTANT_3D; + double dz2 = dz1; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, dx2, dy2, dz2); + } + + // Contribution (0,0,1) + double dx3 = dx2; + double dy3 = dy1; + double dz3 = dz0 - 1 - SQUISH_CONSTANT_3D; + double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3; + if (attn3 > 0) { + attn3 *= attn3; + value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, dx3, dy3, dz3); + } + + // Contribution (1,1,0) + double dx4 = dx0 - 1 - 2 * SQUISH_CONSTANT_3D; + double dy4 = dy0 - 1 - 2 * SQUISH_CONSTANT_3D; + double dz4 = dz0 - 0 - 2 * SQUISH_CONSTANT_3D; + double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4; + if (attn4 > 0) { + attn4 *= attn4; + value += attn4 * attn4 * extrapolate(xsb + 1, ysb + 1, zsb + 0, dx4, dy4, dz4); + } + + // Contribution (1,0,1) + double dx5 = dx4; + double dy5 = dy0 - 0 - 2 * SQUISH_CONSTANT_3D; + double dz5 = dz0 - 1 - 2 * SQUISH_CONSTANT_3D; + double attn5 = 2 - dx5 * dx5 - dy5 * dy5 - dz5 * dz5; + if (attn5 > 0) { + attn5 *= attn5; + value += attn5 * attn5 * extrapolate(xsb + 1, ysb + 0, zsb + 1, dx5, dy5, dz5); + } + + // Contribution (0,1,1) + double dx6 = dx0 - 0 - 2 * SQUISH_CONSTANT_3D; + double dy6 = dy4; + double dz6 = dz5; + double attn6 = 2 - dx6 * dx6 - dy6 * dy6 - dz6 * dz6; + if (attn6 > 0) { + attn6 *= attn6; + value += attn6 * attn6 * extrapolate(xsb + 0, ysb + 1, zsb + 1, dx6, dy6, dz6); + } + } + + // First extra vertex + double attn_ext0 = 2 - dx_ext0 * dx_ext0 - dy_ext0 * dy_ext0 - dz_ext0 * dz_ext0; + if (attn_ext0 > 0) { + attn_ext0 *= attn_ext0; + value += attn_ext0 * attn_ext0 * extrapolate(xsv_ext0, ysv_ext0, zsv_ext0, dx_ext0, dy_ext0, dz_ext0); + } + + // Second extra vertex + double attn_ext1 = 2 - dx_ext1 * dx_ext1 - dy_ext1 * dy_ext1 - dz_ext1 * dz_ext1; + if (attn_ext1 > 0) { + attn_ext1 *= attn_ext1; + value += attn_ext1 * attn_ext1 * extrapolate(xsv_ext1, ysv_ext1, zsv_ext1, dx_ext1, dy_ext1, dz_ext1); + } + + return value / NORM_CONSTANT_3D; + } + + // 4D OpenSimplex Noise. + public double eval(double x, double y, double z, double w) { + + // Place input coordinates on simplectic honeycomb. + double stretchOffset = (x + y + z + w) * STRETCH_CONSTANT_4D; + double xs = x + stretchOffset; + double ys = y + stretchOffset; + double zs = z + stretchOffset; + double ws = w + stretchOffset; + + // Floor to get simplectic honeycomb coordinates of rhombo-hypercube + // super-cell origin. + int xsb = fastFloor(xs); + int ysb = fastFloor(ys); + int zsb = fastFloor(zs); + int wsb = fastFloor(ws); + + // Skew out to get actual coordinates of stretched rhombo-hypercube + // origin. We'll need these later. + double squishOffset = (xsb + ysb + zsb + wsb) * SQUISH_CONSTANT_4D; + double xb = xsb + squishOffset; + double yb = ysb + squishOffset; + double zb = zsb + squishOffset; + double wb = wsb + squishOffset; + + // Compute simplectic honeycomb coordinates relative to rhombo-hypercube + // origin. + double xins = xs - xsb; + double yins = ys - ysb; + double zins = zs - zsb; + double wins = ws - wsb; + + // Sum those together to get a value that determines which region we're + // in. + double inSum = xins + yins + zins + wins; + + // Positions relative to origin point. + double dx0 = x - xb; + double dy0 = y - yb; + double dz0 = z - zb; + double dw0 = w - wb; + + // We'll be defining these inside the next block and using them + // afterwards. + double dx_ext0, dy_ext0, dz_ext0, dw_ext0; + double dx_ext1, dy_ext1, dz_ext1, dw_ext1; + double dx_ext2, dy_ext2, dz_ext2, dw_ext2; + int xsv_ext0, ysv_ext0, zsv_ext0, wsv_ext0; + int xsv_ext1, ysv_ext1, zsv_ext1, wsv_ext1; + int xsv_ext2, ysv_ext2, zsv_ext2, wsv_ext2; + + double value = 0; + if (inSum <= 1) { // We're inside the pentachoron (4-Simplex) at + // (0,0,0,0) + + // Determine which two of (0,0,0,1), (0,0,1,0), (0,1,0,0), (1,0,0,0) + // are closest. + byte aPoint = 0x01; + double aScore = xins; + byte bPoint = 0x02; + double bScore = yins; + if (aScore >= bScore && zins > bScore) { + bScore = zins; + bPoint = 0x04; + } else if (aScore < bScore && zins > aScore) { + aScore = zins; + aPoint = 0x04; + } + if (aScore >= bScore && wins > bScore) { + bScore = wins; + bPoint = 0x08; + } else if (aScore < bScore && wins > aScore) { + aScore = wins; + aPoint = 0x08; + } + + // Now we determine the three lattice points not part of the + // pentachoron that may contribute. + // This depends on the closest two pentachoron vertices, including + // (0,0,0,0) + double uins = 1 - inSum; + if (uins > aScore || uins > bScore) { // (0,0,0,0) is one of the + // closest two pentachoron + // vertices. + byte c = (bScore > aScore ? bPoint : aPoint); // Our other + // closest + // vertex is the + // closest out + // of a and b. + if ((c & 0x01) == 0) { + xsv_ext0 = xsb - 1; + xsv_ext1 = xsv_ext2 = xsb; + dx_ext0 = dx0 + 1; + dx_ext1 = dx_ext2 = dx0; + } else { + xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb + 1; + dx_ext0 = dx_ext1 = dx_ext2 = dx0 - 1; + } + + if ((c & 0x02) == 0) { + ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb; + dy_ext0 = dy_ext1 = dy_ext2 = dy0; + if ((c & 0x01) == 0x01) { + ysv_ext0 -= 1; + dy_ext0 += 1; + } else { + ysv_ext1 -= 1; + dy_ext1 += 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1; + dy_ext0 = dy_ext1 = dy_ext2 = dy0 - 1; + } + + if ((c & 0x04) == 0) { + zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb; + dz_ext0 = dz_ext1 = dz_ext2 = dz0; + if ((c & 0x03) != 0) { + if ((c & 0x03) == 0x03) { + zsv_ext0 -= 1; + dz_ext0 += 1; + } else { + zsv_ext1 -= 1; + dz_ext1 += 1; + } + } else { + zsv_ext2 -= 1; + dz_ext2 += 1; + } + } else { + zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1; + dz_ext0 = dz_ext1 = dz_ext2 = dz0 - 1; + } + + if ((c & 0x08) == 0) { + wsv_ext0 = wsv_ext1 = wsb; + wsv_ext2 = wsb - 1; + dw_ext0 = dw_ext1 = dw0; + dw_ext2 = dw0 + 1; + } else { + wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb + 1; + dw_ext0 = dw_ext1 = dw_ext2 = dw0 - 1; + } + } else { // (0,0,0,0) is not one of the closest two pentachoron + // vertices. + byte c = (byte) (aPoint | bPoint); // Our three extra vertices + // are determined by the + // closest two. + + if ((c & 0x01) == 0) { + xsv_ext0 = xsv_ext2 = xsb; + xsv_ext1 = xsb - 1; + dx_ext0 = dx0 - 2 * SQUISH_CONSTANT_4D; + dx_ext1 = dx0 + 1 - SQUISH_CONSTANT_4D; + dx_ext2 = dx0 - SQUISH_CONSTANT_4D; + } else { + xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb + 1; + dx_ext0 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + dx_ext1 = dx_ext2 = dx0 - 1 - SQUISH_CONSTANT_4D; + } + + if ((c & 0x02) == 0) { + ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb; + dy_ext0 = dy0 - 2 * SQUISH_CONSTANT_4D; + dy_ext1 = dy_ext2 = dy0 - SQUISH_CONSTANT_4D; + if ((c & 0x01) == 0x01) { + ysv_ext1 -= 1; + dy_ext1 += 1; + } else { + ysv_ext2 -= 1; + dy_ext2 += 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1; + dy_ext0 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + dy_ext1 = dy_ext2 = dy0 - 1 - SQUISH_CONSTANT_4D; + } + + if ((c & 0x04) == 0) { + zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb; + dz_ext0 = dz0 - 2 * SQUISH_CONSTANT_4D; + dz_ext1 = dz_ext2 = dz0 - SQUISH_CONSTANT_4D; + if ((c & 0x03) == 0x03) { + zsv_ext1 -= 1; + dz_ext1 += 1; + } else { + zsv_ext2 -= 1; + dz_ext2 += 1; + } + } else { + zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1; + dz_ext0 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + dz_ext1 = dz_ext2 = dz0 - 1 - SQUISH_CONSTANT_4D; + } + + if ((c & 0x08) == 0) { + wsv_ext0 = wsv_ext1 = wsb; + wsv_ext2 = wsb - 1; + dw_ext0 = dw0 - 2 * SQUISH_CONSTANT_4D; + dw_ext1 = dw0 - SQUISH_CONSTANT_4D; + dw_ext2 = dw0 + 1 - SQUISH_CONSTANT_4D; + } else { + wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb + 1; + dw_ext0 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + dw_ext1 = dw_ext2 = dw0 - 1 - SQUISH_CONSTANT_4D; + } + } + + // Contribution (0,0,0,0) + double attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0 - dw0 * dw0; + if (attn0 > 0) { + attn0 *= attn0; + value += attn0 * attn0 * extrapolate(xsb + 0, ysb + 0, zsb + 0, wsb + 0, dx0, dy0, dz0, dw0); + } + + // Contribution (1,0,0,0) + double dx1 = dx0 - 1 - SQUISH_CONSTANT_4D; + double dy1 = dy0 - 0 - SQUISH_CONSTANT_4D; + double dz1 = dz0 - 0 - SQUISH_CONSTANT_4D; + double dw1 = dw0 - 0 - SQUISH_CONSTANT_4D; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 0, dx1, dy1, dz1, dw1); + } + + // Contribution (0,1,0,0) + double dx2 = dx0 - 0 - SQUISH_CONSTANT_4D; + double dy2 = dy0 - 1 - SQUISH_CONSTANT_4D; + double dz2 = dz1; + double dw2 = dw1; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 0, dx2, dy2, dz2, dw2); + } + + // Contribution (0,0,1,0) + double dx3 = dx2; + double dy3 = dy1; + double dz3 = dz0 - 1 - SQUISH_CONSTANT_4D; + double dw3 = dw1; + double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3; + if (attn3 > 0) { + attn3 *= attn3; + value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 0, dx3, dy3, dz3, dw3); + } + + // Contribution (0,0,0,1) + double dx4 = dx2; + double dy4 = dy1; + double dz4 = dz1; + double dw4 = dw0 - 1 - SQUISH_CONSTANT_4D; + double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4; + if (attn4 > 0) { + attn4 *= attn4; + value += attn4 * attn4 * extrapolate(xsb + 0, ysb + 0, zsb + 0, wsb + 1, dx4, dy4, dz4, dw4); + } + } else if (inSum >= 3) { // We're inside the pentachoron (4-Simplex) at + // (1,1,1,1) + // Determine which two of (1,1,1,0), + // (1,1,0,1), (1,0,1,1), (0,1,1,1) + // are closest. + byte aPoint = 0x0E; + double aScore = xins; + byte bPoint = 0x0D; + double bScore = yins; + if (aScore <= bScore && zins < bScore) { + bScore = zins; + bPoint = 0x0B; + } else if (aScore > bScore && zins < aScore) { + aScore = zins; + aPoint = 0x0B; + } + if (aScore <= bScore && wins < bScore) { + bScore = wins; + bPoint = 0x07; + } else if (aScore > bScore && wins < aScore) { + aScore = wins; + aPoint = 0x07; + } + + // Now we determine the three lattice points not part of the + // pentachoron that may contribute. + // This depends on the closest two pentachoron vertices, including + // (0,0,0,0) + double uins = 4 - inSum; + if (uins < aScore || uins < bScore) { // (1,1,1,1) is one of the + // closest two pentachoron + // vertices. + byte c = (bScore < aScore ? bPoint : aPoint); // Our other + // closest + // vertex is the + // closest out + // of a and b. + + if ((c & 0x01) != 0) { + xsv_ext0 = xsb + 2; + xsv_ext1 = xsv_ext2 = xsb + 1; + dx_ext0 = dx0 - 2 - 4 * SQUISH_CONSTANT_4D; + dx_ext1 = dx_ext2 = dx0 - 1 - 4 * SQUISH_CONSTANT_4D; + } else { + xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb; + dx_ext0 = dx_ext1 = dx_ext2 = dx0 - 4 * SQUISH_CONSTANT_4D; + } + + if ((c & 0x02) != 0) { + ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1; + dy_ext0 = dy_ext1 = dy_ext2 = dy0 - 1 - 4 * SQUISH_CONSTANT_4D; + if ((c & 0x01) != 0) { + ysv_ext1 += 1; + dy_ext1 -= 1; + } else { + ysv_ext0 += 1; + dy_ext0 -= 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb; + dy_ext0 = dy_ext1 = dy_ext2 = dy0 - 4 * SQUISH_CONSTANT_4D; + } + + if ((c & 0x04) != 0) { + zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1; + dz_ext0 = dz_ext1 = dz_ext2 = dz0 - 1 - 4 * SQUISH_CONSTANT_4D; + if ((c & 0x03) != 0x03) { + if ((c & 0x03) == 0) { + zsv_ext0 += 1; + dz_ext0 -= 1; + } else { + zsv_ext1 += 1; + dz_ext1 -= 1; + } + } else { + zsv_ext2 += 1; + dz_ext2 -= 1; + } + } else { + zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb; + dz_ext0 = dz_ext1 = dz_ext2 = dz0 - 4 * SQUISH_CONSTANT_4D; + } + + if ((c & 0x08) != 0) { + wsv_ext0 = wsv_ext1 = wsb + 1; + wsv_ext2 = wsb + 2; + dw_ext0 = dw_ext1 = dw0 - 1 - 4 * SQUISH_CONSTANT_4D; + dw_ext2 = dw0 - 2 - 4 * SQUISH_CONSTANT_4D; + } else { + wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb; + dw_ext0 = dw_ext1 = dw_ext2 = dw0 - 4 * SQUISH_CONSTANT_4D; + } + } else { // (1,1,1,1) is not one of the closest two pentachoron + // vertices. + byte c = (byte) (aPoint & bPoint); // Our three extra vertices + // are determined by the + // closest two. + + if ((c & 0x01) != 0) { + xsv_ext0 = xsv_ext2 = xsb + 1; + xsv_ext1 = xsb + 2; + dx_ext0 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + dx_ext1 = dx0 - 2 - 3 * SQUISH_CONSTANT_4D; + dx_ext2 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; + } else { + xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb; + dx_ext0 = dx0 - 2 * SQUISH_CONSTANT_4D; + dx_ext1 = dx_ext2 = dx0 - 3 * SQUISH_CONSTANT_4D; + } + + if ((c & 0x02) != 0) { + ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1; + dy_ext0 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + dy_ext1 = dy_ext2 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; + if ((c & 0x01) != 0) { + ysv_ext2 += 1; + dy_ext2 -= 1; + } else { + ysv_ext1 += 1; + dy_ext1 -= 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb; + dy_ext0 = dy0 - 2 * SQUISH_CONSTANT_4D; + dy_ext1 = dy_ext2 = dy0 - 3 * SQUISH_CONSTANT_4D; + } + + if ((c & 0x04) != 0) { + zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1; + dz_ext0 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + dz_ext1 = dz_ext2 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; + if ((c & 0x03) != 0) { + zsv_ext2 += 1; + dz_ext2 -= 1; + } else { + zsv_ext1 += 1; + dz_ext1 -= 1; + } + } else { + zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb; + dz_ext0 = dz0 - 2 * SQUISH_CONSTANT_4D; + dz_ext1 = dz_ext2 = dz0 - 3 * SQUISH_CONSTANT_4D; + } + + if ((c & 0x08) != 0) { + wsv_ext0 = wsv_ext1 = wsb + 1; + wsv_ext2 = wsb + 2; + dw_ext0 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + dw_ext1 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; + dw_ext2 = dw0 - 2 - 3 * SQUISH_CONSTANT_4D; + } else { + wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb; + dw_ext0 = dw0 - 2 * SQUISH_CONSTANT_4D; + dw_ext1 = dw_ext2 = dw0 - 3 * SQUISH_CONSTANT_4D; + } + } + + // Contribution (1,1,1,0) + double dx4 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; + double dy4 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; + double dz4 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; + double dw4 = dw0 - 3 * SQUISH_CONSTANT_4D; + double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4; + if (attn4 > 0) { + attn4 *= attn4; + value += attn4 * attn4 * extrapolate(xsb + 1, ysb + 1, zsb + 1, wsb + 0, dx4, dy4, dz4, dw4); + } + + // Contribution (1,1,0,1) + double dx3 = dx4; + double dy3 = dy4; + double dz3 = dz0 - 3 * SQUISH_CONSTANT_4D; + double dw3 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; + double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3; + if (attn3 > 0) { + attn3 *= attn3; + value += attn3 * attn3 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 1, dx3, dy3, dz3, dw3); + } + + // Contribution (1,0,1,1) + double dx2 = dx4; + double dy2 = dy0 - 3 * SQUISH_CONSTANT_4D; + double dz2 = dz4; + double dw2 = dw3; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 1, dx2, dy2, dz2, dw2); + } + + // Contribution (0,1,1,1) + double dx1 = dx0 - 3 * SQUISH_CONSTANT_4D; + double dz1 = dz4; + double dy1 = dy4; + double dw1 = dw3; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 1, dx1, dy1, dz1, dw1); + } + + // Contribution (1,1,1,1) + dx0 = dx0 - 1 - 4 * SQUISH_CONSTANT_4D; + dy0 = dy0 - 1 - 4 * SQUISH_CONSTANT_4D; + dz0 = dz0 - 1 - 4 * SQUISH_CONSTANT_4D; + dw0 = dw0 - 1 - 4 * SQUISH_CONSTANT_4D; + double attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0 - dw0 * dw0; + if (attn0 > 0) { + attn0 *= attn0; + value += attn0 * attn0 * extrapolate(xsb + 1, ysb + 1, zsb + 1, wsb + 1, dx0, dy0, dz0, dw0); + } + } else if (inSum <= 2) { // We're inside the first dispentachoron + // (Rectified 4-Simplex) + double aScore; + byte aPoint; + boolean aIsBiggerSide = true; + double bScore; + byte bPoint; + boolean bIsBiggerSide = true; + + // Decide between (1,1,0,0) and (0,0,1,1) + if (xins + yins > zins + wins) { + aScore = xins + yins; + aPoint = 0x03; + } else { + aScore = zins + wins; + aPoint = 0x0C; + } + + // Decide between (1,0,1,0) and (0,1,0,1) + if (xins + zins > yins + wins) { + bScore = xins + zins; + bPoint = 0x05; + } else { + bScore = yins + wins; + bPoint = 0x0A; + } + + // Closer between (1,0,0,1) and (0,1,1,0) will replace the further + // of a and b, if closer. + if (xins + wins > yins + zins) { + double score = xins + wins; + if (aScore >= bScore && score > bScore) { + bScore = score; + bPoint = 0x09; + } else if (aScore < bScore && score > aScore) { + aScore = score; + aPoint = 0x09; + } + } else { + double score = yins + zins; + if (aScore >= bScore && score > bScore) { + bScore = score; + bPoint = 0x06; + } else if (aScore < bScore && score > aScore) { + aScore = score; + aPoint = 0x06; + } + } + + // Decide if (1,0,0,0) is closer. + double p1 = 2 - inSum + xins; + if (aScore >= bScore && p1 > bScore) { + bScore = p1; + bPoint = 0x01; + bIsBiggerSide = false; + } else if (aScore < bScore && p1 > aScore) { + aScore = p1; + aPoint = 0x01; + aIsBiggerSide = false; + } + + // Decide if (0,1,0,0) is closer. + double p2 = 2 - inSum + yins; + if (aScore >= bScore && p2 > bScore) { + bScore = p2; + bPoint = 0x02; + bIsBiggerSide = false; + } else if (aScore < bScore && p2 > aScore) { + aScore = p2; + aPoint = 0x02; + aIsBiggerSide = false; + } + + // Decide if (0,0,1,0) is closer. + double p3 = 2 - inSum + zins; + if (aScore >= bScore && p3 > bScore) { + bScore = p3; + bPoint = 0x04; + bIsBiggerSide = false; + } else if (aScore < bScore && p3 > aScore) { + aScore = p3; + aPoint = 0x04; + aIsBiggerSide = false; + } + + // Decide if (0,0,0,1) is closer. + double p4 = 2 - inSum + wins; + if (aScore >= bScore && p4 > bScore) { + bScore = p4; + bPoint = 0x08; + bIsBiggerSide = false; + } else if (aScore < bScore && p4 > aScore) { + aScore = p4; + aPoint = 0x08; + aIsBiggerSide = false; + } + + // Where each of the two closest points are determines how the extra + // three vertices are calculated. + if (aIsBiggerSide == bIsBiggerSide) { + if (aIsBiggerSide) { // Both closest points on the bigger side + byte c1 = (byte) (aPoint | bPoint); + byte c2 = (byte) (aPoint & bPoint); + if ((c1 & 0x01) == 0) { + xsv_ext0 = xsb; + xsv_ext1 = xsb - 1; + dx_ext0 = dx0 - 3 * SQUISH_CONSTANT_4D; + dx_ext1 = dx0 + 1 - 2 * SQUISH_CONSTANT_4D; + } else { + xsv_ext0 = xsv_ext1 = xsb + 1; + dx_ext0 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; + dx_ext1 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + } + + if ((c1 & 0x02) == 0) { + ysv_ext0 = ysb; + ysv_ext1 = ysb - 1; + dy_ext0 = dy0 - 3 * SQUISH_CONSTANT_4D; + dy_ext1 = dy0 + 1 - 2 * SQUISH_CONSTANT_4D; + } else { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; + dy_ext1 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + } + + if ((c1 & 0x04) == 0) { + zsv_ext0 = zsb; + zsv_ext1 = zsb - 1; + dz_ext0 = dz0 - 3 * SQUISH_CONSTANT_4D; + dz_ext1 = dz0 + 1 - 2 * SQUISH_CONSTANT_4D; + } else { + zsv_ext0 = zsv_ext1 = zsb + 1; + dz_ext0 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; + dz_ext1 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + } + + if ((c1 & 0x08) == 0) { + wsv_ext0 = wsb; + wsv_ext1 = wsb - 1; + dw_ext0 = dw0 - 3 * SQUISH_CONSTANT_4D; + dw_ext1 = dw0 + 1 - 2 * SQUISH_CONSTANT_4D; + } else { + wsv_ext0 = wsv_ext1 = wsb + 1; + dw_ext0 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; + dw_ext1 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + } + + // One combination is a permutation of (0,0,0,2) based on c2 + xsv_ext2 = xsb; + ysv_ext2 = ysb; + zsv_ext2 = zsb; + wsv_ext2 = wsb; + dx_ext2 = dx0 - 2 * SQUISH_CONSTANT_4D; + dy_ext2 = dy0 - 2 * SQUISH_CONSTANT_4D; + dz_ext2 = dz0 - 2 * SQUISH_CONSTANT_4D; + dw_ext2 = dw0 - 2 * SQUISH_CONSTANT_4D; + if ((c2 & 0x01) != 0) { + xsv_ext2 += 2; + dx_ext2 -= 2; + } else if ((c2 & 0x02) != 0) { + ysv_ext2 += 2; + dy_ext2 -= 2; + } else if ((c2 & 0x04) != 0) { + zsv_ext2 += 2; + dz_ext2 -= 2; + } else { + wsv_ext2 += 2; + dw_ext2 -= 2; + } + + } else { // Both closest points on the smaller side + // One of the two extra points is (0,0,0,0) + xsv_ext2 = xsb; + ysv_ext2 = ysb; + zsv_ext2 = zsb; + wsv_ext2 = wsb; + dx_ext2 = dx0; + dy_ext2 = dy0; + dz_ext2 = dz0; + dw_ext2 = dw0; + + // Other two points are based on the omitted axes. + byte c = (byte) (aPoint | bPoint); + + if ((c & 0x01) == 0) { + xsv_ext0 = xsb - 1; + xsv_ext1 = xsb; + dx_ext0 = dx0 + 1 - SQUISH_CONSTANT_4D; + dx_ext1 = dx0 - SQUISH_CONSTANT_4D; + } else { + xsv_ext0 = xsv_ext1 = xsb + 1; + dx_ext0 = dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_4D; + } + + if ((c & 0x02) == 0) { + ysv_ext0 = ysv_ext1 = ysb; + dy_ext0 = dy_ext1 = dy0 - SQUISH_CONSTANT_4D; + if ((c & 0x01) == 0x01) { + ysv_ext0 -= 1; + dy_ext0 += 1; + } else { + ysv_ext1 -= 1; + dy_ext1 += 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_4D; + } + + if ((c & 0x04) == 0) { + zsv_ext0 = zsv_ext1 = zsb; + dz_ext0 = dz_ext1 = dz0 - SQUISH_CONSTANT_4D; + if ((c & 0x03) == 0x03) { + zsv_ext0 -= 1; + dz_ext0 += 1; + } else { + zsv_ext1 -= 1; + dz_ext1 += 1; + } + } else { + zsv_ext0 = zsv_ext1 = zsb + 1; + dz_ext0 = dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_4D; + } + + if ((c & 0x08) == 0) { + wsv_ext0 = wsb; + wsv_ext1 = wsb - 1; + dw_ext0 = dw0 - SQUISH_CONSTANT_4D; + dw_ext1 = dw0 + 1 - SQUISH_CONSTANT_4D; + } else { + wsv_ext0 = wsv_ext1 = wsb + 1; + dw_ext0 = dw_ext1 = dw0 - 1 - SQUISH_CONSTANT_4D; + } + + } + } else { // One point on each "side" + byte c1, c2; + if (aIsBiggerSide) { + c1 = aPoint; + c2 = bPoint; + } else { + c1 = bPoint; + c2 = aPoint; + } + + // Two contributions are the bigger-sided point with each 0 + // replaced with -1. + if ((c1 & 0x01) == 0) { + xsv_ext0 = xsb - 1; + xsv_ext1 = xsb; + dx_ext0 = dx0 + 1 - SQUISH_CONSTANT_4D; + dx_ext1 = dx0 - SQUISH_CONSTANT_4D; + } else { + xsv_ext0 = xsv_ext1 = xsb + 1; + dx_ext0 = dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_4D; + } + + if ((c1 & 0x02) == 0) { + ysv_ext0 = ysv_ext1 = ysb; + dy_ext0 = dy_ext1 = dy0 - SQUISH_CONSTANT_4D; + if ((c1 & 0x01) == 0x01) { + ysv_ext0 -= 1; + dy_ext0 += 1; + } else { + ysv_ext1 -= 1; + dy_ext1 += 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_4D; + } + + if ((c1 & 0x04) == 0) { + zsv_ext0 = zsv_ext1 = zsb; + dz_ext0 = dz_ext1 = dz0 - SQUISH_CONSTANT_4D; + if ((c1 & 0x03) == 0x03) { + zsv_ext0 -= 1; + dz_ext0 += 1; + } else { + zsv_ext1 -= 1; + dz_ext1 += 1; + } + } else { + zsv_ext0 = zsv_ext1 = zsb + 1; + dz_ext0 = dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_4D; + } + + if ((c1 & 0x08) == 0) { + wsv_ext0 = wsb; + wsv_ext1 = wsb - 1; + dw_ext0 = dw0 - SQUISH_CONSTANT_4D; + dw_ext1 = dw0 + 1 - SQUISH_CONSTANT_4D; + } else { + wsv_ext0 = wsv_ext1 = wsb + 1; + dw_ext0 = dw_ext1 = dw0 - 1 - SQUISH_CONSTANT_4D; + } + + // One contribution is a permutation of (0,0,0,2) based on the + // smaller-sided point + xsv_ext2 = xsb; + ysv_ext2 = ysb; + zsv_ext2 = zsb; + wsv_ext2 = wsb; + dx_ext2 = dx0 - 2 * SQUISH_CONSTANT_4D; + dy_ext2 = dy0 - 2 * SQUISH_CONSTANT_4D; + dz_ext2 = dz0 - 2 * SQUISH_CONSTANT_4D; + dw_ext2 = dw0 - 2 * SQUISH_CONSTANT_4D; + if ((c2 & 0x01) != 0) { + xsv_ext2 += 2; + dx_ext2 -= 2; + } else if ((c2 & 0x02) != 0) { + ysv_ext2 += 2; + dy_ext2 -= 2; + } else if ((c2 & 0x04) != 0) { + zsv_ext2 += 2; + dz_ext2 -= 2; + } else { + wsv_ext2 += 2; + dw_ext2 -= 2; + } + } + + // Contribution (1,0,0,0) + double dx1 = dx0 - 1 - SQUISH_CONSTANT_4D; + double dy1 = dy0 - 0 - SQUISH_CONSTANT_4D; + double dz1 = dz0 - 0 - SQUISH_CONSTANT_4D; + double dw1 = dw0 - 0 - SQUISH_CONSTANT_4D; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 0, dx1, dy1, dz1, dw1); + } + + // Contribution (0,1,0,0) + double dx2 = dx0 - 0 - SQUISH_CONSTANT_4D; + double dy2 = dy0 - 1 - SQUISH_CONSTANT_4D; + double dz2 = dz1; + double dw2 = dw1; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 0, dx2, dy2, dz2, dw2); + } + + // Contribution (0,0,1,0) + double dx3 = dx2; + double dy3 = dy1; + double dz3 = dz0 - 1 - SQUISH_CONSTANT_4D; + double dw3 = dw1; + double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3; + if (attn3 > 0) { + attn3 *= attn3; + value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 0, dx3, dy3, dz3, dw3); + } + + // Contribution (0,0,0,1) + double dx4 = dx2; + double dy4 = dy1; + double dz4 = dz1; + double dw4 = dw0 - 1 - SQUISH_CONSTANT_4D; + double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4; + if (attn4 > 0) { + attn4 *= attn4; + value += attn4 * attn4 * extrapolate(xsb + 0, ysb + 0, zsb + 0, wsb + 1, dx4, dy4, dz4, dw4); + } + + // Contribution (1,1,0,0) + double dx5 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dy5 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dz5 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dw5 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; + double attn5 = 2 - dx5 * dx5 - dy5 * dy5 - dz5 * dz5 - dw5 * dw5; + if (attn5 > 0) { + attn5 *= attn5; + value += attn5 * attn5 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 0, dx5, dy5, dz5, dw5); + } + + // Contribution (1,0,1,0) + double dx6 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dy6 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dz6 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dw6 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; + double attn6 = 2 - dx6 * dx6 - dy6 * dy6 - dz6 * dz6 - dw6 * dw6; + if (attn6 > 0) { + attn6 *= attn6; + value += attn6 * attn6 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 0, dx6, dy6, dz6, dw6); + } + + // Contribution (1,0,0,1) + double dx7 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dy7 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dz7 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dw7 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + double attn7 = 2 - dx7 * dx7 - dy7 * dy7 - dz7 * dz7 - dw7 * dw7; + if (attn7 > 0) { + attn7 *= attn7; + value += attn7 * attn7 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 1, dx7, dy7, dz7, dw7); + } + + // Contribution (0,1,1,0) + double dx8 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dy8 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dz8 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dw8 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; + double attn8 = 2 - dx8 * dx8 - dy8 * dy8 - dz8 * dz8 - dw8 * dw8; + if (attn8 > 0) { + attn8 *= attn8; + value += attn8 * attn8 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 0, dx8, dy8, dz8, dw8); + } + + // Contribution (0,1,0,1) + double dx9 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dy9 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dz9 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dw9 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + double attn9 = 2 - dx9 * dx9 - dy9 * dy9 - dz9 * dz9 - dw9 * dw9; + if (attn9 > 0) { + attn9 *= attn9; + value += attn9 * attn9 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 1, dx9, dy9, dz9, dw9); + } + + // Contribution (0,0,1,1) + double dx10 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dy10 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dz10 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dw10 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + double attn10 = 2 - dx10 * dx10 - dy10 * dy10 - dz10 * dz10 - dw10 * dw10; + if (attn10 > 0) { + attn10 *= attn10; + value += attn10 * attn10 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 1, dx10, dy10, dz10, dw10); + } + } else { // We're inside the second dispentachoron (Rectified 4-Simplex) + double aScore; + byte aPoint; + boolean aIsBiggerSide = true; + double bScore; + byte bPoint; + boolean bIsBiggerSide = true; + + // Decide between (0,0,1,1) and (1,1,0,0) + if (xins + yins < zins + wins) { + aScore = xins + yins; + aPoint = 0x0C; + } else { + aScore = zins + wins; + aPoint = 0x03; + } + + // Decide between (0,1,0,1) and (1,0,1,0) + if (xins + zins < yins + wins) { + bScore = xins + zins; + bPoint = 0x0A; + } else { + bScore = yins + wins; + bPoint = 0x05; + } + + // Closer between (0,1,1,0) and (1,0,0,1) will replace the further + // of a and b, if closer. + if (xins + wins < yins + zins) { + double score = xins + wins; + if (aScore <= bScore && score < bScore) { + bScore = score; + bPoint = 0x06; + } else if (aScore > bScore && score < aScore) { + aScore = score; + aPoint = 0x06; + } + } else { + double score = yins + zins; + if (aScore <= bScore && score < bScore) { + bScore = score; + bPoint = 0x09; + } else if (aScore > bScore && score < aScore) { + aScore = score; + aPoint = 0x09; + } + } + + // Decide if (0,1,1,1) is closer. + double p1 = 3 - inSum + xins; + if (aScore <= bScore && p1 < bScore) { + bScore = p1; + bPoint = 0x0E; + bIsBiggerSide = false; + } else if (aScore > bScore && p1 < aScore) { + aScore = p1; + aPoint = 0x0E; + aIsBiggerSide = false; + } + + // Decide if (1,0,1,1) is closer. + double p2 = 3 - inSum + yins; + if (aScore <= bScore && p2 < bScore) { + bScore = p2; + bPoint = 0x0D; + bIsBiggerSide = false; + } else if (aScore > bScore && p2 < aScore) { + aScore = p2; + aPoint = 0x0D; + aIsBiggerSide = false; + } + + // Decide if (1,1,0,1) is closer. + double p3 = 3 - inSum + zins; + if (aScore <= bScore && p3 < bScore) { + bScore = p3; + bPoint = 0x0B; + bIsBiggerSide = false; + } else if (aScore > bScore && p3 < aScore) { + aScore = p3; + aPoint = 0x0B; + aIsBiggerSide = false; + } + + // Decide if (1,1,1,0) is closer. + double p4 = 3 - inSum + wins; + if (aScore <= bScore && p4 < bScore) { + bScore = p4; + bPoint = 0x07; + bIsBiggerSide = false; + } else if (aScore > bScore && p4 < aScore) { + aScore = p4; + aPoint = 0x07; + aIsBiggerSide = false; + } + + // Where each of the two closest points are determines how the extra + // three vertices are calculated. + if (aIsBiggerSide == bIsBiggerSide) { + if (aIsBiggerSide) { // Both closest points on the bigger side + byte c1 = (byte) (aPoint & bPoint); + byte c2 = (byte) (aPoint | bPoint); + + // Two contributions are permutations of (0,0,0,1) and + // (0,0,0,2) based on c1 + xsv_ext0 = xsv_ext1 = xsb; + ysv_ext0 = ysv_ext1 = ysb; + zsv_ext0 = zsv_ext1 = zsb; + wsv_ext0 = wsv_ext1 = wsb; + dx_ext0 = dx0 - SQUISH_CONSTANT_4D; + dy_ext0 = dy0 - SQUISH_CONSTANT_4D; + dz_ext0 = dz0 - SQUISH_CONSTANT_4D; + dw_ext0 = dw0 - SQUISH_CONSTANT_4D; + dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_4D; + dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_4D; + dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_4D; + dw_ext1 = dw0 - 2 * SQUISH_CONSTANT_4D; + if ((c1 & 0x01) != 0) { + xsv_ext0 += 1; + dx_ext0 -= 1; + xsv_ext1 += 2; + dx_ext1 -= 2; + } else if ((c1 & 0x02) != 0) { + ysv_ext0 += 1; + dy_ext0 -= 1; + ysv_ext1 += 2; + dy_ext1 -= 2; + } else if ((c1 & 0x04) != 0) { + zsv_ext0 += 1; + dz_ext0 -= 1; + zsv_ext1 += 2; + dz_ext1 -= 2; + } else { + wsv_ext0 += 1; + dw_ext0 -= 1; + wsv_ext1 += 2; + dw_ext1 -= 2; + } + + // One contribution is a permutation of (1,1,1,-1) based on + // c2 + xsv_ext2 = xsb + 1; + ysv_ext2 = ysb + 1; + zsv_ext2 = zsb + 1; + wsv_ext2 = wsb + 1; + dx_ext2 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + dy_ext2 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + dz_ext2 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + dw_ext2 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + if ((c2 & 0x01) == 0) { + xsv_ext2 -= 2; + dx_ext2 += 2; + } else if ((c2 & 0x02) == 0) { + ysv_ext2 -= 2; + dy_ext2 += 2; + } else if ((c2 & 0x04) == 0) { + zsv_ext2 -= 2; + dz_ext2 += 2; + } else { + wsv_ext2 -= 2; + dw_ext2 += 2; + } + } else { // Both closest points on the smaller side + // One of the two extra points is (1,1,1,1) + xsv_ext2 = xsb + 1; + ysv_ext2 = ysb + 1; + zsv_ext2 = zsb + 1; + wsv_ext2 = wsb + 1; + dx_ext2 = dx0 - 1 - 4 * SQUISH_CONSTANT_4D; + dy_ext2 = dy0 - 1 - 4 * SQUISH_CONSTANT_4D; + dz_ext2 = dz0 - 1 - 4 * SQUISH_CONSTANT_4D; + dw_ext2 = dw0 - 1 - 4 * SQUISH_CONSTANT_4D; + + // Other two points are based on the shared axes. + byte c = (byte) (aPoint & bPoint); + + if ((c & 0x01) != 0) { + xsv_ext0 = xsb + 2; + xsv_ext1 = xsb + 1; + dx_ext0 = dx0 - 2 - 3 * SQUISH_CONSTANT_4D; + dx_ext1 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; + } else { + xsv_ext0 = xsv_ext1 = xsb; + dx_ext0 = dx_ext1 = dx0 - 3 * SQUISH_CONSTANT_4D; + } + + if ((c & 0x02) != 0) { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy_ext1 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; + if ((c & 0x01) == 0) { + ysv_ext0 += 1; + dy_ext0 -= 1; + } else { + ysv_ext1 += 1; + dy_ext1 -= 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysb; + dy_ext0 = dy_ext1 = dy0 - 3 * SQUISH_CONSTANT_4D; + } + + if ((c & 0x04) != 0) { + zsv_ext0 = zsv_ext1 = zsb + 1; + dz_ext0 = dz_ext1 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; + if ((c & 0x03) == 0) { + zsv_ext0 += 1; + dz_ext0 -= 1; + } else { + zsv_ext1 += 1; + dz_ext1 -= 1; + } + } else { + zsv_ext0 = zsv_ext1 = zsb; + dz_ext0 = dz_ext1 = dz0 - 3 * SQUISH_CONSTANT_4D; + } + + if ((c & 0x08) != 0) { + wsv_ext0 = wsb + 1; + wsv_ext1 = wsb + 2; + dw_ext0 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; + dw_ext1 = dw0 - 2 - 3 * SQUISH_CONSTANT_4D; + } else { + wsv_ext0 = wsv_ext1 = wsb; + dw_ext0 = dw_ext1 = dw0 - 3 * SQUISH_CONSTANT_4D; + } + } + } else { // One point on each "side" + byte c1, c2; + if (aIsBiggerSide) { + c1 = aPoint; + c2 = bPoint; + } else { + c1 = bPoint; + c2 = aPoint; + } + + // Two contributions are the bigger-sided point with each 1 + // replaced with 2. + if ((c1 & 0x01) != 0) { + xsv_ext0 = xsb + 2; + xsv_ext1 = xsb + 1; + dx_ext0 = dx0 - 2 - 3 * SQUISH_CONSTANT_4D; + dx_ext1 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; + } else { + xsv_ext0 = xsv_ext1 = xsb; + dx_ext0 = dx_ext1 = dx0 - 3 * SQUISH_CONSTANT_4D; + } + + if ((c1 & 0x02) != 0) { + ysv_ext0 = ysv_ext1 = ysb + 1; + dy_ext0 = dy_ext1 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; + if ((c1 & 0x01) == 0) { + ysv_ext0 += 1; + dy_ext0 -= 1; + } else { + ysv_ext1 += 1; + dy_ext1 -= 1; + } + } else { + ysv_ext0 = ysv_ext1 = ysb; + dy_ext0 = dy_ext1 = dy0 - 3 * SQUISH_CONSTANT_4D; + } + + if ((c1 & 0x04) != 0) { + zsv_ext0 = zsv_ext1 = zsb + 1; + dz_ext0 = dz_ext1 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; + if ((c1 & 0x03) == 0) { + zsv_ext0 += 1; + dz_ext0 -= 1; + } else { + zsv_ext1 += 1; + dz_ext1 -= 1; + } + } else { + zsv_ext0 = zsv_ext1 = zsb; + dz_ext0 = dz_ext1 = dz0 - 3 * SQUISH_CONSTANT_4D; + } + + if ((c1 & 0x08) != 0) { + wsv_ext0 = wsb + 1; + wsv_ext1 = wsb + 2; + dw_ext0 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; + dw_ext1 = dw0 - 2 - 3 * SQUISH_CONSTANT_4D; + } else { + wsv_ext0 = wsv_ext1 = wsb; + dw_ext0 = dw_ext1 = dw0 - 3 * SQUISH_CONSTANT_4D; + } + + // One contribution is a permutation of (1,1,1,-1) based on the + // smaller-sided point + xsv_ext2 = xsb + 1; + ysv_ext2 = ysb + 1; + zsv_ext2 = zsb + 1; + wsv_ext2 = wsb + 1; + dx_ext2 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + dy_ext2 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + dz_ext2 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + dw_ext2 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + if ((c2 & 0x01) == 0) { + xsv_ext2 -= 2; + dx_ext2 += 2; + } else if ((c2 & 0x02) == 0) { + ysv_ext2 -= 2; + dy_ext2 += 2; + } else if ((c2 & 0x04) == 0) { + zsv_ext2 -= 2; + dz_ext2 += 2; + } else { + wsv_ext2 -= 2; + dw_ext2 += 2; + } + } + + // Contribution (1,1,1,0) + double dx4 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; + double dy4 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; + double dz4 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; + double dw4 = dw0 - 3 * SQUISH_CONSTANT_4D; + double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4; + if (attn4 > 0) { + attn4 *= attn4; + value += attn4 * attn4 * extrapolate(xsb + 1, ysb + 1, zsb + 1, wsb + 0, dx4, dy4, dz4, dw4); + } + + // Contribution (1,1,0,1) + double dx3 = dx4; + double dy3 = dy4; + double dz3 = dz0 - 3 * SQUISH_CONSTANT_4D; + double dw3 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; + double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3; + if (attn3 > 0) { + attn3 *= attn3; + value += attn3 * attn3 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 1, dx3, dy3, dz3, dw3); + } + + // Contribution (1,0,1,1) + double dx2 = dx4; + double dy2 = dy0 - 3 * SQUISH_CONSTANT_4D; + double dz2 = dz4; + double dw2 = dw3; + double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2; + if (attn2 > 0) { + attn2 *= attn2; + value += attn2 * attn2 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 1, dx2, dy2, dz2, dw2); + } + + // Contribution (0,1,1,1) + double dx1 = dx0 - 3 * SQUISH_CONSTANT_4D; + double dz1 = dz4; + double dy1 = dy4; + double dw1 = dw3; + double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1; + if (attn1 > 0) { + attn1 *= attn1; + value += attn1 * attn1 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 1, dx1, dy1, dz1, dw1); + } + + // Contribution (1,1,0,0) + double dx5 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dy5 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dz5 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dw5 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; + double attn5 = 2 - dx5 * dx5 - dy5 * dy5 - dz5 * dz5 - dw5 * dw5; + if (attn5 > 0) { + attn5 *= attn5; + value += attn5 * attn5 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 0, dx5, dy5, dz5, dw5); + } + + // Contribution (1,0,1,0) + double dx6 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dy6 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dz6 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dw6 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; + double attn6 = 2 - dx6 * dx6 - dy6 * dy6 - dz6 * dz6 - dw6 * dw6; + if (attn6 > 0) { + attn6 *= attn6; + value += attn6 * attn6 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 0, dx6, dy6, dz6, dw6); + } + + // Contribution (1,0,0,1) + double dx7 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dy7 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dz7 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dw7 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + double attn7 = 2 - dx7 * dx7 - dy7 * dy7 - dz7 * dz7 - dw7 * dw7; + if (attn7 > 0) { + attn7 *= attn7; + value += attn7 * attn7 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 1, dx7, dy7, dz7, dw7); + } + + // Contribution (0,1,1,0) + double dx8 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dy8 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dz8 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dw8 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; + double attn8 = 2 - dx8 * dx8 - dy8 * dy8 - dz8 * dz8 - dw8 * dw8; + if (attn8 > 0) { + attn8 *= attn8; + value += attn8 * attn8 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 0, dx8, dy8, dz8, dw8); + } + + // Contribution (0,1,0,1) + double dx9 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dy9 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dz9 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dw9 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + double attn9 = 2 - dx9 * dx9 - dy9 * dy9 - dz9 * dz9 - dw9 * dw9; + if (attn9 > 0) { + attn9 *= attn9; + value += attn9 * attn9 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 1, dx9, dy9, dz9, dw9); + } + + // Contribution (0,0,1,1) + double dx10 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dy10 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; + double dz10 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; + double dw10 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; + double attn10 = 2 - dx10 * dx10 - dy10 * dy10 - dz10 * dz10 - dw10 * dw10; + if (attn10 > 0) { + attn10 *= attn10; + value += attn10 * attn10 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 1, dx10, dy10, dz10, dw10); + } + } + + // First extra vertex + double attn_ext0 = 2 - dx_ext0 * dx_ext0 - dy_ext0 * dy_ext0 - dz_ext0 * dz_ext0 - dw_ext0 * dw_ext0; + if (attn_ext0 > 0) { + attn_ext0 *= attn_ext0; + value += attn_ext0 * attn_ext0 * extrapolate( + xsv_ext0, + ysv_ext0, + zsv_ext0, + wsv_ext0, + dx_ext0, + dy_ext0, + dz_ext0, + dw_ext0 + ); + } + + // Second extra vertex + double attn_ext1 = 2 - dx_ext1 * dx_ext1 - dy_ext1 * dy_ext1 - dz_ext1 * dz_ext1 - dw_ext1 * dw_ext1; + if (attn_ext1 > 0) { + attn_ext1 *= attn_ext1; + value += attn_ext1 * attn_ext1 * extrapolate( + xsv_ext1, + ysv_ext1, + zsv_ext1, + wsv_ext1, + dx_ext1, + dy_ext1, + dz_ext1, + dw_ext1 + ); + } + + // Third extra vertex + double attn_ext2 = 2 - dx_ext2 * dx_ext2 - dy_ext2 * dy_ext2 - dz_ext2 * dz_ext2 - dw_ext2 * dw_ext2; + if (attn_ext2 > 0) { + attn_ext2 *= attn_ext2; + value += attn_ext2 * attn_ext2 * extrapolate( + xsv_ext2, + ysv_ext2, + zsv_ext2, + wsv_ext2, + dx_ext2, + dy_ext2, + dz_ext2, + dw_ext2 + ); + } + + return value / NORM_CONSTANT_4D; + } + + private double extrapolate(int xsb, int ysb, double dx, double dy) { + int index = perm[(perm[xsb & 0xFF] + ysb) & 0xFF] & 0x0E; + return gradients2D[index] * dx + gradients2D[index + 1] * dy; + } + + private double extrapolate(int xsb, int ysb, int zsb, double dx, double dy, double dz) { + int index = permGradIndex3D[(perm[(perm[xsb & 0xFF] + ysb) & 0xFF] + zsb) & 0xFF]; + return gradients3D[index] * dx + gradients3D[index + 1] * dy + gradients3D[index + 2] * dz; + } + + private double extrapolate(int xsb, int ysb, int zsb, int wsb, double dx, double dy, double dz, double dw) { + int index = perm[(perm[(perm[(perm[xsb & 0xFF] + ysb) & 0xFF] + zsb) & 0xFF] + wsb) & 0xFF] & 0xFC; + return gradients4D[index] * dx + gradients4D[index + 1] * dy + gradients4D[index + 2] * dz + gradients4D[index + 3] * dw; + } + + private static int fastFloor(double x) { + int xi = (int) x; + return x < xi ? xi - 1 : xi; + } + + // Gradients for 2D. They approximate the directions to the + // vertices of an octagon from the center. + private static final byte[] gradients2D = new byte[]{5, 2, 2, 5, -5, 2, -2, 5, 5, -2, 2, -5, -5, -2, -2, -5,}; + + // Gradients for 3D. They approximate the directions to the + // vertices of a rhombicuboctahedron from the center, skewed so + // that the triangular and square facets can be inscribed inside + // circles of the same radius. + private static final byte[] gradients3D = new byte[]{ + -11, + 4, + 4, + -4, + 11, + 4, + -4, + 4, + 11, + 11, + 4, + 4, + 4, + 11, + 4, + 4, + 4, + 11, + -11, + -4, + 4, + -4, + -11, + 4, + -4, + -4, + 11, + 11, + -4, + 4, + 4, + -11, + 4, + 4, + -4, + 11, + -11, + 4, + -4, + -4, + 11, + -4, + -4, + 4, + -11, + 11, + 4, + -4, + 4, + 11, + -4, + 4, + 4, + -11, + -11, + -4, + -4, + -4, + -11, + -4, + -4, + -4, + -11, + 11, + -4, + -4, + 4, + -11, + -4, + 4, + -4, + -11, + }; + + // Gradients for 4D. They approximate the directions to the + // vertices of a disprismatotesseractihexadecachoron from the center, + // skewed so that the tetrahedral and cubic facets can be inscribed inside + // spheres of the same radius. + private static final byte[] gradients4D = new byte[]{ + 3, + 1, + 1, + 1, + 1, + 3, + 1, + 1, + 1, + 1, + 3, + 1, + 1, + 1, + 1, + 3, + -3, + 1, + 1, + 1, + -1, + 3, + 1, + 1, + -1, + 1, + 3, + 1, + -1, + 1, + 1, + 3, + 3, + -1, + 1, + 1, + 1, + -3, + 1, + 1, + 1, + -1, + 3, + 1, + 1, + -1, + 1, + 3, + -3, + -1, + 1, + 1, + -1, + -3, + 1, + 1, + -1, + -1, + 3, + 1, + -1, + -1, + 1, + 3, + 3, + 1, + -1, + 1, + 1, + 3, + -1, + 1, + 1, + 1, + -3, + 1, + 1, + 1, + -1, + 3, + -3, + 1, + -1, + 1, + -1, + 3, + -1, + 1, + -1, + 1, + -3, + 1, + -1, + 1, + -1, + 3, + 3, + -1, + -1, + 1, + 1, + -3, + -1, + 1, + 1, + -1, + -3, + 1, + 1, + -1, + -1, + 3, + -3, + -1, + -1, + 1, + -1, + -3, + -1, + 1, + -1, + -1, + -3, + 1, + -1, + -1, + -1, + 3, + 3, + 1, + 1, + -1, + 1, + 3, + 1, + -1, + 1, + 1, + 3, + -1, + 1, + 1, + 1, + -3, + -3, + 1, + 1, + -1, + -1, + 3, + 1, + -1, + -1, + 1, + 3, + -1, + -1, + 1, + 1, + -3, + 3, + -1, + 1, + -1, + 1, + -3, + 1, + -1, + 1, + -1, + 3, + -1, + 1, + -1, + 1, + -3, + -3, + -1, + 1, + -1, + -1, + -3, + 1, + -1, + -1, + -1, + 3, + -1, + -1, + -1, + 1, + -3, + 3, + 1, + -1, + -1, + 1, + 3, + -1, + -1, + 1, + 1, + -3, + -1, + 1, + 1, + -1, + -3, + -3, + 1, + -1, + -1, + -1, + 3, + -1, + -1, + -1, + 1, + -3, + -1, + -1, + 1, + -1, + -3, + 3, + -1, + -1, + -1, + 1, + -3, + -1, + -1, + 1, + -1, + -3, + -1, + 1, + -1, + -1, + -3, + -3, + -1, + -1, + -1, + -1, + -3, + -1, + -1, + -1, + -1, + -3, + -1, + -1, + -1, + -1, + -3, + }; +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/noise/VoronoiNoise.java b/src/main/java/org/betterx/bclib/noise/VoronoiNoise.java new file mode 100644 index 00000000..80b421a9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/noise/VoronoiNoise.java @@ -0,0 +1,149 @@ +package org.betterx.bclib.noise; + +import org.betterx.bclib.util.MHelper; + +import net.minecraft.core.BlockPos; + +import java.util.Random; + +public class VoronoiNoise { + private static final Random RANDOM = new Random(); + final int seed; + + public VoronoiNoise() { + this(0); + } + + public VoronoiNoise(int seed) { + this.seed = seed; + } + + private int getSeed(int x, int y, int z) { + int h = seed + x * 374761393 + y * 668265263 + z; + h = (h ^ (h >> 13)) * 1274126177; + return h ^ (h >> 16); + } + + public double sample(double x, double y, double z) { + int ix = MHelper.floor(x); + int iy = MHelper.floor(y); + int iz = MHelper.floor(z); + + float px = (float) (x - ix); + float py = (float) (y - iy); + float pz = (float) (z - iz); + + float d = 10; + + for (int pox = -1; pox < 2; pox++) { + for (int poy = -1; poy < 2; poy++) { + for (int poz = -1; poz < 2; poz++) { + RANDOM.setSeed(getSeed(pox + ix, poy + iy, poz + iz)); + float pointX = pox + RANDOM.nextFloat(); + float pointY = poy + RANDOM.nextFloat(); + float pointZ = poz + RANDOM.nextFloat(); + float d2 = MHelper.lengthSqr(pointX - px, pointY - py, pointZ - pz); + if (d2 < d) { + d = d2; + } + } + } + } + + return Math.sqrt(d); + } + + public Random getRandom(double x, double y, double z) { + int ix = MHelper.floor(x); + int iy = MHelper.floor(y); + int iz = MHelper.floor(z); + + float px = (float) (x - ix); + float py = (float) (y - iy); + float pz = (float) (z - iz); + + float d = 10; + + int posX = 0; + int posY = 0; + int posZ = 0; + + for (int pox = -1; pox < 2; pox++) { + for (int poy = -1; poy < 2; poy++) { + for (int poz = -1; poz < 2; poz++) { + RANDOM.setSeed(getSeed(pox + ix, poy + iy, poz + iz)); + float pointX = pox + RANDOM.nextFloat(); + float pointY = poy + RANDOM.nextFloat(); + float pointZ = poz + RANDOM.nextFloat(); + float d2 = MHelper.lengthSqr(pointX - px, pointY - py, pointZ - pz); + if (d2 < d) { + d = d2; + posX = pox; + posY = poy; + posZ = poz; + } + } + } + } + + posX += ix; + posY += iy; + posZ += iz; + + int seed = MHelper.getSeed(posY, posX, posZ); + RANDOM.setSeed(seed); + + return RANDOM; + } + + public BlockPos[] getPos(double x, double y, double z, double scale) { + int ix = MHelper.floor(x); + int iy = MHelper.floor(y); + int iz = MHelper.floor(z); + + float px = (float) (x - ix); + float py = (float) (y - iy); + float pz = (float) (z - iz); + + float d = 10; + float selX = 0; + float selY = 0; + float selZ = 0; + float selXPre = 0; + float selYPre = 0; + float selZPre = 0; + + for (int pox = -1; pox < 2; pox++) { + for (int poy = -1; poy < 2; poy++) { + for (int poz = -1; poz < 2; poz++) { + RANDOM.setSeed(getSeed(pox + ix, poy + iy, poz + iz)); + float pointX = pox + RANDOM.nextFloat(); + float pointY = poy + RANDOM.nextFloat(); + float pointZ = poz + RANDOM.nextFloat(); + float d2 = MHelper.lengthSqr(pointX - px, pointY - py, pointZ - pz); + if (d2 < d) { + d = d2; + selXPre = selX; + selYPre = selY; + selZPre = selZ; + selX = pointX; + selY = pointY; + selZ = pointZ; + } + } + } + } + + BlockPos p1 = new BlockPos( + (int) ((ix + (double) selX) * scale), + (int) ((iy + (double) selY) * scale), + (int) ((iz + (double) selZ) * scale) + ); + BlockPos p2 = new BlockPos( + (int) ((ix + (double) selXPre) * scale), + (int) ((iy + (double) selYPre) * scale), + (int) ((iz + (double) selZPre) * scale) + ); + return new BlockPos[]{p1, p2}; + } +} diff --git a/src/main/java/org/betterx/bclib/particles/BCLParticleType.java b/src/main/java/org/betterx/bclib/particles/BCLParticleType.java new file mode 100644 index 00000000..f4ec3a16 --- /dev/null +++ b/src/main/java/org/betterx/bclib/particles/BCLParticleType.java @@ -0,0 +1,89 @@ +package org.betterx.bclib.particles; + +import com.mojang.serialization.Codec; +import net.minecraft.core.Registry; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.core.particles.ParticleType; +import net.minecraft.core.particles.SimpleParticleType; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; + +import net.fabricmc.fabric.api.client.particle.v1.ParticleFactoryRegistry; + +public class BCLParticleType { + + public static ParticleType deserializer( + ParticleOptions.Deserializer factory, + Codec codec + ) { + return deserializer(false, factory, codec); + } + + public static ParticleType deserializer( + boolean overrideLimiter, + ParticleOptions.Deserializer factory, + Codec codec + ) { + return new ParticleType(overrideLimiter, factory) { + @Override + public Codec codec() { + return codec; + } + }; + } + + public static ParticleType register( + ResourceLocation location, + ParticleOptions.Deserializer factory, + Codec codec + ) { + return register(location, false, factory, codec); + } + + public static ParticleType register( + ResourceLocation location, + boolean overrideLimiter, + ParticleOptions.Deserializer factory, + Codec codec + ) { + return Registry.register( + BuiltInRegistries.PARTICLE_TYPE, + location, + deserializer(overrideLimiter, factory, codec) + ); + } + + public static SimpleParticleType simple(boolean overrideLimiter) { + return new SimpleParticleType(overrideLimiter) { + }; + } + + public static SimpleParticleType simple() { + return simple(false); + } + + public static SimpleParticleType register(ResourceLocation location) { + return register(location, false); + } + + public static SimpleParticleType register(ResourceLocation location, boolean overrideLimiter) { + return Registry.register(BuiltInRegistries.PARTICLE_TYPE, location, simple(overrideLimiter)); + } + + public static SimpleParticleType register( + ResourceLocation location, + ParticleFactoryRegistry.PendingParticleFactory provider + ) { + return register(location, false, provider); + } + + public static SimpleParticleType register( + ResourceLocation location, + boolean overrideLimiter, + ParticleFactoryRegistry.PendingParticleFactory provider + ) { + SimpleParticleType type = Registry.register(BuiltInRegistries.PARTICLE_TYPE, location, simple(overrideLimiter)); + ParticleFactoryRegistry.getInstance().register(type, provider); + return type; + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/AbstractBaseRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/AbstractBaseRecipeBuilder.java new file mode 100644 index 00000000..173ae4da --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/AbstractBaseRecipeBuilder.java @@ -0,0 +1,173 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.datagen.DatapackRecipeBuilder; +import org.betterx.bclib.api.v3.datagen.RecipeDataProvider; +import org.betterx.bclib.util.RecipeHelper; + +import net.minecraft.advancements.CriterionTriggerInstance; +import net.minecraft.advancements.critereon.InventoryChangeTrigger; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeProvider; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; + +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public abstract class AbstractBaseRecipeBuilder implements DatapackRecipeBuilder { + protected final ResourceLocation id; + protected final ItemStack output; + protected String group; + + protected RecipeCategory category; + + protected boolean alright; + + protected AbstractBaseRecipeBuilder(ResourceLocation id, ItemStack output) { + this.id = id; + this.output = output; + this.category = RecipeCategory.MISC; + this.alright = RecipeHelper.exists(output.getItem()); + } + + protected AbstractBaseRecipeBuilder(ResourceLocation id, ItemLike output) { + this(id, new ItemStack(output, 1)); + } + + public T setCategory(RecipeCategory category) { + this.category = category; + return (T) this; + } + + protected T setGroup(String group) { + this.group = group; + return (T) this; + } + + protected T setOutputCount(int count) { + this.output.setCount(count); + return (T) this; + } + + protected T setOutputTag(CompoundTag tag) { + this.output.setTag(tag); + return (T) this; + } + + protected T unlockedBy(ItemLike item) { + this.unlocks( + "has_" + item.asItem().getDescriptionId(), + RecipeProvider.has(item.asItem()) + ); + + return (T) this; + } + + protected T unlockedBy(TagKey tag) { + this.unlocks( + "has_tag_" + tag.location().getNamespace() + "_" + tag.location().getPath(), + RecipeProvider.has(tag) + ); + + return (T) this; + } + + /** + * The Recipe will be unlocked by one of the passed Items. As sonn als players have one in their Inventory + * the recipe will unlock. Those Items are mostly the input Items for the recipe. + *

+ * This method will automatically derive a unique name for the criterion and call + * {@link #unlocks(String, ItemLike...)} + * + * @param items {@link Item}s or {@link Block}s that will unlock the recipe. + */ + protected T unlockedBy(ItemLike... items) { + String name = "has_" + + Arrays.stream(items) + .map(block -> (block instanceof Block) + ? BuiltInRegistries.BLOCK.getKey((Block) block) + : BuiltInRegistries.ITEM.getKey((Item) block)) + .filter(id -> id != null) + .map(id -> id.getPath()) + .collect(Collectors.joining("_")); + if (name.length() > 45) name = name.substring(0, 42); + return unlocks(name, items); + } + + /** + * The Recipe will be unlocked by one of the passed Items. As sonn als players have one in their Inventory + * the recipe will unlock. Those Items are mostly the input Items for the recipe. + * + * @param name The name for this unlock-Criteria + * @param items {@link Item}s or {@link Block}s that will unlock the recipe. + */ + protected T unlocks(String name, ItemLike... items) { + return unlocks(name, InventoryChangeTrigger.TriggerInstance.hasItems(items)); + } + + /** + * The Recipe will be unlocked by one of the passed Items. As sonn als players have one in their Inventory + * the recipe will unlock. Those Items are mostly the input Items for the recipe. + *

+ * This method will automatically get the Items from the stacl and call {@link #unlockedBy(ItemLike...)} + * + * @param stacks {@link ItemStack}s that will unlock the recipe. The count is ignored. + */ + protected T unlockedBy(ItemStack... stacks) { + ItemLike[] items = Arrays.stream(stacks) + .filter(stack -> stack.getCount() > 0) + .map(stack -> (ItemLike) stack.getItem()) + .toArray(ItemLike[]::new); + + return unlockedBy(items); + } + + protected abstract T unlocks(String name, CriterionTriggerInstance trigger); + + + public final T build() { + if (!checkRecipe()) + return (T) this; + RecipeDataProvider.register(this); + return (T) this; + } + + protected boolean checkRecipe() { + if (output == null) { + BCLib.LOGGER.warning("Output for Recipe can't be 'null', recipe {} will be ignored!", id); + return false; + } + if (output.is(Items.AIR)) { + BCLib.LOGGER.warning("Unable to build Recipe " + id + ": Result is AIR"); + return false; + } + if (!alright) { + BCLib.LOGGER.debug("Can't add recipe {}! Ingeredient or output do not exists.", id); + return false; + } + return true; + } + + protected abstract void buildRecipe(Consumer cc); + + @Override + public final void build(Consumer cc) { + if (!checkRecipe()) return; + buildRecipe(cc); + } + + @Override + public final ResourceLocation getId() { + return id; + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/AbstractDoubleInputRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/AbstractDoubleInputRecipeBuilder.java new file mode 100644 index 00000000..a3a56c1e --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/AbstractDoubleInputRecipeBuilder.java @@ -0,0 +1,79 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.util.ItemUtil; +import org.betterx.bclib.util.RecipeHelper; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.Container; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.level.ItemLike; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +public abstract class AbstractDoubleInputRecipeBuilder> extends AbstractSingleInputRecipeBuilder { + protected Ingredient secondaryInput; + + protected AbstractDoubleInputRecipeBuilder( + ResourceLocation id, + ItemLike output + ) { + super(id, output); + } + + public T setSecondaryInput(ItemLike... inputs) { + for (ItemLike item : inputs) { + this.alright &= RecipeHelper.exists(item); + } + this.secondaryInput = Ingredient.of(inputs); + return (T) this; + } + + public T setSecondaryInput(TagKey input) { + this.secondaryInput = Ingredient.of(input); + return (T) this; + } + + public T setSecondaryInputAndUnlock(TagKey input) { + setPrimaryInput(input); + this.unlockedBy(input); + return (T) this; + } + + public T setSecondaryInputAndUnlock(ItemLike... inputs) { + setSecondaryInput(inputs); + for (ItemLike item : inputs) unlockedBy(item); + + return (T) this; + } + + @Override + protected boolean checkRecipe() { + if (secondaryInput == null) { + BCLib.LOGGER.warning( + "Secondary input for Recipe can't be 'null', recipe {} will be ignored!", + id + ); + return false; + } + return super.checkRecipe(); + } + + @Override + protected void serializeRecipeData(JsonObject root) { + final JsonArray ingredients = new JsonArray(); + ingredients.add(primaryInput.toJson()); + ingredients.add(secondaryInput.toJson()); + root.add("ingredients", ingredients); + + if (group != null && !group.isEmpty()) { + root.addProperty("group", group); + } + + root.add("result", ItemUtil.toJsonRecipe(output)); + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/AbstractSimpleRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/AbstractSimpleRecipeBuilder.java new file mode 100644 index 00000000..fa83ac49 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/AbstractSimpleRecipeBuilder.java @@ -0,0 +1,61 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.util.RecipeHelper; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; + +public abstract class AbstractSimpleRecipeBuilder extends AbstractBaseRecipeBuilder { + protected Ingredient primaryInput; + + protected AbstractSimpleRecipeBuilder(ResourceLocation id, ItemLike output) { + this(id, new ItemStack(output, 1)); + } + + protected AbstractSimpleRecipeBuilder(ResourceLocation id, ItemStack stack) { + super(id, stack); + } + + public T setPrimaryInput(ItemLike... inputs) { + for (ItemLike item : inputs) { + this.alright &= RecipeHelper.exists(item); + } + this.primaryInput = Ingredient.of(inputs); + return (T) this; + } + + public T setPrimaryInput(TagKey input) { + this.primaryInput = Ingredient.of(input); + return (T) this; + } + + public T setPrimaryInputAndUnlock(TagKey input) { + setPrimaryInput(input); + this.unlockedBy(input); + return (T) this; + } + + public T setPrimaryInputAndUnlock(ItemLike... inputs) { + setPrimaryInput(inputs); + for (ItemLike item : inputs) unlockedBy(item); + + return (T) this; + } + + + protected boolean checkRecipe() { + if (primaryInput == null) { + BCLib.LOGGER.warning( + "Primary input for Recipe can't be 'null', recipe {} will be ignored!", + id + ); + return false; + } + return super.checkRecipe(); + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/AbstractSingleInputRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/AbstractSingleInputRecipeBuilder.java new file mode 100644 index 00000000..962853c2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/AbstractSingleInputRecipeBuilder.java @@ -0,0 +1,118 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.util.ItemUtil; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.CriterionTriggerInstance; +import net.minecraft.advancements.RequirementsStrategy; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.Container; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.level.ItemLike; + +import com.google.gson.JsonObject; + +import java.util.function.Consumer; +import org.jetbrains.annotations.Nullable; + +public abstract class AbstractSingleInputRecipeBuilder> extends AbstractSimpleRecipeBuilder { + protected final Advancement.Builder advancement; + + + protected AbstractSingleInputRecipeBuilder(ResourceLocation id, ItemLike output) { + this(id, new ItemStack(output, 1)); + } + + protected AbstractSingleInputRecipeBuilder(ResourceLocation id, ItemStack output) { + super(id, output); + this.advancement = Advancement.Builder.advancement(); + } + + @Override + public T unlockedBy(ItemLike item) { + return super.unlockedBy(item); + } + + @Override + public T unlockedBy(TagKey tag) { + return super.unlockedBy(tag); + } + + @Override + protected T unlocks(String name, CriterionTriggerInstance trigger) { + this.advancement.addCriterion(name, trigger); + return (T) this; + } + + + @Override + protected void buildRecipe(Consumer cc) { + setupAdvancementForResult(); + cc.accept(new AbstractSingleInputRecipeBuilder.Result()); + } + + protected abstract RecipeSerializer getSerializer(); + + protected void serializeRecipeData(JsonObject root) { + root.add("input", ItemUtil.toJsonIngredientWithNBT(primaryInput)); + + if (group != null && !group.isEmpty()) { + root.addProperty("group", group); + } + + root.add("result", ItemUtil.toJsonRecipeWithNBT(output)); + } + + protected void setupAdvancementForResult() { + advancement.parent(RecipeBuilder.ROOT_RECIPE_ADVANCEMENT) + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id)) + .rewards(net.minecraft.advancements.AdvancementRewards.Builder.recipe(id)) + .requirements(RequirementsStrategy.OR); + } + + protected ResourceLocation createAdvancementId() { + return id.withPrefix("recipes/" + category.getFolderName() + "/"); + } + + public class Result implements FinishedRecipe { + private final ResourceLocation advancementId; + + protected Result() { + this.advancementId = createAdvancementId(); + } + + @Override + public ResourceLocation getId() { + return AbstractSingleInputRecipeBuilder.this.getId(); + } + + @Override + public RecipeSerializer getType() { + return getSerializer(); + } + + @Nullable + @Override + public JsonObject serializeAdvancement() { + return advancement.serializeToJson(); + } + + @Nullable + @Override + public ResourceLocation getAdvancementId() { + return advancementId; + } + + @Override + public void serializeRecipeData(JsonObject root) { + AbstractSingleInputRecipeBuilder.this.serializeRecipeData(root); + } + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/AbstractUnlockableRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/AbstractUnlockableRecipeBuilder.java new file mode 100644 index 00000000..73e069d3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/AbstractUnlockableRecipeBuilder.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.recipes; + +import net.minecraft.advancements.CriterionTriggerInstance; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; + +import java.util.HashMap; +import java.util.Map; + +public abstract class AbstractUnlockableRecipeBuilder extends AbstractSimpleRecipeBuilder { + + protected AbstractUnlockableRecipeBuilder(ResourceLocation id, ItemLike output) { + super(id, output); + } + + protected final Map unlocks = new HashMap<>(); + + @Override + public T unlockedBy(ItemLike item) { + return super.unlockedBy(item); + } + + @Override + public T unlockedBy(TagKey tag) { + return super.unlockedBy(tag); + } + + @Override + protected T unlocks(String name, CriterionTriggerInstance trigger) { + this.unlocks.put(name, trigger); + return (T) this; + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java b/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java new file mode 100644 index 00000000..621970b7 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/AlloyingRecipe.java @@ -0,0 +1,257 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.interfaces.AlloyingRecipeWorkstation; +import org.betterx.bclib.interfaces.UnknownReceipBookCategory; +import org.betterx.bclib.util.ItemUtil; + +import net.minecraft.core.NonNullList; +import net.minecraft.core.RegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.Container; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.Level; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +public class AlloyingRecipe implements Recipe, UnknownReceipBookCategory { + public final static String GROUP = "alloying"; + public final static RecipeType TYPE = BCLRecipeManager.registerType(BCLib.MOD_ID, GROUP); + public final static Serializer SERIALIZER = BCLRecipeManager.registerSerializer( + BCLib.MOD_ID, + GROUP, + new Serializer() + ); + + protected final RecipeType type; + protected final ResourceLocation id; + protected final Ingredient primaryInput; + protected final Ingredient secondaryInput; + protected final ItemStack output; + protected final String group; + protected final float experience; + protected final int smeltTime; + + public AlloyingRecipe( + ResourceLocation id, + String group, + Ingredient primaryInput, + Ingredient secondaryInput, + ItemStack output, + float experience, + int smeltTime + ) { + this.group = group; + this.id = id; + this.primaryInput = primaryInput; + this.secondaryInput = secondaryInput; + this.output = output; + this.experience = experience; + this.smeltTime = smeltTime; + this.type = TYPE; + } + + public float getExperience() { + return this.experience; + } + + public int getSmeltTime() { + return this.smeltTime; + } + + @Override + public NonNullList getIngredients() { + NonNullList defaultedList = NonNullList.create(); + defaultedList.add(primaryInput); + defaultedList.add(secondaryInput); + + return defaultedList; + } + + @Override + public boolean matches(Container inv, Level world) { + return this.primaryInput.test(inv.getItem(0)) && this.secondaryInput.test(inv.getItem(1)) || this.primaryInput.test( + inv.getItem(1)) && this.secondaryInput.test(inv.getItem(0)); + } + + @Override + public ItemStack assemble(Container inv, RegistryAccess registryAccess) { + return this.output.copy(); + } + + @Override + public boolean canCraftInDimensions(int width, int height) { + return true; + } + + @Override + public ItemStack getResultItem(RegistryAccess acc) { + return this.output; + } + + @Override + public ResourceLocation getId() { + return this.id; + } + + @Override + public RecipeSerializer getSerializer() { + return SERIALIZER; + } + + @Override + public RecipeType getType() { + return this.type; + } + + @Override + @Environment(EnvType.CLIENT) + public String getGroup() { + return this.group; + } + + @Environment(EnvType.CLIENT) + public ItemStack getToastSymbol() { + return AlloyingRecipeWorkstation.getWorkstationIcon(); + } + + public static class Builder extends AbstractDoubleInputRecipeBuilder { + private Builder(ResourceLocation id, ItemLike output) { + super(id, output); + this.experience = 0.0F; + this.smeltTime = 350; + } + + static Builder create(ResourceLocation id, ItemLike output) { + return new Builder(id, output); + } + + private float experience; + private int smeltTime; + + + @Override + public Builder setOutputCount(int count) { + return super.setOutputCount(count); + } + + @Override + public Builder setOutputTag(CompoundTag tag) { + return super.setOutputTag(tag); + } + + public Builder setInput(ItemLike primaryInput, ItemLike secondaryInput) { + this.setPrimaryInput(primaryInput); + this.setSecondaryInput(secondaryInput); + return this; + } + + public Builder setInput(TagKey primaryInput, TagKey secondaryInput) { + this.setPrimaryInput(primaryInput); + this.setSecondaryInput(secondaryInput); + return this; + } + + public Builder setExperience(float amount) { + this.experience = amount; + return this; + } + + public Builder setSmeltTime(int time) { + this.smeltTime = time; + return this; + } + + @Override + public Builder setGroup(String group) { + return super.setGroup(group); + } + + @Override + protected boolean checkRecipe() { + if (smeltTime < 0) { + BCLib.LOGGER.warning("Semelt-time for recipe {} most be positive!", id); + return false; + } + return super.checkRecipe(); + } + + @Override + protected RecipeSerializer getSerializer() { + return SERIALIZER; + } + + @Override + protected void serializeRecipeData(JsonObject root) { + super.serializeRecipeData(root); + + if (experience != 0) { + root.addProperty("experience", experience); + } + if (experience != 350) { + root.addProperty("smelttime", smeltTime); + } + } + } + + public static class Serializer implements RecipeSerializer { + @Override + public AlloyingRecipe fromJson(ResourceLocation id, JsonObject json) { + JsonArray ingredients = GsonHelper.getAsJsonArray(json, "ingredients"); + Ingredient primaryInput = Ingredient.fromJson(ingredients.get(0)); + Ingredient secondaryInput = Ingredient.fromJson(ingredients.get(1)); + + String group = GsonHelper.getAsString(json, "group", ""); + + JsonObject result = GsonHelper.getAsJsonObject(json, "result"); + ItemStack output = ItemUtil.fromJsonRecipeWithNBT(result); + if (output == null) { + throw new IllegalStateException("Output item does not exists!"); + } + float experience = GsonHelper.getAsFloat(json, "experience", 0.0F); + int smeltTime = GsonHelper.getAsInt(json, "smelttime", 350); + + return new AlloyingRecipe(id, group, primaryInput, secondaryInput, output, experience, smeltTime); + } + + @Override + public AlloyingRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf packetBuffer) { + String group = packetBuffer.readUtf(32767); + Ingredient primary = Ingredient.fromNetwork(packetBuffer); + Ingredient secondary = Ingredient.fromNetwork(packetBuffer); + ItemStack output = packetBuffer.readItem(); + float experience = packetBuffer.readFloat(); + int smeltTime = packetBuffer.readVarInt(); + + return new AlloyingRecipe(id, group, primary, secondary, output, experience, smeltTime); + } + + @Override + public void toNetwork(FriendlyByteBuf packetBuffer, AlloyingRecipe recipe) { + packetBuffer.writeUtf(recipe.group); + recipe.primaryInput.toNetwork(packetBuffer); + recipe.secondaryInput.toNetwork(packetBuffer); + packetBuffer.writeItem(recipe.output); + packetBuffer.writeFloat(recipe.experience); + packetBuffer.writeVarInt(recipe.smeltTime); + } + } + + public static void register() { + //we call this to make sure that TYPE is initialized + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java b/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java new file mode 100644 index 00000000..f35ae7be --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/AnvilRecipe.java @@ -0,0 +1,399 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.interfaces.UnknownReceipBookCategory; +import org.betterx.bclib.util.ItemUtil; +import org.betterx.worlds.together.tag.v3.CommonItemTags; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.client.Minecraft; +import net.minecraft.core.Holder; +import net.minecraft.core.NonNullList; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.Container; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TieredItem; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.Level; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.gson.JsonObject; + +import java.util.Objects; +import org.jetbrains.annotations.NotNull; + +public class AnvilRecipe implements Recipe, UnknownReceipBookCategory { + public final static String GROUP = "smithing"; + public final static RecipeType TYPE = BCLRecipeManager.registerType(BCLib.MOD_ID, GROUP); + public final static Serializer SERIALIZER = BCLRecipeManager.registerSerializer( + BCLib.MOD_ID, + GROUP, + new Serializer() + ); + public final static ResourceLocation ID = BCLib.makeID(GROUP); + + + public static void register() { + //we call this to make sure that TYPE is initialized + } + + private final ResourceLocation id; + private final Ingredient input; + private final ItemStack output; + private final int damage; + private final int toolLevel; + private final int anvilLevel; + private final int inputCount; + + public AnvilRecipe( + ResourceLocation identifier, + Ingredient input, + ItemStack output, + int inputCount, + int toolLevel, + int anvilLevel, + int damage + ) { + this.id = identifier; + this.input = input; + this.output = output; + this.toolLevel = toolLevel; + this.anvilLevel = anvilLevel; + this.inputCount = inputCount; + this.damage = damage; + } + + static Builder create(ResourceLocation id, ItemLike output) { + return new Builder(id, output); + } + + @Override + public RecipeSerializer getSerializer() { + return SERIALIZER; + } + + @Override + public ItemStack getResultItem(RegistryAccess acc) { + return this.output; + } + + @Override + public boolean matches(@NotNull Container craftingInventory, @NotNull Level world) { + return this.matches(craftingInventory); + } + + @Override + public ItemStack assemble(@NotNull Container craftingInventory, RegistryAccess acc) { + return this.output.copy(); + } + + public static Iterable> getAllHammers() { + Registry registry = WorldBootstrap.getLastRegistryAccessOrElseBuiltin() + .registryOrThrow(CommonItemTags.HAMMERS.registry()); + return registry.getTagOrEmpty(CommonItemTags.HAMMERS); + } + + public static int getHammerSlot(Container c) { + ItemStack h = c.getItem(0); + if (!h.isEmpty() && h.is(CommonItemTags.HAMMERS)) return 0; + + //this is the default slot + return 1; + } + + public static int getIngredientSlot(Container c) { + return Math.abs(getHammerSlot(c) - 1); + } + + public ItemStack getHammer(Container c) { + ItemStack h = c.getItem(1); + if (!h.isEmpty() && h.is(CommonItemTags.HAMMERS)) return h; + h = c.getItem(0); + if (!h.isEmpty() && h.is(CommonItemTags.HAMMERS)) return h; + return null; + } + + public ItemStack getIngredient(Container c) { + ItemStack i = c.getItem(0); + if (i.is(CommonItemTags.HAMMERS)) i = c.getItem(1); + return i; + } + + public ItemStack craft(Container craftingInventory, Player player) { + if (!player.isCreative()) { + if (!checkHammerDurability(craftingInventory, player)) return ItemStack.EMPTY; + ItemStack hammer = getHammer(craftingInventory); + if (hammer != null) { + hammer.hurtAndBreak(this.damage, player, entity -> entity.broadcastBreakEvent((InteractionHand) null)); + return ItemStack.EMPTY; + } + } + return this.assemble(craftingInventory, Minecraft.getInstance().level.registryAccess()); + } + + public boolean checkHammerDurability(Container craftingInventory, Player player) { + if (player.isCreative()) return true; + ItemStack hammer = getHammer(craftingInventory); + if (hammer != null) { + int damage = hammer.getDamageValue() + this.damage; + return damage < hammer.getMaxDamage(); + } + return true; + } + + public boolean matches(Container craftingInventory) { + ItemStack hammer = getHammer(craftingInventory); + if (hammer == null) { + return false; + } + ItemStack material = getIngredient(craftingInventory); + int materialCount = material.getCount(); + int level = ((TieredItem) hammer.getItem()).getTier().getLevel(); + return this.input.test(getIngredient(craftingInventory)) && materialCount >= this.inputCount && level >= this.toolLevel; + } + + public int getDamage() { + return this.damage; + } + + public int getInputCount() { + return this.inputCount; + } + + public Ingredient getMainIngredient() { + return this.input; + } + + public int getAnvilLevel() { + return this.anvilLevel; + } + + public boolean canUse(Item tool) { + if (tool instanceof TieredItem ti) { + return ti.getTier().getLevel() >= toolLevel; + } + return false; + } + + public static boolean isHammer(Item tool) { + if (tool == null) return false; + return tool.getDefaultInstance().is(CommonItemTags.HAMMERS); + } + + @Override + public NonNullList getIngredients() { + NonNullList defaultedList = NonNullList.create(); + defaultedList.add(Ingredient.of(BuiltInRegistries.ITEM.stream() + .filter(AnvilRecipe::isHammer) + .filter(this::canUse) + .map(ItemStack::new)) + ); + defaultedList.add(input); + return defaultedList; + } + + @Override + @Environment(EnvType.CLIENT) + public boolean canCraftInDimensions(int width, int height) { + return true; + } + + @Override + public ResourceLocation getId() { + return this.id; + } + + @Override + public RecipeType getType() { + return TYPE; + } + + @Override + public boolean isSpecial() { + return true; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AnvilRecipe that = (AnvilRecipe) o; + return damage == that.damage && toolLevel == that.toolLevel && id.equals(that.id) && input.equals(that.input) && output.equals( + that.output); + } + + @Override + public int hashCode() { + return Objects.hash(id, input, output, damage, toolLevel); + } + + @Override + public String toString() { + return "AnvilRecipe [" + id + "]"; + } + + public static class Builder extends AbstractSingleInputRecipeBuilder { + private int inputCount; + private int toolLevel; + private int anvilLevel; + private int damage; + + protected Builder(ResourceLocation id, ItemLike output) { + super(id, output); + + this.inputCount = 1; + this.toolLevel = 1; + this.anvilLevel = 1; + this.damage = 1; + } + + @Override + protected Builder setOutputTag(CompoundTag tag) { + return super.setOutputTag(tag); + } + + @Override + protected Builder setOutputCount(int count) { + return super.setOutputCount(count); + } + + /** + * @param inputItems + * @return + * @deprecated Use {@link #setPrimaryInput(ItemLike...)} instead + */ + @Deprecated(forRemoval = true) + public Builder setInput(ItemLike... inputItems) { + return super.setPrimaryInput(inputItems); + } + + /** + * @param inputTag + * @return + * @deprecated Use {@link #setPrimaryInput(TagKey)} instead + */ + @Deprecated(forRemoval = true) + public Builder setInput(TagKey inputTag) { + return super.setPrimaryInput(inputTag); + } + + @Deprecated(forRemoval = true) + public Builder setInput(Ingredient ingredient) { + this.primaryInput = ingredient; + return this; + } + + public Builder setInputCount(int count) { + this.inputCount = count; + return this; + } + + + public Builder setToolLevel(int level) { + this.toolLevel = level; + return this; + } + + public Builder setAnvilLevel(int level) { + this.anvilLevel = level; + return this; + } + + public Builder setDamage(int damage) { + this.damage = damage; + return this; + } + + @Override + protected RecipeSerializer getSerializer() { + return SERIALIZER; + } + + @Override + protected boolean checkRecipe() { + if (inputCount <= 0) { + BCLib.LOGGER.warning( + "Number of input items for Recipe must be positive. Recipe {} will be ignored!", + id + ); + return false; + } + return super.checkRecipe(); + } + + @Override + protected void serializeRecipeData(JsonObject root) { + super.serializeRecipeData(root); + + if (inputCount > 1) { + root.addProperty("inputCount", inputCount); + } + if (toolLevel != 1) { + root.addProperty("toolLevel", toolLevel); + } + if (anvilLevel != 1) { + root.addProperty("anvilLevel", anvilLevel); + } + if (damage != 1) { + root.addProperty("damage", damage); + } + } + } + + public static class Serializer implements RecipeSerializer { + @Override + public AnvilRecipe fromJson(ResourceLocation id, JsonObject json) { + Ingredient input = Ingredient.fromJson(json.get("input")); + JsonObject result = GsonHelper.getAsJsonObject(json, "result"); + ItemStack output = ItemUtil.fromJsonRecipeWithNBT(result); + if (output == null) { + throw new IllegalStateException("Output item does not exists!"); + } + + int inputCount = GsonHelper.getAsInt(json, "inputCount", 1); + int toolLevel = GsonHelper.getAsInt(json, "toolLevel", 1); + int anvilLevel = GsonHelper.getAsInt(json, "anvilLevel", 1); + int damage = GsonHelper.getAsInt(json, "damage", 1); + + return new AnvilRecipe(id, input, output, inputCount, toolLevel, anvilLevel, damage); + } + + @Override + public AnvilRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf packetBuffer) { + Ingredient input = Ingredient.fromNetwork(packetBuffer); + ItemStack output = packetBuffer.readItem(); + int inputCount = packetBuffer.readVarInt(); + int toolLevel = packetBuffer.readVarInt(); + int anvilLevel = packetBuffer.readVarInt(); + int damage = packetBuffer.readVarInt(); + + return new AnvilRecipe(id, input, output, inputCount, toolLevel, anvilLevel, damage); + } + + @Override + public void toNetwork(FriendlyByteBuf packetBuffer, AnvilRecipe recipe) { + recipe.input.toNetwork(packetBuffer); + packetBuffer.writeItem(recipe.output); + packetBuffer.writeVarInt(recipe.inputCount); + packetBuffer.writeVarInt(recipe.toolLevel); + packetBuffer.writeVarInt(recipe.anvilLevel); + packetBuffer.writeVarInt(recipe.damage); + } + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/BCLRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/BCLRecipeBuilder.java new file mode 100644 index 00000000..8c12e452 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/BCLRecipeBuilder.java @@ -0,0 +1,88 @@ +package org.betterx.bclib.recipes; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.ItemLike; + +public class BCLRecipeBuilder { + public static AlloyingRecipe.Builder alloying(ResourceLocation id, ItemLike output) { + return AlloyingRecipe.Builder.create(id, output); + } + + public static AnvilRecipe.Builder anvil(ResourceLocation id, ItemLike output) { + return AnvilRecipe.create(id, output); + } + + public static CookingRecipeBuilder blasting(ResourceLocation id, ItemLike output) { + return CookingRecipeBuilder.make(id, output).disableSmelter().enableBlastFurnace(); + } + + public static CraftingRecipeBuilder crafting(ResourceLocation id, ItemLike output) { + return CraftingRecipeBuilder.make(id, output); + } + + private static CraftingRecipeBuilder copySmithingTemplateBase( + ResourceLocation id, + ItemLike filler, + ItemLike output + ) { + return CraftingRecipeBuilder + .make(id, output) + .setOutputCount(2) + .setCategory(RecipeCategory.MISC) + .addMaterial('#', filler) + .addMaterial('S', output) + .setShape("#S#", "#C#", "###"); + } + + public static CraftingRecipeBuilder copySmithingTemplate( + ResourceLocation id, + ItemLike output, + TagKey tagKey + ) { + return copySmithingTemplateBase(id, Items.DIAMOND, output) + .addMaterial('C', tagKey); + } + + public static CraftingRecipeBuilder copySmithingTemplate( + ResourceLocation id, + ItemLike output, + ItemLike ingredient + ) { + return copySmithingTemplateBase(id, Items.DIAMOND, output) + .addMaterial('C', ingredient); + } + + public static CraftingRecipeBuilder copyCheapSmithingTemplate( + ResourceLocation id, + ItemLike output, + TagKey tagKey + ) { + return copyCheapSmithingTemplate(id, Items.STICK, output) + .addMaterial('C', tagKey); + } + + public static CraftingRecipeBuilder copyCheapSmithingTemplate( + ResourceLocation id, + ItemLike output, + ItemLike ingredient + ) { + return copySmithingTemplateBase(id, Items.STICK, output) + .addMaterial('C', ingredient); + } + + public static CookingRecipeBuilder smelting(ResourceLocation id, ItemLike output) { + return CookingRecipeBuilder.make(id, output); + } + + public static SmithingRecipeBuilder smithing(ResourceLocation id, ItemLike output) { + return SmithingRecipeBuilder.make(id, output); + } + + public static StonecutterRecipeBuilder stonecutting(ResourceLocation id, ItemLike output) { + return StonecutterRecipeBuilder.make(id, output); + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/BCLRecipeManager.java b/src/main/java/org/betterx/bclib/recipes/BCLRecipeManager.java new file mode 100644 index 00000000..6d9d129d --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/BCLRecipeManager.java @@ -0,0 +1,85 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.Configs; +import org.betterx.worlds.together.util.DatapackConfigs; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.world.Container; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.HashSet; +import java.util.Map; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +public class BCLRecipeManager { + public static , T extends Recipe> S registerSerializer( + String modID, + String id, + S serializer + ) { + return Registry.register(BuiltInRegistries.RECIPE_SERIALIZER, modID + ":" + id, serializer); + } + + public static > RecipeType registerType(String modID, String type) { + ResourceLocation recipeTypeId = new ResourceLocation(modID, type); + return Registry.register(BuiltInRegistries.RECIPE_TYPE, recipeTypeId, new RecipeType() { + public String toString() { + return type; + } + }); + } + + public static boolean exists(ItemLike item) { + if (item instanceof Block) { + return BuiltInRegistries.BLOCK.getKey((Block) item) != BuiltInRegistries.BLOCK.getDefaultKey(); + } else { + return item != Items.AIR && BuiltInRegistries.ITEM.getKey(item.asItem()) != BuiltInRegistries.ITEM.getDefaultKey(); + } + } + + private final static HashSet disabledRecipes = new HashSet<>(); + + private static void clearRecipeConfig() { + disabledRecipes.clear(); + } + + private static void processRecipeConfig(@NotNull ResourceLocation sourceId, @NotNull JsonObject root) { + if (root.has("disable")) { + root + .getAsJsonArray("disable") + .asList() + .stream() + .map(el -> ResourceLocation.tryParse(el.getAsString())) + .filter(id -> id != null) + .forEach(disabledRecipes::add); + } + } + + @ApiStatus.Internal + public static void removeDisabledRecipes(ResourceManager manager, Map map) { + clearRecipeConfig(); + DatapackConfigs + .instance() + .runForResources(manager, BCLib.MOD_ID, "recipes.json", BCLRecipeManager::processRecipeConfig); + + for (ResourceLocation id : disabledRecipes) { + if (Configs.MAIN_CONFIG.verboseLogging()) { + BCLib.LOGGER.info("Disabling Recipe: {}", id); + } + map.remove(id); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/bclib/recipes/CookingRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/CookingRecipeBuilder.java new file mode 100644 index 00000000..8ef7c97e --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/CookingRecipeBuilder.java @@ -0,0 +1,208 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; + +import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.SimpleCookingRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; + +import java.util.function.Consumer; + +public class CookingRecipeBuilder extends AbstractUnlockableRecipeBuilder { + + protected float xp; + protected int cookingTime; + + boolean blasting, campfire, smoker, smelting; + + static CookingRecipeBuilder make(ResourceLocation id, ItemLike output) { + return new CookingRecipeBuilder(id, output); + } + + protected CookingRecipeBuilder(ResourceLocation id, ItemLike output) { + super(id, output); + this.xp = 0; + this.cookingTime = 200; + this.smelting = true; + } + + + /** + * Use {@link #setPrimaryInputAndUnlock(ItemLike...)} instead + * + * @deprecated Use {@link #setPrimaryInputAndUnlock(ItemLike...)} instead + */ + @Deprecated(forRemoval = true) + public CookingRecipeBuilder setInput(ItemLike in) { + return this.setPrimaryInputAndUnlock(in); + } + + /** + * Use {@link #setPrimaryInputAndUnlock(ItemLike...)} instead + * + * @param in + * @return + * @deprecated Use {@link #setPrimaryInputAndUnlock(ItemLike...)} instead + */ + @Deprecated(forRemoval = true) + public CookingRecipeBuilder setInput(TagKey in) { + return this.setPrimaryInputAndUnlock(in); + } + + public CookingRecipeBuilder setExperience(float xp) { + this.xp = xp; + return this; + } + + public CookingRecipeBuilder setCookingTime(int time) { + this.cookingTime = time; + return this; + } + + @Override + protected boolean checkRecipe() { + if (smelting == false && blasting == false && campfire == false && smoker == false) { + BCLib.LOGGER.warning( + "No target (smelting, blasting, campfire or somer) for cooking recipe was selected. Recipe {} will be ignored!", + id + ); + return false; + } + + if (cookingTime < 0) { + BCLib.LOGGER.warning( + "cooking time must be positive. Recipe {} will be ignored!", + id + ); + return false; + } + return super.checkRecipe(); + } + + public CookingRecipeBuilder enableSmelter() { + this.smelting = true; + return this; + } + + public CookingRecipeBuilder disableSmelter() { + this.smelting = false; + return this; + } + + public CookingRecipeBuilder enableBlastFurnace() { + this.blasting = true; + return this; + } + + public CookingRecipeBuilder disableBlastFurnace() { + this.blasting = false; + return this; + } + + public CookingRecipeBuilder enableCampfire() { + this.campfire = true; + return this; + } + + public CookingRecipeBuilder disableCampfire() { + this.campfire = false; + return this; + } + + public CookingRecipeBuilder enableSmoker() { + this.smoker = true; + return this; + } + + public CookingRecipeBuilder disableSmoker() { + this.smoker = false; + return this; + } + + public void build(boolean blasting, boolean campfire, boolean smoker) { + this.enableSmelter(); + this.blasting = blasting; + this.campfire = campfire; + this.smoker = smoker; + + build(); + } + + public void buildWithBlasting() { + build(true, false, false); + } + + public void buildFoodlike() { + build(false, true, true); + } + + private void buildRecipe(Consumer cc, SimpleCookingRecipeBuilder builder, String postfix) { + ResourceLocation loc = new ResourceLocation(id.getNamespace(), id.getPath() + "_" + postfix); + for (var item : unlocks.entrySet()) { + builder.unlockedBy(item.getKey(), item.getValue()); + } + builder.save(cc, loc); + } + + @Override + protected void buildRecipe(Consumer cc) { + if (smelting) { + buildRecipe( + cc, + SimpleCookingRecipeBuilder.smelting( + primaryInput, + category, + output.getItem(), + xp, + cookingTime + ), + "smelting" + ); + } + + if (blasting) { + buildRecipe( + cc, + SimpleCookingRecipeBuilder.blasting( + primaryInput, + category, + output.getItem(), + xp, + cookingTime / 2 + ), + "blasting" + ); + } + + if (campfire) { + buildRecipe( + cc, + SimpleCookingRecipeBuilder.campfireCooking( + primaryInput, + category, + output.getItem(), + xp, + cookingTime * 3 + ), + "campfire" + ); + } + + if (smoker) { + buildRecipe( + cc, + SimpleCookingRecipeBuilder.campfireCooking( + primaryInput, + category, + output.getItem(), + xp, + cookingTime / 2 + ), + "smoker" + ); + } + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/CraftingRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/CraftingRecipeBuilder.java new file mode 100644 index 00000000..ba1e0437 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/CraftingRecipeBuilder.java @@ -0,0 +1,222 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; + +import net.minecraft.advancements.CriterionTriggerInstance; +import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.ShapedRecipeBuilder; +import net.minecraft.data.recipes.ShapelessRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +public class CraftingRecipeBuilder extends AbstractBaseRecipeBuilder { + private String[] shape; + private boolean showNotification; + + protected CraftingRecipeBuilder( + ResourceLocation id, + ItemLike output + ) { + super(id, output); + this.showNotification = true; + this.shape = new String[]{"#"}; + } + + static CraftingRecipeBuilder make(ResourceLocation id, ItemLike output) { + return new CraftingRecipeBuilder(id, output); + } + + protected final Map unlocks = new HashMap<>(); + protected final Map materials = new HashMap<>(); + + @Override + public CraftingRecipeBuilder setOutputCount(int count) { + return super.setOutputCount(count); + } + + public CraftingRecipeBuilder addMaterial(char key, TagKey value) { + unlockedBy(value); + materials.put(key, Ingredient.of(value)); + return this; + } + + public CraftingRecipeBuilder addMaterial(char key, ItemStack... values) { + unlockedBy(values); + return addMaterial(key, Ingredient.of(Arrays.stream(values))); + } + + public CraftingRecipeBuilder addMaterial(char key, ItemLike... values) { + for (ItemLike item : values) { + this.alright &= BCLRecipeManager.exists(item); + } + unlockedBy(values); + return addMaterial(key, Ingredient.of(values)); + } + + private CraftingRecipeBuilder addMaterial(char key, Ingredient value) { + materials.put(key, value); + return this; + } + + public CraftingRecipeBuilder setShape(String... shape) { + this.shape = shape; + return this; + } + + public CraftingRecipeBuilder shapeless() { + this.shape = null; + return this; + } + + /** + * @param shape + * @return + * @deprecated Use {@link #shapeless()} instead + */ + @Deprecated(forRemoval = true) + public CraftingRecipeBuilder setList(String shape) { + return shapeless(); + } + + public CraftingRecipeBuilder showNotification(boolean showNotification) { + this.showNotification = showNotification; + return this; + } + + + @Override + public CraftingRecipeBuilder unlockedBy(ItemLike item) { + return super.unlockedBy(item); + } + + @Override + public CraftingRecipeBuilder unlockedBy(TagKey tag) { + return super.unlockedBy(tag); + } + + @Override + public CraftingRecipeBuilder unlockedBy(ItemLike... items) { + return super.unlockedBy(items); + } + + @Override + public CraftingRecipeBuilder unlockedBy(ItemStack... stacks) { + return super.unlockedBy(stacks); + } + + @Override + protected CraftingRecipeBuilder unlocks(String name, CriterionTriggerInstance trigger) { + this.unlocks.put(name, trigger); + return this; + } + + @Override + public CraftingRecipeBuilder setGroup(String group) { + return super.setGroup(group); + } + + + @Override + protected boolean checkRecipe() { + if (shape != null) return checkShaped(); + else return checkShapeless(); + } + + @Override + protected void buildRecipe(Consumer cc) { + if (shape != null) buildShaped(cc); + else buildShapeless(cc); + } + + protected boolean checkShapeless() { + if (materials.size() == 0) { + BCLib.LOGGER.warning("Recipe {} does not contain a material!", id); + return false; + } + return super.checkRecipe(); + } + + protected void buildShapeless(Consumer cc) { + final ShapelessRecipeBuilder builder = ShapelessRecipeBuilder.shapeless( + category, output.getItem(), output.getCount() + ); + for (Map.Entry item : unlocks.entrySet()) { + builder.unlockedBy(item.getKey(), item.getValue()); + } + for (Map.Entry mat : materials.entrySet()) { + builder.requires(mat.getValue()); + } + + builder.group(group); + + builder.save(cc, id); + } + + protected boolean checkShaped() { + if (shape == null || shape.length == 0) { + BCLib.LOGGER.warning("Recipe {} does not contain a shape!", id); + return false; + } + if (shape.length > 3) { + BCLib.LOGGER.warning("Recipe {} shape contains more than three lines!", id); + return false; + } + int width = shape[0].length(); + if (width > 3) { + BCLib.LOGGER.warning("Recipe {} shape is wider than three!", id); + return false; + } + String allLines = ""; + for (int i = 0; i < shape.length; i++) { + if (shape[i].length() != width) { + BCLib.LOGGER.warning("All lines in the shape of Recipe {} should be the same length!", id); + return false; + } + allLines += shape[i]; + } + allLines = allLines.replaceAll(" ", ""); + if (allLines.length() == 1) { + BCLib.LOGGER.warning("Recipe {} only takes in a single item and should be shapeless", id); + return false; + } + + for (int i = 0; i < allLines.length(); i++) { + char c = allLines.charAt(i); + if (!materials.containsKey(c)) { + BCLib.LOGGER.warning("Recipe {} is missing the material definition for '" + c + "'!", id); + return false; + } + } + + return super.checkRecipe(); + } + + + protected void buildShaped(Consumer cc) { + final ShapedRecipeBuilder builder = ShapedRecipeBuilder.shaped( + category, output.getItem(), output.getCount() + ); + for (Map.Entry item : unlocks.entrySet()) { + builder.unlockedBy(item.getKey(), item.getValue()); + } + for (Map.Entry mat : materials.entrySet()) { + builder.define(mat.getKey(), mat.getValue()); + } + for (String row : shape) { + builder.pattern(row); + } + builder.group(group); + builder.showNotification(showNotification); + builder.save(cc, id); + } + +} diff --git a/src/main/java/org/betterx/bclib/recipes/CraftingRecipes.java b/src/main/java/org/betterx/bclib/recipes/CraftingRecipes.java new file mode 100644 index 00000000..5ee980f4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/CraftingRecipes.java @@ -0,0 +1,81 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; +import org.betterx.worlds.together.tag.v3.CommonItemTags; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Blocks; + +public class CraftingRecipes { + public static void init() { + BCLRecipeBuilder.crafting(BCLib.makeID("tag_smith_table"), Blocks.SMITHING_TABLE) + .setShape("II", "##", "##") + .addMaterial('#', ItemTags.PLANKS) + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .setCategory(RecipeCategory.DECORATIONS) + .build(); + BCLRecipeBuilder.crafting(BCLib.makeID("tag_cauldron"), Blocks.CAULDRON) + .setShape("I I", "I I", "III") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .setCategory(RecipeCategory.BREWING) + .build(); + BCLRecipeBuilder.crafting(BCLib.makeID("tag_hopper"), Blocks.HOPPER) + .setShape("I I", "ICI", " I ") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .addMaterial('C', CommonItemTags.CHEST) + .setCategory(RecipeCategory.REDSTONE) + .build(); + BCLRecipeBuilder.crafting(BCLib.makeID("tag_piston"), Blocks.PISTON) + .setShape("WWW", "CIC", "CDC") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .addMaterial('D', Items.REDSTONE) + .addMaterial('C', Items.COBBLESTONE) + .addMaterial('W', ItemTags.PLANKS) + .setCategory(RecipeCategory.REDSTONE) + .build(); + BCLRecipeBuilder.crafting(BCLib.makeID("tag_rail"), Blocks.RAIL) + .setOutputCount(16) + .setShape("I I", "ISI", "I I") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .addMaterial('S', Items.STICK) + .setCategory(RecipeCategory.TRANSPORTATION) + .build(); + BCLRecipeBuilder.crafting(BCLib.makeID("tag_stonecutter"), Blocks.STONECUTTER) + .setShape(" I ", "SSS") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .addMaterial('S', Items.STONE) + .setCategory(RecipeCategory.DECORATIONS) + .build(); + BCLRecipeBuilder.crafting(BCLib.makeID("tag_compass"), Items.COMPASS) + .setShape(" I ", "IDI", " I ") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .addMaterial('D', Items.REDSTONE) + .setCategory(RecipeCategory.TOOLS) + .build(); + BCLRecipeBuilder.crafting(BCLib.makeID("tag_bucket"), Items.BUCKET) + .setShape("I I", " I ") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .setCategory(RecipeCategory.MISC) + .build(); + BCLRecipeBuilder.crafting(BCLib.makeID("tag_minecart"), Items.MINECART) + .setShape("I I", "III") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .setCategory(RecipeCategory.TRANSPORTATION) + .build(); + BCLRecipeBuilder.crafting(BCLib.makeID("tag_shield"), Items.SHIELD) + .setShape("WIW", "WWW", " W ") + .addMaterial('I', CommonItemTags.IRON_INGOTS) + .addMaterial('W', ItemTags.PLANKS) + .setCategory(RecipeCategory.COMBAT) + .build(); + + BCLRecipeBuilder.crafting(BCLib.makeID("tag_shulker_box"), Blocks.SHULKER_BOX) + .setShape("S", "C", "S") + .addMaterial('S', Items.SHULKER_SHELL) + .addMaterial('C', CommonItemTags.CHEST) + .setCategory(RecipeCategory.DECORATIONS) + .build(); + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/SmithingRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/SmithingRecipeBuilder.java new file mode 100644 index 00000000..11f0ebd3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/SmithingRecipeBuilder.java @@ -0,0 +1,102 @@ +package org.betterx.bclib.recipes; + +import org.betterx.bclib.BCLib; + +import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.SmithingTransformRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; + +import java.util.function.Consumer; + +public class SmithingRecipeBuilder extends AbstractUnlockableRecipeBuilder { + protected Ingredient addon; + protected Item template; + + protected SmithingRecipeBuilder( + ResourceLocation id, + ItemLike output + ) { + super(id, output); + } + + static SmithingRecipeBuilder make(ResourceLocation id, ItemLike output) { + return new SmithingRecipeBuilder(id, output); + } + + public SmithingRecipeBuilder setTemplate(Item in) { + this.template = in; + return this; + } + + /** + * @param in + * @return + * @deprecated Use {@link #setPrimaryInputAndUnlock(ItemLike...)} instead + */ + @Deprecated(forRemoval = true) + public SmithingRecipeBuilder setBase(ItemLike in) { + return super.setPrimaryInputAndUnlock(in); + } + + /** + * @param in + * @return + * @deprecated use {@link #setPrimaryInputAndUnlock(TagKey)} instead + */ + @Deprecated(forRemoval = true) + public SmithingRecipeBuilder setBase(TagKey in) { + return super.setPrimaryInputAndUnlock(in); + } + + public SmithingRecipeBuilder setAddition(ItemLike item) { + this.addon = Ingredient.of(item); + return this; + } + + public SmithingRecipeBuilder setAdditionAndUnlock(ItemLike item) { + this.addon = Ingredient.of(item); + this.unlockedBy(item); + return this; + } + + + @Override + protected boolean checkRecipe() { + if (addon == null || addon.isEmpty()) { + BCLib.LOGGER.warning( + "Addon for Recipe can't be 'null', recipe {} will be ignored!", + id + ); + return false; + } + + if (template == null) { + BCLib.LOGGER.warning( + "Smithing Recipes need a template. Recipe {} will be ignored!", + id + ); + return false; + } + return super.checkRecipe(); + } + + @Override + protected void buildRecipe(Consumer cc) { + final SmithingTransformRecipeBuilder builder = SmithingTransformRecipeBuilder.smithing( + Ingredient.of(template), + primaryInput, + addon, + category, + output.getItem() + ); + + for (var item : unlocks.entrySet()) { + builder.unlocks(item.getKey(), item.getValue()); + } + builder.save(cc, id); + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/SmithingTemplates.java b/src/main/java/org/betterx/bclib/recipes/SmithingTemplates.java new file mode 100644 index 00000000..f6a136a9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/SmithingTemplates.java @@ -0,0 +1,121 @@ +package org.betterx.bclib.recipes; + +import net.minecraft.ChatFormatting; +import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.SmithingTemplateItem; + +import org.spongepowered.include.com.google.common.collect.ImmutableList; + +import java.util.List; + +public class SmithingTemplates { + public static final ChatFormatting TITLE_FORMAT = ChatFormatting.GRAY; + public static final ChatFormatting DESCRIPTION_FORMAT = ChatFormatting.BLUE; + + public static final ResourceLocation EMPTY_SLOT_HELMET = new ResourceLocation("item/empty_armor_slot_helmet"); + public static final ResourceLocation EMPTY_SLOT_CHESTPLATE = new ResourceLocation("item/empty_armor_slot_chestplate"); + public static final ResourceLocation EMPTY_SLOT_LEGGINGS = new ResourceLocation("item/empty_armor_slot_leggings"); + public static final ResourceLocation EMPTY_SLOT_BOOTS = new ResourceLocation("item/empty_armor_slot_boots"); + public static final ResourceLocation EMPTY_SLOT_HOE = new ResourceLocation("item/empty_slot_hoe"); + public static final ResourceLocation EMPTY_SLOT_AXE = new ResourceLocation("item/empty_slot_axe"); + public static final ResourceLocation EMPTY_SLOT_SWORD = new ResourceLocation("item/empty_slot_sword"); + public static final ResourceLocation EMPTY_SLOT_SHOVEL = new ResourceLocation("item/empty_slot_shovel"); + public static final ResourceLocation EMPTY_SLOT_PICKAXE = new ResourceLocation("item/empty_slot_pickaxe"); + public static final ResourceLocation EMPTY_SLOT_INGOT = new ResourceLocation("item/empty_slot_ingot"); + public static final ResourceLocation EMPTY_SLOT_REDSTONE_DUST = new ResourceLocation("item/empty_slot_redstone_dust"); + public static final ResourceLocation EMPTY_SLOT_DIAMOND = new ResourceLocation("item/empty_slot_diamond"); + + public static final List TOOLS = List.of( + EMPTY_SLOT_SWORD, + EMPTY_SLOT_PICKAXE, + EMPTY_SLOT_AXE, + EMPTY_SLOT_HOE, + EMPTY_SLOT_SHOVEL + ); + + public static final List ARMOR = List.of( + EMPTY_SLOT_HELMET, + EMPTY_SLOT_CHESTPLATE, + EMPTY_SLOT_LEGGINGS, + EMPTY_SLOT_BOOTS + ); + public static final List ARMOR_AND_TOOLS = combine(ARMOR, TOOLS); + + public static List combine(List... sources) { + final ImmutableList.Builder builder = ImmutableList.builder(); + for (var s : sources) { + builder.addAll(s); + } + return builder.build(); + } + + public static Builder create(ResourceLocation id) { + return new Builder(id); + } + + public static class Builder { + private final ResourceLocation ID; + List baseSlotEmptyIcons; + List additionalSlotEmptyIcons; + + protected Builder(ResourceLocation id) { + ID = id; + } + + public Builder setBaseSlotEmptyIcons(List baseSlotEmptyIcons) { + this.baseSlotEmptyIcons = baseSlotEmptyIcons; + return this; + } + + public Builder setAdditionalSlotEmptyIcons(List additionalSlotEmptyIcons) { + this.additionalSlotEmptyIcons = additionalSlotEmptyIcons; + return this; + } + + public SmithingTemplateItem build() { + if (baseSlotEmptyIcons == null || baseSlotEmptyIcons.isEmpty()) { + throw new IllegalStateException("Base slot empty icons must contain at least one icon"); + } + if (additionalSlotEmptyIcons == null || additionalSlotEmptyIcons.isEmpty()) { + throw new IllegalStateException("Additional slot empty icons must contain at least one icon"); + } + + + return new SmithingTemplateItem( + Component.translatable(Util.makeDescriptionId( + "item", + new ResourceLocation(ID.getNamespace(), "smithing_template." + ID.getPath() + ".applies_to") + )).withStyle(DESCRIPTION_FORMAT), + Component.translatable(Util.makeDescriptionId( + "item", + new ResourceLocation( + ID.getNamespace(), + "smithing_template." + ID.getPath() + ".ingredients" + ) + )).withStyle(DESCRIPTION_FORMAT), + Component.translatable(Util.makeDescriptionId( + "upgrade", + ID + )).withStyle(TITLE_FORMAT), + Component.translatable(Util.makeDescriptionId( + "item", + new ResourceLocation( + ID.getNamespace(), + "smithing_template." + ID.getPath() + ".base_slot_description" + ) + )), + Component.translatable(Util.makeDescriptionId( + "item", + new ResourceLocation( + ID.getNamespace(), + "smithing_template." + ID.getPath() + ".additions_slot_description" + ) + )), + baseSlotEmptyIcons, + additionalSlotEmptyIcons + ); + } + } +} diff --git a/src/main/java/org/betterx/bclib/recipes/StonecutterRecipeBuilder.java b/src/main/java/org/betterx/bclib/recipes/StonecutterRecipeBuilder.java new file mode 100644 index 00000000..0fc23d71 --- /dev/null +++ b/src/main/java/org/betterx/bclib/recipes/StonecutterRecipeBuilder.java @@ -0,0 +1,67 @@ +package org.betterx.bclib.recipes; + +import net.minecraft.data.recipes.FinishedRecipe; +import net.minecraft.data.recipes.SingleItemRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; + +import java.util.function.Consumer; + +public class StonecutterRecipeBuilder extends AbstractUnlockableRecipeBuilder { + + protected StonecutterRecipeBuilder( + ResourceLocation id, + ItemLike output + ) { + super(id, output); + } + + static StonecutterRecipeBuilder make(ResourceLocation id, ItemLike output) { + return new StonecutterRecipeBuilder(id, output); + } + + @Override + public StonecutterRecipeBuilder setGroup(String group) { + return super.setGroup(group); + } + + /** + * @param in + * @return + * @deprecated Use {@link #setPrimaryInputAndUnlock(ItemLike...)} instead + */ + @Deprecated(forRemoval = true) + public StonecutterRecipeBuilder setInput(ItemLike in) { + return super.setPrimaryInputAndUnlock(in); + } + + /** + * @param in + * @return + * @deprecated use {@link #setPrimaryInputAndUnlock(TagKey)} instead + */ + @Deprecated(forRemoval = true) + public StonecutterRecipeBuilder setInput(TagKey in) { + return super.setPrimaryInputAndUnlock(in); + } + + @Override + public StonecutterRecipeBuilder setOutputCount(int count) { + return super.setOutputCount(count); + } + + @Override + protected void buildRecipe(Consumer cc) { + final SingleItemRecipeBuilder builder = SingleItemRecipeBuilder.stonecutting( + primaryInput, category, output.getItem(), output.getCount() + ); + for (var item : unlocks.entrySet()) { + builder.unlockedBy(item.getKey(), item.getValue()); + } + builder.group(group); + builder.save(cc, id); + } + +} diff --git a/src/main/java/org/betterx/bclib/registry/BaseBlockEntities.java b/src/main/java/org/betterx/bclib/registry/BaseBlockEntities.java new file mode 100644 index 00000000..7f6f39f3 --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/BaseBlockEntities.java @@ -0,0 +1,89 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.blockentities.BaseBarrelBlockEntity; +import org.betterx.bclib.blockentities.BaseChestBlockEntity; +import org.betterx.bclib.blockentities.BaseFurnaceBlockEntity; +import org.betterx.bclib.blockentities.DynamicBlockEntityType; +import org.betterx.bclib.blockentities.DynamicBlockEntityType.BlockEntitySupplier; +import org.betterx.bclib.blocks.BaseBarrelBlock; +import org.betterx.bclib.blocks.BaseChestBlock; +import org.betterx.bclib.blocks.BaseFurnaceBlock; +import org.betterx.bclib.blocks.signs.BaseSignBlock; +import org.betterx.bclib.furniture.entity.EntityChair; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; + +import net.fabricmc.fabric.api.object.builder.v1.entity.FabricEntityTypeBuilder; + +public class BaseBlockEntities { + public static final DynamicBlockEntityType CHEST = registerBlockEntityType(BCLib.makeID( + "chest"), BaseChestBlockEntity::new); + public static final DynamicBlockEntityType BARREL = registerBlockEntityType(BCLib.makeID( + "barrel"), BaseBarrelBlockEntity::new); + + public static final DynamicBlockEntityType FURNACE = registerBlockEntityType(BCLib.makeID( + "furnace"), BaseFurnaceBlockEntity::new); + + public static final EntityType CHAIR = registerEntity(BCLib.makeID("chair"), FabricEntityTypeBuilder + .create(MobCategory.MISC, EntityChair::new) + .dimensions(EntityDimensions.fixed(0.5F, 0.8F)) + .fireImmune() + .disableSummon() + .build()); + + + public static EntityType registerEntity( + ResourceLocation id, + EntityType entity + ) { + Registry.register(BuiltInRegistries.ENTITY_TYPE, id, entity); + return entity; + } + + public static DynamicBlockEntityType registerBlockEntityType( + ResourceLocation typeId, + BlockEntitySupplier supplier + ) { + return Registry.register(BuiltInRegistries.BLOCK_ENTITY_TYPE, typeId, new DynamicBlockEntityType<>(supplier)); + } + + public static void register() { + } + + public static Block[] getChests() { + return BuiltInRegistries.BLOCK + .stream() + .filter(block -> block instanceof BaseChestBlock) + .toArray(Block[]::new); + } + + public static Block[] getBarrels() { + return BuiltInRegistries.BLOCK + .stream() + .filter(block -> block instanceof BaseBarrelBlock) + .toArray(Block[]::new); + } + + public static Block[] getSigns() { + return BuiltInRegistries.BLOCK + .stream() + .filter(block -> block instanceof BaseSignBlock) + .toArray(Block[]::new); + } + + public static Block[] getFurnaces() { + return BuiltInRegistries.BLOCK + .stream() + .filter(block -> block instanceof BaseFurnaceBlock) + .toArray(Block[]::new); + } +} diff --git a/src/main/java/org/betterx/bclib/registry/BaseBlockEntityRenders.java b/src/main/java/org/betterx/bclib/registry/BaseBlockEntityRenders.java new file mode 100644 index 00000000..f98119f4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/BaseBlockEntityRenders.java @@ -0,0 +1,54 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.client.render.BaseChestBlockEntityRenderer; +import org.betterx.bclib.furniture.renderer.RenderChair; +import org.betterx.bclib.items.boat.BoatTypeOverride; + +import net.minecraft.client.model.BoatModel; +import net.minecraft.client.model.ChestBoatModel; +import net.minecraft.client.model.ChestRaftModel; +import net.minecraft.client.model.RaftModel; +import net.minecraft.client.model.geom.builders.LayerDefinition; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.world.entity.EntityType; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.rendering.v1.BlockEntityRendererRegistry; +import net.fabricmc.fabric.api.client.rendering.v1.EntityModelLayerRegistry; +import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry; + +@Environment(EnvType.CLIENT) +public class BaseBlockEntityRenders { + public static void register() { + BlockEntityRendererRegistry.register(BaseBlockEntities.CHEST, BaseChestBlockEntityRenderer::new); + + LayerDefinition boatModel = BoatModel.createBodyModel(); + LayerDefinition chestBoatModel = ChestBoatModel.createBodyModel(); + LayerDefinition raftModel = RaftModel.createBodyModel(); + LayerDefinition chestRaftModel = ChestRaftModel.createBodyModel(); + + BoatTypeOverride.values().forEach(type -> { + EntityModelLayerRegistry.registerModelLayer(type.boatModelName, () -> type.isRaft ? raftModel : boatModel); + EntityModelLayerRegistry.registerModelLayer( + type.chestBoatModelName, + () -> type.isRaft ? chestRaftModel : chestBoatModel + ); + }); + + registerRender(BaseBlockEntities.CHAIR, RenderChair.class); + } + + public static void registerRender(EntityType entity, Class> renderer) { + EntityRendererRegistry.register(entity, (context) -> { + EntityRenderer render = null; + try { + render = renderer.getConstructor(context.getClass()) + .newInstance(context); + } catch (Exception e) { + e.printStackTrace(); + } + return render; + }); + } +} diff --git a/src/main/java/org/betterx/bclib/registry/BaseRegistry.java b/src/main/java/org/betterx/bclib/registry/BaseRegistry.java new file mode 100644 index 00000000..f9a67092 --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/BaseRegistry.java @@ -0,0 +1,79 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.config.PathConfig; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.List; +import java.util.Map; +import org.jetbrains.annotations.ApiStatus; + +public abstract class BaseRegistry { + private static final List> REGISTRIES = Lists.newArrayList(); + private static final Map> MOD_BLOCK_ITEMS = Maps.newHashMap(); + private static final Map> MOD_BLOCKS = Maps.newHashMap(); + private static final Map> MOD_ITEMS = Maps.newHashMap(); + protected final PathConfig config; + + protected BaseRegistry(PathConfig config) { + this.config = config; + REGISTRIES.add(this); + } + + public abstract T register(ResourceLocation objId, T obj); + + public abstract void registerItem(ResourceLocation id, Item item); + + public Item.Properties makeItemSettings() { + Item.Properties properties = new Item.Properties(); + return properties; + } + + private void registerInternal() { + } + + public static Map> getRegisteredBlocks() { + return MOD_BLOCK_ITEMS; + } + + public static Map> getRegisteredItems() { + return MOD_ITEMS; + } + + public static List getModBlockItems(String modId) { + if (MOD_BLOCK_ITEMS.containsKey(modId)) { + return MOD_BLOCK_ITEMS.get(modId); + } + List modBlocks = Lists.newArrayList(); + MOD_BLOCK_ITEMS.put(modId, modBlocks); + return modBlocks; + } + + public static List getModItems(String modId) { + if (MOD_ITEMS.containsKey(modId)) { + return MOD_ITEMS.get(modId); + } + List modBlocks = Lists.newArrayList(); + MOD_ITEMS.put(modId, modBlocks); + return modBlocks; + } + + public static List getModBlocks(String modId) { + if (MOD_BLOCKS.containsKey(modId)) { + return MOD_BLOCKS.get(modId); + } + List modBlocks = Lists.newArrayList(); + MOD_BLOCKS.put(modId, modBlocks); + return modBlocks; + } + + @ApiStatus.Internal + public static void register() { + REGISTRIES.forEach(BaseRegistry::registerInternal); + } +} diff --git a/src/main/java/org/betterx/bclib/registry/BlockRegistry.java b/src/main/java/org/betterx/bclib/registry/BlockRegistry.java new file mode 100644 index 00000000..971d2b9a --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/BlockRegistry.java @@ -0,0 +1,70 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.config.PathConfig; +import org.betterx.bclib.interfaces.CustomItemProvider; + +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; + +import net.fabricmc.fabric.api.registry.FlammableBlockRegistry; + +public class BlockRegistry extends BaseRegistry { + public BlockRegistry(PathConfig config) { + super(config); + } + + @Override + public Block register(ResourceLocation id, Block block) { + if (!config.getBooleanRoot(id.getNamespace(), true)) { + BCLib.LOGGER.warning("Block " + id + " disabled"); + return block; + } + block = Registry.register(BuiltInRegistries.BLOCK, id, block); + + BlockItem item = null; + if (block instanceof CustomItemProvider) { + item = ((CustomItemProvider) block).getCustomItem(id, makeItemSettings()); + } else { + item = new BlockItem(block, makeItemSettings()); + + } + registerBlockItem(id, item); + if (block.defaultBlockState().ignitedByLava() + && FlammableBlockRegistry.getDefaultInstance() + .get(block) + .getBurnChance() == 0) { + FlammableBlockRegistry.getDefaultInstance().add(block, 5, 5); + } + + getModBlocks(id.getNamespace()).add(block); + + return block; + } + + public Block registerBlockOnly(ResourceLocation id, Block block) { + if (!config.getBooleanRoot(id.getNamespace(), true)) { + return block; + } + getModBlocks(id.getNamespace()).add(block); + return Registry.register(BuiltInRegistries.BLOCK, id, block); + } + + private Item registerBlockItem(ResourceLocation id, BlockItem item) { + registerItem(id, item); + return item; + } + + @Override + public void registerItem(ResourceLocation id, Item item) { + if (item != null && item != Items.AIR) { + Registry.register(BuiltInRegistries.ITEM, id, item); + getModBlockItems(id.getNamespace()).add(item); + } + } +} diff --git a/src/main/java/org/betterx/bclib/registry/ItemRegistry.java b/src/main/java/org/betterx/bclib/registry/ItemRegistry.java new file mode 100644 index 00000000..e4faa014 --- /dev/null +++ b/src/main/java/org/betterx/bclib/registry/ItemRegistry.java @@ -0,0 +1,150 @@ +package org.betterx.bclib.registry; + +import org.betterx.bclib.behaviours.BehaviourBuilders; +import org.betterx.bclib.config.PathConfig; +import org.betterx.bclib.items.BaseDiscItem; +import org.betterx.bclib.items.BaseDrinkItem; +import org.betterx.bclib.items.BaseSpawnEggItem; +import org.betterx.bclib.items.ModelProviderItem; +import org.betterx.bclib.models.RecordItemModelProvider; +import org.betterx.bclib.recipes.SmithingTemplates; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.BlockSource; +import net.minecraft.core.Direction; +import net.minecraft.core.Registry; +import net.minecraft.core.dispenser.DefaultDispenseItemBehavior; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.item.*; +import net.minecraft.world.level.block.DispenserBlock; + +import java.util.List; + +public class ItemRegistry extends BaseRegistry { + public ItemRegistry(PathConfig config) { + super(config); + } + + public Item registerDisc(ResourceLocation itemId, int power, SoundEvent sound, int lengthInSeconds) { + RecordItem item = BaseDiscItem.create(power, sound, BehaviourBuilders.createDisc(), lengthInSeconds); + if (item != null) { + RecordItemModelProvider.add(item); + if (!config.getBoolean("musicDiscs", itemId.getPath(), true)) { + return item; + } + register(itemId, item); + TagManager.ITEMS.add(ItemTags.MUSIC_DISCS, item); + } + return item; + } + + public SmithingTemplateItem registerSmithingTemplateItem( + ResourceLocation id, + List baseSlotEmptyIcons, + List additionalSlotEmptyIcons + ) { + final SmithingTemplateItem item = SmithingTemplates + .create(id) + .setBaseSlotEmptyIcons(baseSlotEmptyIcons) + .setAdditionalSlotEmptyIcons(additionalSlotEmptyIcons) + .build(); + register(new ResourceLocation(id.getNamespace(), id.getPath() + "_smithing_template"), item); + return item; + } + + public Item register(ResourceLocation itemId) { + return register(itemId, new ModelProviderItem(makeItemSettings())); + } + + @Override + public Item register(ResourceLocation itemId, Item item) { + if (!config.getBoolean("items", itemId.getPath(), true)) { + return item; + } + + registerItem(itemId, item); + + return item; + } + + public Item registerTool(ResourceLocation itemId, Item item) { + if (!config.getBoolean("tools", itemId.getPath(), true)) { + return item; + } + registerItem(itemId, item); + + return item; + } + + public Item registerEgg(ResourceLocation itemId, EntityType type, int background, int dots) { + SpawnEggItem item = new BaseSpawnEggItem(type, background, dots, makeItemSettings()); + + if (!config.getBoolean("spawnEggs", itemId.getPath(), true)) { + return item; + } + + DefaultDispenseItemBehavior behavior = new DefaultDispenseItemBehavior() { + public ItemStack execute(BlockSource pointer, ItemStack stack) { + Direction direction = pointer.getBlockState().getValue(DispenserBlock.FACING); + EntityType entityType = ((SpawnEggItem) stack.getItem()).getType(stack.getTag()); + entityType.spawn( + pointer.getLevel(), + stack, + null, + pointer.getPos().relative(direction), + MobSpawnType.DISPENSER, + direction != Direction.UP, + false + ); + stack.shrink(1); + return stack; + } + }; + DispenserBlock.registerBehavior(item, behavior); + return register(itemId, item); + } + + public Item registerFood(ResourceLocation itemId, int hunger, float saturation, MobEffectInstance... effects) { + FoodProperties.Builder builder = new FoodProperties.Builder().nutrition(hunger).saturationMod(saturation); + for (MobEffectInstance effect : effects) { + builder.effect(effect, 1F); + } + return registerFood(itemId, builder.build()); + } + + public Item registerFood(ResourceLocation itemId, FoodProperties foodComponent) { + return register(itemId, new ModelProviderItem(makeItemSettings().food(foodComponent))); + } + + public Item registerDrink(ResourceLocation itemId, FoodProperties foodComponent) { + return register(itemId, new BaseDrinkItem(makeItemSettings().stacksTo(1).food(foodComponent))); + } + + public Item registerDrink(ResourceLocation itemId, int hunger, float saturation) { + FoodProperties.Builder builder = new FoodProperties.Builder().nutrition(hunger).saturationMod(saturation); + return registerDrink(itemId, builder.build()); + } + + @Override + public void registerItem(ResourceLocation id, Item item) { + if (item != null && item != Items.AIR) { + Registry.register(BuiltInRegistries.ITEM, id, item); + getModItems(id.getNamespace()).add(item); + } + } + + public Item register(ResourceLocation itemId, Item item, String category) { + if (config.getBoolean(category, itemId.getPath(), true)) { + registerItem(itemId, item); + } + return item; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/PosInfo.java b/src/main/java/org/betterx/bclib/sdf/PosInfo.java new file mode 100644 index 00000000..97b5eb1a --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/PosInfo.java @@ -0,0 +1,104 @@ +package org.betterx.bclib.sdf; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.Map; + +public class PosInfo implements Comparable { + private static final BlockState AIR = Blocks.AIR.defaultBlockState(); + private final Map blocks; + private final Map add; + private final BlockPos pos; + private BlockState state; + + public static PosInfo create(Map blocks, Map add, BlockPos pos) { + return new PosInfo(blocks, add, pos); + } + + private PosInfo(Map blocks, Map add, BlockPos pos) { + this.blocks = blocks; + this.add = add; + this.pos = pos; + blocks.put(pos, this); + } + + public BlockState getState() { + return state; + } + + public BlockState getState(BlockPos pos) { + PosInfo info = blocks.get(pos); + if (info == null) { + info = add.get(pos); + return info == null ? AIR : info.getState(); + } + return info.getState(); + } + + public void setState(BlockState state) { + this.state = state; + } + + public void setState(BlockPos pos, BlockState state) { + PosInfo info = blocks.get(pos); + if (info != null) { + info.setState(state); + } + } + + public BlockState getState(Direction dir) { + PosInfo info = blocks.get(pos.relative(dir)); + if (info == null) { + info = add.get(pos.relative(dir)); + return info == null ? AIR : info.getState(); + } + return info.getState(); + } + + public BlockState getState(Direction dir, int distance) { + PosInfo info = blocks.get(pos.relative(dir, distance)); + if (info == null) { + return AIR; + } + return info.getState(); + } + + public BlockState getStateUp() { + return getState(Direction.UP); + } + + public BlockState getStateDown() { + return getState(Direction.DOWN); + } + + @Override + public int hashCode() { + return pos.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof PosInfo)) { + return false; + } + return pos.equals(((PosInfo) obj).pos); + } + + @Override + public int compareTo(PosInfo info) { + return this.pos.getY() - info.pos.getY(); + } + + public BlockPos getPos() { + return pos; + } + + public void setBlockPos(BlockPos pos, BlockState state) { + PosInfo info = new PosInfo(blocks, add, pos); + info.state = state; + add.put(pos, info); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/SDF.java b/src/main/java/org/betterx/bclib/sdf/SDF.java new file mode 100644 index 00000000..5d865265 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/SDF.java @@ -0,0 +1,307 @@ +package org.betterx.bclib.sdf; + +import org.betterx.bclib.api.v2.levelgen.structures.StructureWorld; +import org.betterx.bclib.util.BlocksHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.ServerLevelAccessor; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.util.*; +import java.util.function.Function; + +public abstract class SDF { + private final List> postProcesses = Lists.newArrayList(); + private Function canReplace = (state) -> { + return state.canBeReplaced(); + }; + + public abstract float getDistance(float x, float y, float z); + + public abstract BlockState getBlockState(BlockPos pos); + + public SDF addPostProcess(Function postProcess) { + this.postProcesses.add(postProcess); + return this; + } + + public SDF setReplaceFunction(Function canReplace) { + this.canReplace = canReplace; + return this; + } + + public void fillRecursive(ServerLevelAccessor world, BlockPos start) { + Map mapWorld = Maps.newHashMap(); + Map addInfo = Maps.newHashMap(); + Set blocks = Sets.newHashSet(); + Set ends = Sets.newHashSet(); + Set add = Sets.newHashSet(); + ends.add(new BlockPos(0, 0, 0)); + boolean run = true; + + MutableBlockPos bPos = new MutableBlockPos(); + + while (run) { + for (BlockPos center : ends) { + for (Direction dir : Direction.values()) { + bPos.set(center).move(dir); + BlockPos wpos = bPos.offset(start); + + if (!blocks.contains(bPos) && canReplace.apply(world.getBlockState(wpos))) { + if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) { + BlockState state = getBlockState(wpos); + PosInfo.create(mapWorld, addInfo, wpos).setState(state); + add.add(bPos.immutable()); + } + } + } + } + + blocks.addAll(ends); + ends.clear(); + ends.addAll(add); + add.clear(); + + run &= !ends.isEmpty(); + } + + List infos = new ArrayList(mapWorld.values()); + if (infos.size() > 0) { + Collections.sort(infos); + postProcesses.forEach((postProcess) -> { + infos.forEach((info) -> { + info.setState(postProcess.apply(info)); + }); + }); + infos.forEach((info) -> { + BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); + }); + + infos.clear(); + infos.addAll(addInfo.values()); + Collections.sort(infos); + postProcesses.forEach((postProcess) -> { + infos.forEach((info) -> { + info.setState(postProcess.apply(info)); + }); + }); + infos.forEach((info) -> { + if (canReplace.apply(world.getBlockState(info.getPos()))) { + BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); + } + }); + } + } + + public void fillArea(ServerLevelAccessor world, BlockPos center, AABB box) { + Map mapWorld = Maps.newHashMap(); + Map addInfo = Maps.newHashMap(); + + MutableBlockPos mut = new MutableBlockPos(); + for (int y = (int) box.minY; y <= box.maxY; y++) { + mut.setY(y); + for (int x = (int) box.minX; x <= box.maxX; x++) { + mut.setX(x); + for (int z = (int) box.minZ; z <= box.maxZ; z++) { + mut.setZ(z); + if (canReplace.apply(world.getBlockState(mut))) { + BlockPos fpos = mut.subtract(center); + if (this.getDistance(fpos.getX(), fpos.getY(), fpos.getZ()) < 0) { + PosInfo.create(mapWorld, addInfo, mut.immutable()).setState(getBlockState(mut)); + } + } + } + } + } + + List infos = new ArrayList(mapWorld.values()); + if (infos.size() > 0) { + Collections.sort(infos); + postProcesses.forEach((postProcess) -> { + infos.forEach((info) -> { + info.setState(postProcess.apply(info)); + }); + }); + infos.forEach((info) -> { + BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); + }); + + infos.clear(); + infos.addAll(addInfo.values()); + Collections.sort(infos); + postProcesses.forEach((postProcess) -> { + infos.forEach((info) -> { + info.setState(postProcess.apply(info)); + }); + }); + infos.forEach((info) -> { + if (canReplace.apply(world.getBlockState(info.getPos()))) { + BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); + } + }); + } + } + + public void fillRecursiveIgnore(ServerLevelAccessor world, BlockPos start, Function ignore) { + Map mapWorld = Maps.newHashMap(); + Map addInfo = Maps.newHashMap(); + Set blocks = Sets.newHashSet(); + Set ends = Sets.newHashSet(); + Set add = Sets.newHashSet(); + ends.add(new BlockPos(0, 0, 0)); + boolean run = true; + + MutableBlockPos bPos = new MutableBlockPos(); + + while (run) { + for (BlockPos center : ends) { + for (Direction dir : Direction.values()) { + bPos.set(center).move(dir); + BlockPos wpos = bPos.offset(start); + BlockState state = world.getBlockState(wpos); + boolean ign = ignore.apply(state); + if (!blocks.contains(bPos) && (ign || canReplace.apply(state))) { + if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) { + PosInfo.create(mapWorld, addInfo, wpos).setState(ign ? state : getBlockState(bPos)); + add.add(bPos.immutable()); + } + } + } + } + + blocks.addAll(ends); + ends.clear(); + ends.addAll(add); + add.clear(); + + run &= !ends.isEmpty(); + } + + List infos = new ArrayList(mapWorld.values()); + if (infos.size() > 0) { + Collections.sort(infos); + postProcesses.forEach((postProcess) -> { + infos.forEach((info) -> { + info.setState(postProcess.apply(info)); + }); + }); + infos.forEach((info) -> { + BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); + }); + + infos.clear(); + infos.addAll(addInfo.values()); + Collections.sort(infos); + postProcesses.forEach((postProcess) -> { + infos.forEach((info) -> { + info.setState(postProcess.apply(info)); + }); + }); + infos.forEach((info) -> { + if (canReplace.apply(world.getBlockState(info.getPos()))) { + BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); + } + }); + } + } + + public void fillRecursive(StructureWorld world, BlockPos start) { + Map mapWorld = Maps.newHashMap(); + Map addInfo = Maps.newHashMap(); + Set blocks = Sets.newHashSet(); + Set ends = Sets.newHashSet(); + Set add = Sets.newHashSet(); + ends.add(new BlockPos(0, 0, 0)); + boolean run = true; + + MutableBlockPos bPos = new MutableBlockPos(); + + while (run) { + for (BlockPos center : ends) { + for (Direction dir : Direction.values()) { + bPos.set(center).move(dir); + BlockPos wpos = bPos.offset(start); + + if (!blocks.contains(bPos)) { + if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) { + BlockState state = getBlockState(wpos); + PosInfo.create(mapWorld, addInfo, wpos).setState(state); + add.add(bPos.immutable()); + } + } + } + } + + blocks.addAll(ends); + ends.clear(); + ends.addAll(add); + add.clear(); + + run &= !ends.isEmpty(); + } + + List infos = new ArrayList(mapWorld.values()); + Collections.sort(infos); + postProcesses.forEach((postProcess) -> { + infos.forEach((info) -> { + info.setState(postProcess.apply(info)); + }); + }); + infos.forEach((info) -> { + world.setBlock(info.getPos(), info.getState()); + }); + + infos.clear(); + infos.addAll(addInfo.values()); + Collections.sort(infos); + postProcesses.forEach((postProcess) -> { + infos.forEach((info) -> { + info.setState(postProcess.apply(info)); + }); + }); + infos.forEach((info) -> { + world.setBlock(info.getPos(), info.getState()); + }); + } + + public Set getPositions(ServerLevelAccessor world, BlockPos start) { + Set blocks = Sets.newHashSet(); + Set ends = Sets.newHashSet(); + Set add = Sets.newHashSet(); + ends.add(new BlockPos(0, 0, 0)); + boolean run = true; + + MutableBlockPos bPos = new MutableBlockPos(); + + while (run) { + for (BlockPos center : ends) { + for (Direction dir : Direction.values()) { + bPos.set(center).move(dir); + BlockPos wpos = bPos.offset(start); + BlockState state = world.getBlockState(wpos); + if (!blocks.contains(wpos) && canReplace.apply(state)) { + if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) { + add.add(bPos.immutable()); + } + } + } + } + + ends.forEach((end) -> blocks.add(end.offset(start))); + ends.clear(); + ends.addAll(add); + add.clear(); + + run &= !ends.isEmpty(); + } + + return blocks; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFBinary.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFBinary.java new file mode 100644 index 00000000..cff78329 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFBinary.java @@ -0,0 +1,35 @@ +package org.betterx.bclib.sdf.operator; + +import org.betterx.bclib.sdf.SDF; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; + +public abstract class SDFBinary extends SDF { + protected SDF sourceA; + protected SDF sourceB; + protected boolean firstValue; + + public SDFBinary setSourceA(SDF sourceA) { + this.sourceA = sourceA; + return this; + } + + public SDFBinary setSourceB(SDF sourceB) { + this.sourceB = sourceB; + return this; + } + + protected void selectValue(float a, float b) { + firstValue = a < b; + } + + @Override + public BlockState getBlockState(BlockPos pos) { + if (firstValue) { + return sourceA.getBlockState(pos); + } else { + return sourceB.getBlockState(pos); + } + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFCoordModify.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFCoordModify.java new file mode 100644 index 00000000..00a82c5c --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFCoordModify.java @@ -0,0 +1,23 @@ +package org.betterx.bclib.sdf.operator; + + +import org.joml.Vector3f; + +import java.util.function.Consumer; + +public class SDFCoordModify extends SDFUnary { + private final Vector3f pos = new Vector3f(); + private Consumer function; + + public SDFCoordModify setFunction(Consumer function) { + this.function = function; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + pos.set(x, y, z); + function.accept(pos); + return this.source.getDistance(pos.x(), pos.y(), pos.z()); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFCopyRotate.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFCopyRotate.java new file mode 100644 index 00000000..906fd97d --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFCopyRotate.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.sdf.operator; + +import org.betterx.bclib.util.MHelper; + +public class SDFCopyRotate extends SDFUnary { + int count = 1; + + public SDFCopyRotate setCount(int count) { + this.count = count; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + float px = (float) Math.atan2(x, z); + float pz = MHelper.length(x, z); + return this.source.getDistance(px, y, pz); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFDisplacement.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFDisplacement.java new file mode 100644 index 00000000..a91c16d0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFDisplacement.java @@ -0,0 +1,22 @@ +package org.betterx.bclib.sdf.operator; + + +import org.joml.Vector3f; + +import java.util.function.Function; + +public class SDFDisplacement extends SDFUnary { + private final Vector3f pos = new Vector3f(); + private Function displace; + + public SDFDisplacement setFunction(Function displace) { + this.displace = displace; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + pos.set(x, y, z); + return this.source.getDistance(x, y, z) + displace.apply(pos); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFFlatWave.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFFlatWave.java new file mode 100644 index 00000000..efe7ec4a --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFFlatWave.java @@ -0,0 +1,28 @@ +package org.betterx.bclib.sdf.operator; + +public class SDFFlatWave extends SDFDisplacement { + private int rayCount = 1; + private float intensity; + private float angle; + + public SDFFlatWave() { + setFunction((pos) -> { + return (float) Math.cos(Math.atan2(pos.x(), pos.z()) * rayCount + angle) * intensity; + }); + } + + public SDFFlatWave setRaysCount(int count) { + this.rayCount = count; + return this; + } + + public SDFFlatWave setAngle(float angle) { + this.angle = angle; + return this; + } + + public SDFFlatWave setIntensity(float intensity) { + this.intensity = intensity; + return this; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFHeightmap.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFHeightmap.java new file mode 100644 index 00000000..2943720d --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFHeightmap.java @@ -0,0 +1,63 @@ +package org.betterx.bclib.sdf.operator; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.util.Mth; + +public class SDFHeightmap extends SDFDisplacement { + private float intensity = 1F; + private NativeImage map; + private float offsetX; + private float offsetZ; + private float scale; + private float cos = 1; + private float sin = 0; + + public SDFHeightmap() { + setFunction((pos) -> { + if (map == null) { + return 0F; + } + float px = Mth.clamp(pos.x() * scale + offsetX, 0, map.getWidth() - 2); + float pz = Mth.clamp(pos.z() * scale + offsetZ, 0, map.getHeight() - 2); + float dx = (px * cos - pz * sin); + float dz = (pz * cos + px * sin); + int x1 = Mth.floor(dx); + int z1 = Mth.floor(dz); + int x2 = x1 + 1; + int z2 = z1 + 1; + dx = dx - x1; + dz = dz - z1; + float a = (map.getPixelRGBA(x1, z1) & 255) / 255F; + float b = (map.getPixelRGBA(x2, z1) & 255) / 255F; + float c = (map.getPixelRGBA(x1, z2) & 255) / 255F; + float d = (map.getPixelRGBA(x2, z2) & 255) / 255F; + a = Mth.lerp(dx, a, b); + b = Mth.lerp(dx, c, d); + return -Mth.lerp(dz, a, b) * intensity; + }); + } + + public SDFHeightmap setMap(NativeImage map) { + this.map = map; + offsetX = map.getWidth() * 0.5F; + offsetZ = map.getHeight() * 0.5F; + scale = map.getWidth(); + return this; + } + + public SDFHeightmap setAngle(float angle) { + sin = Mth.sin(angle); + cos = Mth.cos(angle); + return this; + } + + public SDFHeightmap setScale(float scale) { + this.scale = map.getWidth() * scale; + return this; + } + + public SDFHeightmap setIntensity(float intensity) { + this.intensity = intensity; + return this; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFIntersection.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFIntersection.java new file mode 100644 index 00000000..3d6be14d --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFIntersection.java @@ -0,0 +1,13 @@ +package org.betterx.bclib.sdf.operator; + +import org.betterx.bclib.util.MHelper; + +public class SDFIntersection extends SDFBinary { + @Override + public float getDistance(float x, float y, float z) { + float a = this.sourceA.getDistance(x, y, z); + float b = this.sourceB.getDistance(x, y, z); + this.selectValue(a, b); + return MHelper.max(a, b); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFInvert.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFInvert.java new file mode 100644 index 00000000..8ce1dfe6 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFInvert.java @@ -0,0 +1,8 @@ +package org.betterx.bclib.sdf.operator; + +public class SDFInvert extends SDFUnary { + @Override + public float getDistance(float x, float y, float z) { + return -this.source.getDistance(x, y, z); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFRadialNoiseMap.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFRadialNoiseMap.java new file mode 100644 index 00000000..8b8cc21b --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFRadialNoiseMap.java @@ -0,0 +1,64 @@ +package org.betterx.bclib.sdf.operator; + +import org.betterx.bclib.noise.OpenSimplexNoise; +import org.betterx.bclib.util.MHelper; + +import net.minecraft.util.Mth; + +public class SDFRadialNoiseMap extends SDFDisplacement { + private static final float SIN = Mth.sin(0.5F); + private static final float COS = Mth.cos(0.5F); + + private OpenSimplexNoise noise; + private float intensity = 1F; + private float radius = 1F; + private short offsetX; + private short offsetZ; + + public SDFRadialNoiseMap() { + setFunction((pos) -> { + if (intensity == 0) { + return 0F; + } + float px = pos.x() / radius; + float pz = pos.z() / radius; + float distance = MHelper.lengthSqr(px, pz); + if (distance > 1) { + return 0F; + } + distance = 1 - Mth.sqrt(distance); + float nx = px * COS - pz * SIN; + float nz = pz * COS + px * SIN; + distance *= getNoise(nx * 0.75 + offsetX, nz * 0.75 + offsetZ); + return distance * intensity; + }); + } + + private float getNoise(double x, double z) { + return (float) noise.eval(x, z) + (float) noise.eval( + x * 3 + 1000, + z * 3 + ) * 0.5F + (float) noise.eval(x * 9 + 1000, z * 9) * 0.2F; + } + + public SDFRadialNoiseMap setSeed(long seed) { + noise = new OpenSimplexNoise(seed); + return this; + } + + public SDFRadialNoiseMap setIntensity(float intensity) { + this.intensity = intensity; + return this; + } + + public SDFRadialNoiseMap setRadius(float radius) { + this.radius = radius; + return this; + } + + public SDFRadialNoiseMap setOffset(int x, int z) { + offsetX = (short) (x & 32767); + offsetZ = (short) (z & 32767); + return this; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFRotation.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFRotation.java new file mode 100644 index 00000000..a6acee25 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFRotation.java @@ -0,0 +1,29 @@ +package org.betterx.bclib.sdf.operator; + + +import com.mojang.math.Axis; + +import org.joml.Quaternionf; +import org.joml.Vector3f; + +public class SDFRotation extends SDFUnary { + private final Vector3f pos = new Vector3f(); + private Quaternionf rotation; + + public SDFRotation setRotation(Axis axis, float rotationAngle) { + rotation = axis.rotation(rotationAngle); + return this; + } + + public SDFRotation setRotation(Vector3f axis, float rotationAngle) { + rotation = new Quaternionf().setAngleAxis(rotationAngle, axis.x, axis.y, axis.z); + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + pos.set(x, y, z); + pos.rotate(rotation); + return source.getDistance(pos.x(), pos.y(), pos.z()); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFRound.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFRound.java new file mode 100644 index 00000000..36bd54b4 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFRound.java @@ -0,0 +1,15 @@ +package org.betterx.bclib.sdf.operator; + +public class SDFRound extends SDFUnary { + private float radius; + + public SDFRound setRadius(float radius) { + this.radius = radius; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + return this.source.getDistance(x, y, z) - radius; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFScale.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFScale.java new file mode 100644 index 00000000..7aa258f2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFScale.java @@ -0,0 +1,15 @@ +package org.betterx.bclib.sdf.operator; + +public class SDFScale extends SDFUnary { + private float scale; + + public SDFScale setScale(float scale) { + this.scale = scale; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + return source.getDistance(x / scale, y / scale, z / scale) * scale; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFScale3D.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFScale3D.java new file mode 100644 index 00000000..6defc4aa --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFScale3D.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.sdf.operator; + +public class SDFScale3D extends SDFUnary { + private float x; + private float y; + private float z; + + public SDFScale3D setScale(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + return source.getDistance(x / this.x, y / this.y, z / this.z); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFSmoothIntersection.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFSmoothIntersection.java new file mode 100644 index 00000000..e10f784d --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFSmoothIntersection.java @@ -0,0 +1,21 @@ +package org.betterx.bclib.sdf.operator; + +import net.minecraft.util.Mth; + +public class SDFSmoothIntersection extends SDFBinary { + private float radius; + + public SDFSmoothIntersection setRadius(float radius) { + this.radius = radius; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + float a = this.sourceA.getDistance(x, y, z); + float b = this.sourceB.getDistance(x, y, z); + this.selectValue(a, b); + float h = Mth.clamp(0.5F - 0.5F * (b - a) / radius, 0F, 1F); + return Mth.lerp(h, b, a) + radius * h * (1F - h); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFSmoothSubtraction.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFSmoothSubtraction.java new file mode 100644 index 00000000..3d12385a --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFSmoothSubtraction.java @@ -0,0 +1,21 @@ +package org.betterx.bclib.sdf.operator; + +import net.minecraft.util.Mth; + +public class SDFSmoothSubtraction extends SDFBinary { + private float radius; + + public SDFSmoothSubtraction setRadius(float radius) { + this.radius = radius; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + float a = this.sourceA.getDistance(x, y, z); + float b = this.sourceB.getDistance(x, y, z); + this.selectValue(a, b); + float h = Mth.clamp(0.5F - 0.5F * (b + a) / radius, 0F, 1F); + return Mth.lerp(h, b, -a) + radius * h * (1F - h); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFSmoothUnion.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFSmoothUnion.java new file mode 100644 index 00000000..e09f22a2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFSmoothUnion.java @@ -0,0 +1,21 @@ +package org.betterx.bclib.sdf.operator; + +import net.minecraft.util.Mth; + +public class SDFSmoothUnion extends SDFBinary { + private float radius; + + public SDFSmoothUnion setRadius(float radius) { + this.radius = radius; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + float a = this.sourceA.getDistance(x, y, z); + float b = this.sourceB.getDistance(x, y, z); + this.selectValue(a, b); + float h = Mth.clamp(0.5F + 0.5F * (b - a) / radius, 0F, 1F); + return Mth.lerp(h, b, a) - radius * h * (1F - h); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFSubtraction.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFSubtraction.java new file mode 100644 index 00000000..345c5670 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFSubtraction.java @@ -0,0 +1,13 @@ +package org.betterx.bclib.sdf.operator; + +import org.betterx.bclib.util.MHelper; + +public class SDFSubtraction extends SDFBinary { + @Override + public float getDistance(float x, float y, float z) { + float a = this.sourceA.getDistance(x, y, z); + float b = this.sourceB.getDistance(x, y, z); + this.selectValue(a, b); + return MHelper.max(a, -b); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFTranslate.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFTranslate.java new file mode 100644 index 00000000..2bbc2c1c --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFTranslate.java @@ -0,0 +1,19 @@ +package org.betterx.bclib.sdf.operator; + +public class SDFTranslate extends SDFUnary { + float x; + float y; + float z; + + public SDFTranslate setTranslate(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + return source.getDistance(x - this.x, y - this.y, z - this.z); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFUnary.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFUnary.java new file mode 100644 index 00000000..6dcaab91 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFUnary.java @@ -0,0 +1,20 @@ +package org.betterx.bclib.sdf.operator; + +import org.betterx.bclib.sdf.SDF; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; + +public abstract class SDFUnary extends SDF { + protected SDF source; + + public SDFUnary setSource(SDF source) { + this.source = source; + return this; + } + + @Override + public BlockState getBlockState(BlockPos pos) { + return source.getBlockState(pos); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/operator/SDFUnion.java b/src/main/java/org/betterx/bclib/sdf/operator/SDFUnion.java new file mode 100644 index 00000000..c461aa8f --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/operator/SDFUnion.java @@ -0,0 +1,13 @@ +package org.betterx.bclib.sdf.operator; + +import org.betterx.bclib.util.MHelper; + +public class SDFUnion extends SDFBinary { + @Override + public float getDistance(float x, float y, float z) { + float a = this.sourceA.getDistance(x, y, z); + float b = this.sourceB.getDistance(x, y, z); + this.selectValue(a, b); + return MHelper.min(a, b); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/primitive/SDFCappedCone.java b/src/main/java/org/betterx/bclib/sdf/primitive/SDFCappedCone.java new file mode 100644 index 00000000..712fba02 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/primitive/SDFCappedCone.java @@ -0,0 +1,44 @@ +package org.betterx.bclib.sdf.primitive; + +import org.betterx.bclib.util.MHelper; + +import net.minecraft.util.Mth; + +public class SDFCappedCone extends SDFPrimitive { + private float radius1; + private float radius2; + private float height; + + public SDFCappedCone setRadius1(float radius) { + this.radius1 = radius; + return this; + } + + public SDFCappedCone setRadius2(float radius) { + this.radius2 = radius; + return this; + } + + public SDFCappedCone setHeight(float height) { + this.height = height; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + float qx = MHelper.length(x, z); + float k2x = radius2 - radius1; + float k2y = 2 * height; + float cax = qx - MHelper.min(qx, (y < 0F) ? radius1 : radius2); + float cay = Math.abs(y) - height; + float mlt = Mth.clamp( + MHelper.dot(radius2 - qx, height - y, k2x, k2y) / MHelper.dot(k2x, k2y, k2x, k2y), + 0F, + 1F + ); + float cbx = qx - radius2 + k2x * mlt; + float cby = y - height + k2y * mlt; + float s = (cbx < 0F && cay < 0F) ? -1F : 1F; + return s * (float) Math.sqrt(MHelper.min(MHelper.dot(cax, cay, cax, cay), MHelper.dot(cbx, cby, cbx, cby))); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/primitive/SDFCapsule.java b/src/main/java/org/betterx/bclib/sdf/primitive/SDFCapsule.java new file mode 100644 index 00000000..e1a50ff0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/primitive/SDFCapsule.java @@ -0,0 +1,25 @@ +package org.betterx.bclib.sdf.primitive; + +import org.betterx.bclib.util.MHelper; + +import net.minecraft.util.Mth; + +public class SDFCapsule extends SDFPrimitive { + private float radius; + private float height; + + public SDFCapsule setRadius(float radius) { + this.radius = radius; + return this; + } + + public SDFCapsule setHeight(float height) { + this.height = height; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + return MHelper.length(x, y - Mth.clamp(y, 0, height), z) - radius; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/primitive/SDFFlatland.java b/src/main/java/org/betterx/bclib/sdf/primitive/SDFFlatland.java new file mode 100644 index 00000000..a1aac88c --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/primitive/SDFFlatland.java @@ -0,0 +1,8 @@ +package org.betterx.bclib.sdf.primitive; + +public class SDFFlatland extends SDFPrimitive { + @Override + public float getDistance(float x, float y, float z) { + return y; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/primitive/SDFHexPrism.java b/src/main/java/org/betterx/bclib/sdf/primitive/SDFHexPrism.java new file mode 100644 index 00000000..ad2bd24a --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/primitive/SDFHexPrism.java @@ -0,0 +1,26 @@ +package org.betterx.bclib.sdf.primitive; + +import org.betterx.bclib.util.MHelper; + +public class SDFHexPrism extends SDFPrimitive { + private float radius; + private float height; + + public SDFHexPrism setRadius(float radius) { + this.radius = radius; + return this; + } + + public SDFHexPrism setHeight(float height) { + this.height = height; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + float px = Math.abs(x); + float py = Math.abs(y); + float pz = Math.abs(z); + return MHelper.max(py - height, MHelper.max((px * 0.866025F + pz * 0.5F), pz) - radius); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/primitive/SDFLine.java b/src/main/java/org/betterx/bclib/sdf/primitive/SDFLine.java new file mode 100644 index 00000000..16094bea --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/primitive/SDFLine.java @@ -0,0 +1,50 @@ +package org.betterx.bclib.sdf.primitive; + +import org.betterx.bclib.util.MHelper; + +import net.minecraft.util.Mth; + +public class SDFLine extends SDFPrimitive { + private float radius; + private float x1; + private float y1; + private float z1; + private float x2; + private float y2; + private float z2; + + public SDFLine setRadius(float radius) { + this.radius = radius; + return this; + } + + public SDFLine setStart(float x, float y, float z) { + this.x1 = x; + this.y1 = y; + this.z1 = z; + return this; + } + + public SDFLine setEnd(float x, float y, float z) { + this.x2 = x; + this.y2 = y; + this.z2 = z; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + float pax = x - x1; + float pay = y - y1; + float paz = z - z1; + + float bax = x2 - x1; + float bay = y2 - y1; + float baz = z2 - z1; + + float dpb = MHelper.dot(pax, pay, paz, bax, bay, baz); + float dbb = MHelper.dot(bax, bay, baz, bax, bay, baz); + float h = Mth.clamp(dpb / dbb, 0F, 1F); + return MHelper.length(pax - bax * h, pay - bay * h, paz - baz * h) - radius; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/primitive/SDFPie.java b/src/main/java/org/betterx/bclib/sdf/primitive/SDFPie.java new file mode 100644 index 00000000..aaa3419d --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/primitive/SDFPie.java @@ -0,0 +1,32 @@ +package org.betterx.bclib.sdf.primitive; + +import org.betterx.bclib.util.MHelper; + +import net.minecraft.util.Mth; + +public class SDFPie extends SDFPrimitive { + private float sin; + private float cos; + private float radius; + + public SDFPie setAngle(float angle) { + this.sin = (float) Math.sin(angle); + this.cos = (float) Math.cos(angle); + return this; + } + + public SDFPie setRadius(float radius) { + this.radius = radius; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + float px = Math.abs(x); + float l = MHelper.length(px, y, z) - radius; + float m = MHelper.dot(px, z, sin, cos); + m = Mth.clamp(m, 0, radius); + m = MHelper.length(px - sin * m, z - cos * m); + return MHelper.max(l, m * Math.signum(cos * px - sin * z)); + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/primitive/SDFPrimitive.java b/src/main/java/org/betterx/bclib/sdf/primitive/SDFPrimitive.java new file mode 100644 index 00000000..716c7610 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/primitive/SDFPrimitive.java @@ -0,0 +1,40 @@ +package org.betterx.bclib.sdf.primitive; + +import org.betterx.bclib.sdf.SDF; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.function.Function; + +public abstract class SDFPrimitive extends SDF { + protected Function placerFunction; + + public SDFPrimitive setBlock(Function placerFunction) { + this.placerFunction = placerFunction; + return this; + } + + public SDFPrimitive setBlock(BlockState state) { + this.placerFunction = (pos) -> { + return state; + }; + return this; + } + + public SDFPrimitive setBlock(Block block) { + this.placerFunction = (pos) -> { + return block.defaultBlockState(); + }; + return this; + } + + public BlockState getBlockState(BlockPos pos) { + return placerFunction.apply(pos); + } + + /*public abstract CompoundTag toNBT(CompoundTag root) { + + }*/ +} diff --git a/src/main/java/org/betterx/bclib/sdf/primitive/SDFSphere.java b/src/main/java/org/betterx/bclib/sdf/primitive/SDFSphere.java new file mode 100644 index 00000000..08d2731d --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/primitive/SDFSphere.java @@ -0,0 +1,17 @@ +package org.betterx.bclib.sdf.primitive; + +import org.betterx.bclib.util.MHelper; + +public class SDFSphere extends SDFPrimitive { + private float radius; + + public SDFSphere setRadius(float radius) { + this.radius = radius; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + return MHelper.length(x, y, z) - radius; + } +} diff --git a/src/main/java/org/betterx/bclib/sdf/primitive/SDFTorus.java b/src/main/java/org/betterx/bclib/sdf/primitive/SDFTorus.java new file mode 100644 index 00000000..424dad67 --- /dev/null +++ b/src/main/java/org/betterx/bclib/sdf/primitive/SDFTorus.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.sdf.primitive; + +import org.betterx.bclib.util.MHelper; + +public class SDFTorus extends SDFPrimitive { + private float radiusSmall; + private float radiusBig; + + public SDFTorus setBigRadius(float radius) { + this.radiusBig = radius; + return this; + } + + public SDFTorus setSmallRadius(float radius) { + this.radiusSmall = radius; + return this; + } + + @Override + public float getDistance(float x, float y, float z) { + float nx = MHelper.length(x, z) - radiusBig; + return MHelper.length(nx, y) - radiusSmall; + } +} diff --git a/src/main/java/org/betterx/bclib/server/BCLibServer.java b/src/main/java/org/betterx/bclib/server/BCLibServer.java new file mode 100644 index 00000000..63ca5724 --- /dev/null +++ b/src/main/java/org/betterx/bclib/server/BCLibServer.java @@ -0,0 +1,18 @@ +package org.betterx.bclib.server; + +import org.betterx.bclib.api.v2.ModIntegrationAPI; +import org.betterx.bclib.api.v2.PostInitAPI; +import org.betterx.bclib.api.v2.dataexchange.DataExchangeAPI; + +import net.fabricmc.api.DedicatedServerModInitializer; + +public class BCLibServer implements DedicatedServerModInitializer { + @Override + public void onInitializeServer() { + ModIntegrationAPI.registerAll(); + DataExchangeAPI.prepareServerside(); + + PostInitAPI.postInit(false); + } + +} diff --git a/src/main/java/org/betterx/bclib/util/BackgroundInfo.java b/src/main/java/org/betterx/bclib/util/BackgroundInfo.java new file mode 100644 index 00000000..4e4d2054 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/BackgroundInfo.java @@ -0,0 +1,9 @@ +package org.betterx.bclib.util; + +public class BackgroundInfo { + public static float fogColorRed; + public static float fogColorGreen; + public static float fogColorBlue; + public static float fogDensity = 1; + public static float blindness; +} diff --git a/src/main/java/org/betterx/bclib/util/BlocksHelper.java b/src/main/java/org/betterx/bclib/util/BlocksHelper.java new file mode 100644 index 00000000..ef827b11 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/BlocksHelper.java @@ -0,0 +1,401 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.behaviours.interfaces.BehaviourPlantLike; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.material.LavaFluid; +import net.minecraft.world.level.material.PushReaction; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class BlocksHelper { + private static final Map COLOR_BY_BLOCK = Maps.newHashMap(); + + public static final int FLAG_UPDATE_BLOCK = 1; + public static final int FLAG_SEND_CLIENT_CHANGES = 2; + public static final int FLAG_NO_RERENDER = 4; + public static final int FORSE_RERENDER = 8; + public static final int FLAG_IGNORE_OBSERVERS = 16; + + public static final int SET_SILENT = FLAG_IGNORE_OBSERVERS | FLAG_SEND_CLIENT_CHANGES; + public static final int SET_OBSERV = FLAG_UPDATE_BLOCK | FLAG_SEND_CLIENT_CHANGES; + public static final Direction[] HORIZONTAL = makeHorizontal(); + public static final Direction[] DIRECTIONS = Direction.values(); + + private static final ThreadLocal TL_POS = ThreadLocal.withInitial(() -> new MutableBlockPos()); + + protected static final BlockState AIR = Blocks.AIR.defaultBlockState(); + protected static final BlockState WATER = Blocks.WATER.defaultBlockState(); + + public static void addBlockColor(Block block, int color) { + COLOR_BY_BLOCK.put(block, color); + } + + public static int getBlockColor(Block block) { + return COLOR_BY_BLOCK.getOrDefault(block, 0xFF000000); + } + + public static void setWithoutUpdate(LevelAccessor world, BlockPos pos, BlockState state) { + world.setBlock(pos, state, SET_SILENT); + } + + public static void setWithoutUpdate(LevelAccessor world, BlockPos pos, Block block) { + world.setBlock(pos, block.defaultBlockState(), SET_SILENT); + } + + public static void setWithUpdate(LevelAccessor world, BlockPos pos, BlockState state) { + world.setBlock(pos, state, SET_OBSERV); + } + + public static void setWithUpdate(LevelAccessor world, BlockPos pos, Block block) { + world.setBlock(pos, block.defaultBlockState(), SET_OBSERV); + } + + public static int upRay(LevelAccessor world, BlockPos pos, int maxDist) { + int length = 0; + for (int j = 1; j < maxDist && (world.isEmptyBlock(pos.above(j))); j++) { + length++; + } + return length; + } + + public static int downRay(LevelAccessor world, BlockPos pos, int maxDist) { + int length = 0; + for (int j = 1; j < maxDist && (world.isEmptyBlock(pos.below(j))); j++) { + length++; + } + return length; + } + + public static int downRayRep(LevelAccessor world, BlockPos pos, int maxDist) { + final MutableBlockPos POS = TL_POS.get(); + POS.set(pos); + for (int j = 1; j < maxDist && (world.getBlockState(POS)).canBeReplaced(); j++) { + POS.setY(POS.getY() - 1); + } + return pos.getY() - POS.getY(); + } + + public static int raycastSqr(LevelAccessor world, BlockPos pos, int dx, int dy, int dz, int maxDist) { + final MutableBlockPos POS = TL_POS.get(); + POS.set(pos); + for (int j = 1; j < maxDist && (world.getBlockState(POS)).canBeReplaced(); j++) { + POS.move(dx, dy, dz); + } + return (int) pos.distSqr(POS); + } + + /** + * Rotates {@link BlockState} horizontally. Used in block classes with {@link Direction} {@link Property} in rotate function. + * + * @param state - {@link BlockState} to mirror; + * @param rotation - {@link Rotation}; + * @param facing - Block {@link Direction} {@link Property}. + * @return Rotated {@link BlockState}. + */ + public static BlockState rotateHorizontal(BlockState state, Rotation rotation, Property facing) { + return state.setValue(facing, rotation.rotate(state.getValue(facing))); + } + + /** + * Mirrors {@link BlockState} horizontally. Used in block classes with {@link Direction} {@link Property} in mirror function. + * + * @param state - {@link BlockState} to mirror; + * @param mirror - {@link Mirror}; + * @param facing - Block {@link Direction} {@link Property}. + * @return Mirrored {@link BlockState}. + */ + public static BlockState mirrorHorizontal(BlockState state, Mirror mirror, Property facing) { + return state.rotate(mirror.getRotation(state.getValue(facing))); + } + + /** + * Counts the amount of same block down. + * + * @param world - {@link LevelAccessor} world; + * @param pos - {@link BlockPos} start position; + * @param block - {@link Block} to count. + * @return Integer amount of blocks. + */ + public static int getLengthDown(LevelAccessor world, BlockPos pos, Block block) { + int count = 1; + while (world.getBlockState(pos.below(count)).getBlock() == block) { + count++; + } + return count; + } + + /** + * Creates a new {@link Direction} array with clockwise order: + * NORTH, EAST, SOUTH, WEST + * + * @return Array of {@link Direction}. + */ + public static Direction[] makeHorizontal() { + return new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; + } + + /** + * Get any random horizontal {@link Direction}. + * + * @param random - {@link Random}. + * @return {@link Direction}. + */ + public static Direction randomHorizontal(RandomSource random) { + return HORIZONTAL[random.nextInt(4)]; + } + + /** + * Get any random {@link Direction} including vertical and horizontal. + * + * @param random - {@link Random}. + * @return {@link Direction}. + */ + public static Direction randomDirection(RandomSource random) { + return DIRECTIONS[random.nextInt(6)]; + } + + + /** + * Check if block is "invulnerable" like Bedrock. + * + * @param state - {@link BlockState} to check; + * @param world - {@link BlockGetter} world where BlockState exist; + * @param pos - {@link BlockPos} where BlockState is. + * @return {@code true} if block is "invulnerable" and {@code false} if not. + */ + public static boolean isInvulnerable(BlockState state, BlockGetter world, BlockPos pos) { + return state.getDestroySpeed(world, pos) < 0; + } + + /** + * Check if block is "invulnerable" like Bedrock. Unlike safe function will pass world and position parameters as {@code null}. + * + * @param state - {@link BlockState} to check. + * @return {@code true} if block is "invulnerable" and {@code false} if not. + */ + public static boolean isInvulnerableUnsafe(BlockState state) { + try { + return isInvulnerable(state, null, null); + } catch (Exception e) { + return false; + } + } + + public static Optional findSurfaceBelow( + LevelAccessor level, + BlockPos startPos, + int minY, + Predicate surface + ) { + final MutableBlockPos POS = new MutableBlockPos(startPos.getX(), startPos.getY(), startPos.getZ()); + for (int y = startPos.getY(); y >= minY; y--) { + POS.setY(y); + if (surface.test(level.getBlockState(POS))) return Optional.of(POS); + } + return Optional.empty(); + } + + public static boolean findSurface( + LevelAccessor level, + MutableBlockPos startPos, + Direction dir, + int length, + Predicate surface + ) { + for (int len = 0; len < length; len++) { + if (surface.test(level.getBlockState(startPos))) return true; + startPos.move(dir, 1); + } + return false; + } + + public static boolean findOnSurroundingSurface( + LevelAccessor level, + MutableBlockPos startPos, + Direction dir, + int length, + Predicate surface + ) { + for (int len = 0; len < length; len++) { + if (surface.test(level.getBlockState(startPos))) { + if (len == 0) { //we started inside of the surface + for (int lenUp = 0; lenUp < length; lenUp++) { + startPos.move(dir, -1); + if (!surface.test(level.getBlockState(startPos))) { + return true; + } + } + return false; + } + startPos.move(dir, -1); + return true; + } + + startPos.move(dir, 1); + } + return false; + } + + public static boolean findSurroundingSurface( + LevelAccessor level, + MutableBlockPos startPos, + Direction dir, + int length, + Predicate surface + ) { + BlockState beforeState = null; + BlockState nowState; + for (int len = 0; len < length; len++) { + nowState = level.getBlockState(startPos); + if (surface.test(nowState)) { + if (len == 0) { //we started inside of the surface + beforeState = nowState; + for (int lenUp = 0; lenUp < length; lenUp++) { + startPos.move(dir, -1); + nowState = level.getBlockState(startPos); + if (BlocksHelper.isFree(nowState)) { + return surface.test(beforeState); + } + beforeState = nowState; + } + return false; + } else { + startPos.move(dir, -1); + return BlocksHelper.isFree(beforeState); + } + } + beforeState = nowState; + startPos.move(dir, 1); + } + return false; + } + + + public static boolean isFreeSpace( + LevelAccessor level, + BlockPos startPos, + Direction dir, + int length, + Predicate freeSurface + ) { + MutableBlockPos POS = startPos.mutable(); + for (int len = 0; len < length; len++) { + if (!freeSurface.test(level.getBlockState(POS))) { + return false; + } + POS.move(dir, 1); + } + return true; + } + + public static int blockCount( + LevelAccessor level, + BlockPos startPos, + Direction dir, + int length, + Predicate freeSurface + ) { + MutableBlockPos POS = startPos.mutable(); + for (int len = 0; len < length; len++) { + if (!freeSurface.test(level.getBlockState(POS))) { + return len; + } + POS.move(dir, 1); + } + return length; + } + + public static boolean isLava(BlockState state) { + return state.getFluidState().getType() instanceof LavaFluid; + } + + public static boolean isFluid(BlockState state) { + return state.liquid(); + } + + public static boolean isFree(BlockState state) { + return state.isAir(); + } + + public static boolean isFreeOrReplaceable(BlockState state) { + return state.isAir() || state.canBeReplaced(); + } + + public static boolean isFreeOrFluid(BlockState state) { + return state.isAir() || isFluid(state); + } + + public static boolean isTerrain(BlockState state) { + return state.is(CommonBlockTags.TERRAIN); + } + + public static boolean isTerrainOrFluid(BlockState state) { + return state.is(CommonBlockTags.TERRAIN) || isFluid(state); + } + + public static Boolean replaceableOrPlant(BlockState state) { + final Block block = state.getBlock(); + if (state.is(CommonBlockTags.PLANT) || state.is(CommonBlockTags.WATER_PLANT) || block instanceof BehaviourPlantLike) { + return true; + } + if (state.getPistonPushReaction() == PushReaction.DESTROY && block.defaultDestroyTime() == 0) return true; + + if (state.getSoundType() == SoundType.GRASS + || state.getSoundType() == SoundType.WET_GRASS + || state.getSoundType() == SoundType.CROP + || state.getSoundType() == SoundType.CAVE_VINES + + ) { + return true; + } + + return state.canBeReplaced(); + } + + public static void forAllInBounds(BoundingBox bb, Consumer worker) { + for (int x = bb.minX(); x <= bb.maxX(); x++) + for (int y = bb.minY(); y <= bb.maxY(); y++) + for (int z = bb.minZ(); z <= bb.maxZ(); z++) { + BlockPos bp = new BlockPos(x, y, z); + worker.accept(bp); + } + } + + public static void forOutlineInBounds(BoundingBox bb, Consumer worker) { + for (int x = bb.minX(); x <= bb.maxX(); x++) { + worker.accept(new BlockPos(x, bb.minY(), bb.minZ())); + worker.accept(new BlockPos(x, bb.maxY(), bb.minZ())); + worker.accept(new BlockPos(x, bb.minY(), bb.maxZ())); + worker.accept(new BlockPos(x, bb.maxY(), bb.maxZ())); + } + for (int y = bb.minY(); y <= bb.maxY(); y++) { + worker.accept(new BlockPos(bb.minX(), y, bb.minZ())); + worker.accept(new BlockPos(bb.maxX(), y, bb.minZ())); + worker.accept(new BlockPos(bb.minX(), y, bb.maxZ())); + worker.accept(new BlockPos(bb.maxX(), y, bb.maxZ())); + } + for (int z = bb.minZ(); z <= bb.maxZ(); z++) { + worker.accept(new BlockPos(bb.minX(), bb.minY(), z)); + worker.accept(new BlockPos(bb.maxX(), bb.minY(), z)); + worker.accept(new BlockPos(bb.minX(), bb.maxY(), z)); + worker.accept(new BlockPos(bb.maxX(), bb.maxY(), z)); + } + } +} diff --git a/src/main/java/org/betterx/bclib/util/CollectionsUtil.java b/src/main/java/org/betterx/bclib/util/CollectionsUtil.java new file mode 100644 index 00000000..f2076877 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/CollectionsUtil.java @@ -0,0 +1,44 @@ +package org.betterx.bclib.util; + +import java.util.*; + +public class CollectionsUtil { + /** + * Will return mutable copy of list. + * + * @param list {@link List} to make mutable. + * @return {@link ArrayList} or original {@link List} if it is mutable. + */ + public static List getMutable(List list) { + if (list instanceof ArrayList) { + return list; + } + return new ArrayList<>(list); + } + + /** + * Will return mutable copy of set. + * + * @param set {@link Set} to make mutable. + * @return {@link HashSet} or original {@link Set} if it is mutable. + */ + public static Set getMutable(Set set) { + if (set instanceof HashSet) { + return set; + } + return new HashSet<>(set); + } + + /** + * Will return mutable copy of map. + * + * @param map {@link Map} to make mutable. + * @return {@link HashMap} or original {@link Map} if it is mutable. + */ + public static Map getMutable(Map map) { + if (map instanceof HashMap) { + return map; + } + return new HashMap<>(map); + } +} diff --git a/src/main/java/org/betterx/bclib/util/ColorExtractor.java b/src/main/java/org/betterx/bclib/util/ColorExtractor.java new file mode 100644 index 00000000..593e9444 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/ColorExtractor.java @@ -0,0 +1,151 @@ +package org.betterx.bclib.util; + + +import org.betterx.ui.ColorUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + +public class ColorExtractor { + private final List

centers = new ArrayList<>(); + private final List colors; + private Integer result; + + public ColorExtractor(List colors) { + this.colors = colors; + Random rnd = new Random(); + int size = colors.size(); + for (int i = 0; i < 4; i++) { + int color = colors.get(rnd.nextInt(size)); + this.centers.add(new Center(color)); + } + } + + public int analize() { + boolean moved = true; + while (moved) { + this.remap(); + moved = false; + for (Center center : centers) { + if (center.move()) { + moved = true; + } + } + } + List
toClear = new ArrayList<>(); + this.centers.forEach(center -> { + if (center.colors.isEmpty()) { + toClear.add(center); + } + }); + if (toClear.size() > 0) { + toClear.forEach(clear -> centers.remove(clear)); + } + this.centers.sort(Center.COMPARATOR); + + return this.getResult(); + } + + public int getResult() { + if (result == null) { + double weights = 0; + double alpha = 0; + double red = 0; + double green = 0; + double blue = 0; + for (Center center : centers) { + double weight = (double) center.colors.size() / colors.size(); + weights += weight; + alpha += center.a * weight; + red += center.r * weight; + green += center.g * weight; + blue += center.b * weight; + } + + int a = (int) Math.round(alpha / weights); + int r = (int) Math.round(red / weights); + int g = (int) Math.round(green / weights); + int b = (int) Math.round(blue / weights); + + this.result = a << 24 | r << 16 | g << 8 | b; + } + + return this.result; + } + + private void remap() { + this.centers.forEach(entry -> entry.colors.clear()); + this.colors.forEach(color -> { + int id = 0; + int base = centers.get(0).getColor(); + int dst = ColorUtil.colorDistance(color, base); + for (Center center : centers) { + base = center.getColor(); + int dst1 = ColorUtil.colorDistance(color, base); + if (dst1 < dst) { + dst = dst1; + id = centers.indexOf(center); + } + } + this.centers.get(id).colors.add(color); + }); + } + + private static class Center { + static final Comparator
COMPARATOR = new Comparator
() { + @Override + public int compare(Center c1, Center c2) { + return Integer.compare(c1.getColor(), c2.getColor()); + } + }; + + List colors = new ArrayList<>(); + double a, r, g, b; + + Center(int color) { + this.a = (color >> 24) & 255; + this.r = (color >> 16) & 255; + this.g = (color >> 8) & 255; + this.b = color & 255; + } + + private void update(double a, double r, double g, double b) { + this.a = a; + this.r = r; + this.g = g; + this.b = b; + } + + public int getColor() { + int a = (int) Math.round(this.a); + int r = (int) Math.round(this.r); + int g = (int) Math.round(this.g); + int b = (int) Math.round(this.b); + return a << 24 | r << 16 | g << 8 | b; + } + + public boolean move() { + double or = r; + double og = g; + double ob = b; + double a = 0, r = 0, g = 0, b = 0; + int size = this.colors.size(); + for (int col : colors) { + a += (col >> 24) & 255; + r += (col >> 16) & 255; + g += (col >> 8) & 255; + b += col & 255; + } + a /= size; + r /= size; + g /= size; + b /= size; + + this.update(a, r, g, b); + + return Math.abs(r - or) > 0.1 || Math.abs(g - og) > 0.1 || Math.abs(b - ob) > 0.1; + } + } +} diff --git a/src/main/java/org/betterx/bclib/util/FullReferenceHolder.java b/src/main/java/org/betterx/bclib/util/FullReferenceHolder.java new file mode 100644 index 00000000..817b4904 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/FullReferenceHolder.java @@ -0,0 +1,124 @@ +package org.betterx.bclib.util; + +import com.mojang.datafixers.util.Either; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderOwner; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; + +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Stream; +import org.jetbrains.annotations.Nullable; + +public class FullReferenceHolder implements Holder { + private Set> tags = Set.of(); + @Nullable + private ResourceKey key; + @Nullable + private T value; + + private ResourceKey> owner; + + private FullReferenceHolder( + ResourceKey> owner, + @Nullable ResourceKey resourceKey, + @Nullable T object + ) { + this.owner = owner; + this.key = resourceKey; + this.value = object; + } + + public static FullReferenceHolder create( + ResourceKey> owner, + ResourceKey resourceKey, + @Nullable T object + ) { + return new FullReferenceHolder(owner, resourceKey, object); + } + + public static FullReferenceHolder create( + ResourceKey> owner, + ResourceLocation id, + @Nullable T object + ) { + return new FullReferenceHolder(owner, ResourceKey.create(owner, id), object); + } + + + public ResourceKey key() { + if (this.key == null) { + throw new IllegalStateException("Trying to access unbound value '" + this.value + "' from registry " + this.owner); + } else { + return this.key; + } + } + + @Override + public T value() { + if (this.value == null) { + throw new IllegalStateException("Trying to access unbound value '" + this.key + "' from registry " + this.owner); + } else { + return this.value; + } + } + + @Override + public boolean is(ResourceLocation resourceLocation) { + return this.key().location().equals(resourceLocation); + } + + @Override + public boolean is(ResourceKey resourceKey) { + return this.key() == resourceKey; + } + + @Override + public boolean is(TagKey tagKey) { + return this.tags.contains(tagKey); + } + + @Override + public Stream> tags() { + return this.tags.stream(); + } + + @Override + public boolean is(Predicate> predicate) { + return predicate.test(this.key()); + } + + @Override + public boolean canSerializeIn(HolderOwner holderOwner) { + return true; + } + + @Override + public Either, T> unwrap() { + return Either.left(this.key()); + } + + @Override + public Optional> unwrapKey() { + return Optional.of(this.key()); + } + + @Override + public Kind kind() { + return Holder.Kind.REFERENCE; + } + + @Override + public boolean isBound() { + return this.key != null && this.value != null; + } + + @Override + public String toString() { + return "FullReference{" + this.key + "=" + this.value + "}"; + } +} diff --git a/src/main/java/org/betterx/bclib/util/ItemUtil.java b/src/main/java/org/betterx/bclib/util/ItemUtil.java new file mode 100644 index 00000000..bc7a9496 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/ItemUtil.java @@ -0,0 +1,140 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.BCLib; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import org.jetbrains.annotations.Nullable; + +public class ItemUtil { + @Nullable + public static ItemStack fromStackString(String stackString) { + if (stackString == null || stackString.equals("")) { + return null; + } + try { + String[] parts = stackString.split(":"); + if (parts.length < 2) return null; + if (parts.length == 2) { + ResourceLocation itemId = new ResourceLocation(stackString); + Item item = BuiltInRegistries.ITEM.getOptional(itemId).orElseThrow(() -> { + return new IllegalStateException("Output item " + itemId + " does not exists!"); + }); + return new ItemStack(item); + } + ResourceLocation itemId = new ResourceLocation(parts[0], parts[1]); + Item item = BuiltInRegistries.ITEM.getOptional(itemId) + .orElseThrow(() -> new IllegalStateException("Output item " + itemId + " does not exists!")); + return new ItemStack(item, Integer.valueOf(parts[2])); + } catch (Exception ex) { + BCLib.LOGGER.error("ItemStack deserialization error!", ex); + } + return null; + } + + public static CompoundTag readNBT(JsonObject recipe) { + if (recipe.has("nbt")) { + try { + String nbtData = GsonHelper.getAsString(recipe, "nbt"); + CompoundTag nbt = TagParser.parseTag(nbtData); + return nbt; + } catch (CommandSyntaxException ex) { + BCLib.LOGGER.warning("Error parsing nbt data for output.", ex); + } + } + return null; + } + + public static void writeNBT(JsonObject root, CompoundTag nbt) { + if (nbt != null) { + final String nbtData = nbt.toString(); + root.addProperty("nbt", nbtData); + } + } + + public static Ingredient fromJsonIngredientWithNBT(JsonObject ingredient) { + Ingredient ing = Ingredient.fromJson(ingredient); + CompoundTag nbt = readNBT(ingredient); + if (nbt != null && !ing.isEmpty()) { + ing.getItems()[0].setTag(nbt); + } + return ing; + } + + public static ItemStack fromJsonRecipeWithNBT(JsonObject recipe) { + ItemStack output = ItemUtil.fromJsonRecipe(recipe); + CompoundTag nbt = ItemUtil.readNBT(recipe); + if (output != null && nbt != null) { + output.setTag(nbt); + } + return output; + } + + @Nullable + public static ItemStack fromJsonRecipe(JsonObject recipe) { + try { + if (!recipe.has("item")) { + throw new IllegalStateException("Invalid JsonObject. Entry 'item' does not exists!"); + } + ResourceLocation itemId = new ResourceLocation(GsonHelper.getAsString(recipe, "item")); + Item item = BuiltInRegistries.ITEM.getOptional(itemId).orElseThrow(() -> { + return new IllegalStateException("Output item " + itemId + " does not exists!"); + }); + int count = GsonHelper.getAsInt(recipe, "count", 1); + return new ItemStack(item, count); + } catch (Exception ex) { + BCLib.LOGGER.error("ItemStack deserialization error!", ex); + } + return null; + } + + public static JsonElement toJsonIngredientWithNBT(Ingredient ing) { + JsonElement el = ing.toJson(); + if (el.isJsonObject() && !ing.isEmpty() && ing.getItems()[0].hasTag()) { + JsonObject obj = el.getAsJsonObject(); + writeNBT(obj, ing.getItems()[0].getTag()); + } + return el; + } + + + public static JsonObject toJsonRecipeWithNBT(ItemStack stack) { + return toJsonRecipeWithNBT(stack.getItem(), stack.getCount(), stack.getTag()); + } + + public static JsonObject toJsonRecipeWithNBT(ItemLike item, int count, CompoundTag nbt) { + JsonObject root = toJsonRecipe(item, count); + writeNBT(root, nbt); + return root; + } + + public static JsonObject toJsonRecipe(ItemStack stack) { + return toJsonRecipe(stack.getItem(), stack.getCount()); + } + + public static JsonObject toJsonRecipe(ItemLike item, int count) { + final ResourceLocation id = BuiltInRegistries.ITEM.getKey(item.asItem()); + if (id == null) { + throw new IllegalStateException("Unknown Item " + item); + } + + final JsonObject root = new JsonObject(); + root.addProperty("item", BuiltInRegistries.ITEM.getKey(item.asItem()).toString()); + if (count > 1) { + root.addProperty("count", count); + } + return root; + } +} diff --git a/src/main/java/org/betterx/bclib/util/JsonFactory.java b/src/main/java/org/betterx/bclib/util/JsonFactory.java new file mode 100644 index 00000000..3048cec0 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/JsonFactory.java @@ -0,0 +1,133 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.BCLib; + +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import org.jetbrains.annotations.Nullable; + +public class JsonFactory { + public final static Gson GSON = new GsonBuilder().setPrettyPrinting() + .create(); + + public static JsonObject getJsonObject(InputStream stream) { + try { + Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); + JsonElement json = loadJson(reader); + if (json != null && json.isJsonObject()) { + JsonObject jsonObject = json.getAsJsonObject(); + return jsonObject != null ? jsonObject : new JsonObject(); + } + } catch (Exception ex) { + BCLib.LOGGER.catching(ex); + } + return new JsonObject(); + } + + public static JsonObject getJsonObject(File jsonFile) { + if (jsonFile.exists()) { + JsonElement json = loadJson(jsonFile); + if (json != null && json.isJsonObject()) { + JsonObject jsonObject = json.getAsJsonObject(); + return jsonObject != null ? jsonObject : new JsonObject(); + } + } + return new JsonObject(); + } + + /** + * Loads {@link JsonObject} from resource location using Minecraft resource manager. Can be used to load JSON from resourcepacks and resources. + * + * @param location {@link ResourceLocation} to JSON file + * @return {@link JsonObject} + */ + @Nullable + @Environment(EnvType.CLIENT) + public static JsonObject getJsonObject(ResourceLocation location) { + ResourceManager manager = Minecraft.getInstance() + .getResourceManager(); + JsonObject obj = null; + try { + Resource resource = manager.getResource(location).orElse(null); + if (resource != null) { + InputStream stream = resource.open(); + InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); + obj = JsonFactory.GSON.fromJson(reader, JsonObject.class); + reader.close(); + stream.close(); + } + } catch (IOException ex) { + } + return obj; + } + + public static JsonElement loadJson(File jsonFile) { + if (jsonFile.exists()) { + try (Reader reader = new FileReader(jsonFile, StandardCharsets.UTF_8)) { + return loadJson(reader); + } catch (Exception ex) { + BCLib.LOGGER.catching(ex); + } + } + return null; + } + + public static JsonElement loadJson(Reader reader) { + return GSON.fromJson(reader, JsonElement.class); + } + + public static void storeJson(File jsonFile, JsonElement jsonObject) { + try (FileWriter writer = new FileWriter(jsonFile, StandardCharsets.UTF_8)) { + String json = GSON.toJson(jsonObject); + writer.write(json); + writer.flush(); + } catch (IOException ex) { + BCLib.LOGGER.catching(ex); + } + } + + public static void storeJson(OutputStream outStream, JsonElement jsonObject) { + OutputStreamWriter writer = new OutputStreamWriter(outStream, StandardCharsets.UTF_8); + GSON.toJson(jsonObject, writer); + try { + writer.flush(); + } catch (IOException e) { + BCLib.LOGGER.error(e.getMessage()); + e.printStackTrace(); + } + } + + public static int getInt(JsonObject object, String member, int def) { + JsonElement elem = object.get(member); + return elem == null ? def : elem.getAsInt(); + } + + public static float getFloat(JsonObject object, String member, float def) { + JsonElement elem = object.get(member); + return elem == null ? def : elem.getAsFloat(); + } + + public static boolean getBoolean(JsonObject object, String member, boolean def) { + JsonElement elem = object.get(member); + return elem == null ? def : elem.getAsBoolean(); + } + + public static String getString(JsonObject object, String member, String def) { + JsonElement elem = object.get(member); + return elem == null ? def : elem.getAsString(); + } + +} diff --git a/src/main/java/org/betterx/bclib/util/LootUtil.java b/src/main/java/org/betterx/bclib/util/LootUtil.java new file mode 100644 index 00000000..b41a5f75 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/LootUtil.java @@ -0,0 +1,109 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.interfaces.LootPoolAccessor; +import org.betterx.bclib.interfaces.tools.*; +import org.betterx.bclib.items.tool.BaseShearsItem; +import org.betterx.worlds.together.tag.v3.CommonItemTags; +import org.betterx.worlds.together.tag.v3.ToolTags; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.loot.BuiltInLootTables; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.LootPoolEntryContainer; +import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class LootUtil { + public static Optional> getDrops( + BlockBehaviour block, + BlockState state, + LootParams.Builder builder + ) { + ResourceLocation tableID = block.getLootTable(); + if (tableID == BuiltInLootTables.EMPTY) { + return Optional.empty(); + } + + final LootParams ctx = builder.withParameter(LootContextParams.BLOCK_STATE, state) + .create(LootContextParamSets.BLOCK); + final ServerLevel level = ctx.getLevel(); + final LootTable table = level.getServer().getLootData().getLootTable(tableID); + + if (table == LootTable.EMPTY) return Optional.empty(); + return Optional.of(table.getRandomItems(ctx)); + } + + public static boolean addToPool(LootTable.Builder table, int index, ArrayList newEntries) { + List pools = new ArrayList<>(0); + try { + for (Field f : table.getClass() + .getDeclaredFields()) { + if (List.class.isAssignableFrom(f.getType())) { + f.setAccessible(true); + List list = (List) f.get(table); + if (list != null && list.size() > 0) { + Object first = list.get(0); + if (first != null && LootPool.class.isAssignableFrom(first.getClass())) { + pools = (List) list; + break; + } + } + } + } + + if (pools != null && pools.size() > index) { + LootPool pool = pools.get(index); + LootPoolAccessor acc = (LootPoolAccessor) pool; + pools.set(index, acc.bcl_mergeEntries(newEntries)); + + return true; + } + } catch (Throwable t) { + BCLib.LOGGER.error("ERROR building loot table: " + t.getMessage()); + } + + return false; + } + + public static boolean isCorrectTool(ItemLike block, BlockState state, ItemStack tool) { + if (tool == null) return false; + if (state != null && tool.isCorrectToolForDrops(state)) return true; + + if (block instanceof AddMineableAxe) { + if (tool.is(ItemTags.AXES) || tool.is(ToolTags.FABRIC_AXES)) return true; + } + if (block instanceof AddMineablePickaxe) { + if (tool.is(ItemTags.PICKAXES) || tool.is(ToolTags.FABRIC_PICKAXES)) return true; + } + if (block instanceof AddMineableHoe) { + if (tool.is(ItemTags.HOES) || tool.is(ToolTags.FABRIC_HOES)) return true; + } + if (block instanceof AddMineableShovel) { + if (tool.is(ItemTags.SHOVELS) || tool.is(ToolTags.FABRIC_SHOVELS)) return true; + } + if (block instanceof AddMineableSword) { + if (tool.is(ItemTags.SWORDS) || tool.is(ToolTags.FABRIC_SWORDS)) return true; + } + if (block instanceof AddMineableShears) { + if (BaseShearsItem.isShear(tool)) return true; + } + if (block instanceof AddMineableHammer) { + if (tool.is(CommonItemTags.HAMMERS)) return true; + } + return false; + } +} diff --git a/src/main/java/org/betterx/bclib/util/MHelper.java b/src/main/java/org/betterx/bclib/util/MHelper.java new file mode 100644 index 00000000..2dc41409 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/MHelper.java @@ -0,0 +1,287 @@ +package org.betterx.bclib.util; + +import de.ambertation.wunderlib.math.Float3; + +import net.minecraft.core.Vec3i; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.PositionalRandomFactory; + +import org.joml.Vector3f; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class MHelper { + public static final Vector3f YP = Float3.Y_AXIS.toVector3(); + + static class ThreadLocalRandomSource implements RandomSource { + ThreadLocalRandomSource(long seed) { + + } + + @Override + public RandomSource fork() { + return this; + } + + @Override + public PositionalRandomFactory forkPositional() { + return null; + } + + @Override + public void setSeed(long l) { + ThreadLocalRandom.current().setSeed(l); + } + + @Override + public int nextInt() { + return ThreadLocalRandom.current().nextInt(); + } + + @Override + public int nextInt(int i) { + return ThreadLocalRandom.current().nextInt(i); + } + + @Override + public long nextLong() { + return ThreadLocalRandom.current().nextLong(); + } + + @Override + public boolean nextBoolean() { + return ThreadLocalRandom.current().nextBoolean(); + } + + @Override + public float nextFloat() { + return ThreadLocalRandom.current().nextFloat(); + } + + @Override + public double nextDouble() { + return ThreadLocalRandom.current().nextDouble(); + } + + @Override + public double nextGaussian() { + return ThreadLocalRandom.current().nextGaussian(); + } + } + + private static final Vec3i[] RANDOM_OFFSETS = new Vec3i[3 * 3 * 3 - 1]; + private static final float RAD_TO_DEG = 57.295779513082320876798154814105F; + public static final float PHI = (float) (Math.PI * (3 - Math.sqrt(5))); + public static final float PI2 = (float) (Math.PI * 2); + public static final Random RANDOM = new Random(); + public static final RandomSource RANDOM_SOURCE = new ThreadLocalRandomSource(RANDOM.nextLong()); + + public static int randRange(int min, int max, RandomSource random) { + return min + random.nextInt(max - min + 1); + } + + public static double randRange(double min, double max, RandomSource random) { + return min + random.nextDouble() * (max - min); + } + + public static float randRange(float min, float max, RandomSource random) { + return min + random.nextFloat() * (max - min); + } + + public static byte setBit(byte source, int pos, boolean value) { + return value ? setBitTrue(source, pos) : setBitFalse(source, pos); + } + + public static byte setBitTrue(byte source, int pos) { + source |= 1 << pos; + return source; + } + + public static byte setBitFalse(byte source, int pos) { + source &= ~(1 << pos); + return source; + } + + public static boolean getBit(byte source, int pos) { + return ((source >> pos) & 1) == 1; + } + + public static float wrap(float x, float side) { + return x - floor(x / side) * side; + } + + public static int floor(double x) { + return x < 0 ? (int) (x - 1) : (int) x; + } + + public static int min(int a, int b) { + return a < b ? a : b; + } + + public static int min(int a, int b, int c) { + return min(a, min(b, c)); + } + + public static int max(int a, int b) { + return a > b ? a : b; + } + + public static float min(float a, float b) { + return a < b ? a : b; + } + + public static float max(float a, float b) { + return a > b ? a : b; + } + + public static float max(float a, float b, float c) { + return max(a, max(b, c)); + } + + public static int max(int a, int b, int c) { + return max(a, max(b, c)); + } + + public static boolean isEven(int num) { + return (num & 1) == 0; + } + + public static float lengthSqr(float x, float y, float z) { + return x * x + y * y + z * z; + } + + public static double lengthSqr(double x, double y, double z) { + return x * x + y * y + z * z; + } + + public static float length(float x, float y, float z) { + return (float) Math.sqrt(lengthSqr(x, y, z)); + } + + public static double length(double x, double y, double z) { + return Math.sqrt(lengthSqr(x, y, z)); + } + + public static float lengthSqr(float x, float y) { + return x * x + y * y; + } + + public static double lengthSqr(double x, double y) { + return x * x + y * y; + } + + public static float length(float x, float y) { + return (float) Math.sqrt(lengthSqr(x, y)); + } + + public static double length(double x, double y) { + return Math.sqrt(lengthSqr(x, y)); + } + + public static float dot(float x1, float y1, float z1, float x2, float y2, float z2) { + return x1 * x2 + y1 * y2 + z1 * z2; + } + + public static float dot(float x1, float y1, float x2, float y2) { + return x1 * x2 + y1 * y2; + } + + public static int getRandom(int x, int z) { + int h = x * 374761393 + z * 668265263; + h = (h ^ (h >> 13)) * 1274126177; + return h ^ (h >> 16); + } + + public static int getSeed(int seed, int x, int y) { + int h = seed + x * 374761393 + y * 668265263; + h = (h ^ (h >> 13)) * 1274126177; + return h ^ (h >> 16); + } + + public static int getSeed(int seed, int x, int y, int z) { + int h = seed + x * 374761393 + y * 668265263 + z; + h = (h ^ (h >> 13)) * 1274126177; + return h ^ (h >> 16); + } + + public static void shuffle(T[] array, RandomSource random) { + for (int i = 0; i < array.length; i++) { + int i2 = random.nextInt(array.length); + T element = array[i]; + array[i] = array[i2]; + array[i2] = element; + } + } + + public static int sqr(int i) { + return i * i; + } + + public static float sqr(float f) { + return f * f; + } + + public static double sqr(double d) { + return d * d; + } + + public static final float radiansToDegrees(float value) { + return value * RAD_TO_DEG; + } + + public static final float degreesToRadians(float value) { + return value / RAD_TO_DEG; + } + + public static Vector3f cross(Vector3f vec1, Vector3f vec2) { + float cx = vec1.y * vec2.z - vec1.z * vec2.y; + float cy = vec1.z * vec2.x - vec1.x * vec2.z; + float cz = vec1.x * vec2.y - vec1.y * vec2.x; + return new Vector3f(cx, cy, cz); + } + + public static Vector3f normalize(Vector3f vec) { + float length = lengthSqr(vec.x, vec.y, vec.z); + if (length > 0) { + length = (float) Math.sqrt(length); + float x = vec.x / length; + float y = vec.y / length; + float z = vec.z / length; + vec.set(x, y, z); + } + return vec; + } + + public static float angle(Vector3f vec1, Vector3f vec2) { + float dot = vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z; + float length1 = lengthSqr(vec1.x, vec1.y, vec1.z); + float length2 = lengthSqr(vec2.x, vec2.y, vec2.z); + return (float) Math.acos(dot / Math.sqrt(length1 * length2)); + } + + public static Vector3f randomHorizontal(RandomSource random) { + float angleY = randRange(0, PI2, random); + float vx = (float) Math.sin(angleY); + float vz = (float) Math.cos(angleY); + return new Vector3f(vx, 0, vz); + } + + public static Vec3i[] getOffsets(RandomSource random) { + shuffle(RANDOM_OFFSETS, random); + return RANDOM_OFFSETS; + } + + static { + int index = 0; + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + if (x != 0 || y != 0 || z != 0) { + RANDOM_OFFSETS[index++] = new Vec3i(x, y, z); + } + } + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/util/MethodReplace.java b/src/main/java/org/betterx/bclib/util/MethodReplace.java new file mode 100644 index 00000000..05c933ec --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/MethodReplace.java @@ -0,0 +1,31 @@ +package org.betterx.bclib.util; + +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase; + +import java.util.function.Function; +import org.jetbrains.annotations.Nullable; + +public class MethodReplace { + private static Function itemReplace; + private static Function blockReplace; + private static Block block; + private static Item item; + + public static void addItemReplace(Item item, Function itemReplace) { + MethodReplace.itemReplace = itemReplace; + MethodReplace.item = item; + } + + @Nullable + public static Function getItemReplace(Item item) { + if (MethodReplace.item != item) { + return null; + } + Function replace = itemReplace; + itemReplace = null; + return replace; + } +} diff --git a/src/main/java/org/betterx/bclib/util/Pair.java b/src/main/java/org/betterx/bclib/util/Pair.java new file mode 100644 index 00000000..5495c16b --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/Pair.java @@ -0,0 +1,43 @@ +package org.betterx.bclib.util; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import java.util.Objects; + +public class Pair { + public static Codec> pairCodec(Codec a, Codec b, String first, String second) { + return RecordCodecBuilder.create(instance -> instance + .group( + a.fieldOf(first).forGetter(o -> o.first), + b.fieldOf(second).forGetter(o -> o.second) + ) + .apply(instance, Pair::new)); + } + + public final A first; + public final B second; + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } + + @Override + public String toString() { + return "Pair{" + "first=" + first + ", second=" + second + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Pair)) return false; + Pair pair = (Pair) o; + return Objects.equals(first, pair.first) && Objects.equals(second, pair.second); + } + + @Override + public int hashCode() { + return Objects.hash(first, second); + } +} diff --git a/src/main/java/org/betterx/bclib/util/RecipeHelper.java b/src/main/java/org/betterx/bclib/util/RecipeHelper.java new file mode 100644 index 00000000..9530195f --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/RecipeHelper.java @@ -0,0 +1,24 @@ +package org.betterx.bclib.util; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; + +public class RecipeHelper { + public static boolean exists(ItemLike item) { + if (item instanceof Block) { + return BuiltInRegistries.BLOCK.getKey((Block) item) != BuiltInRegistries.BLOCK.getDefaultKey(); + } else { + return BuiltInRegistries.ITEM.getKey(item.asItem()) != BuiltInRegistries.ITEM.getDefaultKey(); + } + } + + public static boolean exists(ItemLike... items) { + for (ItemLike item : items) { + if (!exists(item)) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/org/betterx/bclib/util/RomanNumeral.java b/src/main/java/org/betterx/bclib/util/RomanNumeral.java new file mode 100644 index 00000000..819ffbd9 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/RomanNumeral.java @@ -0,0 +1,45 @@ +package org.betterx.bclib.util; + +import java.util.TreeMap; + +/** + * base on ... + */ + +public class RomanNumeral { + private final static TreeMap LITERALS = new TreeMap<>(); + + private static void lazyInit() { + if (LITERALS.isEmpty()) { + LITERALS.put(1000, "M"); + LITERALS.put(900, "CM"); + LITERALS.put(500, "D"); + LITERALS.put(400, "CD"); + LITERALS.put(100, "C"); + LITERALS.put(90, "XC"); + LITERALS.put(50, "L"); + LITERALS.put(40, "XL"); + LITERALS.put(10, "X"); + LITERALS.put(9, "IX"); + LITERALS.put(5, "V"); + LITERALS.put(4, "IV"); + LITERALS.put(1, "I"); + } + } + + public static String toRoman(int number) { + lazyInit(); + return _toRoman(number); + } + + private static String _toRoman(int number) { + //there is no 0 in roman, but we need it anyway ;) + if (number == 0) return "0"; + + int l = LITERALS.floorKey(number); + if (number == l) { + return LITERALS.get(number); + } + return LITERALS.get(l) + _toRoman(number - l); + } +} diff --git a/src/main/java/org/betterx/bclib/util/SplineHelper.java b/src/main/java/org/betterx/bclib/util/SplineHelper.java new file mode 100644 index 00000000..b4158b78 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/SplineHelper.java @@ -0,0 +1,387 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.sdf.SDF; +import org.betterx.bclib.sdf.operator.SDFUnion; +import org.betterx.bclib.sdf.primitive.SDFLine; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.state.BlockState; + +import com.google.common.collect.Lists; +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class SplineHelper { + public static List makeSpline(float x1, float y1, float z1, float x2, float y2, float z2, int points) { + List spline = Lists.newArrayList(); + spline.add(new Vector3f(x1, y1, z1)); + int count = points - 1; + for (int i = 1; i < count; i++) { + float delta = (float) i / (float) count; + float x = Mth.lerp(delta, x1, x2); + float y = Mth.lerp(delta, y1, y2); + float z = Mth.lerp(delta, z1, z2); + spline.add(new Vector3f(x, y, z)); + } + spline.add(new Vector3f(x2, y2, z2)); + return spline; + } + + public static List smoothSpline(List spline, int segmentPoints) { + List result = Lists.newArrayList(); + Vector3f start = spline.get(0); + for (int i = 1; i < spline.size(); i++) { + Vector3f end = spline.get(i); + for (int j = 0; j < segmentPoints; j++) { + float delta = (float) j / segmentPoints; + delta = 0.5F - 0.5F * Mth.cos(delta * 3.14159F); + result.add(lerp(start, end, delta)); + } + start = end; + } + result.add(start); + return result; + } + + private static Vector3f lerp(Vector3f start, Vector3f end, float delta) { + float x = Mth.lerp(delta, start.x(), end.x()); + float y = Mth.lerp(delta, start.y(), end.y()); + float z = Mth.lerp(delta, start.z(), end.z()); + return new Vector3f(x, y, z); + } + + public static void offsetParts(List spline, RandomSource random, float dx, float dy, float dz) { + int count = spline.size(); + for (int i = 1; i < count; i++) { + Vector3f pos = spline.get(i); + float x = pos.x() + (float) random.nextGaussian() * dx; + float y = pos.y() + (float) random.nextGaussian() * dy; + float z = pos.z() + (float) random.nextGaussian() * dz; + pos.set(x, y, z); + } + } + + public static void powerOffset(List spline, float distance, float power) { + int count = spline.size(); + float max = count + 1; + for (int i = 1; i < count; i++) { + Vector3f pos = spline.get(i); + float x = (float) i / max; + float y = pos.y() + (float) Math.pow(x, power) * distance; + pos.set(pos.x(), y, pos.z()); + } + } + + public static SDF buildSDF( + List spline, + float radius1, + float radius2, + Function placerFunction + ) { + int count = spline.size(); + float max = count - 2; + SDF result = null; + Vector3f start = spline.get(0); + for (int i = 1; i < count; i++) { + Vector3f pos = spline.get(i); + float delta = (float) (i - 1) / max; + SDF line = new SDFLine().setRadius(Mth.lerp(delta, radius1, radius2)) + .setStart(start.x(), start.y(), start.z()) + .setEnd(pos.x(), pos.y(), pos.z()) + .setBlock(placerFunction); + result = result == null ? line : new SDFUnion().setSourceA(result).setSourceB(line); + start = pos; + } + return result; + } + + public static SDF buildSDF( + List spline, + Function radiusFunction, + Function placerFunction + ) { + int count = spline.size(); + float max = count - 2; + SDF result = null; + Vector3f start = spline.get(0); + for (int i = 1; i < count; i++) { + Vector3f pos = spline.get(i); + float delta = (float) (i - 1) / max; + SDF line = new SDFLine().setRadius(radiusFunction.apply(delta)) + .setStart(start.x(), start.y(), start.z()) + .setEnd(pos.x(), pos.y(), pos.z()) + .setBlock(placerFunction); + result = result == null ? line : new SDFUnion().setSourceA(result).setSourceB(line); + start = pos; + } + return result; + } + + public static boolean fillSpline( + List spline, + WorldGenLevel world, + BlockState state, + BlockPos pos, + Function replace + ) { + Vector3f startPos = spline.get(0); + for (int i = 1; i < spline.size(); i++) { + Vector3f endPos = spline.get(i); + if (!(fillLine(startPos, endPos, world, state, pos, replace))) { + return false; + } + startPos = endPos; + } + + return true; + } + + public static void fillSplineForce( + List spline, + WorldGenLevel world, + BlockState state, + BlockPos pos, + Function replace + ) { + Vector3f startPos = spline.get(0); + for (int i = 1; i < spline.size(); i++) { + Vector3f endPos = spline.get(i); + fillLineForce(startPos, endPos, world, state, pos, replace); + startPos = endPos; + } + } + + public static boolean fillLine( + Vector3f start, + Vector3f end, + WorldGenLevel world, + BlockState state, + BlockPos pos, + Function replace + ) { + float dx = end.x() - start.x(); + float dy = end.y() - start.y(); + float dz = end.z() - start.z(); + float max = MHelper.max(Math.abs(dx), Math.abs(dy), Math.abs(dz)); + int count = MHelper.floor(max + 1); + dx /= max; + dy /= max; + dz /= max; + float x = start.x(); + float y = start.y(); + float z = start.z(); + boolean down = Math.abs(dy) > 0.2; + + BlockState bState; + MutableBlockPos bPos = new MutableBlockPos(); + for (int i = 0; i < count; i++) { + bPos.set(x + pos.getX(), y + pos.getY(), z + pos.getZ()); + bState = world.getBlockState(bPos); + if (bState.equals(state) || replace.apply(bState)) { + BlocksHelper.setWithoutUpdate(world, bPos, state); + bPos.setY(bPos.getY() - 1); + bState = world.getBlockState(bPos); + if (down && bState.equals(state) || replace.apply(bState)) { + BlocksHelper.setWithoutUpdate(world, bPos, state); + } + } else { + return false; + } + x += dx; + y += dy; + z += dz; + } + bPos.set(end.x() + pos.getX(), end.y() + pos.getY(), end.z() + pos.getZ()); + bState = world.getBlockState(bPos); + if (bState.equals(state) || replace.apply(bState)) { + BlocksHelper.setWithoutUpdate(world, bPos, state); + bPos.setY(bPos.getY() - 1); + bState = world.getBlockState(bPos); + if (down && bState.equals(state) || replace.apply(bState)) { + BlocksHelper.setWithoutUpdate(world, bPos, state); + } + return true; + } else { + return false; + } + } + + public static void fillLineForce( + Vector3f start, + Vector3f end, + WorldGenLevel world, + BlockState state, + BlockPos pos, + Function replace + ) { + float dx = end.x() - start.x(); + float dy = end.y() - start.y(); + float dz = end.z() - start.z(); + float max = MHelper.max(Math.abs(dx), Math.abs(dy), Math.abs(dz)); + int count = MHelper.floor(max + 1); + dx /= max; + dy /= max; + dz /= max; + float x = start.x(); + float y = start.y(); + float z = start.z(); + boolean down = Math.abs(dy) > 0.2; + + BlockState bState; + MutableBlockPos bPos = new MutableBlockPos(); + for (int i = 0; i < count; i++) { + bPos.set(x + pos.getX(), y + pos.getY(), z + pos.getZ()); + bState = world.getBlockState(bPos); + if (replace.apply(bState)) { + BlocksHelper.setWithoutUpdate(world, bPos, state); + bPos.setY(bPos.getY() - 1); + bState = world.getBlockState(bPos); + if (down && replace.apply(bState)) { + BlocksHelper.setWithoutUpdate(world, bPos, state); + } + } + x += dx; + y += dy; + z += dz; + } + bPos.set(end.x() + pos.getX(), end.y() + pos.getY(), end.z() + pos.getZ()); + bState = world.getBlockState(bPos); + if (replace.apply(bState)) { + BlocksHelper.setWithoutUpdate(world, bPos, state); + bPos.setY(bPos.getY() - 1); + bState = world.getBlockState(bPos); + if (down && replace.apply(bState)) { + BlocksHelper.setWithoutUpdate(world, bPos, state); + } + } + } + + public static boolean canGenerate( + List spline, + float scale, + BlockPos start, + WorldGenLevel world, + Function canReplace + ) { + int count = spline.size(); + Vector3f vec = spline.get(0); + MutableBlockPos mut = new MutableBlockPos(); + float x1 = start.getX() + vec.x() * scale; + float y1 = start.getY() + vec.y() * scale; + float z1 = start.getZ() + vec.z() * scale; + for (int i = 1; i < count; i++) { + vec = spline.get(i); + float x2 = start.getX() + vec.x() * scale; + float y2 = start.getY() + vec.y() * scale; + float z2 = start.getZ() + vec.z() * scale; + + for (float py = y1; py < y2; py += 3) { + if (py - start.getY() < 10) continue; + float lerp = (py - y1) / (y2 - y1); + float x = Mth.lerp(lerp, x1, x2); + float z = Mth.lerp(lerp, z1, z2); + mut.set(x, py, z); + if (!canReplace.apply(world.getBlockState(mut))) { + return false; + } + } + + x1 = x2; + y1 = y2; + z1 = z2; + } + return true; + } + + public static boolean canGenerate( + List spline, + BlockPos start, + WorldGenLevel world, + Function canReplace + ) { + int count = spline.size(); + Vector3f vec = spline.get(0); + MutableBlockPos mut = new MutableBlockPos(); + float x1 = start.getX() + vec.x(); + float y1 = start.getY() + vec.y(); + float z1 = start.getZ() + vec.z(); + for (int i = 1; i < count; i++) { + vec = spline.get(i); + float x2 = start.getX() + vec.x(); + float y2 = start.getY() + vec.y(); + float z2 = start.getZ() + vec.z(); + + for (float py = y1; py < y2; py += 3) { + if (py - start.getY() < 10) continue; + float lerp = (py - y1) / (y2 - y1); + float x = Mth.lerp(lerp, x1, x2); + float z = Mth.lerp(lerp, z1, z2); + mut.set(x, py, z); + if (!canReplace.apply(world.getBlockState(mut))) { + return false; + } + } + + x1 = x2; + y1 = y2; + z1 = z2; + } + return true; + } + + public static Vector3f getPos(List spline, float index) { + int i = (int) index; + int last = spline.size() - 1; + if (i >= last) { + return spline.get(last); + } + float delta = index - i; + Vector3f p1 = spline.get(i); + Vector3f p2 = spline.get(i + 1); + float x = Mth.lerp(delta, p1.x(), p2.x()); + float y = Mth.lerp(delta, p1.y(), p2.y()); + float z = Mth.lerp(delta, p1.z(), p2.z()); + return new Vector3f(x, y, z); + } + + public static void rotateSpline(List spline, float angle) { + for (Vector3f v : spline) { + float sin = (float) Math.sin(angle); + float cos = (float) Math.cos(angle); + float x = v.x() * cos + v.z() * sin; + float z = v.x() * sin + v.z() * cos; + v.set(x, v.y(), z); + } + } + + public static List copySpline(List spline) { + List result = new ArrayList(spline.size()); + for (Vector3f v : spline) { + result.add(new Vector3f(v.x(), v.y(), v.z())); + } + return result; + } + + public static void scale(List spline, float scale) { + scale(spline, scale, scale, scale); + } + + public static void scale(List spline, float x, float y, float z) { + for (Vector3f v : spline) { + v.set(v.x() * x, v.y() * y, v.z() * z); + } + } + + public static void offset(List spline, Vector3f offset) { + for (Vector3f v : spline) { + v.set(offset.x() + v.x(), offset.y() + v.y(), offset.z() + v.z()); + } + } +} diff --git a/src/main/java/org/betterx/bclib/util/StructureErode.java b/src/main/java/org/betterx/bclib/util/StructureErode.java new file mode 100644 index 00000000..1345ebf2 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/StructureErode.java @@ -0,0 +1,279 @@ +package org.betterx.bclib.util; + +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; +import org.betterx.worlds.together.tag.v3.CommonBlockTags; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.core.Direction; +import net.minecraft.tags.BlockTags; +import net.minecraft.util.RandomSource; +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.block.state.properties.NoteBlockInstrument; +import net.minecraft.world.level.levelgen.structure.BoundingBox; + +import com.google.common.collect.Sets; + +import java.util.Set; + +public class StructureErode { + private static final Direction[] DIR = BlocksHelper.makeHorizontal(); + + public static void erode(WorldGenLevel world, BoundingBox bounds, int iterations, RandomSource random) { + MutableBlockPos mut = new MutableBlockPos(); + boolean canDestruct = true; + for (int i = 0; i < iterations; i++) { + for (int x = bounds.minX(); x <= bounds.maxX(); x++) { + mut.setX(x); + for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) { + mut.setZ(z); + for (int y = bounds.maxY(); y >= bounds.minY(); y--) { + mut.setY(y); + BlockState state = world.getBlockState(mut); + boolean ignore = ignore(state, world, mut); + if (canDestruct && BlocksHelper.isInvulnerable( + state, + world, + mut + ) && random.nextInt(8) == 0 && world.isEmptyBlock( + mut.below(2))) { + int r = MHelper.randRange(1, 4, random); + int cx = mut.getX(); + int cy = mut.getY(); + int cz = mut.getZ(); + int x1 = cx - r; + int y1 = cy - r; + int z1 = cz - r; + int x2 = cx + r; + int y2 = cy + r; + int z2 = cz + r; + for (int px = x1; px <= x2; px++) { + int dx = px - cx; + dx *= dx; + mut.setX(px); + for (int py = y1; py <= y2; py++) { + int dy = py - cy; + dy *= dy; + mut.setY(py); + for (int pz = z1; pz <= z2; pz++) { + int dz = pz - cz; + dz *= dz; + mut.setZ(pz); + if (dx + dy + dz <= r && BlocksHelper.isInvulnerable( + world.getBlockState(mut), + world, + mut + )) { + BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); + } + } + } + } + mut.setX(cx); + mut.setY(cy); + mut.setZ(cz); + canDestruct = false; + continue; + } else if (ignore) { + continue; + } + if (!state.isAir() && random.nextBoolean()) { + MHelper.shuffle(DIR, random); + for (Direction dir : DIR) { + if (world.isEmptyBlock(mut.relative(dir)) && world.isEmptyBlock(mut.below() + .relative(dir))) { + BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); + mut.move(dir).move(Direction.DOWN); + for (int py = mut.getY(); y >= bounds.minY() - 10; y--) { + mut.setY(py - 1); + if (!world.isEmptyBlock(mut)) { + mut.setY(py); + BlocksHelper.setWithoutUpdate(world, mut, state); + break; + } + } + } + } + break; + } else if (random.nextInt(8) == 0 && !BlocksHelper.isInvulnerable( + world.getBlockState(mut.above()), + world, + mut + )) { + BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); + } + } + } + } + } + for (int x = bounds.minX(); x <= bounds.maxX(); x++) { + mut.setX(x); + for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) { + mut.setZ(z); + for (int y = bounds.maxY(); y >= bounds.minY(); y--) { + mut.setY(y); + BlockState state = world.getBlockState(mut); + if (!ignore(state, world, mut) && world.isEmptyBlock(mut.below())) { + BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); + for (int py = mut.getY(); py >= bounds.minY() - 10; py--) { + mut.setY(py - 1); + if (!world.isEmptyBlock(mut)) { + mut.setY(py); + BlocksHelper.setWithoutUpdate(world, mut, state); + break; + } + } + } + } + } + } + } + + public static void erodeIntense(WorldGenLevel world, BoundingBox bounds, RandomSource random) { + MutableBlockPos mut = new MutableBlockPos(); + MutableBlockPos mut2 = new MutableBlockPos(); + int minY = bounds.minY() - 10; + for (int x = bounds.minX(); x <= bounds.maxX(); x++) { + mut.setX(x); + for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) { + mut.setZ(z); + for (int y = bounds.maxY(); y >= bounds.minY(); y--) { + mut.setY(y); + BlockState state = world.getBlockState(mut); + if (!ignore(state, world, mut)) { + if (random.nextInt(6) == 0) { + BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); + if (random.nextBoolean()) { + int px = MHelper.floor(random.nextGaussian() * 2 + x + 0.5); + int pz = MHelper.floor(random.nextGaussian() * 2 + z + 0.5); + mut2.set(px, y, pz); + while (world.getBlockState(mut2).canBeReplaced() && mut2.getY() > minY) { + mut2.setY(mut2.getY() - 1); + } + if (!world.getBlockState(mut2).isAir() && state.canSurvive(world, mut2)) { + mut2.setY(mut2.getY() + 1); + BlocksHelper.setWithoutUpdate(world, mut2, state); + } + } + } else if (random.nextInt(8) == 0) { + BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); + } + } + } + } + } + + drop(world, bounds); + } + + private static void drop(WorldGenLevel world, BoundingBox bounds) { + MutableBlockPos mut = new MutableBlockPos(); + + Set blocks = Sets.newHashSet(); + Set edge = Sets.newHashSet(); + Set add = Sets.newHashSet(); + + for (int x = bounds.minX(); x <= bounds.maxX(); x++) { + mut.setX(x); + for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) { + mut.setZ(z); + for (int y = bounds.minY(); y <= bounds.maxY(); y++) { + mut.setY(y); + BlockState state = world.getBlockState(mut); + if (!ignore(state, world, mut) && isTerrainNear(world, mut)) { + edge.add(mut.immutable()); + } + } + } + } + + if (edge.isEmpty()) { + return; + } + + while (!edge.isEmpty()) { + for (BlockPos center : edge) { + for (Direction dir : BlocksHelper.DIRECTIONS) { + BlockState state = world.getBlockState(center); + if (state.isCollisionShapeFullBlock(world, center)) { + mut.set(center).move(dir); + if (bounds.isInside(mut)) { + state = world.getBlockState(mut); + if (!ignore(state, world, mut) && !blocks.contains(mut)) { + add.add(mut.immutable()); + } + } + } + } + } + + blocks.addAll(edge); + edge.clear(); + edge.addAll(add); + add.clear(); + } + + int minY = bounds.minY() - 10; + for (int x = bounds.minX(); x <= bounds.maxX(); x++) { + mut.setX(x); + for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) { + mut.setZ(z); + for (int y = bounds.minY(); y <= bounds.maxY(); y++) { + mut.setY(y); + BlockState state = world.getBlockState(mut); + if (!ignore(state, world, mut) && !blocks.contains(mut)) { + BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); + while (world.getBlockState(mut).canBeReplaced() && mut.getY() > minY) { + mut.setY(mut.getY() - 1); + } + if (mut.getY() > minY) { + mut.setY(mut.getY() + 1); + BlocksHelper.setWithoutUpdate(world, mut, state); + } + } + } + } + } + } + + private static boolean ignore(BlockState state, WorldGenLevel world, BlockPos pos) { + if (state.is(CommonBlockTags.GEN_END_STONES) || state.is(BlockTags.NYLIUM)) { + return true; + } + + //NoteBlockInstrument.BASEDRUM this is basically what Material.STONE was previously + return !state.instrument().equals(NoteBlockInstrument.BASEDRUM) + || BlocksHelper.isInvulnerable(state, world, pos); + } + + private static boolean isTerrainNear(WorldGenLevel world, BlockPos pos) { + for (Direction dir : BlocksHelper.DIRECTIONS) { + if (world.getBlockState(pos.relative(dir)).is(CommonBlockTags.GEN_END_STONES)) { + return true; + } + } + return false; + } + + public static void cover(WorldGenLevel world, BoundingBox bounds, RandomSource random, BlockState defaultBlock) { + MutableBlockPos mut = new MutableBlockPos(); + for (int x = bounds.minX(); x <= bounds.maxX(); x++) { + mut.setX(x); + for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) { + mut.setZ(z); + BlockState top = BiomeAPI.findTopMaterial(world.getBiome(mut)).orElse(defaultBlock); + if (top == null) continue; + for (int y = bounds.maxY(); y >= bounds.minY(); y--) { + mut.setY(y); + BlockState state = world.getBlockState(mut); + if (state.is(CommonBlockTags.TERRAIN) && !world.getBlockState(mut.above()) + .isSolid()) { + BlocksHelper.setWithoutUpdate(world, mut, top); + } + } + } + } + } +} diff --git a/src/main/java/org/betterx/bclib/util/StructureHelper.java b/src/main/java/org/betterx/bclib/util/StructureHelper.java new file mode 100644 index 00000000..6bb001ad --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/StructureHelper.java @@ -0,0 +1,144 @@ +package org.betterx.bclib.util; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; +import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; +import net.minecraft.world.phys.Vec3; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class StructureHelper { + public static StructureTemplate readStructure(ResourceLocation resource) { + String ns = resource.getNamespace(); + String nm = resource.getPath(); + return readStructure("/data/" + ns + "/structures/" + nm + ".nbt"); + } + + public static StructureTemplate readStructure(File datapack, String path) { + if (datapack.isDirectory()) { + return readStructure(datapack + "/" + path); + } else if (datapack.isFile() && datapack.getName().endsWith(".zip")) { + try { + ZipFile zipFile = new ZipFile(datapack); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + long compressedSize = entry.getCompressedSize(); + long normalSize = entry.getSize(); + String type = entry.isDirectory() ? "DIR" : "FILE"; + + System.out.println(name); + System.out.format("\t %s - %d - %d\n", type, compressedSize, normalSize); + } + zipFile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + public static StructureTemplate readStructure(String path) { + try { + InputStream inputstream = StructureHelper.class.getResourceAsStream(path); + return readStructureFromStream(inputstream); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + private static StructureTemplate readStructureFromStream(InputStream stream) throws IOException { + CompoundTag nbttagcompound = NbtIo.readCompressed(stream); + + StructureTemplate template = new StructureTemplate(); + template.load(BuiltInRegistries.BLOCK.asLookup(), nbttagcompound); + + return template; + } + + public static BlockPos offsetPos(BlockPos pos, StructureTemplate structure, Rotation rotation, Mirror mirror) { + Vec3 offset = StructureTemplate.transform( + Vec3.atCenterOf(structure.getSize()), + mirror, + rotation, + BlockPos.ZERO + ); + return pos.offset((int) (-offset.x * 0.5), 0, (int) (-offset.z * 0.5)); + } + + public static void placeCenteredBottom( + WorldGenLevel world, + BlockPos pos, + StructureTemplate structure, + Rotation rotation, + Mirror mirror, + RandomSource random + ) { + placeCenteredBottom(world, pos, structure, rotation, mirror, makeBox(pos), random); + } + + public static void placeCenteredBottom( + WorldGenLevel world, + BlockPos pos, + StructureTemplate structure, + Rotation rotation, + Mirror mirror, + BoundingBox bounds, + RandomSource random + ) { + BlockPos offset = offsetPos(pos, structure, rotation, mirror); + StructurePlaceSettings placementData = new StructurePlaceSettings().setRotation(rotation) + .setMirror(mirror) + .setBoundingBox(bounds); + structure.placeInWorld(world, offset, offset, placementData, random, 4); + } + + private static BoundingBox makeBox(BlockPos pos) { + int sx = ((pos.getX() >> 4) << 4) - 16; + int sz = ((pos.getZ() >> 4) << 4) - 16; + int ex = sx + 47; + int ez = sz + 47; + return BoundingBox.fromCorners(new Vec3i(sx, 0, sz), new Vec3i(ex, 255, ez)); + } + + public static BoundingBox getStructureBounds( + BlockPos pos, + StructureTemplate structure, + Rotation rotation, + Mirror mirror + ) { + Vec3i max = structure.getSize(); + Vec3 min = StructureTemplate.transform(Vec3.atCenterOf(structure.getSize()), mirror, rotation, BlockPos.ZERO); + max = max.offset((int) -min.x, (int) -min.y, (int) -min.z); + return BoundingBox.fromCorners(pos.offset((int) min.x, (int) min.y, (int) min.z), max.offset(pos)); + } + + public static BoundingBox intersectBoxes(BoundingBox box1, BoundingBox box2) { + int x1 = MHelper.max(box1.minX(), box2.minX()); + int y1 = MHelper.max(box1.minY(), box2.minY()); + int z1 = MHelper.max(box1.minZ(), box2.minZ()); + + int x2 = MHelper.min(box1.maxX(), box2.maxX()); + int y2 = MHelper.min(box1.maxY(), box2.maxY()); + int z2 = MHelper.min(box1.maxZ(), box2.maxZ()); + + return BoundingBox.fromCorners(new Vec3i(x1, y1, z1), new Vec3i(x2, y2, z2)); + } +} diff --git a/src/main/java/org/betterx/bclib/util/TriFunction.java b/src/main/java/org/betterx/bclib/util/TriFunction.java new file mode 100644 index 00000000..6e568e13 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/TriFunction.java @@ -0,0 +1,6 @@ +package org.betterx.bclib.util; + +@FunctionalInterface +public interface TriFunction { + R apply(A a, B b, C c); +} diff --git a/src/main/java/org/betterx/bclib/util/Triple.java b/src/main/java/org/betterx/bclib/util/Triple.java new file mode 100644 index 00000000..b47d5a81 --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/Triple.java @@ -0,0 +1,31 @@ +package org.betterx.bclib.util; + +import java.util.Objects; + +public class Triple extends Pair { + public final C third; + + public Triple(A first, B second, C third) { + super(first, second); + this.third = third; + } + + @Override + public String toString() { + return "Triple{" + "first=" + first + ", second=" + second + ", third=" + third + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Triple)) return false; + if (!super.equals(o)) return false; + Triple triple = (Triple) o; + return Objects.equals(third, triple.third); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), third); + } +} diff --git a/src/main/java/org/betterx/bclib/util/WeighTree.java b/src/main/java/org/betterx/bclib/util/WeighTree.java new file mode 100644 index 00000000..f02655ff --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/WeighTree.java @@ -0,0 +1,91 @@ +package org.betterx.bclib.util; + +import net.minecraft.world.level.levelgen.WorldgenRandom; + +import java.util.Locale; +import java.util.Random; + +public class WeighTree { + private final float maxWeight; + private final Node root; + + public WeighTree(WeightedList list) { + maxWeight = list.getMaxWeight(); + root = getNode(list); + } + + /** + * Get eandom value from tree. + * + * @param random - {@link Random}. + * @return {@link T} value. + */ + public T get(WorldgenRandom random) { + return root.get(random.nextFloat() * maxWeight); + } + + private Node getNode(WeightedList list) { + int size = list.size(); + if (size == 1) { + return new Leaf(list.get(0)); + } else if (size == 2) { + T first = list.get(0); + return new Branch(list.getWeight(0), new Leaf(first), new Leaf(list.get(1))); + } else { + int index = size >> 1; + float separator = list.getWeight(index); + Node a = getNode(list.subList(0, index + 1)); + Node b = getNode(list.subList(index, size)); + return new Branch(separator, a, b); + } + } + + private abstract class Node { + abstract T get(float value); + } + + private class Branch extends Node { + final float separator; + final Node min; + final Node max; + + public Branch(float separator, Node min, Node max) { + this.separator = separator; + this.min = min; + this.max = max; + } + + @Override + T get(float value) { + return value < separator ? min.get(value) : max.get(value); + } + + @Override + public String toString() { + return String.format(Locale.ROOT, "[%f, %s, %s]", separator, min.toString(), max.toString()); + } + } + + private class Leaf extends Node { + final T biome; + + Leaf(T value) { + this.biome = value; + } + + @Override + T get(float value) { + return biome; + } + + @Override + public String toString() { + return String.format(Locale.ROOT, "[%s]", biome.toString()); + } + } + + @Override + public String toString() { + return root.toString(); + } +} diff --git a/src/main/java/org/betterx/bclib/util/WeightedList.java b/src/main/java/org/betterx/bclib/util/WeightedList.java new file mode 100644 index 00000000..decf079b --- /dev/null +++ b/src/main/java/org/betterx/bclib/util/WeightedList.java @@ -0,0 +1,182 @@ +package org.betterx.bclib.util; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.util.RandomSource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.function.Consumer; +import java.util.function.Function; + +public class WeightedList { + + + private final List weights = new ArrayList(); + private final List values = new ArrayList(); + private float maxWeight; + + public static Codec> pairCodec(Codec elementCodec, String fieldName) { + return Pair.pairCodec(Codec.FLOAT, elementCodec, "weight", fieldName); + } + + public static Codec> listCodec(Codec elementCodec, String fieldName, String elementName) { + return RecordCodecBuilder.create(instance -> instance + .group( + pairCodec(elementCodec, elementName).listOf() + .fieldOf(fieldName) + .forGetter(WeightedList::pairs) + ) + .apply(instance, WeightedList::new) + ); + } + + private List> pairs() { + List> pairs = new ArrayList<>(weights.size()); + for (int i = 0; i < weights.size(); i++) { + pairs.add(new Pair<>(weights.get(i), values.get(i))); + } + return pairs; + } + + private WeightedList(List> pairs) { + maxWeight = 0; + for (var pair : pairs) { + maxWeight += pair.first; + weights.add(pair.first); + values.add(pair.second); + } + } + + public WeightedList() { + } + + public WeightedList map(Function map) { + List> pairs = new ArrayList<>(weights.size()); + for (int i = 0; i < weights.size(); i++) { + pairs.add(new Pair<>(weights.get(i), map.apply(values.get(i)))); + } + return new WeightedList<>(pairs); + } + + public void addAll(WeightedList other) { + weights.addAll(other.weights); + values.addAll(other.values); + maxWeight += other.maxWeight; + } + + /** + * Adds value with specified weight to the list + * + * @param value + * @param weight + */ + public void add(T value, float weight) { + maxWeight += weight; + weights.add(maxWeight); + values.add(value); + } + + /** + * Get random value. + * + * @param random - {@link Random}. + * @return {@link T} value. + */ + public T get(RandomSource random) { + if (maxWeight <= 0) { + return null; + } + float weight = random.nextFloat() * maxWeight; + for (int i = 0; i < weights.size(); i++) { + if (weight <= weights.get(i)) { + return values.get(i); + } + weight -= weights.get(i); + } + return null; + } + + /** + * Get value by index. + * + * @param index - {@code int} index. + * @return {@link T} value. + */ + public T get(int index) { + return values.get(index); + } + + /** + * Get value weight. Weight is summed with all previous values weights. + * + * @param index - {@code int} index. + * @return {@code float} weight. + */ + public float getWeight(int index) { + return weights.get(index); + } + + /** + * Chech if the list is empty. + * + * @return {@code true} if list is empty and {@code false} if not. + */ + public boolean isEmpty() { + return maxWeight == 0; + } + + /** + * Get the list size. + * + * @return {@code int} list size. + */ + public int size() { + return values.size(); + } + + /** + * Makes a sublist of this list with same weights. Used only in {@link WeighTree} + * + * @param start - {@code int} start index (inclusive). + * @param end - {@code int} end index (exclusive). + * @return {@link WeightedList}. + */ + protected WeightedList subList(int start, int end) { + WeightedList list = new WeightedList(); + for (int i = start; i < end; i++) { + list.weights.add(weights.get(i)); + list.values.add(values.get(i)); + } + return list; + } + + /** + * Check if list contains certain value. + * + * @param value - {@link T} value. + * @return {@code true} if value is in list and {@code false} if not. + */ + public boolean contains(T value) { + return values.contains(value); + } + + /** + * Applies {@link Consumer} to all values in list. + * + * @param function - {@link Consumer}. + */ + public void forEach(Consumer function) { + values.forEach(function); + } + + /** + * Get the maximum weight of the tree. + * + * @return {@code float} maximum weight. + */ + public float getMaxWeight() { + return maxWeight; + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/BCLRegistrySupplier.java b/src/main/java/org/betterx/datagen/bclib/BCLRegistrySupplier.java new file mode 100644 index 00000000..b40815f9 --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/BCLRegistrySupplier.java @@ -0,0 +1,59 @@ +package org.betterx.datagen.bclib; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeData; +import org.betterx.bclib.api.v3.datagen.RegistrySupplier; +import org.betterx.datagen.bclib.tests.TestConfiguredFeatures; +import org.betterx.datagen.bclib.tests.TestPlacedFeatures; +import org.betterx.datagen.bclib.tests.TestStructure; +import org.betterx.datagen.bclib.worldgen.BiomeDatagenProvider; +import org.betterx.datagen.bclib.worldgen.VanillaBCLBiomesDataProvider; +import org.betterx.worlds.together.WorldsTogether; + +import net.minecraft.core.registries.Registries; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureSet; + +import java.util.List; +import org.jetbrains.annotations.Nullable; + +public class BCLRegistrySupplier extends RegistrySupplier { + public static final BCLRegistrySupplier INSTANCE = new BCLRegistrySupplier(); + + private BCLRegistrySupplier() { + super(List.of( + BCLib.MOD_ID, + WorldsTogether.MOD_ID + )); + } + + @Override + protected List> initializeRegistryList(@Nullable List modIDs) { + InfoList registries = new InfoList(); + + registries.addUnfiltered( + BCLBiomeRegistry.BCL_BIOMES_REGISTRY, + BiomeData.CODEC, + VanillaBCLBiomesDataProvider::bootstrap + ); + + if (BCLib.ADD_TEST_DATA) { + registries.add(Registries.STRUCTURE, Structure.DIRECT_CODEC, TestStructure::bootstrap); + registries.add(Registries.STRUCTURE_SET, StructureSet.DIRECT_CODEC, TestStructure::bootstrapSet); + registries.add( + Registries.CONFIGURED_FEATURE, + ConfiguredFeature.DIRECT_CODEC, + TestConfiguredFeatures::bootstrap + ); + registries.add(Registries.PLACED_FEATURE, PlacedFeature.DIRECT_CODEC, TestPlacedFeatures::bootstrap); + } else { + registries.add(Registries.BIOME, Biome.DIRECT_CODEC, BiomeDatagenProvider::bootstrap); + } + + return registries; + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/BCLibDatagen.java b/src/main/java/org/betterx/datagen/bclib/BCLibDatagen.java new file mode 100644 index 00000000..d739eab5 --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/BCLibDatagen.java @@ -0,0 +1,45 @@ +package org.betterx.datagen.bclib; + +import org.betterx.bclib.BCLib; +import org.betterx.datagen.bclib.advancement.BCLAdvancementDataProvider; +import org.betterx.datagen.bclib.advancement.RecipeDataProvider; +import org.betterx.datagen.bclib.integrations.NullscapeBiomes; +import org.betterx.datagen.bclib.tests.TestWorldgenProvider; +import org.betterx.datagen.bclib.worldgen.BCLibRegistriesDataProvider; +import org.betterx.datagen.bclib.worldgen.BiomeDatagenProvider; +import org.betterx.datagen.bclib.worldgen.BlockTagProvider; +import org.betterx.datagen.bclib.worldgen.ItemTagProvider; + +import net.minecraft.core.RegistrySetBuilder; + +import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; +import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; + +public class BCLibDatagen implements DataGeneratorEntrypoint { + + @Override + public void onInitializeDataGenerator(FabricDataGenerator dataGenerator) { + BCLib.LOGGER.info("Bootstrap onInitializeDataGenerator"); + final FabricDataGenerator.Pack pack = dataGenerator.createPack(); + + NullscapeBiomes.ensureStaticallyLoaded(); + if (BCLib.ADD_TEST_DATA) { + pack.addProvider(TestWorldgenProvider::new); + RecipeDataProvider.createTestRecipes(); + } else { + pack.addProvider(BiomeDatagenProvider::new); + } + + pack.addProvider(BlockTagProvider::new); + pack.addProvider(ItemTagProvider::new); + pack.addProvider(RecipeDataProvider::new); + pack.addProvider(BCLibRegistriesDataProvider::new); + pack.addProvider(BCLAdvancementDataProvider::new); + } + + + @Override + public void buildRegistry(RegistrySetBuilder registryBuilder) { + BCLRegistrySupplier.INSTANCE.bootstrapRegistries(registryBuilder); + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/advancement/BCLAdvancementDataProvider.java b/src/main/java/org/betterx/datagen/bclib/advancement/BCLAdvancementDataProvider.java new file mode 100644 index 00000000..ca01ce0d --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/advancement/BCLAdvancementDataProvider.java @@ -0,0 +1,22 @@ +package org.betterx.datagen.bclib.advancement; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.datagen.AdvancementDataProvider; +import org.betterx.worlds.together.WorldsTogether; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; + +import java.util.List; + +public class BCLAdvancementDataProvider extends AdvancementDataProvider { + public BCLAdvancementDataProvider( + FabricDataOutput output + ) { + super(List.of(BCLib.MOD_ID, WorldsTogether.MOD_ID), output); + } + + @Override + protected void bootstrap() { + + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/advancement/RecipeDataProvider.java b/src/main/java/org/betterx/datagen/bclib/advancement/RecipeDataProvider.java new file mode 100644 index 00000000..52129fe9 --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/advancement/RecipeDataProvider.java @@ -0,0 +1,30 @@ +package org.betterx.datagen.bclib.advancement; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.recipes.BCLRecipeBuilder; +import org.betterx.worlds.together.WorldsTogether; + +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.world.item.Items; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; + +import java.util.List; + +public class RecipeDataProvider extends org.betterx.bclib.api.v3.datagen.RecipeDataProvider { + public RecipeDataProvider(FabricDataOutput output) { + super(List.of(BCLib.MOD_ID, WorldsTogether.MOD_ID), output); + } + + public static void createTestRecipes() { + BCLRecipeBuilder + .crafting(BCLib.makeID("test_star"), Items.NETHER_STAR) + .setOutputCount(1) + .setShape("ggg", "glg", "ggg") + .addMaterial('g', Items.GLASS_PANE) + .addMaterial('l', Items.LAPIS_LAZULI) + .setCategory(RecipeCategory.TOOLS) + .build(); + } + +} diff --git a/src/main/java/org/betterx/datagen/bclib/integrations/NullscapeBiomes.java b/src/main/java/org/betterx/datagen/bclib/integrations/NullscapeBiomes.java new file mode 100644 index 00000000..a1580446 --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/integrations/NullscapeBiomes.java @@ -0,0 +1,17 @@ +package org.betterx.datagen.bclib.integrations; + +import org.betterx.worlds.together.tag.v3.CommonBiomeTags; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; + +public class NullscapeBiomes { + public static void ensureStaticallyLoaded() { + TagManager.BIOMES.addOptional( + CommonBiomeTags.IS_SMALL_END_ISLAND, + ResourceKey.create(Registries.BIOME, new ResourceLocation("nullscape", "void_barrens")) + ); + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/tests/TestConfiguredFeatures.java b/src/main/java/org/betterx/datagen/bclib/tests/TestConfiguredFeatures.java new file mode 100644 index 00000000..4ee6c0de --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/tests/TestConfiguredFeatures.java @@ -0,0 +1,40 @@ +package org.betterx.datagen.bclib.tests; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.levelgen.features.BCLConfigureFeature; +import org.betterx.bclib.api.v3.levelgen.features.BCLFeatureBuilder; + +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.RandomPatchFeature; +import net.minecraft.world.level.levelgen.feature.configurations.RandomPatchConfiguration; + +public class TestConfiguredFeatures { + static BCLConfigureFeature YELLOW_FEATURE = BCLFeatureBuilder + .startWeighted(BCLib.makeID("temp_yellow_feature")) + .add(Blocks.YELLOW_STAINED_GLASS, 30) + .add(Blocks.YELLOW_CONCRETE_POWDER, 30) + .add(Blocks.YELLOW_GLAZED_TERRACOTTA, 5) + .inlinePlace() + .isEmpty() + .inRandomPatch(BCLib.makeID("yellow_feature")) + .build(); + + public static void bootstrap(BootstapContext> bootstrapContext) { + Holder> holder = bootstrapContext.lookup(Registries.CONFIGURED_FEATURE) + .getOrThrow(ResourceKey.create( + Registries.CONFIGURED_FEATURE, + YELLOW_FEATURE.id + )); + + if (BCLib.ADD_TEST_DATA && BCLib.isDevEnvironment()) { + BCLib.LOGGER.info("Bootstrap CONFIGUREDFeatures" + holder); + //YELLOW_FEATURE = YELLOW_FEATURE.register(bootstrapContext); + BCLFeatureBuilder.registerUnbound(bootstrapContext); + } + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/tests/TestPlacedFeatures.java b/src/main/java/org/betterx/datagen/bclib/tests/TestPlacedFeatures.java new file mode 100644 index 00000000..dff3eee4 --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/tests/TestPlacedFeatures.java @@ -0,0 +1,33 @@ +package org.betterx.datagen.bclib.tests; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.levelgen.features.BCLFeature; + +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.blockpredicates.BlockPredicate; +import net.minecraft.world.level.levelgen.feature.RandomPatchFeature; +import net.minecraft.world.level.levelgen.feature.configurations.RandomPatchConfiguration; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; + +public class TestPlacedFeatures { + static BCLFeature YELLOW_PLACED = TestConfiguredFeatures + .YELLOW_FEATURE + .place() + .count(10) + .squarePlacement() + .onHeightmap(Heightmap.Types.WORLD_SURFACE) + .decoration(GenerationStep.Decoration.VEGETAL_DECORATION) + .isEmptyAndOn(BlockPredicate.matchesBlocks(Blocks.YELLOW_CONCRETE)) + .onlyInBiome() + .build(); + + public static void bootstrap(BootstapContext bootstrapContext) { + if (BCLib.ADD_TEST_DATA && BCLib.isDevEnvironment()) { + BCLib.LOGGER.info("Bootstrap PLACEDFeatures"); + YELLOW_PLACED = YELLOW_PLACED.register(bootstrapContext); + } + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/tests/TestStructure.java b/src/main/java/org/betterx/datagen/bclib/tests/TestStructure.java new file mode 100644 index 00000000..54e61994 --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/tests/TestStructure.java @@ -0,0 +1,168 @@ +package org.betterx.datagen.bclib.tests; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v2.levelgen.structures.BCLStructure; +import org.betterx.bclib.api.v2.levelgen.structures.BCLStructureBuilder; +import org.betterx.bclib.util.BlocksHelper; +import org.betterx.bclib.util.MHelper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.StructureManager; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.levelgen.GenerationStep; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.RandomState; +import net.minecraft.world.level.levelgen.structure.*; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceType; +import net.minecraft.world.level.levelgen.structure.pieces.StructurePiecesBuilder; + +import java.util.Optional; + +class TestStructurePiece extends StructurePiece { + static final ResourceKey KEY = ResourceKey.create( + Registries.STRUCTURE_PIECE, + BCLib.makeID("test_piece") + ); + static final StructurePieceType INSTANCE = TestStructurePiece::new; + + protected TestStructurePiece(int genDepth, BoundingBox boundingBox) { + super(INSTANCE, genDepth, boundingBox); + } + + public TestStructurePiece(CompoundTag compoundTag) { + super(INSTANCE, compoundTag); + } + + public TestStructurePiece( + StructurePieceSerializationContext context, + CompoundTag tag + ) { + super(INSTANCE, tag); + } + + @Override + protected void addAdditionalSaveData( + StructurePieceSerializationContext structurePieceSerializationContext, + CompoundTag compoundTag + ) { + + } + + @Override + public void postProcess( + WorldGenLevel worldGenLevel, + StructureManager structureManager, + ChunkGenerator chunkGenerator, + RandomSource randomSource, + BoundingBox boundingBox, + ChunkPos chunkPos, + BlockPos blockPos + ) { + for (int x = this.boundingBox.minX(); x < this.boundingBox.maxX(); x++) { + for (int y = this.boundingBox.minY(); y < this.boundingBox.maxY(); y++) { + for (int z = this.boundingBox.minZ(); z < this.boundingBox.maxZ(); z++) { + BlocksHelper.setWithoutUpdate( + worldGenLevel, + new BlockPos(x, y, z), + Blocks.AMETHYST_BLOCK.defaultBlockState() + ); + } + } + } + } +} + +public class TestStructure extends Structure { + public static final BCLStructure TEST = BCLStructureBuilder + .start(BCLib.makeID("test_structure"), TestStructure::new) + .adjustment(TerrainAdjustment.BEARD_THIN) + .randomPlacement(16, 8) + .step(GenerationStep.Decoration.SURFACE_STRUCTURES) + .build(); + + protected TestStructure(StructureSettings structureSettings) { + super(structureSettings); + } + + @Override + protected Optional findGenerationPoint(GenerationContext context) { + BlockPos pos = getGenerationHeight( + context.chunkPos(), + context.chunkGenerator(), + context.heightAccessor(), + context.randomState() + ); + if (pos.getY() >= 10) { + return Optional.of(new Structure.GenerationStub(pos, (structurePiecesBuilder) -> { + generatePieces(structurePiecesBuilder, context); + })); + } + return Optional.empty(); + } + + private static BlockPos getGenerationHeight( + ChunkPos chunkPos, + ChunkGenerator chunkGenerator, + LevelHeightAccessor levelHeightAccessor, + RandomState rState + ) { + final int blockX = chunkPos.getBlockX(7); + final int blockZ = chunkPos.getBlockZ(7); + int z = chunkGenerator.getFirstOccupiedHeight( + blockX, blockZ, Heightmap.Types.WORLD_SURFACE_WG, levelHeightAccessor, rState + ); + + return new BlockPos.MutableBlockPos(blockX, z, blockZ); + } + + protected void generatePieces(StructurePiecesBuilder structurePiecesBuilder, GenerationContext context) { + final RandomSource random = context.random(); + final ChunkPos chunkPos = context.chunkPos(); + final ChunkGenerator chunkGenerator = context.chunkGenerator(); + final LevelHeightAccessor levelHeightAccessor = context.heightAccessor(); + final RandomState rState = context.randomState(); + + int x = chunkPos.getBlockX(MHelper.randRange(4, 12, random)); + int z = chunkPos.getBlockZ(MHelper.randRange(4, 12, random)); + int y = chunkGenerator.getBaseHeight(x, z, Heightmap.Types.WORLD_SURFACE_WG, levelHeightAccessor, rState); + if (y > 50) { + structurePiecesBuilder.addPiece(new TestStructurePiece( + 5, + new BoundingBox(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1) + )); + } + } + + @Override + public StructureType type() { + return TEST.structureType; + } + + public static void bootstrap(BootstapContext bootstrapContext) { + BCLib.LOGGER.info("Bootstrap Structure"); + TEST.register(bootstrapContext); + } + + public static void bootstrapSet(BootstapContext bootstrapContext) { + BCLib.LOGGER.info("Bootstrap StructureSet"); + TEST.registerSet(bootstrapContext); + } + + public static void registerBase() { + if (BCLib.ADD_TEST_DATA && BCLib.isDevEnvironment()) { + Registry.register(BuiltInRegistries.STRUCTURE_PIECE, TestStructurePiece.KEY, TestStructurePiece.INSTANCE); + } + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/tests/TestWorldgenProvider.java b/src/main/java/org/betterx/datagen/bclib/tests/TestWorldgenProvider.java new file mode 100644 index 00000000..d0b77b2d --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/tests/TestWorldgenProvider.java @@ -0,0 +1,34 @@ +package org.betterx.datagen.bclib.tests; + +import org.betterx.bclib.BCLib; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.Registries; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider; + +import java.util.concurrent.CompletableFuture; + +public class TestWorldgenProvider extends FabricDynamicRegistryProvider { + public TestWorldgenProvider( + FabricDataOutput output, + CompletableFuture registriesFuture + ) { + super(output, registriesFuture); + } + + @Override + protected void configure(HolderLookup.Provider registries, Entries entries) { + if (BCLib.ADD_TEST_DATA) { + entries.addAll(registries.lookupOrThrow(Registries.CONFIGURED_FEATURE)); + entries.addAll(registries.lookupOrThrow(Registries.PLACED_FEATURE)); + entries.addAll(registries.lookupOrThrow(Registries.BIOME)); + } + } + + @Override + public String getName() { + return "Test WorldGen Provider"; + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/worldgen/BCLibRegistriesDataProvider.java b/src/main/java/org/betterx/datagen/bclib/worldgen/BCLibRegistriesDataProvider.java new file mode 100644 index 00000000..b43bceaf --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/worldgen/BCLibRegistriesDataProvider.java @@ -0,0 +1,20 @@ +package org.betterx.datagen.bclib.worldgen; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.datagen.RegistriesDataProvider; +import org.betterx.datagen.bclib.BCLRegistrySupplier; + +import net.minecraft.core.HolderLookup; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; + +import java.util.concurrent.CompletableFuture; + +public class BCLibRegistriesDataProvider extends RegistriesDataProvider { + public BCLibRegistriesDataProvider( + FabricDataOutput generator, + CompletableFuture registriesFuture + ) { + super(BCLib.LOGGER, BCLRegistrySupplier.INSTANCE, generator, registriesFuture); + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/worldgen/BiomeDatagenProvider.java b/src/main/java/org/betterx/datagen/bclib/worldgen/BiomeDatagenProvider.java new file mode 100644 index 00000000..dd43794e --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/worldgen/BiomeDatagenProvider.java @@ -0,0 +1,28 @@ +package org.betterx.datagen.bclib.worldgen; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.datagen.TagDataProvider; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.HolderLookup; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.world.level.biome.Biome; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class BiomeDatagenProvider extends TagDataProvider { + public BiomeDatagenProvider( + FabricDataOutput output, + CompletableFuture registriesFuture + ) { + super(TagManager.BIOMES, List.of(BCLib.MOD_ID, WorldsTogether.MOD_ID, "c"), output, registriesFuture); + } + + public static void bootstrap(BootstapContext bootstrapContext) { + + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/worldgen/BlockTagProvider.java b/src/main/java/org/betterx/datagen/bclib/worldgen/BlockTagProvider.java new file mode 100644 index 00000000..59b28f10 --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/worldgen/BlockTagProvider.java @@ -0,0 +1,29 @@ +package org.betterx.datagen.bclib.worldgen; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.datagen.TagDataProvider; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.HolderLookup; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.world.level.block.Block; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class BlockTagProvider extends TagDataProvider { + public BlockTagProvider( + FabricDataOutput output, + CompletableFuture registriesFuture + ) { + super(TagManager.BLOCKS, List.of(BCLib.MOD_ID, WorldsTogether.MOD_ID, "c"), output, registriesFuture); + } + + public static void bootstrap(BootstapContext bootstrapContext) { + + } +} + diff --git a/src/main/java/org/betterx/datagen/bclib/worldgen/ItemTagProvider.java b/src/main/java/org/betterx/datagen/bclib/worldgen/ItemTagProvider.java new file mode 100644 index 00000000..355b2fa5 --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/worldgen/ItemTagProvider.java @@ -0,0 +1,28 @@ +package org.betterx.datagen.bclib.worldgen; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.api.v3.datagen.TagDataProvider; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.core.HolderLookup; +import net.minecraft.data.worldgen.BootstapContext; +import net.minecraft.world.item.Item; + +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class ItemTagProvider extends TagDataProvider { + public ItemTagProvider( + FabricDataOutput output, + CompletableFuture registriesFuture + ) { + super(TagManager.ITEMS, List.of(BCLib.MOD_ID, WorldsTogether.MOD_ID, "c"), output, registriesFuture); + } + + public static void bootstrap(BootstapContext bootstrapContext) { + + } +} diff --git a/src/main/java/org/betterx/datagen/bclib/worldgen/VanillaBCLBiomesDataProvider.java b/src/main/java/org/betterx/datagen/bclib/worldgen/VanillaBCLBiomesDataProvider.java new file mode 100644 index 00000000..b18b362d --- /dev/null +++ b/src/main/java/org/betterx/datagen/bclib/worldgen/VanillaBCLBiomesDataProvider.java @@ -0,0 +1,38 @@ +package org.betterx.datagen.bclib.worldgen; + +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiome; +import org.betterx.bclib.api.v2.levelgen.biomes.BCLBiomeRegistry; +import org.betterx.bclib.api.v2.levelgen.biomes.BiomeAPI; + +import net.minecraft.data.worldgen.BootstapContext; + +public class VanillaBCLBiomesDataProvider { + public static void bootstrap(BootstapContext ctx) { + ctx.register(BiomeAPI.SMALL_END_ISLANDS.getBCLBiomeKey(), BiomeAPI.SMALL_END_ISLANDS); + ctx.register(BiomeAPI.END_BARRENS.getBCLBiomeKey(), BiomeAPI.END_BARRENS); + ctx.register(BiomeAPI.END_HIGHLANDS.getBCLBiomeKey(), BiomeAPI.END_HIGHLANDS); + ctx.register(BiomeAPI.END_MIDLANDS.getBCLBiomeKey(), BiomeAPI.END_MIDLANDS); + ctx.register(BiomeAPI.THE_END.getBCLBiomeKey(), BiomeAPI.THE_END); + ctx.register( + BiomeAPI.BASALT_DELTAS_BIOME.getBCLBiomeKey(), + BiomeAPI.BASALT_DELTAS_BIOME + ); + ctx.register( + BiomeAPI.SOUL_SAND_VALLEY_BIOME.getBCLBiomeKey(), + BiomeAPI.SOUL_SAND_VALLEY_BIOME + ); + ctx.register( + BiomeAPI.WARPED_FOREST_BIOME.getBCLBiomeKey(), + BiomeAPI.WARPED_FOREST_BIOME + ); + ctx.register( + BiomeAPI.CRIMSON_FOREST_BIOME.getBCLBiomeKey(), + BiomeAPI.CRIMSON_FOREST_BIOME + ); + ctx.register( + BiomeAPI.NETHER_WASTES_BIOME.getBCLBiomeKey(), + BiomeAPI.NETHER_WASTES_BIOME + ); + ctx.register(BCLBiomeRegistry.EMPTY_BIOME.getBCLBiomeKey(), BCLBiomeRegistry.EMPTY_BIOME); + } +} diff --git a/src/main/java/org/betterx/ui/ColorUtil.java b/src/main/java/org/betterx/ui/ColorUtil.java new file mode 100644 index 00000000..8762c560 --- /dev/null +++ b/src/main/java/org/betterx/ui/ColorUtil.java @@ -0,0 +1,349 @@ +package org.betterx.ui; + +import de.ambertation.wunderlib.ui.ColorHelper; +import org.betterx.bclib.BCLib; +import org.betterx.bclib.util.ColorExtractor; +import org.betterx.bclib.util.MHelper; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.Minecraft; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.Mth; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.Item; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import com.google.common.collect.Maps; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ColorUtil { + public static final int BLACK = ColorHelper.BLACK; + public static final int DARK_BLUE = ColorHelper.DARK_BLUE; + public static final int DARK_GREEN = ColorHelper.DARK_GREEN; + public static final int DARK_AQUA = ColorHelper.DARK_AQUA; + public static final int DARK_RED = ColorHelper.DARK_RED; + public static final int DARK_PURPLE = ColorHelper.DARK_PURPLE; + public static final int GOLD = ColorHelper.GOLD; + public static final int GRAY = ColorHelper.GRAY; + public static final int DARK_GRAY = ColorHelper.DARK_GRAY; + public static final int BLUE = ColorHelper.BLUE; + public static final int GREEN = ColorHelper.GREEN; + public static final int AQUA = ColorHelper.AQUA; + public static final int RED = ColorHelper.RED; + public static final int LIGHT_PURPLE = ColorHelper.LIGHT_PURPLE; + public static final int YELLOW = ColorHelper.YELLOW; + public static final int WHITE = ColorHelper.WHITE; + public static final int DEFAULT_TEXT = ColorHelper.WHITE; + private static final float[] FLOAT_BUFFER = new float[4]; + private static final int ALPHA = 255 << 24; + + public static int color(int r, int g, int b) { + return ALPHA | (r << 16) | (g << 8) | b; + } + + public static int color(String hex) { + int r = Integer.parseInt(hex.substring(0, 2), 16); + int g = Integer.parseInt(hex.substring(2, 4), 16); + int b = Integer.parseInt(hex.substring(4, 6), 16); + return color(r, g, b); + } + + public static int[] toIntArray(int color) { + return new int[]{(color >> 24) & 255, (color >> 16) & 255, (color >> 8) & 255, color & 255}; + } + + public static float[] toFloatArray(int color) { + FLOAT_BUFFER[0] = ((color >> 16 & 255) / 255.0F); + FLOAT_BUFFER[1] = ((color >> 8 & 255) / 255.0F); + FLOAT_BUFFER[2] = ((color & 255) / 255.0F); + FLOAT_BUFFER[3] = ((color >> 24 & 255) / 255.0F); + + return FLOAT_BUFFER; + } + + public static float[] RGBtoHSB(int r, int g, int b, float[] hsbvals) { + float hue, saturation, brightness; + if (hsbvals == null) { + hsbvals = FLOAT_BUFFER; + } + int cmax = (r > g) ? r : g; + if (b > cmax) cmax = b; + int cmin = (r < g) ? r : g; + if (b < cmin) cmin = b; + + brightness = ((float) cmax) / 255.0F; + if (cmax != 0) saturation = ((float) (cmax - cmin)) / ((float) cmax); + else saturation = 0; + if (saturation == 0) hue = 0; + else { + float redc = ((float) (cmax - r)) / ((float) (cmax - cmin)); + float greenc = ((float) (cmax - g)) / ((float) (cmax - cmin)); + float bluec = ((float) (cmax - b)) / ((float) (cmax - cmin)); + if (r == cmax) hue = bluec - greenc; + else if (g == cmax) hue = 2.0F + redc - bluec; + else hue = 4.0F + greenc - redc; + hue = hue / 6.0F; + if (hue < 0) hue = hue + 1.0F; + } + hsbvals[0] = hue; + hsbvals[1] = saturation; + hsbvals[2] = brightness; + return hsbvals; + } + + public static int HSBtoRGB(float hue, float saturation, float brightness) { + int r = 0, g = 0, b = 0; + if (saturation == 0) { + r = g = b = (int) (brightness * 255.0F + 0.5F); + } else { + float h = (hue - (float) Math.floor(hue)) * 6.0F; + float f = h - (float) Math.floor(h); + float p = brightness * (1.0F - saturation); + float q = brightness * (1.0F - saturation * f); + float t = brightness * (1.0F - (saturation * (1.0F - f))); + switch ((int) h) { + case 0: + r = (int) (brightness * 255.0F + 0.5F); + g = (int) (t * 255.0F + 0.5F); + b = (int) (p * 255.0F + 0.5F); + break; + case 1: + r = (int) (q * 255.0F + 0.5F); + g = (int) (brightness * 255.0F + 0.5F); + b = (int) (p * 255.0F + 0.5F); + break; + case 2: + r = (int) (p * 255.0F + 0.5F); + g = (int) (brightness * 255.0F + 0.5F); + b = (int) (t * 255.0F + 0.5F); + break; + case 3: + r = (int) (p * 255.0F + 0.5F); + g = (int) (q * 255.0F + 0.5F); + b = (int) (brightness * 255.0F + 0.5F); + break; + case 4: + r = (int) (t * 255.0F + 0.5F); + g = (int) (p * 255.0F + 0.5F); + b = (int) (brightness * 255.0F + 0.5F); + break; + case 5: + r = (int) (brightness * 255.0F + 0.5F); + g = (int) (p * 255.0F + 0.5F); + b = (int) (q * 255.0F + 0.5F); + break; + } + } + return 0xFF000000 | (r << 16) | (g << 8) | (b << 0); + } + + public static String toRGBHex(int color) { + return "#" + + Integer.toHexString((color >> 16) & 0xFF) + + Integer.toHexString((color >> 8) & 0xFF) + + Integer.toHexString(color & 0xFF); + } + + public static boolean validHexColor(String hexColor) { + if (hexColor.startsWith("#")) hexColor = hexColor.substring(1); + if (hexColor.startsWith("0x")) hexColor = hexColor.substring(2); + + int len = hexColor.length(); + if (len != 6 && len != 8 && len != 3 && len != 4) { + return false; + } + + int color, shift; + if (len == 3) { + hexColor = "" + + hexColor.charAt(0) + hexColor.charAt(0) + + hexColor.charAt(1) + hexColor.charAt(1) + + hexColor.charAt(2) + hexColor.charAt(2); + len = 6; + } else if (len == 4) { + hexColor = "" + + hexColor.charAt(0) + hexColor.charAt(0) + + hexColor.charAt(1) + hexColor.charAt(1) + + hexColor.charAt(2) + hexColor.charAt(2) + + hexColor.charAt(3) + hexColor.charAt(3); + len = 8; + } + + if (len == 6) { + color = 0xFF000000; + shift = 16; + } else { + color = 0; + shift = 24; + } + + try { + String[] splited = hexColor.split("(?<=\\G.{2})"); + for (String digit : splited) { + color |= Integer.valueOf(digit, 16) << shift; + shift -= 8; + } + } catch (NumberFormatException ex) { + return false; + } + + return true; + } + + public static int parseHex(String hexColor) { + if (hexColor.startsWith("#")) hexColor = hexColor.substring(1); + if (hexColor.startsWith("0x")) hexColor = hexColor.substring(2); + int len = hexColor.length(); + if (len != 6 && len != 8 && len != 3 && len != 4) { + return -1; + } + + int color, shift; + if (len == 3) { + hexColor = "" + + hexColor.charAt(0) + hexColor.charAt(0) + + hexColor.charAt(1) + hexColor.charAt(1) + + hexColor.charAt(2) + hexColor.charAt(2); + len = 6; + } else if (len == 4) { + hexColor = "" + + hexColor.charAt(0) + hexColor.charAt(0) + + hexColor.charAt(1) + hexColor.charAt(1) + + hexColor.charAt(2) + hexColor.charAt(2) + + hexColor.charAt(3) + hexColor.charAt(3); + len = 8; + } + + if (len == 6) { + color = 0xFF000000; + shift = 16; + } else { + color = 0; + shift = 24; + } + + try { + String[] splited = hexColor.split("(?<=\\G.{2})"); + for (String digit : splited) { + color |= Integer.valueOf(digit, 16) << shift; + shift -= 8; + } + } catch (NumberFormatException ex) { + BCLib.LOGGER.catching(ex); + return -1; + } + + return color; + } + + public static int toABGR(int color) { + int r = (color >> 16) & 255; + int g = (color >> 8) & 255; + int b = color & 255; + return 0xFF000000 | b << 16 | g << 8 | r; + } + + public static int ABGRtoARGB(int color) { + int a = (color >> 24) & 255; + int b = (color >> 16) & 255; + int g = (color >> 8) & 255; + int r = color & 255; + return a << 24 | r << 16 | g << 8 | b; + } + + public static int colorBrigtness(int color, float val) { + RGBtoHSB((color >> 16) & 255, (color >> 8) & 255, color & 255, FLOAT_BUFFER); + FLOAT_BUFFER[2] += val / 10.0F; + FLOAT_BUFFER[2] = Mth.clamp(FLOAT_BUFFER[2], 0.0F, 1.0F); + return HSBtoRGB(FLOAT_BUFFER[0], FLOAT_BUFFER[1], FLOAT_BUFFER[2]); + } + + public static int multiplyColor(int color1, int color2) { + if (color1 == -1) { + return color2; + } else if (color2 == -1) { + return color1; + } + + final int alpha = ((color1 >> 24) & 0xFF) * ((color2 >> 24) & 0xFF) / 0xFF; + final int red = ((color1 >> 16) & 0xFF) * ((color2 >> 16) & 0xFF) / 0xFF; + final int green = ((color1 >> 8) & 0xFF) * ((color2 >> 8) & 0xFF) / 0xFF; + final int blue = (color1 & 0xFF) * (color2 & 0xFF) / 0xFF; + + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + public static int applyTint(int color, int tint) { + return colorBrigtness(multiplyColor(color, tint), 1.5F); + } + + public static int colorDistance(int color1, int color2) { + int r1 = (color1 >> 16) & 255; + int g1 = (color1 >> 8) & 255; + int b1 = color1 & 255; + int r2 = (color2 >> 16) & 255; + int g2 = (color2 >> 8) & 255; + int b2 = color2 & 255; + return MHelper.sqr(r1 - r2) + MHelper.sqr(g1 - g2) + MHelper.sqr(b1 - b2); + } + + private static final Map colorPalette = Maps.newHashMap(); + + @Environment(EnvType.CLIENT) + public static int extractColor(Item item) { + ResourceLocation id = BuiltInRegistries.ITEM.getKey(item); + if (id.equals(BuiltInRegistries.ITEM.getDefaultKey())) return -1; + if (colorPalette.containsKey(id)) { + return colorPalette.get(id); + } + ResourceLocation texture; + if (item instanceof BlockItem) { + texture = new ResourceLocation(id.getNamespace(), "textures/block/" + id.getPath() + ".png"); + } else { + texture = new ResourceLocation(id.getNamespace(), "textures/item/" + id.getPath() + ".png"); + } + NativeImage image = loadImage(texture, 16, 16); + List colors = new ArrayList<>(); + for (int i = 0; i < image.getWidth(); i++) { + for (int j = 0; j < 16; j++) { + int col = image.getPixelRGBA(i, j); + if (((col >> 24) & 255) > 0) { + colors.add(ABGRtoARGB(col)); + } + } + } + image.close(); + + if (colors.size() == 0) return -1; + + ColorExtractor extractor = new ColorExtractor(colors); + int color = extractor.analize(); + colorPalette.put(id, color); + + return color; + } + + @Environment(EnvType.CLIENT) + public static NativeImage loadImage(ResourceLocation image, int w, int h) { + Minecraft minecraft = Minecraft.getInstance(); + ResourceManager resourceManager = minecraft.getResourceManager(); + var imgResource = resourceManager.getResource(image); + if (imgResource.isPresent()) { + try { + return NativeImage.read(imgResource.get().open()); + } catch (IOException e) { + BCLib.LOGGER.warning("Can't load texture image: {}. Will be created empty image.", image); + BCLib.LOGGER.warning("Cause: {}.", e.getMessage()); + } + } + return new NativeImage(w, h, false); + } +} \ No newline at end of file diff --git a/src/main/java/org/betterx/worlds/together/WorldsTogether.java b/src/main/java/org/betterx/worlds/together/WorldsTogether.java new file mode 100644 index 00000000..790b5548 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/WorldsTogether.java @@ -0,0 +1,32 @@ +package org.betterx.worlds.together; + +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.fabricmc.loader.api.FabricLoader; + +public class WorldsTogether { + public static boolean SURPRESS_EXPERIMENTAL_DIALOG = false; + public static boolean FORCE_SERVER_TO_BETTERX_PRESET = false; + public static final String MOD_ID = "worlds_together"; + public static final Logger LOGGER = new Logger(MOD_ID); + public static final boolean RUNS_TERRABLENDER = FabricLoader.getInstance() + .getModContainer("terrablender") + .isPresent(); + + public static boolean isDevEnvironment() { + return FabricLoader.getInstance().isDevelopmentEnvironment(); + } + + public static void onInitialize() { + TagManager.ensureStaticallyLoaded(); + WorldConfig.registerModCache(WorldsTogether.MOD_ID); + } + + public static ResourceLocation makeID(String s) { + return new ResourceLocation(MOD_ID, s); + } +} diff --git a/src/main/java/org/betterx/worlds/together/biomesource/BiomeSourceHelper.java b/src/main/java/org/betterx/worlds/together/biomesource/BiomeSourceHelper.java new file mode 100644 index 00000000..f347d185 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/biomesource/BiomeSourceHelper.java @@ -0,0 +1,31 @@ +package org.betterx.worlds.together.biomesource; + +import net.minecraft.core.Holder; +import net.minecraft.world.level.biome.Biome; + +import java.util.Collection; +import java.util.stream.Collectors; + +public class BiomeSourceHelper { + + /** + * Get a list of namespaces from a collection of biomes. + * + * @param biomes The collection of biomes. + * @return A comma-separated list of namespaces including the number of biomes found in each namespace. + */ + public static String getNamespaces(Collection> biomes) { + var namespaces = biomes + .stream() + .filter(h -> h.unwrapKey().isPresent()) + .map(h -> h.unwrapKey().get().location().getNamespace()) + .toList(); + + return namespaces + .stream() + .distinct() + .map(n -> n + "(" + namespaces.stream().filter(n::equals).count() + ")") + .collect(Collectors.joining(", ")); + + } +} diff --git a/src/main/java/org/betterx/worlds/together/biomesource/BiomeSourceWithConfig.java b/src/main/java/org/betterx/worlds/together/biomesource/BiomeSourceWithConfig.java new file mode 100644 index 00000000..a8f75bd3 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/biomesource/BiomeSourceWithConfig.java @@ -0,0 +1,10 @@ +package org.betterx.worlds.together.biomesource; + +import org.betterx.worlds.together.biomesource.config.BiomeSourceConfig; + +import net.minecraft.world.level.biome.BiomeSource; + +public interface BiomeSourceWithConfig> { + C getTogetherConfig(); + void setTogetherConfig(C newConfig); +} diff --git a/src/main/java/org/betterx/worlds/together/biomesource/MergeableBiomeSource.java b/src/main/java/org/betterx/worlds/together/biomesource/MergeableBiomeSource.java new file mode 100644 index 00000000..15cfe6d7 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/biomesource/MergeableBiomeSource.java @@ -0,0 +1,42 @@ +package org.betterx.worlds.together.biomesource; + +import org.betterx.bclib.BCLib; + +import net.minecraft.core.Holder; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeSource; + +import java.util.Set; + +public interface MergeableBiomeSource { + default boolean togetherShouldMerge(BiomeSource inputBiomeSource) { + Set> mySet = ((B) this).possibleBiomes(); + try { + Set> otherSet = inputBiomeSource.possibleBiomes(); + + if (otherSet.size() != mySet.size()) return true; + + for (Holder b : mySet) { + if (!otherSet.contains(b)) + return true; + } + } catch (RuntimeException e) { + BCLib.LOGGER.error("Failed to merge BiomeSource", e); + } catch (Exception e) { + BCLib.LOGGER.error("Failed to merge BiomeSource", e); + } + + return false; + } + + /** + * Returns a BiomeSource that merges the settings of this one with the Biomes (and possibly settings) from the + * {@code inputBiomeSource}. + * + * @param inputBiomeSource The {@link BiomeSource} you want to copy + * @return The merged or new BiomeSource + */ + B mergeWithBiomeSource(BiomeSource inputBiomeSource); + + +} diff --git a/src/main/java/org/betterx/worlds/together/biomesource/ReloadableBiomeSource.java b/src/main/java/org/betterx/worlds/together/biomesource/ReloadableBiomeSource.java new file mode 100644 index 00000000..377c3110 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/biomesource/ReloadableBiomeSource.java @@ -0,0 +1,5 @@ +package org.betterx.worlds.together.biomesource; + +public interface ReloadableBiomeSource { + void reloadBiomes(); +} diff --git a/src/main/java/org/betterx/worlds/together/biomesource/config/BiomeSourceConfig.java b/src/main/java/org/betterx/worlds/together/biomesource/config/BiomeSourceConfig.java new file mode 100644 index 00000000..b37370af --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/biomesource/config/BiomeSourceConfig.java @@ -0,0 +1,8 @@ +package org.betterx.worlds.together.biomesource.config; + +import net.minecraft.world.level.biome.BiomeSource; + +public interface BiomeSourceConfig { + boolean couldSetWithoutRepair(BiomeSourceConfig input); + boolean sameConfig(BiomeSourceConfig input); +} diff --git a/src/main/java/org/betterx/worlds/together/client/WorldsTogetherClient.java b/src/main/java/org/betterx/worlds/together/client/WorldsTogetherClient.java new file mode 100644 index 00000000..62ed1af3 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/client/WorldsTogetherClient.java @@ -0,0 +1,7 @@ +package org.betterx.worlds.together.client; + +public class WorldsTogetherClient { + public static void onInitializeClient() { + + } +} diff --git a/src/main/java/org/betterx/worlds/together/entrypoints/EntrypointUtil.java b/src/main/java/org/betterx/worlds/together/entrypoints/EntrypointUtil.java new file mode 100644 index 00000000..80385a91 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/entrypoints/EntrypointUtil.java @@ -0,0 +1,31 @@ +package org.betterx.worlds.together.entrypoints; + +import net.fabricmc.loader.api.FabricLoader; + +import java.util.List; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class EntrypointUtil { + private static List getEntryPoints(boolean client, Class select) { + return FabricLoader.getInstance() + .getEntrypoints( + client ? "worlds_together_client" : "worlds_together", + WorldsTogetherEntrypoint.class + ) + .stream() + .filter(o -> select.isAssignableFrom(o.getClass())) + .map(e -> (T) e) + .toList(); + } + + @ApiStatus.Internal + public static List getCommon(Class select) { + return getEntryPoints(false, select); + } + + @ApiStatus.Internal + public static List getClient(Class select) { + return getEntryPoints(true, select); + } +} diff --git a/src/main/java/org/betterx/worlds/together/entrypoints/WorldsTogetherEntrypoint.java b/src/main/java/org/betterx/worlds/together/entrypoints/WorldsTogetherEntrypoint.java new file mode 100644 index 00000000..b41b8601 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/entrypoints/WorldsTogetherEntrypoint.java @@ -0,0 +1,4 @@ +package org.betterx.worlds.together.entrypoints; + +public interface WorldsTogetherEntrypoint { +} diff --git a/src/main/java/org/betterx/worlds/together/flatLevel/FlatLevelPresets.java b/src/main/java/org/betterx/worlds/together/flatLevel/FlatLevelPresets.java new file mode 100644 index 00000000..f15a7e25 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/flatLevel/FlatLevelPresets.java @@ -0,0 +1,29 @@ +package org.betterx.worlds.together.flatLevel; + +import org.betterx.worlds.together.tag.v3.TagManager; +import org.betterx.worlds.together.tag.v3.TagRegistry; + +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.FlatLevelGeneratorPresetTags; +import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorPreset; + +public class FlatLevelPresets { + public static TagRegistry.Simple FLAT_LEVEL_PRESETS = + TagManager.registerType( + Registries.FLAT_LEVEL_GENERATOR_PRESET, + "tags/worldgen/flat_level_generator_preset", + (b) -> null + ); + + + public static ResourceKey register(ResourceLocation loc) { + ResourceKey key = ResourceKey.create( + Registries.FLAT_LEVEL_GENERATOR_PRESET, + loc + ); + FLAT_LEVEL_PRESETS.addUntyped(FlatLevelGeneratorPresetTags.VISIBLE, key.location()); + return key; + } +} diff --git a/src/main/java/org/betterx/worlds/together/levelgen/WorldGenUtil.java b/src/main/java/org/betterx/worlds/together/levelgen/WorldGenUtil.java new file mode 100644 index 00000000..2b8866fa --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/levelgen/WorldGenUtil.java @@ -0,0 +1,55 @@ +package org.betterx.worlds.together.levelgen; + +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderGetter; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.minecraft.world.level.levelgen.structure.StructureSet; + +public class WorldGenUtil { + public static class Context extends StemContext { + public Context( + Holder dimension, + HolderGetter structureSets, + Holder generatorSettings + ) { + super(dimension, structureSets, generatorSettings); + } + } + + public static class StemContext { + public final Holder dimension; + public final HolderGetter structureSets; + public final Holder generatorSettings; + + public StemContext( + Holder dimension, + HolderGetter structureSets, + Holder generatorSettings + ) { + this.dimension = dimension; + this.structureSets = structureSets; + this.generatorSettings = generatorSettings; + } + } + + public static ResourceLocation getBiomeID(Biome biome) { + ResourceLocation id = null; + RegistryAccess access = WorldBootstrap.getLastRegistryAccessOrElseBuiltin(); + + id = access.registryOrThrow(Registries.BIOME).getKey(biome); + + if (id == null) { + WorldsTogether.LOGGER.error("Unable to get ID for " + biome + "."); + } + + return id; + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/client/CreateWorldScreen_Mixin.java b/src/main/java/org/betterx/worlds/together/mixin/client/CreateWorldScreen_Mixin.java new file mode 100644 index 00000000..b1051769 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/client/CreateWorldScreen_Mixin.java @@ -0,0 +1,32 @@ +package org.betterx.worlds.together.mixin.client; + +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.world.level.storage.LevelStorageSource; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; + +@Mixin(CreateWorldScreen.class) +public abstract class CreateWorldScreen_Mixin { + @Shadow + public abstract WorldCreationUiState getUiState(); + + @Shadow + private boolean recreated; + + + //this is called when a new world is first created + @Inject(method = "createNewWorldDirectory", at = @At("RETURN")) + void wt_createNewWorld(CallbackInfoReturnable> cir) { + WorldBootstrap.Helpers.onRegistryReady(this.getUiState().getSettings().worldgenLoadContext()); + WorldBootstrap.InGUI.setupNewWorld(cir.getReturnValue()); + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/client/WorldOpenFlowsMixin.java b/src/main/java/org/betterx/worlds/together/mixin/client/WorldOpenFlowsMixin.java new file mode 100644 index 00000000..43988a11 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/client/WorldOpenFlowsMixin.java @@ -0,0 +1,46 @@ +package org.betterx.worlds.together.mixin.client; + +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.worldselection.WorldOpenFlows; +import net.minecraft.world.level.storage.LevelStorageSource; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(WorldOpenFlows.class) +public abstract class WorldOpenFlowsMixin { + + @Shadow + @Final + private LevelStorageSource levelSource; + + @Shadow + protected abstract void doLoadLevel(Screen screen, String levelID, boolean safeMode, boolean canAskForBackup); + + @Inject(method = "loadLevel", cancellable = true, at = @At("HEAD")) + private void wt_callFixerOnLoad(Screen screen, String levelID, CallbackInfo ci) { + WorldBootstrap.InGUI.setupLoadedWorld(levelID, this.levelSource); + + if (WorldBootstrap.InGUI.applyWorldPatches(levelSource, levelID, (appliedFixes) -> { + WorldBootstrap.finishedWorldLoad(); + this.doLoadLevel(screen, levelID, false, false); + })) { + //cancel call when fix-screen is presented + ci.cancel(); + } else { + WorldBootstrap.finishedWorldLoad(); + if (WorldsTogether.SURPRESS_EXPERIMENTAL_DIALOG) { + this.doLoadLevel(screen, levelID, false, false); + //cancel call as we manually start the level load here + ci.cancel(); + } + } + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/DiggerItemAccessor.java b/src/main/java/org/betterx/worlds/together/mixin/common/DiggerItemAccessor.java new file mode 100644 index 00000000..9acf4f48 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/DiggerItemAccessor.java @@ -0,0 +1,16 @@ +package org.betterx.worlds.together.mixin.common; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.DiggerItem; +import net.minecraft.world.level.block.Block; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(DiggerItem.class) +public interface DiggerItemAccessor { + @Accessor("blocks") + @Mutable + TagKey bclib_getBlockTag(); +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/MainMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/MainMixin.java new file mode 100644 index 00000000..7572af23 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/MainMixin.java @@ -0,0 +1,32 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.server.Main; +import net.minecraft.world.level.storage.LevelStorageSource; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(value = Main.class, priority = 200) +abstract public class MainMixin { + private static LevelStorageSource.LevelStorageAccess bcl_levelStorageAccess = null; + + @ModifyVariable(method = "main", ordinal = 0, at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;getSummary()Lnet/minecraft/world/level/storage/LevelSummary;")) + private static LevelStorageSource.LevelStorageAccess bc_createAccess(LevelStorageSource.LevelStorageAccess levelStorageAccess) { + bcl_levelStorageAccess = levelStorageAccess; + WorldBootstrap.DedicatedServer.applyWorldPatches(levelStorageAccess); + return levelStorageAccess; + } + + @ModifyArg(method = "main", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;saveDataTag(Lnet/minecraft/core/RegistryAccess;Lnet/minecraft/world/level/storage/WorldData;)V")) + private static RegistryAccess bcl_onCreate(RegistryAccess registryAccess) { + WorldBootstrap.DedicatedServer.registryReady(registryAccess); + WorldBootstrap.DedicatedServer.setupWorld(bcl_levelStorageAccess); + + return registryAccess; + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/MinecraftServerMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/MinecraftServerMixin.java new file mode 100644 index 00000000..72219662 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/MinecraftServerMixin.java @@ -0,0 +1,36 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.RegistryLayer; +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.level.dimension.LevelStem; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * We need a hook here to alter surface rules after Fabric did add its biomes + * in {@link net.fabricmc.fabric.mixin.biome.modification.MinecraftServerMixin} + */ +@Mixin(value = MinecraftServer.class, priority = 2000) +public class MinecraftServerMixin { + @Shadow + @Final + private LayeredRegistryAccess registries; + + @Inject(method = "createLevels", at = @At(value = "HEAD")) + private void together_addSurfaceRules(ChunkProgressListener worldGenerationProgressListener, CallbackInfo ci) { + final Registry dimensionRegistry = this.registries.compositeAccess() + .registryOrThrow(Registries.LEVEL_STEM); + WorldBootstrap.finalizeWorldGenSettings(dimensionRegistry); + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/TagLoaderMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/TagLoaderMixin.java new file mode 100644 index 00000000..3431afd1 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/TagLoaderMixin.java @@ -0,0 +1,27 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.tag.v3.TagManager; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagLoader; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import java.util.List; +import java.util.Map; + +@Mixin(TagLoader.class) +public class TagLoaderMixin { + @Final + @Shadow + private String directory; + + @ModifyArg(method = "loadAndBuild", at = @At(value = "INVOKE", target = "Lnet/minecraft/tags/TagLoader;build(Ljava/util/Map;)Ljava/util/Map;")) + public Map> be_modifyTags(Map> tagsMap) { + return TagManager.apply(directory, tagsMap); + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/WorldLoaderMixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/WorldLoaderMixin.java new file mode 100644 index 00000000..c1dbaffb --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/WorldLoaderMixin.java @@ -0,0 +1,22 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.server.WorldLoader; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(WorldLoader.class) +public class WorldLoaderMixin { + //this is the place a new Registry access gets first istantiated + //either when a new Datapack was added to a world on the create-screen + //or because we did start world loading + @ModifyArg(method = "load", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ReloadableServerResources;loadResources(Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/core/RegistryAccess$Frozen;Lnet/minecraft/world/flag/FeatureFlagSet;Lnet/minecraft/commands/Commands$CommandSelection;ILjava/util/concurrent/Executor;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;")) + private static RegistryAccess.Frozen wt_newRegistry(RegistryAccess.Frozen frozen) { + WorldBootstrap.InGUI.registryReady(frozen); + return frozen; + } +} diff --git a/src/main/java/org/betterx/worlds/together/mixin/common/WorldStem_Mixin.java b/src/main/java/org/betterx/worlds/together/mixin/common/WorldStem_Mixin.java new file mode 100644 index 00000000..c9317e8f --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/mixin/common/WorldStem_Mixin.java @@ -0,0 +1,20 @@ +package org.betterx.worlds.together.mixin.common; + +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.core.LayeredRegistryAccess; +import net.minecraft.server.RegistryLayer; +import net.minecraft.server.WorldStem; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(value = WorldStem.class, priority = 1500) +public class WorldStem_Mixin { + @ModifyVariable(method = "", argsOnly = true, at = @At(value = "INVOKE", target = "Ljava/lang/Record;()V", shift = At.Shift.AFTER)) + LayeredRegistryAccess wt_bake(LayeredRegistryAccess registries) { + WorldBootstrap.Helpers.onRegistryReady(registries.compositeAccess()); + return registries; + } +} diff --git a/src/main/java/org/betterx/worlds/together/tag/v3/CommonBiomeTags.java b/src/main/java/org/betterx/worlds/together/tag/v3/CommonBiomeTags.java new file mode 100644 index 00000000..361b2c0b --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/CommonBiomeTags.java @@ -0,0 +1,21 @@ +package org.betterx.worlds.together.tag.v3; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.Biomes; + +public class CommonBiomeTags { + public static final TagKey IS_END_CENTER = TagManager.BIOMES.makeCommonTag("is_end_center"); + public static final TagKey IS_END_HIGHLAND = TagManager.BIOMES.makeCommonTag("is_end_highland"); + public static final TagKey IS_END_MIDLAND = TagManager.BIOMES.makeCommonTag("is_end_midland"); + public static final TagKey IS_END_BARRENS = TagManager.BIOMES.makeCommonTag("is_end_barrens"); + public static final TagKey IS_SMALL_END_ISLAND = TagManager.BIOMES.makeCommonTag("is_small_end_island"); + + static void prepareTags() { + TagManager.BIOMES.add(IS_END_CENTER, Biomes.THE_END); + TagManager.BIOMES.add(IS_END_HIGHLAND, Biomes.END_HIGHLANDS); + TagManager.BIOMES.add(IS_END_MIDLAND, Biomes.END_MIDLANDS); + TagManager.BIOMES.add(IS_END_BARRENS, Biomes.END_BARRENS); + TagManager.BIOMES.add(IS_SMALL_END_ISLAND, Biomes.SMALL_END_ISLANDS); + } +} diff --git a/src/main/java/org/betterx/worlds/together/tag/v3/CommonBlockTags.java b/src/main/java/org/betterx/worlds/together/tag/v3/CommonBlockTags.java new file mode 100644 index 00000000..9df3f838 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/CommonBlockTags.java @@ -0,0 +1,206 @@ +package org.betterx.worlds.together.tag.v3; + +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; + +public class CommonBlockTags { + public static final TagKey BARREL = TagManager.BLOCKS.makeCommonTag("barrel"); + public static final TagKey BOOKSHELVES = TagManager.BLOCKS.makeCommonTag("bookshelves"); + public static final TagKey CHEST = TagManager.BLOCKS.makeCommonTag("chest"); + public static final TagKey COMPOSTER = TagManager.BLOCKS.makeCommonTag("composter"); + public static final TagKey END_STONES = TagManager.BLOCKS.makeCommonTag("end_stones"); + public static final TagKey GEN_END_STONES = END_STONES; + public static final TagKey IMMOBILE = TagManager.BLOCKS.makeCommonTag("immobile"); + public static final TagKey LEAVES = TagManager.BLOCKS.makeCommonTag("leaves"); + public static final TagKey NETHERRACK = TagManager.BLOCKS.makeCommonTag("netherrack"); + public static final TagKey MYCELIUM = TagManager.BLOCKS.makeCommonTag("mycelium"); + public static final TagKey NETHER_MYCELIUM = TagManager.BLOCKS.makeCommonTag("nether_mycelium"); + public static final TagKey NETHER_PORTAL_FRAME = TagManager.BLOCKS.makeCommonTag("nether_pframe"); + public static final TagKey NETHER_STONES = TagManager.BLOCKS.makeCommonTag("nether_stones"); + public static final TagKey NETHER_ORES = TagManager.BLOCKS.makeCommonTag("nether_ores"); + public static final TagKey ORES = TagManager.BLOCKS.makeCommonTag("ores"); + public static final TagKey END_ORES = TagManager.BLOCKS.makeCommonTag("end_ores"); + public static final TagKey SAPLINGS = TagManager.BLOCKS.makeCommonTag("saplings"); + public static final TagKey SEEDS = TagManager.BLOCKS.makeCommonTag("seeds"); + public static final TagKey SOUL_GROUND = TagManager.BLOCKS.makeCommonTag("soul_ground"); + public static final TagKey SCULK_LIKE = TagManager.BLOCKS.makeCommonTag("sculk_like"); + public static final TagKey WOODEN_BARREL = TagManager.BLOCKS.makeCommonTag("wooden_barrels"); + public static final TagKey WOODEN_CHEST = TagManager.BLOCKS.makeCommonTag("wooden_chests"); + public static final TagKey WOODEN_COMPOSTER = TagManager.BLOCKS.makeCommonTag("wooden_composter"); + public static final TagKey WORKBENCHES = TagManager.BLOCKS.makeCommonTag("workbench"); + + public static final TagKey DRAGON_IMMUNE = TagManager.BLOCKS.makeCommonTag("dragon_immune"); + + public static final TagKey MINABLE_WITH_HAMMER = TagManager.BLOCKS.makeCommonTag("mineable/hammer"); + + public static final TagKey IS_OBSIDIAN = TagManager.BLOCKS.makeCommonTag("is_obsidian"); + public static final TagKey TERRAIN = TagManager.BLOCKS.makeCommonTag("terrain"); + public static final TagKey GRASS_SOIL = TagManager.BLOCKS.makeCommonTag("grass_soil"); + public static final TagKey NETHER_TERRAIN = TagManager.BLOCKS.makeCommonTag("nether_terrain"); + public static final TagKey BUDDING_BLOCKS = TagManager.BLOCKS.makeCommonTag("budding_blocks"); + public static final TagKey WATER_PLANT = TagManager.BLOCKS.makeCommonTag("water_plant"); + ; + public static final TagKey PLANT = TagManager.BLOCKS.makeCommonTag("plant"); + ; + + static void prepareTags() { + TagManager.BLOCKS.addOtherTags(MINABLE_WITH_HAMMER, BlockTags.MINEABLE_WITH_PICKAXE); + TagManager.BLOCKS.add(SCULK_LIKE, Blocks.SCULK); + TagManager.BLOCKS.addOtherTags(DRAGON_IMMUNE, BlockTags.DRAGON_IMMUNE); + + TagManager.BLOCKS.add(END_STONES, Blocks.END_STONE); + TagManager.BLOCKS.addOtherTags(NETHER_STONES, BlockTags.BASE_STONE_NETHER); + + TagManager.BLOCKS.add( + NETHERRACK, + Blocks.NETHERRACK, + Blocks.NETHER_QUARTZ_ORE, + Blocks.NETHER_GOLD_ORE, + Blocks.CRIMSON_NYLIUM, + Blocks.WARPED_NYLIUM + ); + + TagManager.BLOCKS.add(NETHER_ORES, Blocks.NETHER_QUARTZ_ORE, Blocks.NETHER_GOLD_ORE); + TagManager.BLOCKS.add(SOUL_GROUND, Blocks.SOUL_SAND, Blocks.SOUL_SOIL); + + TagManager.BLOCKS.add(IS_OBSIDIAN, Blocks.OBSIDIAN, Blocks.CRYING_OBSIDIAN); + + TagManager.BLOCKS.add(MYCELIUM, Blocks.MYCELIUM); + TagManager.BLOCKS.addOtherTags(MYCELIUM, NETHER_MYCELIUM); + + TagManager.BLOCKS.addOtherTags(GRASS_SOIL, BlockTags.DIRT, TERRAIN, BlockTags.LOGS, BlockTags.PLANKS); + + + TagManager.BLOCKS.add( + TERRAIN, + Blocks.MAGMA_BLOCK, + Blocks.GRAVEL, + Blocks.SAND, + Blocks.RED_SAND, + Blocks.GLOWSTONE, + Blocks.BONE_BLOCK, + Blocks.SCULK + ); + TagManager.BLOCKS.addOtherTags( + TERRAIN, + NETHER_TERRAIN, + BlockTags.DRIPSTONE_REPLACEABLE, + BlockTags.BASE_STONE_OVERWORLD, + BlockTags.NYLIUM, + MYCELIUM, + END_STONES + ); + + TagManager.BLOCKS.add( + NETHER_TERRAIN, + Blocks.MAGMA_BLOCK, + Blocks.GRAVEL, + Blocks.RED_SAND, + Blocks.GLOWSTONE, + Blocks.BONE_BLOCK, + Blocks.BLACKSTONE + ); + TagManager.BLOCKS.addOtherTags( + NETHER_TERRAIN, + NETHERRACK, + BlockTags.NYLIUM, + NETHER_ORES, + SOUL_GROUND, + NETHER_MYCELIUM + ); + + TagManager.BLOCKS.add(CommonBlockTags.BOOKSHELVES, Blocks.BOOKSHELF); + TagManager.BLOCKS.add(CommonBlockTags.CHEST, Blocks.CHEST); + + TagManager.BLOCKS.add( + BlockTags.NETHER_CARVER_REPLACEABLES, + Blocks.BASALT, + Blocks.RED_SAND, + Blocks.MAGMA_BLOCK, + Blocks.SCULK + ); + TagManager.BLOCKS.addOtherTags( + BlockTags.NETHER_CARVER_REPLACEABLES, + CommonBlockTags.NETHER_STONES, + CommonBlockTags.NETHERRACK + ); + + TagManager.BLOCKS.addOtherTags( + BlockTags.MINEABLE_WITH_AXE, + WOODEN_BARREL, + WOODEN_COMPOSTER, + WOODEN_CHEST, + WORKBENCHES + ); + + TagManager.BLOCKS.add(WATER_PLANT, Blocks.KELP, Blocks.KELP_PLANT, Blocks.SEAGRASS, Blocks.TALL_SEAGRASS); + TagManager.BLOCKS.add( + SAPLINGS, + Blocks.OAK_SAPLING, + Blocks.SPRUCE_SAPLING, + Blocks.BIRCH_SAPLING, + Blocks.JUNGLE_SAPLING, + Blocks.ACACIA_SAPLING, + Blocks.DARK_OAK_SAPLING, + Blocks.CHERRY_SAPLING, + Blocks.BAMBOO_SAPLING, + Blocks.MANGROVE_PROPAGULE + ); + TagManager.BLOCKS.addOtherTags(PLANT, SAPLINGS); + TagManager.BLOCKS.add( + PLANT, + Blocks.MANGROVE_LEAVES, + Blocks.GRASS, + Blocks.FERN, + Blocks.DANDELION, + Blocks.TORCHFLOWER, + Blocks.POPPY, + Blocks.BLUE_ORCHID, + Blocks.ALLIUM, + Blocks.AZURE_BLUET, + Blocks.RED_TULIP, + Blocks.ORANGE_TULIP, + Blocks.WHITE_TULIP, + Blocks.PINK_TULIP, + Blocks.OXEYE_DAISY, + Blocks.CORNFLOWER, + Blocks.WITHER_ROSE, + Blocks.LILY_OF_THE_VALLEY, + Blocks.WHEAT, + Blocks.CACTUS, + Blocks.SUGAR_CANE, + Blocks.ATTACHED_PUMPKIN_STEM, + Blocks.ATTACHED_MELON_STEM, + Blocks.PUMPKIN_STEM, + Blocks.MELON_STEM, + Blocks.VINE, + Blocks.LILY_PAD, + Blocks.COCOA, + Blocks.CARROTS, + Blocks.POTATOES, + Blocks.SUNFLOWER, + Blocks.LILAC, + Blocks.ROSE_BUSH, + Blocks.PEONY, + Blocks.TALL_GRASS, + Blocks.LARGE_FERN, + Blocks.TORCHFLOWER_CROP, + Blocks.PITCHER_CROP, + Blocks.PITCHER_PLANT, + Blocks.BEETROOTS, + Blocks.BAMBOO, + Blocks.SWEET_BERRY_BUSH, + Blocks.CAVE_VINES, + Blocks.CAVE_VINES_PLANT, + Blocks.SPORE_BLOSSOM, + Blocks.AZALEA, + Blocks.FLOWERING_AZALEA, + Blocks.PINK_PETALS, + Blocks.BIG_DRIPLEAF, + Blocks.BIG_DRIPLEAF_STEM, + Blocks.SMALL_DRIPLEAF + ); + } +} diff --git a/src/main/java/org/betterx/worlds/together/tag/v3/CommonItemTags.java b/src/main/java/org/betterx/worlds/together/tag/v3/CommonItemTags.java new file mode 100644 index 00000000..52526447 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/CommonItemTags.java @@ -0,0 +1,35 @@ +package org.betterx.worlds.together.tag.v3; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Blocks; + +public class CommonItemTags { + public final static TagKey HAMMERS = TagManager.ITEMS.makeCommonTag("hammers"); + public static final TagKey BARREL = TagManager.ITEMS.makeCommonTag("barrel"); + public static final TagKey CHEST = TagManager.ITEMS.makeCommonTag("chest"); + public static final TagKey SHEARS = TagManager.ITEMS.makeCommonTag("shears"); + public static final TagKey FURNACES = TagManager.ITEMS.makeCommonTag("furnaces"); + public static final TagKey IRON_INGOTS = TagManager.ITEMS.makeCommonTag("iron_ingots"); + public static final TagKey LEAVES = TagManager.ITEMS.makeCommonTag("leaves"); + public static final TagKey SAPLINGS = TagManager.ITEMS.makeCommonTag("saplings"); + public static final TagKey SEEDS = TagManager.ITEMS.makeCommonTag("seeds"); + public static final TagKey SOUL_GROUND = TagManager.ITEMS.makeCommonTag("soul_ground"); + public static final TagKey WOODEN_BARREL = TagManager.ITEMS.makeCommonTag("wooden_barrels"); + public static final TagKey WOODEN_CHEST = TagManager.ITEMS.makeCommonTag("wooden_chests"); + public static final TagKey WORKBENCHES = TagManager.ITEMS.makeCommonTag("workbench"); + + public static final TagKey WATER_BOTTLES = TagManager.ITEMS.makeCommonTag("water_bottles"); + public static final TagKey COMPOSTABLE = TagManager.ITEMS.makeCommonTag("compostable"); + ; + + static void prepareTags() { + TagManager.ITEMS.add(SOUL_GROUND, Blocks.SOUL_SAND.asItem(), Blocks.SOUL_SOIL.asItem()); + + TagManager.ITEMS.add(CommonItemTags.CHEST, Items.CHEST); + TagManager.ITEMS.add(CommonItemTags.IRON_INGOTS, Items.IRON_INGOT); + TagManager.ITEMS.add(CommonItemTags.FURNACES, Blocks.FURNACE.asItem()); + TagManager.ITEMS.add(CommonItemTags.WATER_BOTTLES, Items.WATER_BUCKET); + } +} diff --git a/src/main/java/org/betterx/worlds/together/tag/v3/CommonPoiTags.java b/src/main/java/org/betterx/worlds/together/tag/v3/CommonPoiTags.java new file mode 100644 index 00000000..757b698f --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/CommonPoiTags.java @@ -0,0 +1,15 @@ +package org.betterx.worlds.together.tag.v3; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; + +public class CommonPoiTags { + public static final TagKey FISHERMAN_WORKSTATION = TagManager.BLOCKS.makeCommonTag("workstation/fisherman"); + public static final TagKey FARMER_WORKSTATION = TagManager.BLOCKS.makeCommonTag("workstation/farmer"); + + static { + TagManager.BLOCKS.addOtherTags(FISHERMAN_WORKSTATION, CommonBlockTags.BARREL, CommonBlockTags.WOODEN_BARREL); + TagManager.BLOCKS.add(FARMER_WORKSTATION, Blocks.COMPOSTER); + } +} diff --git a/src/main/java/org/betterx/worlds/together/tag/v3/MineableTags.java b/src/main/java/org/betterx/worlds/together/tag/v3/MineableTags.java new file mode 100644 index 00000000..9bfe44dc --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/MineableTags.java @@ -0,0 +1,23 @@ +package org.betterx.worlds.together.tag.v3; + +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.TagKey; +import net.minecraft.world.level.block.Block; + +import net.fabricmc.fabric.api.mininglevel.v1.FabricMineableTags; + + +public class MineableTags { + public static final TagKey AXE = BlockTags.MINEABLE_WITH_AXE; + public static final TagKey HOE = BlockTags.MINEABLE_WITH_HOE; + public static final TagKey PICKAXE = BlockTags.MINEABLE_WITH_PICKAXE; + public static final TagKey SHEARS = FabricMineableTags.SHEARS_MINEABLE; + public static final TagKey SHOVEL = BlockTags.MINEABLE_WITH_SHOVEL; + public static final TagKey SWORD = FabricMineableTags.SWORD_MINEABLE; + public static final TagKey HAMMER = TagManager.BLOCKS.makeCommonTag("mineable/hammer"); + + public static final TagKey NEEDS_NETHERITE_TOOL = TagManager.BLOCKS.makeCommonTag("needs_netherite_tool"); + + static void prepareTags() { + } +} diff --git a/src/main/java/org/betterx/worlds/together/tag/v3/TagManager.java b/src/main/java/org/betterx/worlds/together/tag/v3/TagManager.java new file mode 100644 index 00000000..f1c80306 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/TagManager.java @@ -0,0 +1,126 @@ +package org.betterx.worlds.together.tag.v3; + +import org.betterx.worlds.together.levelgen.WorldGenUtil; +import org.betterx.worlds.together.mixin.common.DiggerItemAccessor; +import org.betterx.worlds.together.world.event.WorldEventsImpl; + +import net.minecraft.core.DefaultedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.tags.TagKey; +import net.minecraft.tags.TagLoader; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; + +import com.google.common.collect.Maps; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.jetbrains.annotations.ApiStatus; + +public class TagManager { + private static final Map> TYPES = Maps.newHashMap(); + + public static TagRegistry.RegistryBacked BLOCKS = registerType(BuiltInRegistries.BLOCK); + public static TagRegistry.Items ITEMS = registerItem(); + public static TagRegistry.Biomes BIOMES = registerBiome(); + + public static TagRegistry.RegistryBacked registerType(DefaultedRegistry registry) { + TagRegistry type = new TagRegistry.RegistryBacked<>(registry); + return (TagRegistry.RegistryBacked) TYPES.computeIfAbsent(type.directory, (dir) -> type); + } + + public static TagRegistry.Items registerItem() { + TagRegistry.Items type = new TagRegistry.Items(); + return (TagRegistry.Items) TYPES.computeIfAbsent(type.directory, (dir) -> type); + } + + public static TagRegistry.Simple registerType(Registry registry, String directory) { + return registerType(registry.key(), directory, (o) -> registry.getKey(o)); + } + + public static TagRegistry.Simple registerType( + ResourceKey> registry, + String directory, + Function locationProvider + ) { + return (TagRegistry.Simple) TYPES.computeIfAbsent( + directory, + (dir) -> new TagRegistry.Simple<>( + registry, + dir, + locationProvider + ) + ); + } + + static TagRegistry.Biomes registerBiome() { + return (TagRegistry.Biomes) TYPES.computeIfAbsent( + "tags/worldgen/biome", + (dir) -> new TagRegistry.Biomes( + dir, + b -> WorldGenUtil.getBiomeID(b) + ) + ); + } + + public static TagRegistry.UnTyped registerType( + ResourceKey> registry, + String directory + ) { + return (TagRegistry.UnTyped) TYPES.computeIfAbsent( + directory, + (dir) -> new TagRegistry.UnTyped<>(registry, dir) + ); + } + + /** + * Initializes basic tags. Should be called only in BCLib main class. + */ + @ApiStatus.Internal + public static void ensureStaticallyLoaded() { + CommonItemTags.prepareTags(); + CommonBlockTags.prepareTags(); + CommonBiomeTags.prepareTags(); + MineableTags.prepareTags(); + ToolTags.prepareTags(); + } + + + /** + * Automatically called in {@link net.minecraft.tags.TagLoader#loadAndBuild(ResourceManager)}. + *

+ * In most cases there is no need to call this Method manually. + * + * @param directory The name of the Tag-directory. Should be either "tags/blocks" or + * "tags/items". + * @param tagsMap The map that will hold the registered Tags + * @return The {@code tagsMap} Parameter. + */ + @ApiStatus.Internal + public static Map> apply( + String directory, + Map> tagsMap + ) { + WorldEventsImpl.BEFORE_ADDING_TAGS.emit(e -> e.apply(directory, tagsMap)); + + TagRegistry type = TYPES.get(directory); + if (type != null) { + type.apply(tagsMap); + } + + return tagsMap; + } + + + public static boolean isToolWithMineableTag(ItemStack stack, TagKey tag) { + if (stack.getItem() instanceof DiggerItemAccessor dig) { + return dig.bclib_getBlockTag() == tag; + } + return false; + } +} diff --git a/src/main/java/org/betterx/worlds/together/tag/v3/TagRegistry.java b/src/main/java/org/betterx/worlds/together/tag/v3/TagRegistry.java new file mode 100644 index 00000000..8faea98a --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/TagRegistry.java @@ -0,0 +1,410 @@ +package org.betterx.worlds.together.tag.v3; + +import org.betterx.bclib.BCLib; +import org.betterx.bclib.interfaces.TriConsumer; +import org.betterx.bclib.util.Pair; +import org.betterx.worlds.together.WorldsTogether; + +import net.minecraft.core.DefaultedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagEntry; +import net.minecraft.tags.TagKey; +import net.minecraft.tags.TagLoader; +import net.minecraft.tags.TagManager; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.biome.Biome; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; +import java.util.function.Function; + +public class TagRegistry { + boolean isFrozen = false; + + public static class RegistryBacked extends Simple { + private final DefaultedRegistry registry; + + RegistryBacked(DefaultedRegistry registry) { + super( + registry.key(), + TagManager.getTagDir(registry.key()), + (T element) -> { + ResourceLocation id = registry.getKey(element); + if (id != registry.getDefaultKey()) { + return id; + } + return null; + } + ); + this.registry = registry; + } + + @Override + public TagKey makeTag(ResourceLocation id) { + final TagKey tag = registry + .getTagNames() + .filter(tagKey -> tagKey.location().equals(id)) + .findAny() + .orElse(TagKey.create(registry.key(), id)); + initializeTag(tag); + return tag; + } + } + + public static class Simple extends TagRegistry { + Simple( + ResourceKey> registry, + String directory, + Function locationProvider + ) { + super(registry, directory, locationProvider); + } + + @SafeVarargs + public final void add(TagKey tagID, T... elements) { + super.add(tagID, elements); + } + + @SafeVarargs + public final void addOptional(TagKey tagID, T... elements) { + super.addOptional(tagID, elements); + } + + @SafeVarargs + public final void add(T element, TagKey... tagIDs) { + super.add(element, tagIDs); + } + + public final boolean contains(TagKey tagID, T element) { + return super.contains(tagID, element); + } + } + + public static class Biomes extends Simple { + Biomes(String directory, Function locationProvider) { + super(Registries.BIOME, directory, locationProvider); + } + + /** + * Adds one Tag to multiple Elements. + * + * @param tagID {@link TagKey} tag ID. + * @param elements array of Elements to add into tag. + */ + public void add(TagKey tagID, ResourceKey... elements) { + add(tagID, false, elements); + } + + public void addOptional(TagKey tagID, ResourceKey... elements) { + add(tagID, true, elements); + } + + void add(TagKey tagID, boolean optional, ResourceKey... elements) { + if (isFrozen) WorldsTogether.LOGGER.warning("Adding Tag " + tagID + " after the API was frozen."); + synchronized (this) { + Set set = getSetForTag(tagID); + for (ResourceKey element : elements) { + ResourceLocation id = element.location(); + + //only add if the set doesn't already contain the element + for (TagEntry tagEntry : set) { + if (!tagEntry.elementOrTag().tag() && tagEntry.elementOrTag().id().equals(id)) { + id = null; + break; + } + } + + if (id != null) { + set.add(optional ? TagEntry.optionalElement(id) : TagEntry.element(id)); + } + } + } + } + + public TagKey makeStructureTag(String modID, String name) { + return makeTag(modID, "has_structure/" + name); + } + + public void apply(Map> tagsMap) { + super.apply(tagsMap); + } + } + + public static class Items extends RegistryBacked { + + Items() { + super(BuiltInRegistries.ITEM); + } + + @SafeVarargs + public final void add(TagKey tagID, ItemLike... elements) { + for (ItemLike element : elements) { + add(tagID, element.asItem()); + } + } + + @SafeVarargs + public final void addOptional(TagKey tagID, ItemLike... elements) { + for (ItemLike element : elements) { + addOptional(tagID, element.asItem()); + } + } + + @SafeVarargs + public final void add(ItemLike element, TagKey... tagIDs) { + super.add(element.asItem(), tagIDs); + } + } + + public static class UnTyped extends TagRegistry { + UnTyped( + ResourceKey> registry, + String directory + ) { + super(registry, directory, (t) -> { + throw new RuntimeException("Using Untyped TagType with Type-Dependant access. "); + }); + } + } + + public final String directory; + private final Map, Set> tags = Maps.newConcurrentMap(); + public final ResourceKey> registryKey; + private final Function locationProvider; + + private TagRegistry( + ResourceKey> registry, + String directory, + Function locationProvider + ) { + this.registryKey = registry; + this.directory = directory; + this.locationProvider = locationProvider; + } + + protected void initializeTag(TagKey tag) { + getSetForTag(tag); + } + + + public Set getSetForTag(TagKey tag) { + if (tag == null) { + return new HashSet<>(); + } + return tags.computeIfAbsent(tag, k -> Sets.newHashSet()); + } + + /** + * Get or create a {@link TagKey}. + * + * @param modId - {@link String} mod namespace (mod id); + * @param name - {@link String} tag name. + * @return the corresponding TagKey {@link TagKey}. + */ + public TagKey makeTag(String modId, String name) { + return makeTag(new ResourceLocation(modId, name)); + } + + /** + * Get or create a {@link TagKey}. + * + * @param id - {@link ResourceLocation} of the tag; + * @return the corresponding TagKey {@link TagKey}. + */ + public TagKey makeTag(ResourceLocation id) { + return creatTagKey(id); + } + + protected TagKey creatTagKey(ResourceLocation id) { + final TagKey tag = TagKey.create(registryKey, id); + initializeTag(tag); + return tag; + } + + /** + * Get or create a common {@link TagKey} (namespace is 'c'). + * + * @param name - The name of the Tag; + * @return the corresponding TagKey {@link TagKey}. + * @see Fabric Wiki (Tags) + */ + public TagKey makeCommonTag(String name) { + return creatTagKey(new ResourceLocation("c", name)); + } + + public TagKey makeTogetherTag(String name) { + return creatTagKey(WorldsTogether.makeID(name)); + } + + public void addUntyped(TagKey tagID, ResourceLocation... elements) { + if (isFrozen) WorldsTogether.LOGGER.warning("Adding Tag " + tagID + " after the API was frozen."); + Set set = getSetForTag(tagID); + for (ResourceLocation id : elements) { + if (id != null) { + set.add(TagEntry.element(id)); + } + } + } + + public void addUntyped(ResourceLocation element, TagKey... tagIDs) { + for (TagKey tagID : tagIDs) { + addUntyped(tagID, element); + } + } + + public void addOtherTags(TagKey tagID, TagKey... tags) { + addOtherTags(tagID, false, tags); + } + + public void addOptionalOtherTags(TagKey tagID, TagKey... tags) { + addOtherTags(tagID, true, tags); + } + + void addOtherTags(TagKey tagID, boolean optional, TagKey... tags) { + if (isFrozen) WorldsTogether.LOGGER.warning("Adding Tag " + tagID + " after the API was frozen."); + Set set = getSetForTag(tagID); + for (TagKey tag : tags) { + ResourceLocation id = tag.location(); + if (id != null) { + set.add(optional ? TagEntry.optionalTag(id) : TagEntry.tag(id)); + } + } + } + + /** + * Adds one Tag to multiple Elements. + * + * @param tagID {@link TagKey< Biome >} tag ID. + * @param elements array of Elements to add into tag. + */ + protected void add(TagKey tagID, T... elements) { + add(tagID, false, elements); + } + + protected void addOptional(TagKey tagID, T... elements) { + add(tagID, true, elements); + } + + protected void add(TagKey tagID, boolean optional, T... elements) { + if (isFrozen) WorldsTogether.LOGGER.warning("Adding Tag " + tagID + " after the API was frozen."); + Set set = getSetForTag(tagID); + for (T element : elements) { + ResourceLocation id = locationProvider.apply(element); + + //only add if the set doesn't already contain the element + for (TagEntry tagEntry : set) { + if (!tagEntry.elementOrTag().tag() && tagEntry.elementOrTag().id().equals(id)) { + id = null; + break; + } + } + + if (id != null) { + set.add(optional ? TagEntry.optionalElement(id) : TagEntry.element(id)); + } + } + } + + protected boolean contains(TagKey tagID, T element) { + final Set set = getSetForTag(tagID); + final ResourceLocation id = locationProvider.apply(element); + if (id != null) { + for (var entry : set) + if (!entry.elementOrTag().tag()) { + if (id.equals(entry.elementOrTag().id())) + return true; + } + } + return false; + } + + protected void add(T element, TagKey... tagIDs) { + for (TagKey tagID : tagIDs) { + add(tagID, element); + } + } + + public void forEach(BiConsumer> consumer) { + tags.forEach((a, b) -> consumer.accept(a.location(), b)); + } + + public void forEachTag(TriConsumer, List, List>> consumer) { + forEachTag(consumer, null); + } + + public void forEachTag( + TriConsumer, List, List>> consumer, + BiPredicate, ResourceLocation> allow + ) { + tags.forEach((tag, set) -> { + List locations = new LinkedList<>(); + List> tags = new LinkedList<>(); + + set.forEach(e -> { + ExtraCodecs.TagOrElementLocation t = e.elementOrTag(); + if (allow == null || allow.test(tag, t.id())) { + if (t.tag()) { + tags.add(TagKey.create(registryKey, t.id())); + } else { + locations.add(t.id()); + } + } + }); + + consumer.accept(tag, locations, tags); + }); + } + + public void forEachEntry( + TriConsumer, List>, List, TagEntry>>> consumer, + BiPredicate, ResourceLocation> allow + ) { + tags.forEach((tag, set) -> { + List> locations = new LinkedList<>(); + List, TagEntry>> tags = new LinkedList<>(); + + + set.forEach(e -> { + ExtraCodecs.TagOrElementLocation t = e.elementOrTag(); + if (allow == null || allow.test(tag, t.id())) { + if (t.tag()) { + tags.add(new Pair<>(TagKey.create(registryKey, t.id()), e)); + } else { + locations.add(new Pair<>(t.id(), e)); + } + } + }); + + consumer.accept(tag, locations, tags); + }); + } + + public void apply(Map> tagsMap) { + //this.isFrozen = true; + if (BCLib.isDatagen()) { + this.forEach((id, ids) -> apply(id, tagsMap.computeIfAbsent(id, key -> Lists.newArrayList()), ids)); + } else { + tags.clear(); + } + } + + private static List apply( + ResourceLocation id, + List builder, + Set ids + ) { + ids.forEach(value -> builder.add(new TagLoader.EntryWithSource(value, WorldsTogether.MOD_ID))); + return builder; + } +} diff --git a/src/main/java/org/betterx/worlds/together/tag/v3/ToolTags.java b/src/main/java/org/betterx/worlds/together/tag/v3/ToolTags.java new file mode 100644 index 00000000..d33a1676 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/tag/v3/ToolTags.java @@ -0,0 +1,17 @@ +package org.betterx.worlds.together.tag.v3; + +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; + + +public class ToolTags { + public static final TagKey FABRIC_AXES = TagManager.ITEMS.makeTag("fabric", "axes"); + public static final TagKey FABRIC_HOES = TagManager.ITEMS.makeTag("fabric", "hoes"); + public static final TagKey FABRIC_PICKAXES = TagManager.ITEMS.makeTag("fabric", "pickaxes"); + public static final TagKey FABRIC_SHEARS = TagManager.ITEMS.makeTag("fabric", "shears"); + public static final TagKey FABRIC_SHOVELS = TagManager.ITEMS.makeTag("fabric", "shovels"); + public static final TagKey FABRIC_SWORDS = TagManager.ITEMS.makeTag("fabric", "swords"); + + static void prepareTags() { + } +} diff --git a/src/main/java/org/betterx/worlds/together/util/DatapackConfigs.java b/src/main/java/org/betterx/worlds/together/util/DatapackConfigs.java new file mode 100644 index 00000000..003025e4 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/util/DatapackConfigs.java @@ -0,0 +1,100 @@ +package org.betterx.worlds.together.util; + +import org.betterx.bclib.BCLib; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; + +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; +import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; + +public class DatapackConfigs { + @FunctionalInterface + public interface DatapackConfigReloadHandler { + void onReload(ResourceLocation id, JsonObject root); + } + + @FunctionalInterface + public interface DatapackConfigReloadPrepare { + void onReload(); + } + + private static DatapackConfigs INSTANCE = new DatapackConfigs(); + + public static DatapackConfigs instance() { + return INSTANCE; + } + + private Map handlers = new HashMap<>(); + + public void register( + String modID, + String fileName, + DatapackConfigReloadHandler handler + ) { + register( + modID, fileName, () -> { + //nothing to do + }, handler + ); + } + + public void register( + String modID, + String fileName, + DatapackConfigReloadPrepare prepare, + DatapackConfigReloadHandler handler + ) { + final ResourceLocation handlerID = new ResourceLocation( + modID, + "config_manager_" + fileName.replaceAll("/", "_").replaceAll(".", "_") + ); + ResourceManagerHelper + .get(PackType.SERVER_DATA) + .registerReloadListener(new SimpleSynchronousResourceReloadListener() { + @Override + public ResourceLocation getFabricId() { + return handlerID; + } + + @Override + public void onResourceManagerReload(ResourceManager manager) { + prepare.onReload(); + runForResources(manager, modID, fileName, handler); + } + }); + } + + public void runForResources( + ResourceManager manager, + String modID, + String fileName, + DatapackConfigReloadHandler handler + ) { + for (Map.Entry entry : manager.listResources( + "config", + id -> id.getNamespace().equals(modID) && + id.getPath().endsWith(fileName) + ).entrySet()) { + try (Reader reader = entry.getValue().openAsReader()) { + final JsonObject obj = JsonParser.parseReader(reader).getAsJsonObject(); + if (obj != null) + handler.onReload(entry.getKey(), obj); + } catch (Exception e) { + BCLib.LOGGER.error( + "Error occurred while loading resource json " + entry.getKey(), + e + ); + } + } + } +} diff --git a/src/main/java/org/betterx/worlds/together/util/Logger.java b/src/main/java/org/betterx/worlds/together/util/Logger.java new file mode 100644 index 00000000..46f342b7 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/util/Logger.java @@ -0,0 +1,70 @@ +package org.betterx.worlds.together.util; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; + +public final class Logger { + private static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger(); + private final String modPref; + + public Logger(String modID) { + this.modPref = "[" + modID + "] "; + } + + public void log(Level level, String message) { + LOGGER.log(level, modPref + message); + } + + public void log(Level level, String message, Object... params) { + LOGGER.log(level, modPref + message, params); + } + + public void debug(Object message) { + this.log(Level.DEBUG, message.toString()); + } + + public void debug(Object message, Object... params) { + this.log(Level.DEBUG, message.toString(), params); + } + + public void catching(Throwable ex) { + this.error(ex.getLocalizedMessage()); + LOGGER.catching(ex); + } + + public void info(String message) { + this.log(Level.INFO, message); + } + + public void info(String message, Object... params) { + this.log(Level.INFO, message, params); + } + + public void warning(String message, Object... params) { + this.log(Level.WARN, message, params); + } + + public void warning(String message, Object obj, Exception ex) { + LOGGER.warn(modPref + message, obj, ex); + } + + public void error(String message) { + this.log(Level.ERROR, message); + } + + public void error(String message, Object obj, Exception ex) { + LOGGER.error(modPref + message, obj, ex); + } + + public void error(String message, Object o1, Object o2, Exception ex) { + LOGGER.error(modPref + message, o1, o2, ex); + } + + public void error(String message, Object... params) { + LOGGER.error(modPref + message, params); + } + + public void error(String message, Exception ex) { + LOGGER.error(modPref + message, ex); + } +} diff --git a/src/main/java/org/betterx/worlds/together/util/ModUtil.java b/src/main/java/org/betterx/worlds/together/util/ModUtil.java new file mode 100644 index 00000000..54dd38e1 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/util/ModUtil.java @@ -0,0 +1,433 @@ +package org.betterx.worlds.together.util; + +import org.betterx.worlds.together.WorldsTogether; + +import net.fabricmc.loader.api.*; +import net.fabricmc.loader.api.metadata.*; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ModUtil { + private static Map mods; + + /** + * Unloads the cache of available mods created from {@link #getMods()} + */ + public static void invalidateCachedMods() { + mods = null; + } + + /** + * return a map of all mods that were found in the 'mods'-folder. + *

+ * The method will cache the results. You can clear that cache (and free the memory) by + * calling {@link #invalidateCachedMods()} + *

+ * An error message is printed if a mod fails to load, but the parsing will continue. + * + * @return A map of all found mods. (key=ModID, value={@link ModInfo}) + */ + public static Map getMods() { + if (mods != null) return mods; + + mods = new HashMap<>(); + PathUtil.fileWalker(PathUtil.MOD_FOLDER.toFile(), false, (ModUtil::accept)); + + return mods; + } + + private static ModMetadata readJSON(InputStream is, String sourceFile) throws IOException { + try (JsonReader reader = new JsonReader(new InputStreamReader( + is, + StandardCharsets.UTF_8 + ))) { + JsonObject data = JsonParser.parseReader(reader) + .getAsJsonObject(); + Version ver; + try { + ver = SemanticVersion.parse(data.get("version").getAsString()); + } catch (VersionParsingException e) { + WorldsTogether.LOGGER.error("Unable to parse Version in " + sourceFile); + return null; + } + + if (data.get("id") == null) { + WorldsTogether.LOGGER.error("Unable to read ID in " + sourceFile); + return null; + } + + if (data.get("name") == null) { + WorldsTogether.LOGGER.error("Unable to read name in " + sourceFile); + return null; + } + + return new ModMetadata() { + @Override + public Version getVersion() { + return ver; + } + + @Override + public String getType() { + return "fabric"; + } + + @Override + public String getId() { + return data.get("id") + .getAsString(); + } + + @Override + public Collection getProvides() { + return new ArrayList<>(); + } + + @Override + public ModEnvironment getEnvironment() { + JsonElement env = data.get("environment"); + if (env == null) { + WorldsTogether.LOGGER.warning("No environment specified in " + sourceFile); + //return ModEnvironment.UNIVERSAL; + } + final String environment = env == null ? "" : env.getAsString() + .toLowerCase(Locale.ROOT); + + if (environment.isEmpty() || environment.equals("*") || environment.equals("\"*\"") || environment.equals( + "common")) { + JsonElement entrypoints = data.get("entrypoints"); + boolean hasClient = true; + + //check if there is an actual client entrypoint + if (entrypoints != null && entrypoints.isJsonObject()) { + JsonElement client = entrypoints.getAsJsonObject() + .get("client"); + if (client != null && client.isJsonArray()) { + hasClient = client.getAsJsonArray() + .size() > 0; + } else if (client == null || !client.isJsonPrimitive()) { + hasClient = false; + } else if (!client.getAsJsonPrimitive() + .isString()) { + hasClient = false; + } + } + + //if (hasClient == false) return ModEnvironment.SERVER; + return ModEnvironment.UNIVERSAL; + } else if (environment.equals("client")) { + return ModEnvironment.CLIENT; + } else if (environment.equals("server")) { + return ModEnvironment.SERVER; + } else { + WorldsTogether.LOGGER.error("Unable to read environment in " + sourceFile); + return ModEnvironment.UNIVERSAL; + } + } + + @Override + public Collection getDepends() { + return new ArrayList<>(); + } + + @Override + public Collection getRecommends() { + return new ArrayList<>(); + } + + @Override + public Collection getSuggests() { + return new ArrayList<>(); + } + + @Override + public Collection getConflicts() { + return new ArrayList<>(); + } + + @Override + public Collection getBreaks() { + return new ArrayList<>(); + } + + public Collection getDependencies() { + return new ArrayList<>(); + } + + @Override + public String getName() { + return data.get("name") + .getAsString(); + } + + @Override + public String getDescription() { + return ""; + } + + @Override + public Collection getAuthors() { + return new ArrayList<>(); + } + + @Override + public Collection getContributors() { + return new ArrayList<>(); + } + + @Override + public ContactInformation getContact() { + return null; + } + + @Override + public Collection getLicense() { + return new ArrayList<>(); + } + + @Override + public Optional getIconPath(int size) { + return Optional.empty(); + } + + @Override + public boolean containsCustomValue(String key) { + return false; + } + + @Override + public CustomValue getCustomValue(String key) { + return null; + } + + @Override + public Map getCustomValues() { + return new HashMap<>(); + } + + @Override + public boolean containsCustomElement(String key) { + return false; + } + + public JsonElement getCustomElement(String key) { + return null; + } + }; + } + } + + /** + * Returns the {@link ModInfo} or {@code null} if the mod was not found. + *

+ * The call will also return null if the mode-Version in the jar-File is not the same + * as the version of the loaded Mod. + * + * @param modID The mod ID to query + * @return A {@link ModInfo}-Object for the querried Mod. + */ + public static ModInfo getModInfo(String modID) { + return getModInfo(modID, true); + } + + public static ModInfo getModInfo(String modID, boolean matchVersion) { + getMods(); + final ModInfo mi = mods.get(modID); + if (mi == null || (matchVersion && !getModVersion(modID).equals(mi.getVersion()))) return null; + return mi; + } + + /** + * Local Mod Version for the queried Mod + * + * @param modID The mod ID to query + * @return The version of the locally installed Mod + */ + public static String getModVersion(String modID) { + Optional optional = FabricLoader.getInstance() + .getModContainer(modID); + if (optional.isPresent()) { + ModContainer modContainer = optional.get(); + return ModInfo.versionToString(modContainer.getMetadata() + .getVersion()); + + } + + return getModVersionFromJar(modID); + } + + /** + * Local Mod Version for the queried Mod from the Jar-File in the games mod-directory + * + * @param modID The mod ID to query + * @return The version of the locally installed Mod + */ + public static String getModVersionFromJar(String modID) { + final ModInfo mi = getModInfo(modID, false); + if (mi != null) return mi.getVersion(); + + return "0.0.0"; + } + + /** + * Get mod version from string. String should be in format: %d.%d.%d + * + * @param version - {@link String} mod version. + * @return int mod version. + */ + public static int convertModVersion(String version) { + if (version.isEmpty()) { + return 0; + } + try { + int res = 0; + final String semanticVersionPattern = "(\\d+)\\.(\\d+)(\\.(\\d+))?\\D*"; + final Matcher matcher = Pattern.compile(semanticVersionPattern) + .matcher(version); + if (matcher.find()) { + if (matcher.groupCount() > 0) + res = matcher.group(1) == null ? 0 : ((Integer.parseInt(matcher.group(1)) & 0xFF) << 22); + if (matcher.groupCount() > 1) + res |= matcher.group(2) == null ? 0 : ((Integer.parseInt(matcher.group(2)) & 0xFF) << 14); + if (matcher.groupCount() > 3) + res |= matcher.group(4) == null ? 0 : Integer.parseInt(matcher.group(4)) & 0x3FFF; + } + + return res; + } catch (Exception e) { + return 0; + } + } + + /** + * Get mod version from integer. String will be in format %d.%d.%d + * + * @param version - mod version in integer form. + * @return {@link String} mod version. + */ + public static String convertModVersion(int version) { + int a = (version >> 22) & 0xFF; + int b = (version >> 14) & 0xFF; + int c = version & 0x3FFF; + return String.format(Locale.ROOT, "%d.%d.%d", a, b, c); + } + + /** + * {@code true} if the version v1 is larger than v2 + * + * @param v1 A Version string + * @param v2 Another Version string + * @return v1 > v2 + */ + public static boolean isLargerVersion(String v1, String v2) { + return convertModVersion(v1) > convertModVersion(v2); + } + + /** + * {@code true} if the version v1 is larger or equal v2 + * + * @param v1 A Version string + * @param v2 Another Version string + * @return v1 ≥ v2 + */ + public static boolean isLargerOrEqualVersion(String v1, String v2) { + return convertModVersion(v1) >= convertModVersion(v2); + } + + private static void accept(Path file) { + try { + URI uri = URI.create("jar:" + file.toUri()); + + FileSystem fs; + // boolean doClose = false; + try { + fs = FileSystems.getFileSystem(uri); + } catch (Exception e) { + // doClose = true; + fs = FileSystems.newFileSystem(file); + } + if (fs != null) { + try { + Path modMetaFile = fs.getPath("fabric.mod.json"); + if (modMetaFile != null) { + try (InputStream is = Files.newInputStream(modMetaFile)) { + //ModMetadata mc = ModMetadataParser.parseMetadata(is, uri.toString(), new LinkedList()); + ModMetadata mc = readJSON(is, uri.toString()); + if (mc != null) { + mods.put(mc.getId(), new ModInfo(mc, file)); + } + } + } + } catch (Exception e) { + WorldsTogether.LOGGER.error("Error for " + uri + ": " + e); + } + //if (doClose) fs.close(); + } + } catch (Exception e) { + WorldsTogether.LOGGER.error("Error for " + file.toUri() + ": " + e); + e.printStackTrace(); + } + } + + public static class ModInfo { + public final ModMetadata metadata; + public final Path jarPath; + + ModInfo(ModMetadata metadata, Path jarPath) { + this.metadata = metadata; + this.jarPath = jarPath; + } + + public static String versionToString(Version v) { + if (v instanceof SemanticVersion) { + return versionToString((SemanticVersion) v); + } + return convertModVersion(convertModVersion(v.toString())); + } + + public static String versionToString(SemanticVersion v) { + StringBuilder stringBuilder = new StringBuilder(); + boolean first = true; + final int cCount = Math.min(v.getVersionComponentCount(), 3); + for (int i = 0; i < cCount; i++) { + if (first) { + first = false; + } else { + stringBuilder.append('.'); + } + + stringBuilder.append(v.getVersionComponent(i)); + } + + return stringBuilder.toString(); + } + + @Override + public String toString() { + return "ModInfo{" + "id=" + metadata.getId() + ", version=" + metadata.getVersion() + ", jarPath=" + jarPath + '}'; + } + + public String getVersion() { + if (metadata == null) { + return "0.0.0"; + } + return versionToString(metadata.getVersion()); + } + } +} diff --git a/src/main/java/org/betterx/worlds/together/util/PathUtil.java b/src/main/java/org/betterx/worlds/together/util/PathUtil.java new file mode 100644 index 00000000..b120f4ae --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/util/PathUtil.java @@ -0,0 +1,100 @@ +package org.betterx.worlds.together.util; + +import net.fabricmc.loader.api.FabricLoader; + +import java.io.File; +import java.nio.file.Path; +import java.util.function.Consumer; + +public class PathUtil { + public final static Path GAME_FOLDER = FabricLoader.getInstance() + .getGameDir() + .normalize(); + + public final static Path MOD_FOLDER = FabricLoader.getInstance() + .getGameDir() + .resolve("mods") + .normalize(); + + public final static Path MOD_BAK_FOLDER = MOD_FOLDER.resolve("_bclib_deactivated") + .normalize(); + + /** + * Tests if a path is a child-path. + *

+ * A path is a child of another if it is located in the parent or any of the parents subdirectories + * + * @param parent The folder we search for the {@code child} + * @param child The folder you want to test + * @return {@code true} if {@code child} is in {@code parent} or any of its sub directories + */ + public static boolean isChildOf(Path parent, Path child) { + if (child == null || parent == null) return false; + parent = parent.toAbsolutePath().normalize(); + child = child.toAbsolutePath().normalize(); + + final int pCount = parent.getNameCount(); + final int cCount = child.getNameCount(); + + if (cCount > pCount) return isChildOf(parent, child.getParent()); + if (cCount < pCount) return false; + + return child.equals(parent); + } + + /** + * A simple directory walker that ignores dot-files + * + * @param path The path where you want to start + * @param pathConsumer The consumer called for each valid file. The consumer will get an absolute {@link Path}-Object + * for each visited file + */ + public static void fileWalker(File path, Consumer pathConsumer) { + fileWalker(path, true, pathConsumer); + } + + /** + * A simple directory walker that ignores dot-files + * + * @param path The path where you want to start + * @param recursive if {@code false}, only the {@code path} is traversed + * @param pathConsumer The consumer called for each valid file. The consumer will get an absolute {@link Path}-Object + * for each visited file + */ + public static void fileWalker(File path, boolean recursive, Consumer pathConsumer) { + if (!path.exists()) return; + for (final File f : path.listFiles()) { + if (f.getName() + .startsWith(".")) continue; + if (f.isDirectory()) { + if (recursive) fileWalker(f, pathConsumer); + } else if (f.isFile()) { + pathConsumer.accept(f.toPath()); + } + } + } + + /** + * Creates a human readable File-Size + * + * @param size Filesize in bytes + * @return A Human readable String + */ + public static String humanReadableFileSize(long size) { + final int threshold = 2; + final int factor = 1024; + if (size < 0) return "? Byte"; + if (size < factor * threshold) { + return size + " Byte"; + } + char[] units = {'K', 'M', 'G', 'T', 'P'}; + int unitIndex = 0; + double fSize = size; + do { + unitIndex++; + fSize /= 1024; + } while (fSize > factor * threshold && unitIndex < units.length); + + return String.format("%.1f %ciB", fSize, units[unitIndex - 1]); + } +} diff --git a/src/main/java/org/betterx/worlds/together/world/BiomeSourceWithNoiseRelatedSettings.java b/src/main/java/org/betterx/worlds/together/world/BiomeSourceWithNoiseRelatedSettings.java new file mode 100644 index 00000000..558172e7 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/BiomeSourceWithNoiseRelatedSettings.java @@ -0,0 +1,7 @@ +package org.betterx.worlds.together.world; + +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; + +public interface BiomeSourceWithNoiseRelatedSettings { + void onLoadGeneratorSettings(NoiseGeneratorSettings generator); +} diff --git a/src/main/java/org/betterx/worlds/together/world/BiomeSourceWithSeed.java b/src/main/java/org/betterx/worlds/together/world/BiomeSourceWithSeed.java new file mode 100644 index 00000000..ecb0dafd --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/BiomeSourceWithSeed.java @@ -0,0 +1,5 @@ +package org.betterx.worlds.together.world; + +public interface BiomeSourceWithSeed { + void setSeed(long seed); +} diff --git a/src/main/java/org/betterx/worlds/together/world/WorldConfig.java b/src/main/java/org/betterx/worlds/together/world/WorldConfig.java new file mode 100644 index 00000000..de239a08 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/WorldConfig.java @@ -0,0 +1,170 @@ +package org.betterx.worlds.together.world; + +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.util.ModUtil; +import org.betterx.worlds.together.world.event.WorldBootstrap; + +import net.minecraft.Util; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.jetbrains.annotations.ApiStatus; + +/** + * Mod-specifix data-storage for a world. + *

+ * This class provides the ability for mod to store persistent data inside a world. The Storage for the world is + * currently initialized as part of the {@link WorldBootstrap} in + * org.betterx.worlds.together.world.event.WorldBootstrap.Helpers#initializeWorldDataAPI(File, boolean) + */ +public class WorldConfig { + private static final Map TAGS = Maps.newHashMap(); + private static final List MODS = Lists.newArrayList(); + + private static final String TAG_CREATED = "create_version"; + private static final String TAG_MODIFIED = "modify_version"; + private static File dataDir; + + @ApiStatus.Internal + public static void setDataDir(File dataDir) { + WorldConfig.dataDir = dataDir; + } + + public static void load(File dataDir) { + WorldConfig.setDataDir(dataDir); + MODS.stream() + .parallel() + .forEach(modID -> { + File file = new File(dataDir, modID + ".nbt"); + CompoundTag root = new CompoundTag(); + if (file.exists()) { + try { + root = NbtIo.readCompressed(file); + } catch (IOException e) { + WorldsTogether.LOGGER.error("World data loading failed", e); + } + TAGS.put(modID, root); + } else { + Optional optional = FabricLoader.getInstance() + .getModContainer(modID); + if (optional.isPresent()) { + ModContainer modContainer = optional.get(); + if (WorldsTogether.isDevEnvironment()) { + root.putString("version", "255.255.9999"); + } else { + root.putString("version", modContainer.getMetadata() + .getVersion() + .toString()); + } + TAGS.put(modID, root); + saveFile(modID); + } + } + }); + } + + /** + * Register mod cache, world cache is located in world data folder. + * + * @param modID - {@link String} modID. + */ + public static void registerModCache(String modID) { + if (!MODS.contains(modID)) + MODS.add(modID); + } + + /** + * Get root {@link CompoundTag} for mod cache in world data folder. + * + * @param modID - {@link String} modID. + * @return {@link CompoundTag} + */ + public static CompoundTag getRootTag(String modID) { + CompoundTag root = TAGS.get(modID); + if (root == null) { + root = new CompoundTag(); + root.putString(TAG_CREATED, ModUtil.getModVersion(modID)); + TAGS.put(modID, root); + } + return root; + } + + public static boolean hasMod(String modID) { + return MODS.contains(modID); + } + + /** + * Get {@link CompoundTag} with specified path from mod cache in world data folder. + * + * @param modID - {@link String} path to tag, dot-separated. + * @return {@link CompoundTag} + */ + public static CompoundTag getCompoundTag(String modID, String path) { + String[] parts = path.split("\\."); + CompoundTag tag = getRootTag(modID); + for (String part : parts) { + if (tag.contains(part)) { + tag = tag.getCompound(part); + } else { + CompoundTag t = new CompoundTag(); + tag.put(part, t); + tag = t; + } + } + return tag; + } + + /** + * Forces mod cache file to be saved. + * + * @param modID {@link String} mod ID. + */ + public static void saveFile(String modID) { + try { + if (dataDir != null && !dataDir.exists()) { + dataDir.mkdirs(); + } + CompoundTag tag = getRootTag(modID); + tag.putString(TAG_MODIFIED, ModUtil.getModVersion(modID)); + + + final File tempFile = new File(dataDir, modID + "_temp.nbt"); + NbtIo.writeCompressed(tag, tempFile); + + final File oldFile = new File(dataDir, modID + "_old.nbt"); + final File dataFile = new File(dataDir, modID + ".nbt"); + Util.safeReplaceFile(dataFile, tempFile, oldFile); + } catch (IOException e) { + WorldsTogether.LOGGER.error("World data saving failed", e); + } + } + + /** + * Get stored mod version (only for mods with registered cache). + * + * @return {@link String} mod version. + */ + public static String getModVersion(String modID) { + return getRootTag(modID).getString("version"); + } + + /** + * Get stored mod version as integer (only for mods with registered cache). + * + * @return {@code int} mod version. + */ + public static int getIntModVersion(String modID) { + return ModUtil.convertModVersion(getModVersion(modID)); + } +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/BeforeAddingTags.java b/src/main/java/org/betterx/worlds/together/world/event/BeforeAddingTags.java new file mode 100644 index 00000000..1908161d --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/BeforeAddingTags.java @@ -0,0 +1,14 @@ +package org.betterx.worlds.together.world.event; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagLoader; + +import java.util.List; +import java.util.Map; + +public interface BeforeAddingTags { + void apply( + String directory, + Map> tagsMap + ); +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/BeforeServerWorldLoad.java b/src/main/java/org/betterx/worlds/together/world/event/BeforeServerWorldLoad.java new file mode 100644 index 00000000..d2d45c83 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/BeforeServerWorldLoad.java @@ -0,0 +1,17 @@ +package org.betterx.worlds.together.world.event; + +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.storage.LevelStorageSource; + +import java.util.Map; + +@FunctionalInterface +public interface BeforeServerWorldLoad { + void prepareWorld( + LevelStorageSource.LevelStorageAccess storageAccess, + Map, ChunkGenerator> dimension, + boolean isNewWorld + ); +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/BeforeWorldLoad.java b/src/main/java/org/betterx/worlds/together/world/event/BeforeWorldLoad.java new file mode 100644 index 00000000..501133fd --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/BeforeWorldLoad.java @@ -0,0 +1,11 @@ +package org.betterx.worlds.together.world.event; + +import net.minecraft.world.level.storage.LevelStorageSource; + +public interface BeforeWorldLoad { + void prepareWorld( + LevelStorageSource.LevelStorageAccess storageAccess, + boolean isNewWorld, + boolean isServer + ); +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/Event.java b/src/main/java/org/betterx/worlds/together/world/event/Event.java new file mode 100644 index 00000000..1e16526c --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/Event.java @@ -0,0 +1,5 @@ +package org.betterx.worlds.together.world.event; + +public interface Event { + boolean on(T handler); +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/EventImpl.java b/src/main/java/org/betterx/worlds/together/world/event/EventImpl.java new file mode 100644 index 00000000..5fcb9b5a --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/EventImpl.java @@ -0,0 +1,22 @@ +package org.betterx.worlds.together.world.event; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; + +public class EventImpl implements Event { + final List handlers = new LinkedList<>(); + + public final boolean on(T handler) { + if (!handlers.contains(handler)) { + handlers.add(handler); + return true; + } + + return false; + } + + public final void emit(Consumer c) { + handlers.forEach(c); + } +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/OnFinalizeLevelStem.java b/src/main/java/org/betterx/worlds/together/world/event/OnFinalizeLevelStem.java new file mode 100644 index 00000000..581d993f --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/OnFinalizeLevelStem.java @@ -0,0 +1,10 @@ +package org.betterx.worlds.together.world.event; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.dimension.LevelStem; + +@FunctionalInterface +public interface OnFinalizeLevelStem { + void now(Registry dimensionRegistry, ResourceKey dimensionKey, LevelStem stem); +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/OnFinalizeWorldLoad.java b/src/main/java/org/betterx/worlds/together/world/event/OnFinalizeWorldLoad.java new file mode 100644 index 00000000..980dab74 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/OnFinalizeWorldLoad.java @@ -0,0 +1,8 @@ +package org.betterx.worlds.together.world.event; + +import net.minecraft.core.Registry; +import net.minecraft.world.level.dimension.LevelStem; + +public interface OnFinalizeWorldLoad { + void done(Registry dimensionRegistry); +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/OnWorldLoad.java b/src/main/java/org/betterx/worlds/together/world/event/OnWorldLoad.java new file mode 100644 index 00000000..bf86c687 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/OnWorldLoad.java @@ -0,0 +1,5 @@ +package org.betterx.worlds.together.world.event; + +public interface OnWorldLoad { + void onLoad(); +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/OnWorldPatch.java b/src/main/java/org/betterx/worlds/together/world/event/OnWorldPatch.java new file mode 100644 index 00000000..58d42b60 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/OnWorldPatch.java @@ -0,0 +1,13 @@ +package org.betterx.worlds.together.world.event; + +import net.minecraft.world.level.storage.LevelStorageSource; + +import java.util.function.Consumer; + +@FunctionalInterface +public interface OnWorldPatch { + boolean next( + LevelStorageSource.LevelStorageAccess storageAccess, + Consumer allDone + ); +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/OnWorldRegistryReady.java b/src/main/java/org/betterx/worlds/together/world/event/OnWorldRegistryReady.java new file mode 100644 index 00000000..08a7cfec --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/OnWorldRegistryReady.java @@ -0,0 +1,7 @@ +package org.betterx.worlds.together.world.event; + +import net.minecraft.core.RegistryAccess; + +public interface OnWorldRegistryReady { + void initRegistry(RegistryAccess access); +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/PatchWorldEvent.java b/src/main/java/org/betterx/worlds/together/world/event/PatchWorldEvent.java new file mode 100644 index 00000000..850f27af --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/PatchWorldEvent.java @@ -0,0 +1,40 @@ +package org.betterx.worlds.together.world.event; + +import net.minecraft.world.level.storage.LevelStorageSource; + +import java.util.Iterator; +import java.util.function.Consumer; + +class PatchWorldEvent extends EventImpl { + + public boolean applyPatches( + LevelStorageSource.LevelStorageAccess storageAccess, + Consumer allDone + ) { + return applyPatches(false, false, storageAccess, handlers.iterator(), allDone); + } + + private boolean applyPatches( + boolean didApplyFixes, + boolean didShowUI, + LevelStorageSource.LevelStorageAccess storageAccess, + Iterator iterator, + Consumer allDone + ) { + if (!iterator.hasNext()) { + if (didShowUI) allDone.accept(didApplyFixes); + return didApplyFixes; + } + OnWorldPatch now = iterator.next(); + + boolean shouldHaltForUI = now.next(storageAccess, (appliedFixes) -> applyPatches( + didApplyFixes || appliedFixes, true, storageAccess, iterator, allDone + )); + + if (!shouldHaltForUI) { + applyPatches(didApplyFixes, didShowUI, storageAccess, iterator, allDone); + } + return didApplyFixes || shouldHaltForUI; + } + +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/WorldBootstrap.java b/src/main/java/org/betterx/worlds/together/world/event/WorldBootstrap.java new file mode 100644 index 00000000..6d1f100a --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/WorldBootstrap.java @@ -0,0 +1,205 @@ +package org.betterx.worlds.together.world.event; + +import org.betterx.bclib.config.Configs; +import org.betterx.worlds.together.WorldsTogether; +import org.betterx.worlds.together.world.WorldConfig; + +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.presets.WorldPreset; +import net.minecraft.world.level.storage.LevelResource; +import net.minecraft.world.level.storage.LevelStorageSource; + +import java.io.File; +import java.util.Optional; +import java.util.function.Consumer; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class WorldBootstrap { + private static RegistryAccess LAST_REGISTRY_ACCESS = null; + + public static RegistryAccess getLastRegistryAccess() { + return LAST_REGISTRY_ACCESS; + } + + private static byte WARN_COUNT_GLOBAL_REGISTRY = 0; + + public static RegistryAccess getLastRegistryAccessOrElseBuiltin() { + if (WARN_COUNT_GLOBAL_REGISTRY < 10 && LAST_REGISTRY_ACCESS == null && Configs.MAIN_CONFIG.verboseLogging()) { + WorldsTogether.LOGGER.error("Tried to read from global registry!"); + WARN_COUNT_GLOBAL_REGISTRY++; + } + return LAST_REGISTRY_ACCESS; + } + + + public static class Helpers { + private static void initializeWorldConfig( + LevelStorageSource.LevelStorageAccess levelStorageAccess, + boolean newWorld + ) { + File levelPath = levelStorageAccess.getLevelPath(LevelResource.ROOT).toFile(); + initializeWorldConfig(levelPath, newWorld); + } + + private static void initializeWorldConfig(File levelBaseDir, boolean newWorld) { + WorldConfig.load(new File(levelBaseDir, "data")); + } + + public static void onRegistryReady(RegistryAccess a) { + if (a != LAST_REGISTRY_ACCESS) { + LAST_REGISTRY_ACCESS = a; + WorldEventsImpl.WORLD_REGISTRY_READY.emit(e -> e.initRegistry(a)); + } + } + + + private static Holder presetFromDatapack(Holder currentPreset) { + if (currentPreset != null && LAST_REGISTRY_ACCESS != null) { + Optional> presetKey = currentPreset.unwrapKey(); + if (presetKey.isPresent()) { + Optional> newPreset = LAST_REGISTRY_ACCESS + .registryOrThrow(Registries.WORLD_PRESET) + .getHolder(presetKey.get()); + currentPreset = newPreset.orElse(null); + } + } + return currentPreset; + } + } + + public static class DedicatedServer { + public static void registryReady(RegistryAccess acc) { + Helpers.onRegistryReady(acc); + } + + public static void setupWorld(LevelStorageSource.LevelStorageAccess levelStorageAccess) { + File levelDat = levelStorageAccess.getLevelPath(LevelResource.LEVEL_DATA_FILE).toFile(); + if (!levelDat.exists()) { + WorldsTogether.LOGGER.info("Creating a new World, no fixes needed"); + + WorldBootstrap.setupWorld(levelStorageAccess, true, true); + finishedWorldLoad(); + } else { + WorldBootstrap.setupWorld(levelStorageAccess, false, true); + finishedWorldLoad(); + } + } + + public static boolean applyWorldPatches( + LevelStorageSource.LevelStorageAccess levelStorageAccess + ) { + boolean result = false; + if (levelStorageAccess.getLevelPath(LevelResource.LEVEL_DATA_FILE).toFile().exists()) { + try { + result = WorldEventsImpl.PATCH_WORLD.applyPatches(levelStorageAccess, null); + } catch (Exception e) { + WorldsTogether.LOGGER.error("Failed to initialize data in world", e); + } + } + + return result; + } + } + + public static class InGUI { + + public static void registryReady(RegistryAccess access) { + Helpers.onRegistryReady(access); + } + + public static void setupNewWorld( + Optional levelStorageAccess + ) { + + if (levelStorageAccess.isPresent()) { + setupNewWorldCommon(levelStorageAccess.get()); + } else { + WorldsTogether.LOGGER.error("Unable to access Level Folder."); + } + + } + + public static void setupNewWorldCommon( + LevelStorageSource.LevelStorageAccess levelStorageAccess + ) { + setupWorld(levelStorageAccess, true, false); + finishedWorldLoad(); + } + + /** + * Does not call {@link WorldEventsImpl#ON_WORLD_LOAD} + */ + public static void setupLoadedWorld( + String levelID, + LevelStorageSource levelSource + ) { + try { + var levelStorageAccess = levelSource.createAccess(levelID); + WorldBootstrap.setupWorld( + levelStorageAccess, + false, false + ); + levelStorageAccess.close(); + } catch (Exception e) { + WorldsTogether.LOGGER.error("Failed to acquire storage access", e); + } + } + + public static boolean applyWorldPatches( + LevelStorageSource levelSource, + String levelID, + Consumer onResume + ) { + boolean result = false; + try { + var levelStorageAccess = levelSource.createAccess(levelID); + result = WorldEventsImpl.PATCH_WORLD.applyPatches(levelStorageAccess, onResume); + levelStorageAccess.close(); + } catch (Exception e) { + WorldsTogether.LOGGER.error("Failed to initialize data in world", e); + } + + return result; + } + + } + + private static void setupWorld( + LevelStorageSource.LevelStorageAccess levelStorageAccess, + boolean newWorld, boolean isServer + ) { + try { + Helpers.initializeWorldConfig(levelStorageAccess, newWorld); + WorldEventsImpl.BEFORE_WORLD_LOAD.emit(e -> e.prepareWorld( + levelStorageAccess, + newWorld, isServer + )); + } catch (Exception e) { + WorldsTogether.LOGGER.error("Failed to initialize data in world", e); + } + } + + public static void finishedWorldLoad() { + WorldEventsImpl.ON_WORLD_LOAD.emit(OnWorldLoad::onLoad); + } + + public static void finalizeWorldGenSettings(Registry dimensionRegistry) { + for (var entry : dimensionRegistry.entrySet()) { + WorldEventsImpl.ON_FINALIZE_LEVEL_STEM.emit(e -> e.now( + dimensionRegistry, + entry.getKey(), + entry.getValue() + )); + } + + WorldEventsImpl.ON_FINALIZED_WORLD_LOAD.emit(e -> e.done(dimensionRegistry)); + } + + +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/WorldEvents.java b/src/main/java/org/betterx/worlds/together/world/event/WorldEvents.java new file mode 100644 index 00000000..4ad14f95 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/WorldEvents.java @@ -0,0 +1,12 @@ +package org.betterx.worlds.together.world.event; + +public class WorldEvents { + public static final Event WORLD_REGISTRY_READY = WorldEventsImpl.WORLD_REGISTRY_READY; + public static final Event BEFORE_WORLD_LOAD = WorldEventsImpl.BEFORE_WORLD_LOAD; + public static final Event ON_WORLD_LOAD = WorldEventsImpl.ON_WORLD_LOAD; + public static final Event ON_FINALIZE_LEVEL_STEM = WorldEventsImpl.ON_FINALIZE_LEVEL_STEM; + public static final Event ON_FINALIZED_WORLD_LOAD = WorldEventsImpl.ON_FINALIZED_WORLD_LOAD; + public static final Event PATCH_WORLD = WorldEventsImpl.PATCH_WORLD; + + public static final Event BEFORE_ADDING_TAGS = WorldEventsImpl.BEFORE_ADDING_TAGS; +} diff --git a/src/main/java/org/betterx/worlds/together/world/event/WorldEventsImpl.java b/src/main/java/org/betterx/worlds/together/world/event/WorldEventsImpl.java new file mode 100644 index 00000000..1c2719c4 --- /dev/null +++ b/src/main/java/org/betterx/worlds/together/world/event/WorldEventsImpl.java @@ -0,0 +1,18 @@ +package org.betterx.worlds.together.world.event; + +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class WorldEventsImpl { + public static final EventImpl WORLD_REGISTRY_READY = new EventImpl<>(); + public static final EventImpl BEFORE_WORLD_LOAD = new EventImpl<>(); + + public static final EventImpl ON_WORLD_LOAD = new EventImpl<>(); + public static final EventImpl ON_FINALIZE_LEVEL_STEM = new EventImpl<>(); + public static final EventImpl ON_FINALIZED_WORLD_LOAD = new EventImpl<>(); + + public static final PatchWorldEvent PATCH_WORLD = new PatchWorldEvent(); + + + public static final EventImpl BEFORE_ADDING_TAGS = new EventImpl<>(); +} diff --git a/src/main/java/ru/bclib/BCLib.java b/src/main/java/ru/bclib/BCLib.java deleted file mode 100644 index e3bbee16..00000000 --- a/src/main/java/ru/bclib/BCLib.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.bclib; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.ModInitializer; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.resources.ResourceLocation; -import ru.bclib.api.TagAPI; -import ru.bclib.config.Configs; -import ru.bclib.recipes.CraftingRecipes; -import ru.bclib.registry.BaseBlockEntities; -import ru.bclib.registry.BaseRegistry; -import ru.bclib.util.Logger; -import ru.bclib.world.surface.BCLSurfaceBuilders; - -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() { - BaseRegistry.register(); - BaseBlockEntities.register(); - BCLSurfaceBuilders.register(); - TagAPI.init(); - CraftingRecipes.init(); - Configs.save(); - } - - 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); - } -} diff --git a/src/main/java/ru/bclib/api/BiomeAPI.java b/src/main/java/ru/bclib/api/BiomeAPI.java deleted file mode 100644 index 0a1cb7b5..00000000 --- a/src/main/java/ru/bclib/api/BiomeAPI.java +++ /dev/null @@ -1,158 +0,0 @@ -package ru.bclib.api; - -import java.util.HashMap; -import java.util.Random; - -import com.google.common.collect.Maps; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.impl.biome.InternalBiomeData; -import net.minecraft.client.Minecraft; -import net.minecraft.core.Registry; -import net.minecraft.data.BuiltinRegistries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; -import net.minecraft.world.level.biome.Biome; -import net.minecraft.world.level.biome.Biome.ClimateParameters; -import net.minecraft.world.level.biome.Biomes; -import ru.bclib.util.MHelper; -import ru.bclib.world.biomes.BCLBiome; - -public class BiomeAPI { - /** - * 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(), BuiltinRegistries.BIOME.get(Biomes.THE_VOID), 1, 0); - - private static final HashMap ID_MAP = Maps.newHashMap(); - private static final HashMap CLIENT = Maps.newHashMap(); - private static Registry biomeRegistry; - - /** - * Initialize registry for current server. - * @param server - {@link MinecraftServer} - */ - public static void initRegistry(MinecraftServer server) { - biomeRegistry = server.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY); - CLIENT.clear(); - } - - public static void registerBiome(BCLBiome biome) { - if (BuiltinRegistries.BIOME.get(biome.getID()) == null) { - Registry.register(BuiltinRegistries.BIOME, biome.getID(), biome.getBiome()); - } - ID_MAP.put(biome.getID(), biome); - } - - /** - * Adds {@link BCLBiome} to FabricAPI biomes as the Nether biome (with random {@link ClimateParameters}). - * @param biome - {@link BCLBiome}. - */ - public static void addNetherBiomeToFabricApi(BCLBiome biome) { - ResourceKey key = BuiltinRegistries.BIOME.getResourceKey(biome.getBiome()).get(); - Random random = new Random(biome.getID().toString().hashCode()); - ClimateParameters parameters = new ClimateParameters( - MHelper.randRange(-2F, 2F, random), - MHelper.randRange(-2F, 2F, random), - MHelper.randRange(-2F, 2F, random), - MHelper.randRange(-2F, 2F, random), - MHelper.randRange(-2F, 2F, random) - ); - InternalBiomeData.addNetherBiome(key, parameters); - } - - /** - * Adds {@link BCLBiome} to FabricAPI biomes as an End land biome (generating on islands). - * @param biome - {@link BCLBiome}. - */ - public static void addEndLandBiomeToFabricApi(BCLBiome biome) { - float weight = biome.getGenChance(); - ResourceKey key = BuiltinRegistries.BIOME.getResourceKey(biome.getBiome()).get(); - InternalBiomeData.addEndBiomeReplacement(Biomes.END_HIGHLANDS, key, weight); - InternalBiomeData.addEndBiomeReplacement(Biomes.END_MIDLANDS, key, weight); - } - - /** - * Adds {@link BCLBiome} to FabricAPI biomes as an End void biome (generating between islands in the void). - * @param biome - {@link BCLBiome}. - */ - public static void addEndVoidBiomeToFabricApi(BCLBiome biome) { - float weight = biome.getGenChance(); - ResourceKey key = BuiltinRegistries.BIOME.getResourceKey(biome.getBiome()).get(); - InternalBiomeData.addEndBiomeReplacement(Biomes.SMALL_END_ISLANDS, key, weight); - } - - /** - * Get {@link BCLBiome} from {@link Biome} instance on server. Used to convert world biomes to BCLBiomes. - * @param biome - {@link Biome} from world. - * @return {@link BCLBiome} or {@code BiomeAPI.EMPTY_BIOME}. - */ - public static BCLBiome getFromBiome(Biome biome) { - if (biomeRegistry == null) { - return EMPTY_BIOME; - } - return ID_MAP.getOrDefault(biomeRegistry.getKey(biome), 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 = 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); - CLIENT.put(biome, endBiome); - } - return endBiome; - } - - /** - * 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 = biomeRegistry.getKey(biome); - return id == null ? EMPTY_BIOME.getID() : id; - } - - /** - * 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 actual {@link Biome} from given {@link BCLBiome}. If it is null it will request it from current {@link Registry}. - * @param biome - {@link BCLBiome}. - * @return {@link Biome}. - */ - public static Biome getActualBiome(BCLBiome biome) { - Biome actual = biome.getActualBiome(); - if (actual == null) { - biome.updateActualBiomes(biomeRegistry); - actual = biome.getActualBiome(); - } - return actual; - } - - /** - * 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); - } -} diff --git a/src/main/java/ru/bclib/api/BonemealAPI.java b/src/main/java/ru/bclib/api/BonemealAPI.java deleted file mode 100644 index 4e9a200f..00000000 --- a/src/main/java/ru/bclib/api/BonemealAPI.java +++ /dev/null @@ -1,128 +0,0 @@ -package ru.bclib.api; - -import java.util.Map; -import java.util.Random; -import java.util.Set; - -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.block.Block; -import ru.bclib.util.WeightedList; - -public class BonemealAPI { - private static final Map>> WATER_GRASS_BIOMES = Maps.newHashMap(); - private static final Map>> LAND_GRASS_BIOMES = Maps.newHashMap(); - private static final Map> WATER_GRASS_TYPES = Maps.newHashMap(); - private static final Map> LAND_GRASS_TYPES = Maps.newHashMap(); - private static final Set SPREADABLE_BLOCKS = Sets.newHashSet(); - - public static void addSpreadableBlock(Block block) { - SPREADABLE_BLOCKS.add(block); - } - - public static boolean isSpreadable(Block block) { - return SPREADABLE_BLOCKS.contains(block); - } - - public static void addLandGrass(Block plant, Block... terrain) { - for (Block block: terrain) { - addLandGrass(block, plant, 1F); - } - } - - public static void addLandGrass(ResourceLocation biome, Block plant, Block... terrain) { - for (Block block: terrain) { - addLandGrass(biome, block, plant, 1F); - } - } - - public static void addLandGrass(Block terrain, Block plant, float chance) { - WeightedList list = LAND_GRASS_TYPES.get(terrain); - if (list == null) { - list = new WeightedList(); - LAND_GRASS_TYPES.put(terrain, list); - } - list.add(plant, chance); - } - - public static void addLandGrass(ResourceLocation biome, Block terrain, Block plant, float chance) { - Map> map = LAND_GRASS_BIOMES.get(biome); - if (map == null) { - map = Maps.newHashMap(); - LAND_GRASS_BIOMES.put(biome, map); - } - WeightedList list = map.get(terrain); - if (list == null) { - list = new WeightedList(); - map.put(terrain, list); - } - list.add(plant, chance); - } - - public static void addWaterGrass(Block plant, Block... terrain) { - for (Block block: terrain) { - addWaterGrass(block, plant, 1F); - } - } - - public static void addWaterGrass(ResourceLocation biome, Block plant, Block... terrain) { - for (Block block: terrain) { - addWaterGrass(biome, block, plant, 1F); - } - } - - public static void addWaterGrass(Block terrain, Block plant, float chance) { - WeightedList list = WATER_GRASS_TYPES.get(terrain); - if (list == null) { - list = new WeightedList(); - WATER_GRASS_TYPES.put(terrain, list); - } - list.add(plant, chance); - } - - public static void addWaterGrass(ResourceLocation biome, Block terrain, Block plant, float chance) { - Map> map = WATER_GRASS_BIOMES.get(biome); - if (map == null) { - map = Maps.newHashMap(); - WATER_GRASS_BIOMES.put(biome, map); - } - WeightedList list = map.get(terrain); - if (list == null) { - list = new WeightedList(); - map.put(terrain, list); - } - list.add(plant, chance); - } - - public static Block getLandGrass(ResourceLocation biomeID, Block terrain, Random random) { - Map> map = LAND_GRASS_BIOMES.get(biomeID); - WeightedList list = null; - 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 Block getWaterGrass(ResourceLocation biomeID, Block terrain, Random random) { - Map> map = LAND_GRASS_BIOMES.get(biomeID); - WeightedList list = null; - 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); - } -} diff --git a/src/main/java/ru/bclib/api/DataFixerAPI.java b/src/main/java/ru/bclib/api/DataFixerAPI.java deleted file mode 100644 index cf644539..00000000 --- a/src/main/java/ru/bclib/api/DataFixerAPI.java +++ /dev/null @@ -1,149 +0,0 @@ -package ru.bclib.api; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtIo; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.chunk.storage.RegionFile; - -public class DataFixerAPI { - private static final Map REPLACEMENT = Maps.newHashMap(); - private static final Map FIX_VERSIONS = Maps.newHashMap(); - - public static void fixData(File dir) { - REPLACEMENT.clear(); // API is not finished yet! - if (REPLACEMENT.isEmpty()) { - return; - } - - boolean shoudFix = false; - Collection mods = FabricLoader.getInstance().getAllMods(); - for (ModContainer mod: mods) { - String name = mod.getMetadata().getId(); - int preVersion = WorldDataAPI.getIntModVersion(name); - int version = getModVersion(mod.getMetadata().getVersion().toString()); - if (version > preVersion) { - int fixVersion = FIX_VERSIONS.getOrDefault(name, version); - shoudFix |= fixVersion < version && fixVersion >= preVersion; - } - }; - if (!shoudFix) { - return; - } - - List regions = getAllRegions(dir, null); - regions.parallelStream().forEach((file) -> { - try { - System.out.println("Fixing " + file); - boolean[] changed = new boolean[1]; - RegionFile region = new RegionFile(file, file.getParentFile(), 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)) { - DataInputStream input = region.getChunkDataInputStream(pos); - CompoundTag root = NbtIo.read(input); - input.close(); - ListTag sections = root.getCompound("Level").getList("Sections", 10); - sections.forEach((tag) -> { - ListTag palette = ((CompoundTag) tag).getList("Palette", 10); - palette.forEach((blockTag) -> { - CompoundTag blockTagCompound = ((CompoundTag) blockTag); - String name = blockTagCompound.getString("Name"); - String replace = REPLACEMENT.get(name); - if (replace != null) { - blockTagCompound.putString("Name", replace); - changed[0] = true; - } - }); - }); - if (changed[0]) { - System.out.println("Write!"); - DataOutputStream output = region.getChunkDataOutputStream(pos); - NbtIo.write(root, output); - output.close(); - } - } - } - } - region.close(); - } - catch (Exception e) { - e.printStackTrace(); - } - }); - } - - /** - * Register block data fix. Fix will be applied on world load if current mod version will be newer than specified one. - * @param modID - {@link String} mod id; - * @param modVersion - {@link String} mod version, should be in format: %d.%d.%d - * @param result - {@link String} new block name; - * @param names - array of {@link String}, old block names to convert. - */ - protected static void addFix(String modID, String modVersion, String result, String... names) { - FIX_VERSIONS.put(modID, getModVersion(modVersion)); - for (String name: names) { - REPLACEMENT.put(name, result); - } - } - - private static List getAllRegions(File dir, List list) { - if (list == null) { - list = Lists.newArrayList(); - } - for (File file: dir.listFiles()) { - if (file.isDirectory()) { - getAllRegions(file, list); - } - else if (file.isFile() && file.getName().endsWith(".mca")) { - list.add(file); - } - } - return list; - } - - /** - * Get mod version from string. String should be in format: %d.%d.%d - * @param version - {@link String} mod version. - * @return int mod version. - */ - public static int getModVersion(String version) { - if (version.isEmpty()) { - return 0; - } - try { - String[] values = version.split("\\."); - return Integer.parseInt(values[0]) << 12 | Integer.parseInt(values[1]) << 6 | Integer.parseInt(values[2]); - } - catch (Exception e) { - return 0; - } - } - - /** - * Get mod version from integer. String will be in format %d.%d.%d - * @param version - mod version in integer form. - * @return {@link String} mod version. - */ - public static String getModVersion(int version) { - int a = (version >> 12) & 63; - int b = (version >> 6) & 63; - int c = version & 63; - return String.format(Locale.ROOT, "%d.%d.%d", a, b, c); - } -} diff --git a/src/main/java/ru/bclib/api/ModIntegrationAPI.java b/src/main/java/ru/bclib/api/ModIntegrationAPI.java deleted file mode 100644 index eff311dc..00000000 --- a/src/main/java/ru/bclib/api/ModIntegrationAPI.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.bclib.api; - -import java.util.List; - -import com.google.common.collect.Lists; - -import ru.bclib.integration.ModIntegration; - -public class ModIntegrationAPI { - private static final List INTEGRATIONS = Lists.newArrayList(); - - /** - * 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 getIntegrations() { - return INTEGRATIONS; - } - - /** - * Initialize all integrations, only for internal usage. - */ - public static void registerAll() { - INTEGRATIONS.forEach(integration -> { - if (integration.modIsInstalled()) { - integration.init(); - } - }); - } -} diff --git a/src/main/java/ru/bclib/api/TagAPI.java b/src/main/java/ru/bclib/api/TagAPI.java deleted file mode 100644 index 8a4accba..00000000 --- a/src/main/java/ru/bclib/api/TagAPI.java +++ /dev/null @@ -1,131 +0,0 @@ -package ru.bclib.api; - -import java.util.function.Supplier; - -import net.fabricmc.fabric.api.tag.TagRegistry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.BlockTags; -import net.minecraft.tags.ItemTags; -import net.minecraft.tags.Tag; -import net.minecraft.tags.Tag.Named; -import net.minecraft.tags.TagCollection; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.Items; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import ru.bclib.BCLib; -import ru.bclib.util.TagHelper; - -public class TagAPI { - // Block Tags - public static final Tag.Named BOOKSHELVES = makeCommonBlockTag("bookshelves"); - public static final Tag.Named GEN_TERRAIN = makeBlockTag(BCLib.MOD_ID, "gen_terrain"); - public static final Tag.Named NETHER_GROUND = makeBlockTag(BCLib.MOD_ID, "nether_ground"); - public static final Tag.Named END_GROUND = makeBlockTag(BCLib.MOD_ID, "end_ground"); - - public static final Tag.Named BLOCK_CHEST = makeCommonBlockTag("chest"); - public static final Tag.Named END_STONES = makeCommonBlockTag("end_stones"); - public static final Tag.Named NETHER_STONES = makeCommonBlockTag("nether_stones"); - - public static final Tag.Named DRAGON_IMMUNE = getMCBlockTag("dragon_immune"); - - // Item Tags - public static final Tag.Named ITEM_CHEST = makeCommonItemTag("chest"); - public static final Tag.Named IRON_INGOTS = makeCommonItemTag("iron_ingots"); - public static final Tag.Named FURNACES = makeCommonItemTag("furnaces"); - public final static Tag.Named HAMMERS = makeItemTag("fabric", "hammers"); - - /** - * Get or create {@link Tag.Named}. - * @param containerSupplier - {@link TagCollection} {@link Supplier} tag collection; - * @param id - {@link ResourceLocation} tag id. - * @return {@link Tag.Named}. - */ - public static Tag.Named makeTag(Supplier> containerSupplier, ResourceLocation id) { - Tag tag = containerSupplier.get().getTag(id); - return tag == null ? TagRegistry.create(id, containerSupplier) : (Named) tag; - } - - /** - * Get or create {@link Block} {@link Tag.Named} with mod namespace. - * @param modID - {@link String} mod namespace (mod id); - * @param name - {@link String} tag name. - * @return {@link Block} {@link Tag.Named}. - */ - public static Tag.Named makeBlockTag(String modID, String name) { - return makeTag(BlockTags::getAllTags, new ResourceLocation(modID, name)); - } - - /** - * Get or create {@link Item} {@link Tag.Named} with mod namespace. - * @param modID - {@link String} mod namespace (mod id); - * @param name - {@link String} tag name. - * @return {@link Item} {@link Tag.Named}. - */ - public static Tag.Named makeItemTag(String modID, String name) { - return makeTag(ItemTags::getAllTags, new ResourceLocation(modID, name)); - } - - /** - * Get or create {@link Block} {@link Tag.Named}. - * @see Fabric Wiki (Tags) - * @param name - {@link String} tag name. - * @return {@link Block} {@link Tag.Named}. - */ - public static Tag.Named makeCommonBlockTag(String name) { - return makeTag(BlockTags::getAllTags, new ResourceLocation("c", name)); - } - - /** - * Get or create {@link Item} {@link Tag.Named}. - * @see Fabric Wiki (Tags) - * @param name - {@link String} tag name. - * @return {@link Item} {@link Tag.Named}. - */ - public static Tag.Named makeCommonItemTag(String name) { - return makeTag(ItemTags::getAllTags, new ResourceLocation("c", name)); - } - - /** - * Get or create Minecraft {@link Block} {@link Tag.Named}. - * @param name - {@link String} tag name. - * @return {@link Block} {@link Tag.Named}. - */ - public static Tag.Named getMCBlockTag(String name) { - ResourceLocation id = new ResourceLocation(name); - Tag tag = BlockTags.getAllTags().getTag(id); - return tag == null ? (Named) TagRegistry.block(id) : (Named) tag; - } - - /** - * Adds {@link Block} to NETHER_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic. - * @param block - {@link Block}. - */ - public static void addNetherGround(Block block) { - TagHelper.addTag(NETHER_GROUND, block); - TagHelper.addTag(GEN_TERRAIN, block); - } - - /** - * Adds {@link Block} to END_GROUND and GEN_TERRAIN tags to process it properly in terrain generators and block logic. - * @param block - {@link Block}. - */ - public static void addEndGround(Block block) { - TagHelper.addTag(GEN_TERRAIN, block); - TagHelper.addTag(END_GROUND, block); - } - - /** - * Initializes basic tags. Should be called only in BCLib main class. - */ - public static void init() { - TagHelper.addTag(BOOKSHELVES, Blocks.BOOKSHELF); - TagHelper.addTag(GEN_TERRAIN, Blocks.END_STONE, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL); - TagHelper.addTag(NETHER_GROUND, Blocks.NETHERRACK, Blocks.SOUL_SAND, Blocks.SOUL_SOIL); - TagHelper.addTag(END_GROUND, Blocks.END_STONE); - TagHelper.addTag(BLOCK_CHEST, Blocks.CHEST); - TagHelper.addTag(ITEM_CHEST, Items.CHEST); - TagHelper.addTag(IRON_INGOTS, Items.IRON_INGOT); - TagHelper.addTag(FURNACES, Blocks.FURNACE); - } -} diff --git a/src/main/java/ru/bclib/api/WorldDataAPI.java b/src/main/java/ru/bclib/api/WorldDataAPI.java deleted file mode 100644 index 6927c9ed..00000000 --- a/src/main/java/ru/bclib/api/WorldDataAPI.java +++ /dev/null @@ -1,124 +0,0 @@ -package ru.bclib.api; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import net.fabricmc.loader.api.FabricLoader; -import net.fabricmc.loader.api.ModContainer; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; -import ru.bclib.BCLib; - -public class WorldDataAPI { - private static final Map TAGS = Maps.newHashMap(); - private static final List MODS = Lists.newArrayList(); - private static File dataDir; - - public static void load(File dataDir) { - WorldDataAPI.dataDir = dataDir; - MODS.stream().parallel().forEach(modID -> { - File file = new File(dataDir, modID + ".nbt"); - CompoundTag root = new CompoundTag(); - TAGS.put(modID, root); - if (file.exists()) { - try { - root = NbtIo.readCompressed(file); - } - catch (IOException e) { - BCLib.LOGGER.error("World data loading failed", e); - } - } - else { - Optional optional = FabricLoader.getInstance().getModContainer(modID); - if (optional.isPresent()) { - ModContainer modContainer = optional.get(); - if (BCLib.isDevEnvironment()) { - root.putString("version", "63.63.63"); - } - else { - root.putString("version", modContainer.getMetadata().getVersion().toString()); - } - saveFile(modID); - } - } - }); - } - - /** - * Register mod cache, world cache is located in world data folder. - * @param modID - {@link String} modID. - */ - public static void registerModCache(String modID) { - MODS.add(modID); - } - - /** - * Get root {@link CompoundTag} for mod cache in world data folder. - * @param modID - {@link String} modID. - * @return {@link CompoundTag} - */ - public static CompoundTag getRootTag(String modID) { - CompoundTag root = TAGS.get(modID); - if (root == null) { - root = new CompoundTag(); - TAGS.put(modID, root); - } - return root; - } - - /** - * Get {@link CompoundTag} with specified path from mod cache in world data folder. - * @param modID - {@link String} path to tag, dot-separated. - * @return {@link CompoundTag} - */ - public static CompoundTag getCompoundTag(String modID, String path) { - String[] parts = path.split("\\."); - CompoundTag tag = getRootTag(modID); - for (String part: parts) { - if (tag.contains(part)) { - tag = tag.getCompound(part); - } - else { - CompoundTag t = new CompoundTag(); - tag.put(part, t); - tag = t; - } - } - return tag; - } - - /** - * Forces mod cache file to be saved. - * @param modID {@link String} mod ID. - */ - public static void saveFile(String modID) { - try { - NbtIo.writeCompressed(getRootTag(modID), new File(dataDir, modID + ".nbt")); - } - catch (IOException e) { - BCLib.LOGGER.error("World data saving failed", e); - } - } - - /** - * Get stored mod version (only for mods with registered cache). - * @return {@link String} mod version. - */ - public static String getModVersion(String modID) { - return getRootTag(modID).getString("version"); - } - - /** - * Get stored mod version as integer (only for mods with registered cache). - * @return {@code int} mod version. - */ - public static int getIntModVersion(String modID) { - return DataFixerAPI.getModVersion(getModVersion(modID)); - } -} diff --git a/src/main/java/ru/bclib/blockentities/BaseBarrelBlockEntity.java b/src/main/java/ru/bclib/blockentities/BaseBarrelBlockEntity.java deleted file mode 100644 index 488a86eb..00000000 --- a/src/main/java/ru/bclib/blockentities/BaseBarrelBlockEntity.java +++ /dev/null @@ -1,142 +0,0 @@ -package ru.bclib.blockentities; - -import net.minecraft.core.NonNullList; -import net.minecraft.core.Vec3i; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.TranslatableComponent; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.ContainerHelper; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.ChestMenu; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.BarrelBlock; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.entity.ChestBlockEntity; -import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.blocks.BaseBarrelBlock; -import ru.bclib.registry.BaseBlockEntities; - -public class BaseBarrelBlockEntity extends RandomizableContainerBlockEntity { - private NonNullList inventory; - private int viewerCount; - - private BaseBarrelBlockEntity(BlockEntityType type) { - super(type); - this.inventory = NonNullList.withSize(27, ItemStack.EMPTY); - } - - public BaseBarrelBlockEntity() { - this(BaseBlockEntities.BARREL); - } - - public CompoundTag save(CompoundTag tag) { - super.save(tag); - if (!this.trySaveLootTable(tag)) { - ContainerHelper.saveAllItems(tag, this.inventory); - } - - return tag; - } - - public void load(BlockState state, CompoundTag tag) { - super.load(state, tag); - this.inventory = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); - if (!this.tryLoadLootTable(tag)) { - ContainerHelper.loadAllItems(tag, this.inventory); - } - } - - public int getContainerSize() { - return 27; - } - - protected NonNullList getItems() { - return this.inventory; - } - - protected void setItems(NonNullList list) { - this.inventory = list; - } - - protected Component getDefaultName() { - return new TranslatableComponent("container.barrel"); - } - - protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { - return ChestMenu.threeRows(syncId, playerInventory, this); - } - - public void startOpen(Player player) { - if (!player.isSpectator()) { - if (viewerCount < 0) { - viewerCount = 0; - } - - ++viewerCount; - BlockState blockState = this.getBlockState(); - if (!blockState.getValue(BarrelBlock.OPEN)) { - playSound(blockState, SoundEvents.BARREL_OPEN); - setOpen(blockState, true); - } - - if (level != null) { - scheduleUpdate(); - } - } - } - - private void scheduleUpdate() { - level.getBlockTicks().scheduleTick(getBlockPos(), getBlockState().getBlock(), 5); - } - - public void tick() { - if (level != null) { - int x = worldPosition.getX(); - int y = worldPosition.getY(); - int z = worldPosition.getZ(); - viewerCount = ChestBlockEntity.getOpenCount(level, this, x, y, z); - if (viewerCount > 0) { - scheduleUpdate(); - } else { - BlockState blockState = getBlockState(); - if (!(blockState.getBlock() instanceof BaseBarrelBlock)) { - setRemoved(); - return; - } - if (blockState.getValue(BarrelBlock.OPEN)) { - playSound(blockState, SoundEvents.BARREL_CLOSE); - setOpen(blockState, false); - } - } - } - } - - public void stopOpen(Player player) { - if (!player.isSpectator()) { - --this.viewerCount; - } - } - - private void setOpen(BlockState state, boolean open) { - if (level != null) { - level.setBlock(this.getBlockPos(), state.setValue(BarrelBlock.OPEN, open), 3); - } - } - - private void playSound(BlockState blockState, SoundEvent soundEvent) { - if (level != null) { - Vec3i vec3i = blockState.getValue(BarrelBlock.FACING).getNormal(); - double d = (double) this.worldPosition.getX() + 0.5D + (double) vec3i.getX() / 2.0D; - double e = (double) this.worldPosition.getY() + 0.5D + (double) vec3i.getY() / 2.0D; - double f = (double) this.worldPosition.getZ() + 0.5D + (double) vec3i.getZ() / 2.0D; - level.playSound(null, d, e, f, soundEvent, SoundSource.BLOCKS, 0.5F, - this.level.random.nextFloat() * 0.1F + 0.9F); - } - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/blockentities/BaseChestBlockEntity.java b/src/main/java/ru/bclib/blockentities/BaseChestBlockEntity.java deleted file mode 100644 index 2d1ed5d4..00000000 --- a/src/main/java/ru/bclib/blockentities/BaseChestBlockEntity.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.bclib.blockentities; - -import net.minecraft.world.level.block.entity.ChestBlockEntity; -import ru.bclib.registry.BaseBlockEntities; - -public class BaseChestBlockEntity extends ChestBlockEntity { - public BaseChestBlockEntity() { - super(BaseBlockEntities.CHEST); - } -} diff --git a/src/main/java/ru/bclib/blockentities/BaseFurnaceBlockEntity.java b/src/main/java/ru/bclib/blockentities/BaseFurnaceBlockEntity.java deleted file mode 100644 index 7413bd7c..00000000 --- a/src/main/java/ru/bclib/blockentities/BaseFurnaceBlockEntity.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.bclib.blockentities; - -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.TranslatableComponent; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.FurnaceMenu; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity; -import ru.bclib.registry.BaseBlockEntities; - -public class BaseFurnaceBlockEntity extends AbstractFurnaceBlockEntity { - public BaseFurnaceBlockEntity() { - super(BaseBlockEntities.FURNACE, RecipeType.SMELTING); - } - - protected Component getDefaultName() { - return new TranslatableComponent("container.furnace"); - } - - protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { - return new FurnaceMenu(syncId, playerInventory, this, this.dataAccess); - } -} diff --git a/src/main/java/ru/bclib/blockentities/BaseSignBlockEntity.java b/src/main/java/ru/bclib/blockentities/BaseSignBlockEntity.java deleted file mode 100644 index 90bf2a21..00000000 --- a/src/main/java/ru/bclib/blockentities/BaseSignBlockEntity.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.bclib.blockentities; - -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.entity.SignBlockEntity; -import ru.bclib.registry.BaseBlockEntities; - -public class BaseSignBlockEntity extends SignBlockEntity { - public BaseSignBlockEntity() { - super(); - } - - @Override - public BlockEntityType getType() { - return BaseBlockEntities.SIGN; - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/blockentities/DynamicBlockEntityType.java b/src/main/java/ru/bclib/blockentities/DynamicBlockEntityType.java deleted file mode 100644 index 0b476594..00000000 --- a/src/main/java/ru/bclib/blockentities/DynamicBlockEntityType.java +++ /dev/null @@ -1,29 +0,0 @@ -package ru.bclib.blockentities; - -import java.util.Collections; -import java.util.Set; -import java.util.function.Supplier; - -import com.google.common.collect.Sets; - -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; - -public class DynamicBlockEntityType extends BlockEntityType { - - private final Set validBlocks = Sets.newHashSet(); - - public DynamicBlockEntityType(Supplier supplier) { - super(supplier, Collections.emptySet(), null); - } - - @Override - public boolean isValid(Block block) { - return validBlocks.contains(block); - } - - public void registerBlock(Block block) { - validBlocks.add(block); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseAnvilBlock.java b/src/main/java/ru/bclib/blocks/BaseAnvilBlock.java deleted file mode 100644 index cb17045e..00000000 --- a/src/main/java/ru/bclib/blocks/BaseAnvilBlock.java +++ /dev/null @@ -1,95 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.AnvilBlock; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.material.MaterialColor; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.items.BaseAnvilItem; - -public abstract class BaseAnvilBlock extends AnvilBlock implements BlockModelProvider { - public static final IntegerProperty DESTRUCTION = BlockProperties.DESTRUCTION; - - public BaseAnvilBlock(MaterialColor color) { - super(FabricBlockSettings.copyOf(Blocks.ANVIL).materialColor(color)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(DESTRUCTION); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack dropStack = new ItemStack(this); - int destruction = state.getValue(DESTRUCTION); - dropStack.getOrCreateTag().putInt(BaseAnvilItem.DESTRUCTION, destruction); - return Lists.newArrayList(dropStack); - } - - protected String getTop(ResourceLocation blockId, String block) { - if (block.contains("item")) { - return blockId.getPath() + "_top_0"; - } - char last = block.charAt(block.length() - 1); - return blockId.getPath() + "_top_" + last; - } - - @Override - public abstract Item asItem(); - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - return getBlockModel(blockId, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - int destruction = blockState.getValue(DESTRUCTION); - String name = blockId.getPath(); - Map textures = Maps.newHashMap(); - textures.put("%modid%", blockId.getNamespace()); - textures.put("%anvil%", name); - textures.put("%top%", name + "_top_" + destruction); - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_ANVIL, textures); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - int destruction = blockState.getValue(DESTRUCTION); - String modId = stateId.getNamespace(); - String modelId = "block/" + stateId.getPath() + "_top_" + destruction; - ResourceLocation modelLocation = new ResourceLocation(modId, modelId); - registerBlockModel(stateId, modelLocation, blockState, modelCache); - return ModelsHelper.createFacingModel(modelLocation, blockState.getValue(FACING), false, false); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseAttachedBlock.java b/src/main/java/ru/bclib/blocks/BaseAttachedBlock.java deleted file mode 100644 index 9d021fd0..00000000 --- a/src/main/java/ru/bclib/blocks/BaseAttachedBlock.java +++ /dev/null @@ -1,75 +0,0 @@ -package ru.bclib.blocks; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.tags.BlockTags; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.DirectionProperty; -import ru.bclib.util.BlocksHelper; - -public abstract class BaseAttachedBlock extends BaseBlockNotFull { - public static final DirectionProperty FACING = BlockStateProperties.FACING; - - public BaseAttachedBlock(Properties settings) { - super(settings); - this.registerDefaultState(this.defaultBlockState().setValue(FACING, Direction.UP)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(FACING); - } - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { - BlockState blockState = this.defaultBlockState(); - LevelReader worldView = ctx.getLevel(); - BlockPos blockPos = ctx.getClickedPos(); - Direction[] directions = ctx.getNearestLookingDirections(); - for (Direction direction : directions) { - Direction direction2 = direction.getOpposite(); - blockState = blockState.setValue(FACING, direction2); - if (blockState.canSurvive(worldView, blockPos)) { - return blockState; - } - } - return null; - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - Direction direction = (Direction) state.getValue(FACING); - BlockPos blockPos = pos.relative(direction.getOpposite()); - return canSupportCenter(world, blockPos, direction) || world.getBlockState(blockPos).is(BlockTags.LEAVES); - } - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (!canSurvive(state, world, pos)) { - return Blocks.AIR.defaultBlockState(); - } - else { - return state; - } - } - - - @Override - public BlockState rotate(BlockState state, Rotation rotation) { - return BlocksHelper.rotateHorizontal(state, rotation, FACING); - } - - @Override - public BlockState mirror(BlockState state, Mirror mirror) { - return BlocksHelper.mirrorHorizontal(state, mirror, FACING); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseBarkBlock.java b/src/main/java/ru/bclib/blocks/BaseBarkBlock.java deleted file mode 100644 index 743ff0af..00000000 --- a/src/main/java/ru/bclib/blocks/BaseBarkBlock.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Optional; - -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.PatternsHelper; - -public class BaseBarkBlock extends BaseRotatedPillarBlock { - public BaseBarkBlock(Properties settings) { - super(settings); - } - - @Override - protected Optional createBlockPattern(ResourceLocation blockId) { - blockId = Registry.BLOCK.getKey(this); - return PatternsHelper.createJson(BasePatterns.BLOCK_BASE, replacePath(blockId)); - } - - private ResourceLocation replacePath(ResourceLocation blockId) { - String newPath = blockId.getPath().replace("_bark", "_log_side"); - return new ResourceLocation(blockId.getNamespace(), newPath); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseBarrelBlock.java b/src/main/java/ru/bclib/blocks/BaseBarrelBlock.java deleted file mode 100644 index af93693a..00000000 --- a/src/main/java/ru/bclib/blocks/BaseBarrelBlock.java +++ /dev/null @@ -1,138 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.stats.Stats; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.monster.piglin.PiglinAi; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.BarrelBlock; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.RenderShape; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.phys.BlockHitResult; -import ru.bclib.blockentities.BaseBarrelBlockEntity; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.registry.BaseBlockEntities; - -public class BaseBarrelBlock extends BarrelBlock implements BlockModelProvider { - public BaseBarrelBlock(Block source) { - super(FabricBlockSettings.copyOf(source).noOcclusion()); - } - - @Override - public BlockEntity newBlockEntity(BlockGetter world) { - return BaseBlockEntities.BARREL.create(); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - List drop = super.getDrops(state, builder); - drop.add(new ItemStack(this.asItem())); - return drop; - } - - @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, - BlockHitResult hit) { - if (world.isClientSide) { - return InteractionResult.SUCCESS; - } else { - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof BaseBarrelBlockEntity) { - player.openMenu((BaseBarrelBlockEntity) blockEntity); - player.awardStat(Stats.OPEN_BARREL); - PiglinAi.angerNearbyPiglins(player, true); - } - - return InteractionResult.CONSUME; - } - } - - @Override - public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof BaseBarrelBlockEntity) { - ((BaseBarrelBlockEntity) blockEntity).tick(); - } - } - - @Override - public RenderShape getRenderShape(BlockState state) { - return RenderShape.MODEL; - } - - @Override - public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, - ItemStack itemStack) { - if (itemStack.hasCustomHoverName()) { - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof BaseBarrelBlockEntity) { - ((BaseBarrelBlockEntity) blockEntity).setCustomName(itemStack.getHoverName()); - } - } - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - return getBlockModel(blockId, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - Optional pattern; - if (blockState.getValue(OPEN)) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARREL_OPEN, blockId); - } else { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOTTOM_TOP, blockId); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - String open = blockState.getValue(OPEN) ? "_open" : ""; - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + open); - registerBlockModel(stateId, modelId, blockState, modelCache); - Direction facing = blockState.getValue(FACING); - BlockModelRotation rotation = BlockModelRotation.X0_Y0; - switch (facing) { - case NORTH: rotation = BlockModelRotation.X90_Y0; break; - case EAST: rotation = BlockModelRotation.X90_Y90; break; - case SOUTH: rotation = BlockModelRotation.X90_Y180; break; - case WEST: rotation = BlockModelRotation.X90_Y270; break; - case DOWN: rotation = BlockModelRotation.X180_Y0; break; - default: break; - } - return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseBlock.java b/src/main/java/ru/bclib/blocks/BaseBlock.java deleted file mode 100644 index 274261a5..00000000 --- a/src/main/java/ru/bclib/blocks/BaseBlock.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; - -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BlockModelProvider; - -public class BaseBlock extends Block implements BlockModelProvider { - public BaseBlock(Properties settings) { - super(settings); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - public BlockModel getItemModel(ResourceLocation blockId) { - return getBlockModel(blockId, defaultBlockState()); - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/blocks/BaseBlockNotFull.java b/src/main/java/ru/bclib/blocks/BaseBlockNotFull.java deleted file mode 100644 index 6d1a0a38..00000000 --- a/src/main/java/ru/bclib/blocks/BaseBlockNotFull.java +++ /dev/null @@ -1,25 +0,0 @@ -package ru.bclib.blocks; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.block.state.BlockState; - -public class BaseBlockNotFull extends BaseBlock { - - public BaseBlockNotFull(Properties settings) { - super(settings); - } - - public boolean canSuffocate(BlockState state, BlockGetter view, BlockPos pos) { - return false; - } - - public boolean isSimpleFullBlock(BlockState state, BlockGetter view, BlockPos pos) { - return false; - } - - public boolean allowsSpawning(BlockState state, BlockGetter view, BlockPos pos, EntityType type) { - return false; - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseBlockWithEntity.java b/src/main/java/ru/bclib/blocks/BaseBlockWithEntity.java deleted file mode 100644 index fa731e03..00000000 --- a/src/main/java/ru/bclib/blocks/BaseBlockWithEntity.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; - -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.block.BaseEntityBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; - -public class BaseBlockWithEntity extends BaseEntityBlock { - public BaseBlockWithEntity(Properties settings) { - super(settings); - } - - @Override - public BlockEntity newBlockEntity(BlockGetter world) { - return null; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseBookshelfBlock.java b/src/main/java/ru/bclib/blocks/BaseBookshelfBlock.java deleted file mode 100644 index a1b68b1d..00000000 --- a/src/main/java/ru/bclib/blocks/BaseBookshelfBlock.java +++ /dev/null @@ -1,54 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BaseBookshelfBlock extends BaseBlock { - public BaseBookshelfBlock(Block source) { - super(FabricBlockSettings.copyOf(source)); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && tool.isCorrectToolForDrops(state)) { - int silk = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool); - if (silk > 0) { - return Collections.singletonList(new ItemStack(this)); - } - } - return Collections.singletonList(new ItemStack(Items.BOOK, 3)); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BOOKSHELF, replacePath(blockId)); - return ModelsHelper.fromPattern(pattern); - } - - private ResourceLocation replacePath(ResourceLocation blockId) { - String newPath = blockId.getPath().replace("_bookshelf", ""); - return new ResourceLocation(blockId.getNamespace(), newPath); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseButtonBlock.java b/src/main/java/ru/bclib/blocks/BaseButtonBlock.java deleted file mode 100644 index 4d2c5812..00000000 --- a/src/main/java/ru/bclib/blocks/BaseButtonBlock.java +++ /dev/null @@ -1,85 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.ButtonBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.AttachFace; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public abstract class BaseButtonBlock extends ButtonBlock implements BlockModelProvider { - - private final Block parent; - - protected BaseButtonBlock(Block parent, Properties properties, boolean sensitive) { - super(sensitive, properties); - this.parent = parent; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_BUTTON, parentId); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - Optional pattern = blockState.getValue(POWERED) ? - PatternsHelper.createJson(BasePatterns.BLOCK_BUTTON_PRESSED, parentId) : - PatternsHelper.createJson(BasePatterns.BLOCK_BUTTON, parentId); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - String powered = blockState.getValue(POWERED) ? "_powered" : ""; - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + powered); - registerBlockModel(stateId, modelId, blockState, modelCache); - AttachFace face = blockState.getValue(FACE); - boolean isCeiling = face == AttachFace.CEILING; - int x = 0, y = 0; - switch (face) { - case CEILING: x = 180; break; - case WALL: x = 90; break; - default: break; - } - switch (blockState.getValue(FACING)) { - case NORTH: if (isCeiling) { y = 180; } break; - case EAST: y = isCeiling ? 270 : 90; break; - case SOUTH: if(!isCeiling) { y = 180; } break; - case WEST: y = isCeiling ? 90 : 270; break; - default: break; - } - BlockModelRotation rotation = BlockModelRotation.by(x, y); - return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), face == AttachFace.WALL); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseChainBlock.java b/src/main/java/ru/bclib/blocks/BaseChainBlock.java deleted file mode 100644 index f8ae7090..00000000 --- a/src/main/java/ru/bclib/blocks/BaseChainBlock.java +++ /dev/null @@ -1,67 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.ChainBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.MaterialColor; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public class BaseChainBlock extends ChainBlock implements BlockModelProvider, IRenderTyped { - public BaseChainBlock(MaterialColor color) { - super(FabricBlockSettings.copyOf(Blocks.CHAIN).materialColor(color)); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - return ModelsHelper.createItemModel(blockId); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CHAIN, blockId); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - Direction.Axis axis = blockState.getValue(AXIS); - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath()); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createRotatedModel(modelId, axis); - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseChestBlock.java b/src/main/java/ru/bclib/blocks/BaseChestBlock.java deleted file mode 100644 index 46819d5e..00000000 --- a/src/main/java/ru/bclib/blocks/BaseChestBlock.java +++ /dev/null @@ -1,62 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.ChestBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.registry.BaseBlockEntities; - -public class BaseChestBlock extends ChestBlock implements BlockModelProvider { - private final Block parent; - - public BaseChestBlock(Block source) { - super(FabricBlockSettings.copyOf(source).noOcclusion(), () -> BaseBlockEntities.CHEST); - this.parent = source; - } - - @Override - public BlockEntity newBlockEntity(BlockGetter world) - { - return BaseBlockEntities.CHEST.create(); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) - { - List drop = super.getDrops(state, builder); - drop.add(new ItemStack(this.asItem())); - return drop; - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_CHEST, blockId); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - return ModelsHelper.createBlockEmpty(parentId); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseComposterBlock.java b/src/main/java/ru/bclib/blocks/BaseComposterBlock.java deleted file mode 100644 index 09861f07..00000000 --- a/src/main/java/ru/bclib/blocks/BaseComposterBlock.java +++ /dev/null @@ -1,72 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.ComposterBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.ModelsHelper.MultiPartBuilder; -import ru.bclib.client.models.PatternsHelper; - -public class BaseComposterBlock extends ComposterBlock implements BlockModelProvider { - public BaseComposterBlock(Block source) { - super(FabricBlockSettings.copyOf(source)); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this.asItem())); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_COMPOSTER, blockId); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); - registerBlockModel(stateId, modelId, blockState, modelCache); - - MultiPartBuilder builder = MultiPartBuilder.create(stateDefinition); - LEVEL.getPossibleValues().forEach(level -> { - if (level > 0) { - ResourceLocation contentId; - if (level > 7) { - contentId = new ResourceLocation("block/composter_contents_ready"); - } else { - contentId = new ResourceLocation("block/composter_contents" + level); - } - builder.part(contentId).setCondition(state -> state.getValue(LEVEL).equals(level)).add(); - } - }); - builder.part(modelId).add(); - - return builder.build(); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseCraftingTableBlock.java b/src/main/java/ru/bclib/blocks/BaseCraftingTableBlock.java deleted file mode 100644 index 4cd02952..00000000 --- a/src/main/java/ru/bclib/blocks/BaseCraftingTableBlock.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.CraftingTableBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BaseCraftingTableBlock extends CraftingTableBlock implements BlockModelProvider { - public BaseCraftingTableBlock(Block source) { - super(FabricBlockSettings.copyOf(source)); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this.asItem())); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - String blockName = blockId.getPath(); - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SIDED, new HashMap() { - private static final long serialVersionUID = 1L; - { - put("%modid%", blockId.getNamespace()); - put("%particle%", blockName + "_front"); - put("%down%", blockName + "_bottom"); - put("%up%", blockName + "_top"); - put("%north%", blockName + "_front"); - put("%south%", blockName + "_side"); - put("%west%", blockName + "_front"); - put("%east%", blockName + "_side"); - } - }); - return ModelsHelper.fromPattern(pattern); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseCropBlock.java b/src/main/java/ru/bclib/blocks/BaseCropBlock.java deleted file mode 100644 index 703ffaab..00000000 --- a/src/main/java/ru/bclib/blocks/BaseCropBlock.java +++ /dev/null @@ -1,122 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Random; - -import com.google.common.collect.Lists; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.Mth; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.util.BlocksHelper; -import ru.bclib.util.MHelper; - -public class BaseCropBlock extends BasePlantBlock { - private static final VoxelShape SHAPE = Block.box(2, 0, 2, 14, 14, 14); - public static final IntegerProperty AGE = IntegerProperty.create("age", 0, 3); - - private final Block[] terrain; - private final Item drop; - - public BaseCropBlock(Item drop, Block... terrain) { - super(FabricBlockSettings.of(Material.PLANT) - .breakByTool(FabricToolTags.HOES) - .breakByHand(true) - .sound(SoundType.GRASS) - .randomTicks() - .noCollission()); - this.drop = drop; - this.terrain = terrain; - this.registerDefaultState(defaultBlockState().setValue(AGE, 0)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(AGE); - } - - @Override - protected boolean isTerrain(BlockState state) { - for (Block block: terrain) { - if (state.is(block)) { - return true; - } - } - return false; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - if (state.getValue(AGE) < 3) { - return Collections.singletonList(new ItemStack(this)); - } - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && tool.isCorrectToolForDrops(state)) { - int enchantment = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool); - if (enchantment > 0) { - int countSeeds = MHelper.randRange(Mth.clamp(1 + enchantment, 1, 3), 3, MHelper.RANDOM); - int countDrops = MHelper.randRange(Mth.clamp(1 + enchantment, 1, 2), 2, MHelper.RANDOM); - return Lists.newArrayList(new ItemStack(this, countSeeds), new ItemStack(drop, countDrops)); - } - } - int countSeeds = MHelper.randRange(1, 3, MHelper.RANDOM); - int countDrops = MHelper.randRange(1, 2, MHelper.RANDOM); - return Lists.newArrayList(new ItemStack(this, countSeeds), new ItemStack(drop, countDrops)); - } - - @Override - public BlockBehaviour.OffsetType getOffsetType() { - return BlockBehaviour.OffsetType.NONE; - } - - @Override - public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { - int age = state.getValue(AGE); - if (age < 3) { - BlocksHelper.setWithUpdate(world, pos, state.setValue(AGE, age + 1)); - } - } - - @Override - public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { - return state.getValue(AGE) < 3; - } - - @Override - public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { - return state.getValue(AGE) < 3; - } - - @Override - public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { - super.tick(state, world, pos, random); - if (isBonemealSuccess(world, random, pos, state) && random.nextInt(8) == 0) { - performBonemeal(world, random, pos, state); - } - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - return SHAPE; - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseDoorBlock.java b/src/main/java/ru/bclib/blocks/BaseDoorBlock.java deleted file mode 100644 index f8594a4a..00000000 --- a/src/main/java/ru/bclib/blocks/BaseDoorBlock.java +++ /dev/null @@ -1,163 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.StringRepresentable; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.DoorBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.DoorHingeSide; -import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public class BaseDoorBlock extends DoorBlock implements IRenderTyped, BlockModelProvider { - public BaseDoorBlock(Block source) { - super(FabricBlockSettings.copyOf(source).strength(3F, 3F).noOcclusion()); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - if (state.getValue(HALF) == DoubleBlockHalf.LOWER) - return Collections.singletonList(new ItemStack(this.asItem())); - else - return Collections.emptyList(); - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - DoorType doorType = getDoorType(blockState); - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_BOTTOM, resourceLocation); - switch (doorType) { - case TOP_HINGE: - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_TOP_HINGE, resourceLocation); - break; - case BOTTOM_HINGE: - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_BOTTOM_HINGE, resourceLocation); - break; - case TOP: - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_DOOR_TOP, resourceLocation); - break; - default: break; - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - Direction facing = blockState.getValue(FACING); - DoorType doorType = getDoorType(blockState); - boolean open = blockState.getValue(OPEN); - boolean hinge = doorType.isHinge(); - BlockModelRotation rotation = BlockModelRotation.X0_Y0; - switch (facing) { - case EAST: - if (hinge && open) { - rotation = BlockModelRotation.X0_Y90; - } else if (open) { - rotation = BlockModelRotation.X0_Y270; - } - break; - case SOUTH: - if (!hinge && !open || hinge && !open) { - rotation = BlockModelRotation.X0_Y90; - } else if (hinge) { - rotation = BlockModelRotation.X0_Y180; - } - break; - case WEST: - if (!hinge && !open || hinge && !open) { - rotation = BlockModelRotation.X0_Y180; - } else if (hinge) { - rotation = BlockModelRotation.X0_Y270; - } else { - rotation = BlockModelRotation.X0_Y90; - } - break; - case NORTH: - default: - if (!hinge && !open || hinge && !open) { - rotation = BlockModelRotation.X0_Y270; - } else if (!hinge) { - rotation = BlockModelRotation.X0_Y180; - } - break; - } - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + "_" + doorType); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); - } - - protected DoorType getDoorType(BlockState blockState) { - boolean isHinge = isHinge(blockState.getValue(HINGE), blockState.getValue(OPEN)); - switch (blockState.getValue(HALF)) { - case UPPER: { - return isHinge ? DoorType.TOP_HINGE : DoorType.TOP; - } - case LOWER: { - return isHinge ? DoorType.BOTTOM_HINGE : DoorType.BOTTOM; - } - } - return DoorType.BOTTOM; - } - - private boolean isHinge(DoorHingeSide hingeSide, boolean open) { - boolean isHinge = hingeSide == DoorHingeSide.RIGHT; - return isHinge && !open || !isHinge && open; - } - - protected enum DoorType implements StringRepresentable { - BOTTOM_HINGE("bottom_hinge"), - TOP_HINGE("top_hinge"), - BOTTOM("bottom"), - TOP("top"); - - private final String name; - - DoorType(String name) { - this.name = name; - } - - public boolean isHinge() { - return this == BOTTOM_HINGE || - this == TOP_HINGE; - } - - @Override - public String toString() { - return getSerializedName(); - } - - @Override - public String getSerializedName() { - return name; - } - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseDoublePlantBlock.java b/src/main/java/ru/bclib/blocks/BaseDoublePlantBlock.java deleted file mode 100644 index 90ec93a2..00000000 --- a/src/main/java/ru/bclib/blocks/BaseDoublePlantBlock.java +++ /dev/null @@ -1,149 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Random; - -import com.google.common.collect.Lists; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.BonemealableBlock; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; -import ru.bclib.util.BlocksHelper; - -public abstract class BaseDoublePlantBlock extends BaseBlockNotFull implements IRenderTyped, BonemealableBlock { - private static final VoxelShape SHAPE = Block.box(4, 2, 4, 12, 16, 12); - public static final IntegerProperty ROTATION = BlockProperties.ROTATION; - public static final BooleanProperty TOP = BooleanProperty.create("top"); - - public BaseDoublePlantBlock() { - super(FabricBlockSettings.of(Material.PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.WET_GRASS) - .noCollission()); - this.registerDefaultState(this.stateDefinition.any().setValue(TOP, false)); - } - - public BaseDoublePlantBlock(int light) { - super(FabricBlockSettings.of(Material.PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.WET_GRASS) - .lightLevel((state) -> state.getValue(TOP) ? light : 0) - .noCollission()); - this.registerDefaultState(this.stateDefinition.any().setValue(TOP, false)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(TOP, ROTATION); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - Vec3 vec3d = state.getOffset(view, pos); - return SHAPE.move(vec3d.x, vec3d.y, vec3d.z); - } - - @Override - public BlockBehaviour.OffsetType getOffsetType() { - return BlockBehaviour.OffsetType.XZ; - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - BlockState down = world.getBlockState(pos.below()); - BlockState up = world.getBlockState(pos.above()); - return state.getValue(TOP) ? down.getBlock() == this : isTerrain(down) && (up.getMaterial().isReplaceable()); - } - - public boolean canStayAt(BlockState state, LevelReader world, BlockPos pos) { - BlockState down = world.getBlockState(pos.below()); - BlockState up = world.getBlockState(pos.above()); - return state.getValue(TOP) ? down.getBlock() == this : isTerrain(down) && (up.getBlock() == this); - } - - protected abstract boolean isTerrain(BlockState state); - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (!canStayAt(state, world, pos)) { - return Blocks.AIR.defaultBlockState(); - } - else { - return state; - } - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - if (state.getValue(TOP)) { - return Lists.newArrayList(); - } - - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && tool.getItem().is(FabricToolTags.SHEARS) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { - return Lists.newArrayList(new ItemStack(this)); - } - else { - return Lists.newArrayList(); - } - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { - return true; - } - - @Override - public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { - return true; - } - - @Override - public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { - ItemEntity item = new ItemEntity(world, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, new ItemStack(this)); - world.addFreshEntity(item); - } - - @Override - public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) { - int rot = world.random.nextInt(4); - BlockState bs = this.defaultBlockState().setValue(ROTATION, rot); - BlocksHelper.setWithoutUpdate(world, pos, bs); - BlocksHelper.setWithoutUpdate(world, pos.above(), bs.setValue(TOP, true)); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseFenceBlock.java b/src/main/java/ru/bclib/blocks/BaseFenceBlock.java deleted file mode 100644 index 417f50cb..00000000 --- a/src/main/java/ru/bclib/blocks/BaseFenceBlock.java +++ /dev/null @@ -1,87 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.FenceBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.ModelsHelper.MultiPartBuilder; -import ru.bclib.client.models.PatternsHelper; - -public class BaseFenceBlock extends FenceBlock implements BlockModelProvider { - private final Block parent; - - public BaseFenceBlock(Block source) { - super(FabricBlockSettings.copyOf(source).noOcclusion()); - this.parent = source; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_FENCE, parentId); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - String path = blockId.getPath(); - Optional pattern = Optional.empty(); - if (path.endsWith("_post")) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FENCE_POST, parentId); - } - if (path.endsWith("_side")) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FENCE_SIDE, parentId); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + "_post"); - ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + "_side"); - registerBlockModel(postId, postId, blockState, modelCache); - registerBlockModel(sideId, sideId, blockState, modelCache); - - MultiPartBuilder builder = MultiPartBuilder.create(stateDefinition); - builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add(); - builder.part(sideId).setCondition(state -> state.getValue(EAST)) - .setTransformation(BlockModelRotation.X0_Y90.getRotation()).setUVLock(true).add(); - builder.part(sideId).setCondition(state -> state.getValue(SOUTH)) - .setTransformation(BlockModelRotation.X0_Y180.getRotation()).setUVLock(true).add(); - builder.part(sideId).setCondition(state -> state.getValue(WEST)) - .setTransformation(BlockModelRotation.X0_Y270.getRotation()).setUVLock(true).add(); - builder.part(postId).add(); - - return builder.build(); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseFurnaceBlock.java b/src/main/java/ru/bclib/blocks/BaseFurnaceBlock.java deleted file mode 100644 index e109062e..00000000 --- a/src/main/java/ru/bclib/blocks/BaseFurnaceBlock.java +++ /dev/null @@ -1,111 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.stats.Stats; -import net.minecraft.world.MenuProvider; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.FurnaceBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import ru.bclib.blockentities.BaseFurnaceBlockEntity; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public class BaseFurnaceBlock extends FurnaceBlock implements BlockModelProvider, IRenderTyped { - public BaseFurnaceBlock(Block source) { - super(FabricBlockSettings.copyOf(source).luminance(state -> state.getValue(LIT) ? 13 : 0)); - } - - @Override - public BlockEntity newBlockEntity(BlockGetter world) { - return new BaseFurnaceBlockEntity(); - } - - @Override - protected void openContainer(Level world, BlockPos pos, Player player) { - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof BaseFurnaceBlockEntity) { - player.openMenu((MenuProvider) blockEntity); - player.awardStat(Stats.INTERACT_WITH_FURNACE); - } - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - String blockName = blockId.getPath(); - Map textures = Maps.newHashMap(); - textures.put("%modid%", blockId.getNamespace()); - textures.put("%top%", blockName + "_top"); - textures.put("%side%", blockName + "_side"); - Optional pattern; - if (blockState.getValue(LIT)) { - textures.put("%front%", blockName + "_front_on"); - textures.put("%glow%", blockName + "_glow"); - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE_LIT, textures); - } else { - textures.put("%front%", blockName + "_front"); - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_FURNACE, textures); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - String lit = blockState.getValue(LIT) ? "_lit" : ""; - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + lit); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true); - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - List drop = Lists.newArrayList(new ItemStack(this)); - BlockEntity blockEntity = builder.getOptionalParameter(LootContextParams.BLOCK_ENTITY); - if (blockEntity instanceof BaseFurnaceBlockEntity) { - BaseFurnaceBlockEntity entity = (BaseFurnaceBlockEntity) blockEntity; - for (int i = 0; i < entity.getContainerSize(); i++) { - drop.add(entity.getItem(i)); - } - } - return drop; - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseGateBlock.java b/src/main/java/ru/bclib/blocks/BaseGateBlock.java deleted file mode 100644 index b4393ea9..00000000 --- a/src/main/java/ru/bclib/blocks/BaseGateBlock.java +++ /dev/null @@ -1,74 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.FenceGateBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BaseGateBlock extends FenceGateBlock implements BlockModelProvider { - private final Block parent; - - public BaseGateBlock(Block source) { - super(FabricBlockSettings.copyOf(source).noOcclusion()); - this.parent = source; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - boolean inWall = blockState.getValue(IN_WALL); - boolean isOpen = blockState.getValue(OPEN); - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - Optional pattern; - if (inWall) { - pattern = isOpen ? PatternsHelper.createJson(BasePatterns.BLOCK_GATE_OPEN_WALL, parentId) : - PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED_WALL, parentId); - } else { - pattern = isOpen ? PatternsHelper.createJson(BasePatterns.BLOCK_GATE_OPEN, parentId) : - PatternsHelper.createJson(BasePatterns.BLOCK_GATE_CLOSED, parentId); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - boolean inWall = blockState.getValue(IN_WALL); - boolean isOpen = blockState.getValue(OPEN); - String state = "" + (inWall ? "_wall" : "") + (isOpen ? "_open" : "_closed"); - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + state); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), true, false); - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/blocks/BaseLadderBlock.java b/src/main/java/ru/bclib/blocks/BaseLadderBlock.java deleted file mode 100644 index 84a396cd..00000000 --- a/src/main/java/ru/bclib/blocks/BaseLadderBlock.java +++ /dev/null @@ -1,166 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.HorizontalDirectionalBlock; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.state.properties.DirectionProperty; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; -import ru.bclib.util.BlocksHelper; - -public class BaseLadderBlock extends BaseBlockNotFull implements IRenderTyped, BlockModelProvider { - public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; - public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; - protected static final VoxelShape EAST_SHAPE = Block.box(0.0D, 0.0D, 0.0D, 3.0D, 16.0D, 16.0D); - protected static final VoxelShape WEST_SHAPE = Block.box(13.0D, 0.0D, 0.0D, 16.0D, 16.0D, 16.0D); - protected static final VoxelShape SOUTH_SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 16.0D, 3.0D); - protected static final VoxelShape NORTH_SHAPE = Block.box(0.0D, 0.0D, 13.0D, 16.0D, 16.0D, 16.0D); - - public BaseLadderBlock(Block block) { - super(FabricBlockSettings.copyOf(block).noOcclusion()); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(FACING); - stateManager.add(WATERLOGGED); - } - - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - switch (state.getValue(FACING)) { - case SOUTH: - return SOUTH_SHAPE; - case WEST: - return WEST_SHAPE; - case EAST: - return EAST_SHAPE; - default: - return NORTH_SHAPE; - } - } - - private boolean canPlaceOn(BlockGetter world, BlockPos pos, Direction side) { - BlockState blockState = world.getBlockState(pos); - return !blockState.isSignalSource() && blockState.isFaceSturdy(world, pos, side); - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - Direction direction = state.getValue(FACING); - return this.canPlaceOn(world, pos.relative(direction.getOpposite()), direction); - } - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, - LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (facing.getOpposite() == state.getValue(FACING) && !state.canSurvive(world, pos)) { - return Blocks.AIR.defaultBlockState(); - } else { - if (state.getValue(WATERLOGGED)) { - world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); - } - - return super.updateShape(state, facing, neighborState, world, pos, neighborPos); - } - } - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { - BlockState blockState; - if (!ctx.replacingClickedOnBlock()) { - blockState = ctx.getLevel().getBlockState(ctx.getClickedPos().relative(ctx.getClickedFace().getOpposite())); - if (blockState.getBlock() == this && blockState.getValue(FACING) == ctx.getClickedFace()) { - return null; - } - } - - blockState = defaultBlockState(); - LevelReader worldView = ctx.getLevel(); - BlockPos blockPos = ctx.getClickedPos(); - FluidState fluidState = ctx.getLevel().getFluidState(ctx.getClickedPos()); - Direction[] directions = ctx.getNearestLookingDirections(); - - for (Direction direction : directions) { - if (direction.getAxis().isHorizontal()) { - blockState = blockState.setValue(FACING, direction.getOpposite()); - if (blockState.canSurvive(worldView, blockPos)) { - return blockState.setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER); - } - } - } - - return null; - } - - @Override - public BlockState rotate(BlockState state, Rotation rotation) { - return BlocksHelper.rotateHorizontal(state, rotation, FACING); - } - - @Override - public BlockState mirror(BlockState state, Mirror mirror) { - return BlocksHelper.mirrorHorizontal(state, mirror, FACING); - } - - @Override - public FluidState getFluidState(BlockState state) { - return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - return ModelsHelper.createBlockItem(blockId); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_LADDER, blockId); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createFacingModel(modelId, blockState.getValue(FACING), false, true); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseLeavesBlock.java b/src/main/java/ru/bclib/blocks/BaseLeavesBlock.java deleted file mode 100644 index d7a7b1cb..00000000 --- a/src/main/java/ru/bclib/blocks/BaseLeavesBlock.java +++ /dev/null @@ -1,79 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; - -import com.google.common.collect.Lists; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.LeavesBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.MaterialColor; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; -import ru.bclib.util.MHelper; - -public class BaseLeavesBlock extends LeavesBlock implements BlockModelProvider, IRenderTyped { - private final Block sapling; - - public BaseLeavesBlock(Block sapling, MaterialColor color) { - super(FabricBlockSettings.copyOf(Blocks.OAK_LEAVES) - .materialColor(color) - .breakByTool(FabricToolTags.HOES) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .isValidSpawn((state, world, pos, type) -> false) - .isSuffocating((state, world, pos) -> false) - .isViewBlocking((state, world, pos) -> false)); - this.sapling = sapling; - } - - public BaseLeavesBlock(Block sapling, MaterialColor color, int light) { - super(FabricBlockSettings.copyOf(Blocks.OAK_LEAVES) - .materialColor(color) - .luminance(light) - .breakByTool(FabricToolTags.HOES) - .breakByTool(FabricToolTags.SHEARS) - .isValidSpawn((state, world, pos, type) -> false) - .isSuffocating((state, world, pos) -> false) - .isViewBlocking((state, world, pos) -> false)); - this.sapling = sapling; - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null) { - if (tool.getItem().is(FabricToolTags.SHEARS) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { - return Collections.singletonList(new ItemStack(this)); - } - int fortune = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool); - if (MHelper.RANDOM.nextInt(16) <= fortune) { - return Lists.newArrayList(new ItemStack(sapling)); - } - return Lists.newArrayList(); - } - return MHelper.RANDOM.nextInt(16) == 0 ? Lists.newArrayList(new ItemStack(sapling)) : Lists.newArrayList(); - } - - @Override - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseMetalBarsBlock.java b/src/main/java/ru/bclib/blocks/BaseMetalBarsBlock.java deleted file mode 100644 index 86d5a231..00000000 --- a/src/main/java/ru/bclib/blocks/BaseMetalBarsBlock.java +++ /dev/null @@ -1,112 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Direction; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.IronBarsBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public class BaseMetalBarsBlock extends IronBarsBlock implements BlockModelProvider, IRenderTyped { - public BaseMetalBarsBlock(Block source) { - super(FabricBlockSettings.copyOf(source).strength(5.0F, 6.0F).noOcclusion()); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - public Optional getModelString(String block) { - ResourceLocation blockId = Registry.BLOCK.getKey(this); - if (block.contains("item")) { - return PatternsHelper.createJson(BasePatterns.ITEM_BLOCK, blockId); - } - if (block.contains("post")) { - return PatternsHelper.createJson(BasePatterns.BLOCK_BARS_POST, blockId); - } - else { - return PatternsHelper.createJson(BasePatterns.BLOCK_BARS_SIDE, blockId); - } - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return ModelsHelper.createBlockItem(resourceLocation); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - ResourceLocation thisId = Registry.BLOCK.getKey(this); - String path = blockId.getPath(); - Optional pattern = Optional.empty(); - if (path.endsWith("_post")) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARS_POST, thisId); - } - if (path.endsWith("_side")) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_BARS_SIDE, thisId); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + "_post"); - ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + "_side"); - registerBlockModel(postId, postId, blockState, modelCache); - registerBlockModel(sideId, sideId, blockState, modelCache); - - ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition); - builder.part(postId).setCondition(state -> - !state.getValue(NORTH) && !state.getValue(EAST) && - !state.getValue(SOUTH) && !state.getValue(WEST)).add(); - builder.part(sideId).setCondition(state -> state.getValue(NORTH)).setUVLock(true).add(); - builder.part(sideId).setCondition(state -> state.getValue(EAST)) - .setTransformation(BlockModelRotation.X0_Y90.getRotation()).setUVLock(true).add(); - builder.part(sideId).setCondition(state -> state.getValue(SOUTH)) - .setTransformation(BlockModelRotation.X0_Y180.getRotation()).setUVLock(true).add(); - builder.part(sideId).setCondition(state -> state.getValue(WEST)) - .setTransformation(BlockModelRotation.X0_Y270.getRotation()).setUVLock(true).add(); - - return builder.build(); - } - - @Environment(EnvType.CLIENT) - public boolean skipRendering(BlockState state, BlockState stateFrom, Direction direction) { - if (direction.getAxis().isVertical() && stateFrom.getBlock().is(this) && !stateFrom.equals(state)) { - return false; - } - return super.skipRendering(state, stateFrom, direction); - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseOreBlock.java b/src/main/java/ru/bclib/blocks/BaseOreBlock.java deleted file mode 100644 index 5d5cc847..00000000 --- a/src/main/java/ru/bclib/blocks/BaseOreBlock.java +++ /dev/null @@ -1,76 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Random; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.block.OreBlock; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.material.MaterialColor; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.util.MHelper; - -public class BaseOreBlock extends OreBlock implements BlockModelProvider { - private final Item dropItem; - private final int minCount; - private final int maxCount; - private final int experience; - - public BaseOreBlock(Item drop, int minCount, int maxCount, int experience) { - super(FabricBlockSettings.of(Material.STONE, MaterialColor.SAND) - .hardness(3F) - .resistance(9F) - .requiresCorrectToolForDrops() - .sound(SoundType.STONE)); - this.dropItem = drop; - this.minCount = minCount; - this.maxCount = maxCount; - this.experience = experience; - } - - @Override - protected int xpOnDrop(Random random) { - return this.experience > 0 ? random.nextInt(experience) + 1 : 0; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && tool.isCorrectToolForDrops(state)) { - if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { - return Collections.singletonList(new ItemStack(this)); - } - int count; - int enchantment = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BLOCK_FORTUNE, tool); - if (enchantment > 0) { - int min = Mth.clamp(minCount + enchantment, minCount, maxCount); - int max = maxCount + (enchantment / Enchantments.BLOCK_FORTUNE.getMaxLevel()); - if (min == max) { - return Collections.singletonList(new ItemStack(dropItem, max)); - } - count = MHelper.randRange(min, max, MHelper.RANDOM); - } else { - count = MHelper.randRange(minCount, maxCount, MHelper.RANDOM); - } - return Collections.singletonList(new ItemStack(dropItem, count)); - } - return Collections.emptyList(); - } - - @Override - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } -} diff --git a/src/main/java/ru/bclib/blocks/BasePathBlock.java b/src/main/java/ru/bclib/blocks/BasePathBlock.java deleted file mode 100644 index 75060542..00000000 --- a/src/main/java/ru/bclib/blocks/BasePathBlock.java +++ /dev/null @@ -1,98 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.collect.Maps; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -@SuppressWarnings("deprecation") -public class BasePathBlock extends BaseBlockNotFull { - private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 15, 16); - - private Block baseBlock; - - public BasePathBlock(Block source) { - super(FabricBlockSettings.copyOf(source).isValidSpawn((state, world, pos, type) -> false)); - this.baseBlock = Blocks.DIRT; - if (source instanceof BaseTerrainBlock) { - BaseTerrainBlock terrain = (BaseTerrainBlock) source; - this.baseBlock = terrain.getBaseBlock(); - terrain.setPathBlock(this); - } - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { - return Collections.singletonList(new ItemStack(this)); - } - return Collections.singletonList(new ItemStack(Blocks.END_STONE)); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - return SHAPE; - } - - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - return SHAPE; - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - return getBlockModel(blockId, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - String name = blockId.getPath(); - ResourceLocation bottomId = Registry.BLOCK.getKey(baseBlock); - String bottom = bottomId.getNamespace() + ":block/" + bottomId.getPath(); - Map textures = Maps.newHashMap(); - textures.put("%modid%", blockId.getNamespace()); - textures.put("%top%", name + "_top"); - textures.put("%side%", name.replace("_path", "") + "_side"); - textures.put("%bottom%", bottom); - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PATH, textures); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createRandomTopModel(modelId); - } -} diff --git a/src/main/java/ru/bclib/blocks/BasePlantBlock.java b/src/main/java/ru/bclib/blocks/BasePlantBlock.java deleted file mode 100644 index 2d83be66..00000000 --- a/src/main/java/ru/bclib/blocks/BasePlantBlock.java +++ /dev/null @@ -1,128 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Random; - -import com.google.common.collect.Lists; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.BonemealableBlock; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public abstract class BasePlantBlock extends BaseBlockNotFull implements IRenderTyped, BonemealableBlock { - private static final VoxelShape SHAPE = Block.box(4, 0, 4, 12, 14, 12); - - public BasePlantBlock() { - this(false); - } - - public BasePlantBlock(int light) { - this(false, light); - } - - public BasePlantBlock(boolean replaceable) { - super(FabricBlockSettings.of(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.GRASS) - .noCollission()); - } - - public BasePlantBlock(boolean replaceable, int light) { - super(FabricBlockSettings.of(replaceable ? Material.REPLACEABLE_PLANT : Material.PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .luminance(light) - .sound(SoundType.GRASS) - .noCollission()); - } - - public BasePlantBlock(Properties settings) { - super(settings); - } - - protected abstract boolean isTerrain(BlockState state); - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - Vec3 vec3d = state.getOffset(view, pos); - return SHAPE.move(vec3d.x, vec3d.y, vec3d.z); - } - - @Override - public BlockBehaviour.OffsetType getOffsetType() { - return BlockBehaviour.OffsetType.XZ; - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - BlockState down = world.getBlockState(pos.below()); - return isTerrain(down); - } - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (!canSurvive(state, world, pos)) { - return Blocks.AIR.defaultBlockState(); - } - else { - return state; - } - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && tool.getItem().is(FabricToolTags.SHEARS) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { - return Lists.newArrayList(new ItemStack(this)); - } - else { - return Lists.newArrayList(); - } - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { - return true; - } - - @Override - public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { - return true; - } - - @Override - public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { - ItemEntity item = new ItemEntity(world, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, new ItemStack(this)); - world.addFreshEntity(item); - } -} diff --git a/src/main/java/ru/bclib/blocks/BasePlantWithAgeBlock.java b/src/main/java/ru/bclib/blocks/BasePlantWithAgeBlock.java deleted file mode 100644 index a8c31d54..00000000 --- a/src/main/java/ru/bclib/blocks/BasePlantWithAgeBlock.java +++ /dev/null @@ -1,64 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Random; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.material.Material; - -public abstract class BasePlantWithAgeBlock extends BasePlantBlock { - public static final IntegerProperty AGE = BlockProperties.AGE; - - public BasePlantWithAgeBlock() { - this(FabricBlockSettings.of(Material.PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.GRASS) - .randomTicks() - .noCollission()); - } - - public BasePlantWithAgeBlock(Properties settings) { - super(settings); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(AGE); - } - - public abstract void growAdult(WorldGenLevel world, Random random, BlockPos pos); - - @Override - public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { - int age = state.getValue(AGE); - if (age < 3) { - world.setBlockAndUpdate(pos, state.setValue(AGE, age + 1)); - } - else { - growAdult(world, random, pos); - } - } - - @Override - public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { - return true; - } - - @Override - public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { - super.tick(state, world, pos, random); - if (random.nextInt(8) == 0) { - performBonemeal(world, random, pos, state); - } - } -} diff --git a/src/main/java/ru/bclib/blocks/BasePressurePlateBlock.java b/src/main/java/ru/bclib/blocks/BasePressurePlateBlock.java deleted file mode 100644 index 0d6152ca..00000000 --- a/src/main/java/ru/bclib/blocks/BasePressurePlateBlock.java +++ /dev/null @@ -1,68 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.PressurePlateBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BasePressurePlateBlock extends PressurePlateBlock implements BlockModelProvider { - private final Block parent; - - public BasePressurePlateBlock(Sensitivity rule, Block source) { - super(rule, FabricBlockSettings.copyOf(source).noCollission().noOcclusion().strength(0.5F)); - this.parent = source; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - Optional pattern; - if (blockState.getValue(POWERED)) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_DOWN, parentId); - } else { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_UP, parentId); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - String state = blockState.getValue(POWERED) ? "_down" : "_up"; - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + state); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createBlockSimple(modelId); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseRotatedPillarBlock.java b/src/main/java/ru/bclib/blocks/BaseRotatedPillarBlock.java deleted file mode 100644 index 003408ba..00000000 --- a/src/main/java/ru/bclib/blocks/BaseRotatedPillarBlock.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.RotatedPillarBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BaseRotatedPillarBlock extends RotatedPillarBlock implements BlockModelProvider { - public BaseRotatedPillarBlock(Properties settings) { - super(settings); - } - - public BaseRotatedPillarBlock(Block block) { - super(FabricBlockSettings.copyOf(block)); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - return getBlockModel(blockId, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - Optional pattern = createBlockPattern(blockId); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createRotatedModel(modelId, blockState.getValue(AXIS)); - } - - protected Optional createBlockPattern(ResourceLocation blockId) { - return PatternsHelper.createBlockPillar(blockId); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseSignBlock.java b/src/main/java/ru/bclib/blocks/BaseSignBlock.java deleted file mode 100644 index 2e6b323a..00000000 --- a/src/main/java/ru/bclib/blocks/BaseSignBlock.java +++ /dev/null @@ -1,199 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Registry; -import net.minecraft.network.protocol.game.ClientboundOpenSignEditorPacket; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.SignBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.block.state.properties.WoodType; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.blockentities.BaseSignBlockEntity; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.interfaces.ISpetialItem; -import ru.bclib.util.BlocksHelper; - -public class BaseSignBlock extends SignBlock implements BlockModelProvider, ISpetialItem { - public static final IntegerProperty ROTATION = BlockStateProperties.ROTATION_16; - public static final BooleanProperty FLOOR = BooleanProperty.create("floor"); - private static final VoxelShape[] WALL_SHAPES = new VoxelShape[] { - Block.box(0.0D, 4.5D, 14.0D, 16.0D, 12.5D, 16.0D), - Block.box(0.0D, 4.5D, 0.0D, 2.0D, 12.5D, 16.0D), - Block.box(0.0D, 4.5D, 0.0D, 16.0D, 12.5D, 2.0D), - Block.box(14.0D, 4.5D, 0.0D, 16.0D, 12.5D, 16.0D) - }; - - private final Block parent; - - public BaseSignBlock(Block source) { - super(FabricBlockSettings.copyOf(source).strength(1.0F, 1.0F).noCollission().noOcclusion(), WoodType.OAK); - this.registerDefaultState(this.stateDefinition.any().setValue(ROTATION, 0).setValue(FLOOR, false).setValue(WATERLOGGED, false)); - this.parent = source; - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - builder.add(ROTATION, FLOOR, WATERLOGGED); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - return state.getValue(FLOOR) ? SHAPE : WALL_SHAPES[state.getValue(ROTATION) >> 2]; - } - - @Override - public BlockEntity newBlockEntity(BlockGetter world) { - return new BaseSignBlockEntity(); - } - - @Override - public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) { - if (placer instanceof Player) { - BaseSignBlockEntity sign = (BaseSignBlockEntity) world.getBlockEntity(pos); - if (sign != null) { - if (!world.isClientSide) { - sign.setAllowedPlayerEditor((Player) placer); - ((ServerPlayer) placer).connection.send(new ClientboundOpenSignEditorPacket(pos)); - } else { - sign.setEditable(true); - } - } - } - } - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (state.getValue(WATERLOGGED)) { - world.getLiquidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); - } - if (!canSurvive(state, world, pos)) { - return state.getValue(WATERLOGGED) ? state.getFluidState().createLegacyBlock() : Blocks.AIR.defaultBlockState(); - } - return super.updateShape(state, facing, neighborState, world, pos, neighborPos); - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - if (!state.getValue(FLOOR)) { - int index = (((state.getValue(ROTATION) >> 2) + 2)) & 3; - return world.getBlockState(pos.relative(BlocksHelper.HORIZONTAL[index])).getMaterial().isSolid(); - } - else { - return world.getBlockState(pos.below()).getMaterial().isSolid(); - } - } - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { - if (ctx.getClickedFace() == Direction.UP) { - FluidState fluidState = ctx.getLevel().getFluidState(ctx.getClickedPos()); - return this.defaultBlockState().setValue(FLOOR, true) - .setValue(ROTATION, Mth.floor((180.0 + ctx.getRotation() * 16.0 / 360.0) + 0.5 - 12) & 15) - .setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER); - } else if (ctx.getClickedFace() != Direction.DOWN) { - BlockState blockState = this.defaultBlockState(); - FluidState fluidState = ctx.getLevel().getFluidState(ctx.getClickedPos()); - LevelReader worldView = ctx.getLevel(); - BlockPos blockPos = ctx.getClickedPos(); - Direction[] directions = ctx.getNearestLookingDirections(); - - for (Direction direction : directions) { - if (direction.getAxis().isHorizontal()) { - Direction dir = direction.getOpposite(); - int rot = Mth.floor((180.0 + dir.toYRot() * 16.0 / 360.0) + 0.5 + 4) & 15; - blockState = blockState.setValue(ROTATION, rot); - if (blockState.canSurvive(worldView, blockPos)) { - return blockState.setValue(FLOOR, false).setValue(WATERLOGGED, fluidState.getType() == Fluids.WATER); - } - } - } - } - - return null; - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - return ModelsHelper.createBlockEmpty(parentId); - } - - @Override - public BlockState rotate(BlockState state, Rotation rotation) { - return state.setValue(ROTATION, rotation.rotate((Integer) state.getValue(ROTATION), 16)); - } - - @Override - public BlockState mirror(BlockState state, Mirror mirror) { - return state.setValue(ROTATION, mirror.mirror((Integer) state.getValue(ROTATION), 16)); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - public Fluid takeLiquid(LevelAccessor world, BlockPos pos, BlockState state) { - // TODO Auto-generated method stub - return super.takeLiquid(world, pos, state); - } - - @Override - public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { - // TODO Auto-generated method stub - return super.canPlaceLiquid(world, pos, state, fluid); - } - - @Override - public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { - // TODO Auto-generated method stub - return super.placeLiquid(world, pos, state, fluidState); - } - - @Override - public int getStackSize() { - return 16; - } - - @Override - public boolean canPlaceOnWater() { - return false; - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/blocks/BaseSlabBlock.java b/src/main/java/ru/bclib/blocks/BaseSlabBlock.java deleted file mode 100644 index 368237d5..00000000 --- a/src/main/java/ru/bclib/blocks/BaseSlabBlock.java +++ /dev/null @@ -1,73 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.SlabBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.SlabType; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BaseSlabBlock extends SlabBlock implements BlockModelProvider { - private final Block parent; - - public BaseSlabBlock(Block source) { - super(FabricBlockSettings.copyOf(source)); - this.parent = source; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - Optional pattern; - if (blockState.getValue(TYPE) == SlabType.DOUBLE) { - pattern = PatternsHelper.createBlockSimple(parentId); - } else { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_SLAB, parentId); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - SlabType type = blockState.getValue(TYPE); - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + "_" + type); - registerBlockModel(stateId, modelId, blockState, modelCache); - if (type == SlabType.TOP) { - return ModelsHelper.createMultiVariant(modelId, BlockModelRotation.X180_Y0.getRotation(), true); - } - return ModelsHelper.createBlockSimple(modelId); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseStairsBlock.java b/src/main/java/ru/bclib/blocks/BaseStairsBlock.java deleted file mode 100644 index ce2571c8..00000000 --- a/src/main/java/ru/bclib/blocks/BaseStairsBlock.java +++ /dev/null @@ -1,117 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.StairBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.Half; -import net.minecraft.world.level.block.state.properties.StairsShape; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BaseStairsBlock extends StairBlock implements BlockModelProvider { - - private final Block parent; - - public BaseStairsBlock(Block source) { - super(source.defaultBlockState(), FabricBlockSettings.copyOf(source)); - this.parent = source; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - Optional pattern = Optional.empty(); - switch (blockState.getValue(SHAPE)) { - case STRAIGHT: - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_STAIR, parentId); - break; - case INNER_LEFT: - case INNER_RIGHT: - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_STAIR_INNER, parentId); - break; - case OUTER_LEFT: - case OUTER_RIGHT: - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_STAIR_OUTER, parentId); - break; - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - String state; - StairsShape shape = blockState.getValue(SHAPE); - switch (shape) { - case INNER_LEFT: - case INNER_RIGHT: - state = "_inner"; break; - case OUTER_LEFT: - case OUTER_RIGHT: - state = "_outer"; break; - default: - state = ""; - } - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath() + state); - registerBlockModel(stateId, modelId, blockState, modelCache); - - boolean isTop = blockState.getValue(HALF) == Half.TOP; - boolean isLeft = shape == StairsShape.INNER_LEFT || - shape == StairsShape.OUTER_LEFT; - boolean isRight = shape == StairsShape.INNER_RIGHT || - shape == StairsShape.OUTER_RIGHT; - int y = 0; - int x = isTop ? 180 : 0; - switch (blockState.getValue(FACING)) { - case NORTH: - if (isTop && !isRight) y = 270; - else if (!isTop) y = isLeft ? 180 : 270; - break; - case EAST: - if (isTop && isRight) y = 90; - else if (!isTop && isLeft) y = 270; - break; - case SOUTH: - if (isTop) y = isRight ? 180 : 90; - else if (!isLeft) y = 90; - break; - case WEST: - default: - y = (isTop && isRight) ? 270 : (!isTop && isLeft) ? 90 : 180; - break; - } - BlockModelRotation rotation = BlockModelRotation.by(x, y); - return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), true); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseStoneButtonBlock.java b/src/main/java/ru/bclib/blocks/BaseStoneButtonBlock.java deleted file mode 100644 index aabc865b..00000000 --- a/src/main/java/ru/bclib/blocks/BaseStoneButtonBlock.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.bclib.blocks; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.world.level.block.Block; - -public class BaseStoneButtonBlock extends BaseButtonBlock { - - public BaseStoneButtonBlock(Block source) { - super(source, FabricBlockSettings.copyOf(source).noOcclusion(), false); - } - - @Override - protected SoundEvent getSound(boolean clicked) { - return clicked ? SoundEvents.STONE_BUTTON_CLICK_ON : SoundEvents.STONE_BUTTON_CLICK_OFF; - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseStripableLogBlock.java b/src/main/java/ru/bclib/blocks/BaseStripableLogBlock.java deleted file mode 100644 index f956e04d..00000000 --- a/src/main/java/ru/bclib/blocks/BaseStripableLogBlock.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.bclib.blocks; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.RotatedPillarBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.MaterialColor; -import net.minecraft.world.phys.BlockHitResult; - -public class BaseStripableLogBlock extends BaseRotatedPillarBlock { - private final Block striped; - - public BaseStripableLogBlock(MaterialColor color, Block striped) { - super(FabricBlockSettings.copyOf(striped).materialColor(color)); - this.striped = striped; - } - - @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - if (player.getMainHandItem().getItem().is(FabricToolTags.AXES)) { - world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); - if (!world.isClientSide) { - world.setBlock(pos, striped.defaultBlockState().setValue(RotatedPillarBlock.AXIS, state.getValue(RotatedPillarBlock.AXIS)), 11); - if (!player.isCreative()) { - player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player); - } - } - return InteractionResult.SUCCESS; - } - return InteractionResult.FAIL; - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseTerrainBlock.java b/src/main/java/ru/bclib/blocks/BaseTerrainBlock.java deleted file mode 100644 index b11b25f1..00000000 --- a/src/main/java/ru/bclib/blocks/BaseTerrainBlock.java +++ /dev/null @@ -1,142 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.collect.Maps; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.SnowLayerBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.lighting.LayerLightEngine; -import net.minecraft.world.level.material.MaterialColor; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.BlockHitResult; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.client.sound.BlockSounds; - -@SuppressWarnings("deprecation") -public class BaseTerrainBlock extends BaseBlock { - - private final Block baseBlock; - private Block pathBlock; - - public BaseTerrainBlock(Block baseBlock, MaterialColor color) { - super(FabricBlockSettings.copyOf(baseBlock).materialColor(color).sound(BlockSounds.TERRAIN_SOUND).randomTicks()); - this.baseBlock = baseBlock; - } - - public void setPathBlock(Block roadBlock) { - this.pathBlock = roadBlock; - } - - public Block getBaseBlock() { - return baseBlock; - } - - @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - if (pathBlock != null && player.getMainHandItem().getItem().is(FabricToolTags.SHOVELS)) { - world.playSound(player, pos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); - if (!world.isClientSide) { - world.setBlockAndUpdate(pos, pathBlock.defaultBlockState()); - if (!player.isCreative()) { - player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player); - } - } - return InteractionResult.SUCCESS; - } - return InteractionResult.FAIL; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { - return Collections.singletonList(new ItemStack(this)); - } - return Collections.singletonList(new ItemStack(Blocks.END_STONE)); - } - - @Override - public void randomTick(BlockState state, ServerLevel world, BlockPos pos, Random random) { - if (random.nextInt(16) == 0 && !canStay(state, world, pos)) { - world.setBlockAndUpdate(pos, Blocks.END_STONE.defaultBlockState()); - } - } - - public boolean canStay(BlockState state, LevelReader worldView, BlockPos pos) { - BlockPos blockPos = pos.above(); - BlockState blockState = worldView.getBlockState(blockPos); - if (blockState.is(Blocks.SNOW) && blockState.getValue(SnowLayerBlock.LAYERS) == 1) { - return true; - } - else if (blockState.getFluidState().getAmount() == 8) { - return false; - } - else { - int i = LayerLightEngine.getLightBlockInto(worldView, state, pos, blockState, blockPos, Direction.UP, blockState.getLightBlock(worldView, blockPos)); - return i < 5; - } - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - return getBlockModel(blockId, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - ResourceLocation baseId = Registry.BLOCK.getKey(baseBlock); - String modId = blockId.getNamespace(); - String path = blockId.getPath(); - String bottom = baseId.getNamespace() + ":block/" + baseId.getPath(); - Map textures = Maps.newHashMap(); - textures.put("%top%", modId + ":block/" + path + "_top"); - textures.put("%side%", modId + ":block/" + path + "_side"); - textures.put("%bottom%", bottom); - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_TOP_SIDE_BOTTOM, textures); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createRandomTopModel(modelId); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseTrapdoorBlock.java b/src/main/java/ru/bclib/blocks/BaseTrapdoorBlock.java deleted file mode 100644 index 49ff777c..00000000 --- a/src/main/java/ru/bclib/blocks/BaseTrapdoorBlock.java +++ /dev/null @@ -1,94 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.TrapDoorBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.Half; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public class BaseTrapdoorBlock extends TrapDoorBlock implements IRenderTyped, BlockModelProvider { - public BaseTrapdoorBlock(Block source) { - super(FabricBlockSettings.copyOf(source).strength(3.0F, 3.0F).noOcclusion()); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - String name = resourceLocation.getPath(); - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_TRAPDOOR, new HashMap() { - private static final long serialVersionUID = 1L; - { - put("%modid%", resourceLocation.getNamespace()); - put("%texture%", name); - put("%side%", name.replace("trapdoor", "door_side")); - } - }); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); - registerBlockModel(stateId, modelId, blockState, modelCache); - boolean isTop = blockState.getValue(HALF) == Half.TOP; - boolean isOpen = blockState.getValue(OPEN); - int y = 0; - int x = (isTop && isOpen) ? 270 : isTop ? 180 : isOpen ? 90 : 0; - switch (blockState.getValue(FACING)) { - case EAST: - y = (isTop && isOpen) ? 270 : 90; - break; - case NORTH: - if (isTop && isOpen) y = 180; - break; - case SOUTH: - y = (isTop && isOpen) ? 0 : 180; - break; - case WEST: - y = (isTop && isOpen) ? 90 : 270; - break; - default: break; - } - BlockModelRotation rotation = BlockModelRotation.by(x, y); - return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseUnderwaterWallPlantBlock.java b/src/main/java/ru/bclib/blocks/BaseUnderwaterWallPlantBlock.java deleted file mode 100644 index 5ef38d66..00000000 --- a/src/main/java/ru/bclib/blocks/BaseUnderwaterWallPlantBlock.java +++ /dev/null @@ -1,59 +0,0 @@ -package ru.bclib.blocks; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.LiquidBlockContainer; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.level.material.Material; - -public abstract class BaseUnderwaterWallPlantBlock extends BaseWallPlantBlock implements LiquidBlockContainer { - - public BaseUnderwaterWallPlantBlock() { - super(FabricBlockSettings.of(Material.WATER_PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.WET_GRASS) - .noCollission()); - } - - public BaseUnderwaterWallPlantBlock(int light) { - super(FabricBlockSettings.of(Material.WATER_PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .luminance(light) - .sound(SoundType.WET_GRASS) - .noCollission()); - } - - public BaseUnderwaterWallPlantBlock(Properties settings) { - super(settings); - } - - @Override - public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { - return false; - } - - @Override - public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { - return false; - } - - @Override - public FluidState getFluidState(BlockState state) { - return Fluids.WATER.getSource(false); - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - return world.getFluidState(pos).getType() == Fluids.WATER && super.canSurvive(state, world, pos); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseVineBlock.java b/src/main/java/ru/bclib/blocks/BaseVineBlock.java deleted file mode 100644 index 2ac699d3..00000000 --- a/src/main/java/ru/bclib/blocks/BaseVineBlock.java +++ /dev/null @@ -1,146 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Random; - -import com.google.common.collect.Lists; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.tags.BlockTags; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.BonemealableBlock; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.EnumProperty; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.blocks.BlockProperties.TripleShape; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; -import ru.bclib.util.BlocksHelper; - -public class BaseVineBlock extends BaseBlockNotFull implements IRenderTyped, BonemealableBlock { - public static final EnumProperty SHAPE = BlockProperties.TRIPLE_SHAPE; - private static final VoxelShape VOXEL_SHAPE = Block.box(2, 0, 2, 14, 16, 14); - - public BaseVineBlock() { - this(0, false); - } - - public BaseVineBlock(int light) { - this(light, false); - } - - public BaseVineBlock(int light, boolean bottomOnly) { - super(FabricBlockSettings.of(Material.PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.GRASS) - .lightLevel((state) -> bottomOnly ? state.getValue(SHAPE) == TripleShape.BOTTOM ? light : 0 : light) - .noCollission()); - this.registerDefaultState(this.stateDefinition.any().setValue(SHAPE, TripleShape.BOTTOM)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(SHAPE); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - Vec3 vec3d = state.getOffset(view, pos); - return VOXEL_SHAPE.move(vec3d.x, vec3d.y, vec3d.z); - } - - @Override - public BlockBehaviour.OffsetType getOffsetType() { - return BlockBehaviour.OffsetType.XZ; - } - - public boolean canGenerate(BlockState state, LevelReader world, BlockPos pos) { - return isSupport(state, world, pos); - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - return isSupport(state, world, pos); - } - - protected boolean isSupport(BlockState state, LevelReader world, BlockPos pos) { - BlockState up = world.getBlockState(pos.above()); - return up.is(this) || up.is(BlockTags.LEAVES) || canSupportCenter(world, pos.above(), Direction.DOWN); - } - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (!canSurvive(state, world, pos)) { - return Blocks.AIR.defaultBlockState(); - } - else { - if (world.getBlockState(pos.below()).getBlock() != this) - return state.setValue(SHAPE, TripleShape.BOTTOM); - else if (world.getBlockState(pos.above()).getBlock() != this) - return state.setValue(SHAPE, TripleShape.TOP); - return state.setValue(SHAPE, TripleShape.MIDDLE); - } - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && tool.getItem().is(FabricToolTags.SHEARS) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { - return Lists.newArrayList(new ItemStack(this)); - } - else { - return Lists.newArrayList(); - } - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { - while (world.getBlockState(pos).getBlock() == this) { - pos = pos.below(); - } - return world.getBlockState(pos).isAir(); - } - - @Override - public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { - while (world.getBlockState(pos).getBlock() == this) { - pos = pos.below(); - } - return world.isEmptyBlock(pos); - } - - @Override - public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { - while (world.getBlockState(pos).getBlock() == this) { - pos = pos.below(); - } - world.setBlockAndUpdate(pos, defaultBlockState()); - BlocksHelper.setWithoutUpdate(world, pos, defaultBlockState()); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseWallBlock.java b/src/main/java/ru/bclib/blocks/BaseWallBlock.java deleted file mode 100644 index 7d232a79..00000000 --- a/src/main/java/ru/bclib/blocks/BaseWallBlock.java +++ /dev/null @@ -1,101 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.WallBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.WallSide; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BaseWallBlock extends WallBlock implements BlockModelProvider { - - private final Block parent; - - public BaseWallBlock(Block source) { - super(FabricBlockSettings.copyOf(source).noOcclusion()); - this.parent = source; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_WALL, parentId); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - String path = blockId.getPath(); - Optional pattern = Optional.empty(); - if (path.endsWith("_post")) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_WALL_POST, parentId); - } - if (path.endsWith("_side")) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_WALL_SIDE, parentId); - } - if (path.endsWith("_side_tall")) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_WALL_SIDE_TALL, parentId); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation postId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + "_post"); - ResourceLocation sideId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + "_side"); - ResourceLocation sideTallId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + "_side_tall"); - registerBlockModel(postId, postId, blockState, modelCache); - registerBlockModel(sideId, sideId, blockState, modelCache); - registerBlockModel(sideTallId, sideTallId, blockState, modelCache); - - ModelsHelper.MultiPartBuilder builder = ModelsHelper.MultiPartBuilder.create(stateDefinition); - builder.part(sideId).setCondition(state -> state.getValue(NORTH_WALL) == WallSide.LOW).setUVLock(true).add(); - builder.part(sideId).setCondition(state -> state.getValue(EAST_WALL) == WallSide.LOW) - .setTransformation(BlockModelRotation.X0_Y90.getRotation()).setUVLock(true).add(); - builder.part(sideId).setCondition(state -> state.getValue(SOUTH_WALL) == WallSide.LOW) - .setTransformation(BlockModelRotation.X0_Y180.getRotation()).setUVLock(true).add(); - builder.part(sideId).setCondition(state -> state.getValue(WEST_WALL) == WallSide.LOW) - .setTransformation(BlockModelRotation.X0_Y270.getRotation()).setUVLock(true).add(); - builder.part(sideTallId).setCondition(state -> state.getValue(NORTH_WALL) == WallSide.TALL).setUVLock(true).add(); - builder.part(sideTallId).setCondition(state -> state.getValue(EAST_WALL) == WallSide.TALL) - .setTransformation(BlockModelRotation.X0_Y90.getRotation()).setUVLock(true).add(); - builder.part(sideTallId).setCondition(state -> state.getValue(SOUTH_WALL) == WallSide.TALL) - .setTransformation(BlockModelRotation.X0_Y180.getRotation()).setUVLock(true).add(); - builder.part(sideTallId).setCondition(state -> state.getValue(WEST_WALL) == WallSide.TALL) - .setTransformation(BlockModelRotation.X0_Y270.getRotation()).setUVLock(true).add(); - builder.part(postId).setCondition(state -> state.getValue(UP)).add(); - - return builder.build(); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseWallPlantBlock.java b/src/main/java/ru/bclib/blocks/BaseWallPlantBlock.java deleted file mode 100644 index d833e3fc..00000000 --- a/src/main/java/ru/bclib/blocks/BaseWallPlantBlock.java +++ /dev/null @@ -1,124 +0,0 @@ -package ru.bclib.blocks; - -import java.util.EnumMap; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.HorizontalDirectionalBlock; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.DirectionProperty; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.util.BlocksHelper; - -public abstract class BaseWallPlantBlock extends BasePlantBlock { - private static final EnumMap SHAPES = Maps.newEnumMap(ImmutableMap.of( - Direction.NORTH, Block.box(1, 1, 8, 15, 15, 16), - Direction.SOUTH, Block.box(1, 1, 0, 15, 15, 8), - Direction.WEST, Block.box(8, 1, 1, 16, 15, 15), - Direction.EAST, Block.box(0, 1, 1, 8, 15, 15))); - public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING; - - public BaseWallPlantBlock() { - this(FabricBlockSettings.of(Material.PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.GRASS) - .noCollission()); - } - - public BaseWallPlantBlock(int light) { - this(FabricBlockSettings.of(Material.PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .luminance(light) - .sound(SoundType.GRASS) - .noCollission()); - } - - public BaseWallPlantBlock(Properties settings) { - super(settings); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(FACING); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - return SHAPES.get(state.getValue(FACING)); - } - - @Override - public BlockBehaviour.OffsetType getOffsetType() { - return BlockBehaviour.OffsetType.NONE; - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - Direction direction = state.getValue(FACING); - BlockPos blockPos = pos.relative(direction.getOpposite()); - BlockState blockState = world.getBlockState(blockPos); - return isSupport(world, blockPos, blockState, direction); - } - - public boolean isSupport(LevelReader world, BlockPos pos, BlockState blockState, Direction direction) { - return blockState.getMaterial().isSolid() && blockState.isFaceSturdy(world, pos, direction); - } - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { - BlockState blockState = this.defaultBlockState(); - LevelReader worldView = ctx.getLevel(); - BlockPos blockPos = ctx.getClickedPos(); - Direction[] directions = ctx.getNearestLookingDirections(); - for (Direction direction : directions) { - if (direction.getAxis().isHorizontal()) { - Direction direction2 = direction.getOpposite(); - blockState = blockState.setValue(FACING, direction2); - if (blockState.canSurvive(worldView, blockPos)) { - return blockState; - } - } - } - return null; - } - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (!canSurvive(state, world, pos)) { - return Blocks.AIR.defaultBlockState(); - } - else { - return state; - } - } - - @Override - public BlockState rotate(BlockState state, Rotation rotation) { - return BlocksHelper.rotateHorizontal(state, rotation, FACING); - } - - @Override - public BlockState mirror(BlockState state, Mirror mirror) { - return BlocksHelper.mirrorHorizontal(state, mirror, FACING); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseWeightedPlateBlock.java b/src/main/java/ru/bclib/blocks/BaseWeightedPlateBlock.java deleted file mode 100644 index 744392ca..00000000 --- a/src/main/java/ru/bclib/blocks/BaseWeightedPlateBlock.java +++ /dev/null @@ -1,68 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.WeightedPressurePlateBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.storage.loot.LootContext; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BaseWeightedPlateBlock extends WeightedPressurePlateBlock implements BlockModelProvider { - private final Block parent; - - public BaseWeightedPlateBlock(Block source) { - super(15, FabricBlockSettings.copyOf(source).noCollission().noOcclusion().requiresCorrectToolForDrops().strength(0.5F)); - this.parent = source; - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return getBlockModel(resourceLocation, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - ResourceLocation parentId = Registry.BLOCK.getKey(parent); - Optional pattern; - if (blockState.getValue(POWER) > 0) { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_DOWN, parentId); - } else { - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_PLATE_UP, parentId); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - String state = blockState.getValue(POWER) > 0 ? "_down" : "_up"; - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + state); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createBlockSimple(modelId); - } -} diff --git a/src/main/java/ru/bclib/blocks/BaseWoodenButtonBlock.java b/src/main/java/ru/bclib/blocks/BaseWoodenButtonBlock.java deleted file mode 100644 index daab12c0..00000000 --- a/src/main/java/ru/bclib/blocks/BaseWoodenButtonBlock.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.bclib.blocks; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.world.level.block.Block; - -public class BaseWoodenButtonBlock extends BaseButtonBlock { - - public BaseWoodenButtonBlock(Block source) { - super(source, FabricBlockSettings.copyOf(source).strength(0.5F, 0.5F).noOcclusion(), true); - } - - @Override - protected SoundEvent getSound(boolean clicked) { - return clicked ? SoundEvents.WOODEN_BUTTON_CLICK_ON : SoundEvents.WOODEN_BUTTON_CLICK_OFF; - } -} diff --git a/src/main/java/ru/bclib/blocks/BlockProperties.java b/src/main/java/ru/bclib/blocks/BlockProperties.java deleted file mode 100644 index 7bf55492..00000000 --- a/src/main/java/ru/bclib/blocks/BlockProperties.java +++ /dev/null @@ -1,81 +0,0 @@ -package ru.bclib.blocks; - -import net.minecraft.util.StringRepresentable; -import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.state.properties.EnumProperty; -import net.minecraft.world.level.block.state.properties.IntegerProperty; - -public class BlockProperties { - public static final EnumProperty TRIPLE_SHAPE = EnumProperty.create("shape", TripleShape.class); - public static final EnumProperty PENTA_SHAPE = EnumProperty.create("shape", PentaShape.class); - - public static final BooleanProperty TRANSITION = BooleanProperty.create("transition"); - public static final BooleanProperty HAS_LIGHT = BooleanProperty.create("has_light"); - public static final BooleanProperty IS_FLOOR = BooleanProperty.create("is_floor"); - public static final BooleanProperty NATURAL = BooleanProperty.create("natural"); - public static final BooleanProperty ACTIVE = BooleanProperty.create("active"); - public static final BooleanProperty SMALL = BooleanProperty.create("small"); - - public static final IntegerProperty DESTRUCTION = IntegerProperty.create("destruction", 0, 2); - public static final IntegerProperty ROTATION = IntegerProperty.create("rotation", 0, 3); - public static final IntegerProperty FULLNESS = IntegerProperty.create("fullness", 0, 3); - public static final IntegerProperty COLOR = IntegerProperty.create("color", 0, 7); - public static final IntegerProperty SIZE = IntegerProperty.create("size", 0, 7); - public static final IntegerProperty AGE = IntegerProperty.create("age", 0, 3); - - public enum TripleShape implements StringRepresentable { - TOP("top", 0), - MIDDLE("middle", 1), - BOTTOM("bottom", 2); - - private final String name; - private final int index; - - TripleShape(String name, int index) { - this.name = name; - this.index = index; - } - - @Override - public String getSerializedName() { - return name; - } - - @Override - public String toString() { - return name; - } - - public int getIndex() { - return index; - } - - public static TripleShape fromIndex(int index) { - return index > 1 ? BOTTOM : index == 1 ? MIDDLE : TOP; - } - } - - public enum PentaShape implements StringRepresentable { - BOTTOM("bottom"), - PRE_BOTTOM("pre_bottom"), - MIDDLE("middle"), - PRE_TOP("pre_top"), - TOP("top"); - - private final String name; - - PentaShape(String name) { - this.name = name; - } - - @Override - public String getSerializedName() { - return name; - } - - @Override - public String toString() { - return name; - } - } -} diff --git a/src/main/java/ru/bclib/blocks/FeatureSaplingBlock.java b/src/main/java/ru/bclib/blocks/FeatureSaplingBlock.java deleted file mode 100644 index 47a6974b..00000000 --- a/src/main/java/ru/bclib/blocks/FeatureSaplingBlock.java +++ /dev/null @@ -1,121 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Random; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.SaplingBlock; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.levelgen.feature.Feature; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public abstract class FeatureSaplingBlock extends SaplingBlock implements IRenderTyped, BlockModelProvider { - private static final VoxelShape SHAPE = Block.box(4, 0, 4, 12, 14, 12); - - public FeatureSaplingBlock() { - super(null, FabricBlockSettings.of(Material.PLANT) - .breakByHand(true) - .collidable(false) - .instabreak() - .sound(SoundType.GRASS) - .randomTicks()); - } - - public FeatureSaplingBlock(int light) { - super(null, FabricBlockSettings.of(Material.PLANT) - .breakByHand(true) - .collidable(false) - .luminance(light) - .instabreak() - .sound(SoundType.GRASS) - .randomTicks()); - } - - protected abstract Feature getFeature(); - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Collections.singletonList(new ItemStack(this)); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - return SHAPE; - } - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (!canSurvive(state, world, pos)) - return Blocks.AIR.defaultBlockState(); - else - return state; - } - - @Override - public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { - return random.nextInt(16) == 0; - } - - @Override - public void advanceTree(ServerLevel world, BlockPos pos, BlockState blockState, Random random) { - getFeature().place(world, world.getChunkSource().getGenerator(), random, pos, null); - } - - @Override - public void randomTick(BlockState state, ServerLevel world, BlockPos pos, Random random) { - this.tick(state, world, pos, random); - } - - @Override - public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { - super.tick(state, world, pos, random); - if (isBonemealSuccess(world, random, pos, state)) { - performBonemeal(world, random, pos, state); - } - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return ModelsHelper.createBlockItem(resourceLocation); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CROSS, resourceLocation); - return ModelsHelper.fromPattern(pattern); - } -} diff --git a/src/main/java/ru/bclib/blocks/SimpleLeavesBlock.java b/src/main/java/ru/bclib/blocks/SimpleLeavesBlock.java deleted file mode 100644 index 6c1895e5..00000000 --- a/src/main/java/ru/bclib/blocks/SimpleLeavesBlock.java +++ /dev/null @@ -1,38 +0,0 @@ -package ru.bclib.blocks; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.material.MaterialColor; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public class SimpleLeavesBlock extends BaseBlockNotFull implements IRenderTyped { - public SimpleLeavesBlock(MaterialColor color) { - super(FabricBlockSettings.of(Material.LEAVES) - .strength(0.2F) - .materialColor(color) - .sound(SoundType.GRASS) - .noOcclusion() - .isValidSpawn((state, world, pos, type) -> false) - .isSuffocating((state, world, pos) -> false) - .isViewBlocking((state, world, pos) -> false)); - } - - public SimpleLeavesBlock(MaterialColor color, int light) { - super(FabricBlockSettings.of(Material.LEAVES) - .luminance(light) - .materialColor(color) - .strength(0.2F) - .sound(SoundType.GRASS) - .noOcclusion() - .isValidSpawn((state, world, pos, type) -> false) - .isSuffocating((state, world, pos) -> false) - .isViewBlocking((state, world, pos) -> false)); - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/blocks/StalactiteBlock.java b/src/main/java/ru/bclib/blocks/StalactiteBlock.java deleted file mode 100644 index 23add876..00000000 --- a/src/main/java/ru/bclib/blocks/StalactiteBlock.java +++ /dev/null @@ -1,250 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.BlockPos.MutableBlockPos; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.LiquidBlockContainer; -import net.minecraft.world.level.block.SimpleWaterloggedBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public class StalactiteBlock extends BaseBlockNotFull implements SimpleWaterloggedBlock, LiquidBlockContainer, IRenderTyped { - public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; - public static final BooleanProperty IS_FLOOR = BlockProperties.IS_FLOOR; - public static final IntegerProperty SIZE = BlockProperties.SIZE; - private static final VoxelShape[] SHAPES; - - public StalactiteBlock(Block source) { - super(FabricBlockSettings.copy(source).noOcclusion()); - this.registerDefaultState(getStateDefinition().any().setValue(SIZE, 0).setValue(IS_FLOOR, true).setValue(WATERLOGGED, false)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(WATERLOGGED, IS_FLOOR, SIZE); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - return SHAPES[state.getValue(SIZE)]; - } - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { - LevelReader world = ctx.getLevel(); - BlockPos pos = ctx.getClickedPos(); - Direction dir = ctx.getClickedFace(); - boolean water = world.getFluidState(pos).getType() == Fluids.WATER; - - if (dir == Direction.DOWN) { - if (isThis(world, pos.above()) || canSupportCenter(world, pos.above(), Direction.DOWN)) { - return defaultBlockState().setValue(IS_FLOOR, false).setValue(WATERLOGGED, water); - } - else if (isThis(world, pos.below()) || canSupportCenter(world, pos.below(), Direction.UP)) { - return defaultBlockState().setValue(IS_FLOOR, true).setValue(WATERLOGGED, water); - } - else { - return null; - } - } - else { - if (isThis(world, pos.below()) || canSupportCenter(world, pos.below(), Direction.UP)) { - return defaultBlockState().setValue(IS_FLOOR, true).setValue(WATERLOGGED, water); - } - else if (isThis(world, pos.above()) || canSupportCenter(world, pos.above(), Direction.DOWN)) { - return defaultBlockState().setValue(IS_FLOOR, false).setValue(WATERLOGGED, water); - } - else { - return null; - } - } - } - - @Override - public void setPlacedBy(Level world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack itemStack) { - boolean hasUp = isThis(world, pos.above()); - boolean hasDown = isThis(world, pos.below()); - MutableBlockPos mut = new MutableBlockPos(); - if (hasUp && hasDown) { - boolean floor = state.getValue(IS_FLOOR); - BlockPos second = floor ? pos.above() : pos.below(); - BlockState bState = world.getBlockState(second); - world.setBlockAndUpdate(pos, state.setValue(SIZE, 1).setValue(IS_FLOOR, floor)); - world.setBlockAndUpdate(second, bState.setValue(SIZE, 1).setValue(IS_FLOOR, !floor)); - - bState = state; - int startSize = floor ? 1 : 2; - mut.set(pos.getX(), pos.getY() + 1, pos.getZ()); - for (int i = 0; i < 8 && isThis(bState); i++) { - world.setBlockAndUpdate(mut, bState.setValue(SIZE, startSize++).setValue(IS_FLOOR, false)); - mut.setY(mut.getY() + 1); - bState = world.getBlockState(mut); - } - - bState = state; - startSize = floor ? 2 : 1; - mut.set(pos.getX(), pos.getY() - 1, pos.getZ()); - for (int i = 0; i < 8 && isThis(bState); i++) { - world.setBlockAndUpdate(mut, bState.setValue(SIZE, startSize++).setValue(IS_FLOOR, true)); - mut.setY(mut.getY() - 1); - bState = world.getBlockState(mut); - } - } - else if (hasDown) { - mut.setX(pos.getX()); - mut.setZ(pos.getZ()); - for (int i = 1; i < 8; i++) { - mut.setY(pos.getY() - i); - if (isThis(world, mut)) { - BlockState state2 = world.getBlockState(mut); - int size = state2.getValue(SIZE); - if (size < i) { - world.setBlockAndUpdate(mut, state2.setValue(SIZE, i).setValue(IS_FLOOR, true)); - } - else { - break; - } - } - else { - break; - } - } - } - else if (hasUp) { - mut.setX(pos.getX()); - mut.setZ(pos.getZ()); - for (int i = 1; i < 8; i++) { - mut.setY(pos.getY() + i); - if (isThis(world, mut)) { - BlockState state2 = world.getBlockState(mut); - int size = state2.getValue(SIZE); - if (size < i) { - world.setBlockAndUpdate(mut, state2.setValue(SIZE, i).setValue(IS_FLOOR, false)); - } - else { - break; - } - } - else { - break; - } - } - } - } - - private boolean isThis(LevelReader world, BlockPos pos) { - return isThis(world.getBlockState(pos)); - } - - private boolean isThis(BlockState state) { - return state.getBlock() instanceof StalactiteBlock; - } - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (!canSurvive(state, world, pos)) { - return Blocks.AIR.defaultBlockState(); - } - return state; - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - int size = state.getValue(SIZE); - return checkUp(world, pos, size) || checkDown(world, pos, size); - } - - private boolean checkUp(BlockGetter world, BlockPos pos, int size) { - BlockPos p = pos.above(); - BlockState state = world.getBlockState(p); - return (isThis(state) && state.getValue(SIZE) >= size) || state.isCollisionShapeFullBlock(world, p); - } - - private boolean checkDown(BlockGetter world, BlockPos pos, int size) { - BlockPos p = pos.below(); - BlockState state = world.getBlockState(p); - return (isThis(state) && state.getValue(SIZE) >= size) || state.isCollisionShapeFullBlock(world, p); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_CROSS_SHADED, resourceLocation); - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - BlockModelRotation rotation = blockState.getValue(IS_FLOOR) ? BlockModelRotation.X0_Y0 : BlockModelRotation.X180_Y0; - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - stateId.getPath() + "_" + blockState.getValue(SIZE)); - registerBlockModel(modelId, modelId, blockState, modelCache); - return ModelsHelper.createMultiVariant(modelId, rotation.getRotation(), false); - } - - @Override - public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { - return false; - } - - @Override - public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { - return false; - } - - @Override - public FluidState getFluidState(BlockState state) { - return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : Fluids.EMPTY.defaultFluidState(); - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - static { - float end = 2F / 8F; - float start = 5F / 8F; - SHAPES = new VoxelShape[8]; - for (int i = 0; i < 8; i++) { - int side = Mth.floor(Mth.lerp(i / 7F, start, end) * 8F + 0.5F); - SHAPES[i] = Block.box(side, 0, side, 16 - side, 16, 16 - side); - } - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/blocks/StonePressurePlateBlock.java b/src/main/java/ru/bclib/blocks/StonePressurePlateBlock.java deleted file mode 100644 index b1da28b9..00000000 --- a/src/main/java/ru/bclib/blocks/StonePressurePlateBlock.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.bclib.blocks; - -import net.minecraft.world.level.block.Block; - -public class StonePressurePlateBlock extends BasePressurePlateBlock { - public StonePressurePlateBlock(Block source) { - super(Sensitivity.MOBS, source); - } -} diff --git a/src/main/java/ru/bclib/blocks/StripableBarkBlock.java b/src/main/java/ru/bclib/blocks/StripableBarkBlock.java deleted file mode 100644 index c86c792a..00000000 --- a/src/main/java/ru/bclib/blocks/StripableBarkBlock.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.bclib.blocks; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.RotatedPillarBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.MaterialColor; -import net.minecraft.world.phys.BlockHitResult; - -public class StripableBarkBlock extends BaseBarkBlock { - private final Block striped; - - public StripableBarkBlock(MaterialColor color, Block striped) { - super(FabricBlockSettings.copyOf(striped).materialColor(color)); - this.striped = striped; - } - - @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - if (player.getMainHandItem().getItem().is(FabricToolTags.AXES)) { - world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); - if (!world.isClientSide) { - world.setBlock(pos, striped.defaultBlockState().setValue(RotatedPillarBlock.AXIS, state.getValue(RotatedPillarBlock.AXIS)), 11); - if (!player.isCreative()) { - player.getMainHandItem().hurt(1, world.random, (ServerPlayer) player); - } - } - return InteractionResult.SUCCESS; - } - return InteractionResult.FAIL; - } -} diff --git a/src/main/java/ru/bclib/blocks/TripleTerrainBlock.java b/src/main/java/ru/bclib/blocks/TripleTerrainBlock.java deleted file mode 100644 index 252ec776..00000000 --- a/src/main/java/ru/bclib/blocks/TripleTerrainBlock.java +++ /dev/null @@ -1,165 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.MultiVariant; -import net.minecraft.client.renderer.block.model.Variant; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.EnumProperty; -import net.minecraft.world.level.material.MaterialColor; -import net.minecraft.world.phys.BlockHitResult; -import ru.bclib.blocks.BlockProperties.TripleShape; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class TripleTerrainBlock extends BaseTerrainBlock { - public static final EnumProperty SHAPE = BlockProperties.TRIPLE_SHAPE; - - public TripleTerrainBlock(Block baseBlock) { - super(baseBlock, baseBlock.defaultMaterialColor()); - this.registerDefaultState(defaultBlockState().setValue(SHAPE, TripleShape.BOTTOM)); - } - - public TripleTerrainBlock(Block baseBlock, MaterialColor color) { - super(baseBlock, color); - this.registerDefaultState(defaultBlockState().setValue(SHAPE, TripleShape.BOTTOM)); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(SHAPE); - } - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { - Direction dir = ctx.getClickedFace(); - TripleShape shape = dir == Direction.UP ? TripleShape.BOTTOM : dir == Direction.DOWN ? TripleShape.TOP : TripleShape.MIDDLE; - return defaultBlockState().setValue(SHAPE, shape); - } - - @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - TripleShape shape = state.getValue(SHAPE); - if (shape == TripleShape.BOTTOM) { - return super.use(state, world, pos, player, hand, hit); - } - return InteractionResult.FAIL; - } - - @Override - public void randomTick(BlockState state, ServerLevel world, BlockPos pos, Random random) { - TripleShape shape = state.getValue(SHAPE); - if (shape == TripleShape.BOTTOM) { - super.randomTick(state, world, pos, random); - } else if (random.nextInt(16) == 0) { - boolean bottom = canStayBottom(world, pos); - if (shape == TripleShape.TOP) { - if (!bottom) { - world.setBlockAndUpdate(pos, Blocks.END_STONE.defaultBlockState()); - } - } - else { - boolean top = canStay(state, world, pos) || isMiddle(world.getBlockState(pos.above())); - if (!top && !bottom) { - world.setBlockAndUpdate(pos, Blocks.END_STONE.defaultBlockState()); - } else if (top && !bottom) { - world.setBlockAndUpdate(pos, state.setValue(SHAPE, TripleShape.BOTTOM)); - } else if (!top) { - world.setBlockAndUpdate(pos, state.setValue(SHAPE, TripleShape.TOP)); - } - } - } - } - - protected boolean canStayBottom(LevelReader world, BlockPos pos) { - BlockPos blockPos = pos.below(); - BlockState blockState = world.getBlockState(blockPos); - if (isMiddle(blockState)) { - return true; - } else if (blockState.getFluidState().getAmount() == 8) { - return false; - } else { - return !blockState.isFaceSturdy(world, blockPos, Direction.UP); - } - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation blockId) { - return getBlockModel(blockId, defaultBlockState()); - } - - @Override - @Environment(EnvType.CLIENT) - public @Nullable BlockModel getBlockModel(ResourceLocation blockId, BlockState blockState) { - String path = blockId.getPath(); - Optional pattern; - if (isMiddle(blockState)) { - ResourceLocation topId = new ResourceLocation(blockId.getNamespace(), path + "_top"); - pattern = PatternsHelper.createBlockSimple(topId); - } else { - Map textures = Maps.newHashMap(); - textures.put("%top%", "betterend:block/" + path + "_top"); - textures.put("%side%", "betterend:block/" + path + "_side"); - textures.put("%bottom%", "minecraft:block/end_stone"); - pattern = PatternsHelper.createJson(BasePatterns.BLOCK_TOP_SIDE_BOTTOM, textures); - } - return ModelsHelper.fromPattern(pattern); - } - - @Override - @Environment(EnvType.CLIENT) - public UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - boolean isMiddle = isMiddle(blockState); - String middle = isMiddle ? "_middle" : ""; - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), - "block/" + stateId.getPath() + middle); - registerBlockModel(stateId, modelId, blockState, modelCache); - if (isMiddle) { - List variants = Lists.newArrayList(); - for (BlockModelRotation rotation : BlockModelRotation.values()) { - variants.add(new Variant(modelId, rotation.getRotation(), false, 1)); - } - return new MultiVariant(variants); - } else if (blockState.getValue(SHAPE) == TripleShape.TOP) { - return new MultiVariant(Lists.newArrayList( - new Variant(modelId, BlockModelRotation.X180_Y0.getRotation(), false, 1), - new Variant(modelId, BlockModelRotation.X180_Y90.getRotation(), false, 1), - new Variant(modelId, BlockModelRotation.X180_Y180.getRotation(), false, 1), - new Variant(modelId, BlockModelRotation.X180_Y270.getRotation(), false, 1) - )); - } - return ModelsHelper.createRandomTopModel(modelId); - } - - protected boolean isMiddle(BlockState blockState) { - return blockState.is(this) && blockState.getValue(SHAPE) == TripleShape.MIDDLE; - } -} diff --git a/src/main/java/ru/bclib/blocks/UnderwaterPlantBlock.java b/src/main/java/ru/bclib/blocks/UnderwaterPlantBlock.java deleted file mode 100644 index 171deae0..00000000 --- a/src/main/java/ru/bclib/blocks/UnderwaterPlantBlock.java +++ /dev/null @@ -1,141 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; -import java.util.Random; - -import com.google.common.collect.Lists; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.BonemealableBlock; -import net.minecraft.world.level.block.LiquidBlockContainer; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public abstract class UnderwaterPlantBlock extends BaseBlockNotFull implements IRenderTyped, BonemealableBlock, LiquidBlockContainer { - private static final VoxelShape SHAPE = Block.box(4, 0, 4, 12, 14, 12); - - public UnderwaterPlantBlock() { - super(FabricBlockSettings.of(Material.WATER_PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.WET_GRASS) - .noCollission()); - } - - public UnderwaterPlantBlock(int light) { - super(FabricBlockSettings.of(Material.WATER_PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .luminance(light) - .sound(SoundType.WET_GRASS) - .noCollission()); - } - - public UnderwaterPlantBlock(Properties settings) { - super(settings); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - Vec3 vec3d = state.getOffset(view, pos); - return SHAPE.move(vec3d.x, vec3d.y, vec3d.z); - } - - @Override - public BlockBehaviour.OffsetType getOffsetType() { - return BlockBehaviour.OffsetType.XZ; - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - BlockState down = world.getBlockState(pos.below()); - state = world.getBlockState(pos); - return isTerrain(down) && state.getFluidState().getType().equals(Fluids.WATER.getSource()); - } - - protected abstract boolean isTerrain(BlockState state); - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (!canSurvive(state, world, pos)) { - world.destroyBlock(pos, true); - return Blocks.WATER.defaultBlockState(); - } - else { - return state; - } - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && tool.getItem().is(FabricToolTags.SHEARS) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { - return Lists.newArrayList(new ItemStack(this)); - } - else { - return Lists.newArrayList(); - } - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - public boolean isValidBonemealTarget(BlockGetter world, BlockPos pos, BlockState state, boolean isClient) { - return true; - } - - @Override - public boolean isBonemealSuccess(Level world, Random random, BlockPos pos, BlockState state) { - return true; - } - - @Override - public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { - ItemEntity item = new ItemEntity(world, pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, new ItemStack(this)); - world.addFreshEntity(item); - } - - @Override - public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { - return false; - } - - @Override - public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { - return false; - } - - @Override - public FluidState getFluidState(BlockState state) { - return Fluids.WATER.getSource(false); - } -} diff --git a/src/main/java/ru/bclib/blocks/UnderwaterPlantWithAgeBlock.java b/src/main/java/ru/bclib/blocks/UnderwaterPlantWithAgeBlock.java deleted file mode 100644 index fb7870b8..00000000 --- a/src/main/java/ru/bclib/blocks/UnderwaterPlantWithAgeBlock.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.bclib.blocks; - -import java.util.Random; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.material.Material; - -public abstract class UnderwaterPlantWithAgeBlock extends UnderwaterPlantBlock { - public static final IntegerProperty AGE = BlockProperties.AGE; - - public UnderwaterPlantWithAgeBlock() { - super(FabricBlockSettings.of(Material.WATER_PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.WET_GRASS) - .randomTicks() - .noCollission()); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder stateManager) { - stateManager.add(AGE); - } - - public abstract void grow(WorldGenLevel world, Random random, BlockPos pos); - - @Override - public void performBonemeal(ServerLevel world, Random random, BlockPos pos, BlockState state) { - if (random.nextInt(4) == 0) { - int age = state.getValue(AGE); - if (age < 3) { - world.setBlockAndUpdate(pos, state.setValue(AGE, age + 1)); - } - else { - grow(world, random, pos); - } - } - } - - @Override - public void tick(BlockState state, ServerLevel world, BlockPos pos, Random random) { - super.tick(state, world, pos, random); - if (isBonemealSuccess(world, random, pos, state)) { - performBonemeal(world, random, pos, state); - } - } -} diff --git a/src/main/java/ru/bclib/blocks/UpDownPlantBlock.java b/src/main/java/ru/bclib/blocks/UpDownPlantBlock.java deleted file mode 100644 index 3d18763a..00000000 --- a/src/main/java/ru/bclib/blocks/UpDownPlantBlock.java +++ /dev/null @@ -1,92 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; - -import com.google.common.collect.Lists; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.Enchantments; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.VoxelShape; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; - -public abstract class UpDownPlantBlock extends BaseBlockNotFull implements IRenderTyped { - private static final VoxelShape SHAPE = Block.box(4, 0, 4, 12, 16, 12); - - public UpDownPlantBlock() { - super(FabricBlockSettings.of(Material.PLANT) - .breakByTool(FabricToolTags.SHEARS) - .breakByHand(true) - .sound(SoundType.GRASS) - .noCollission()); - } - - protected abstract boolean isTerrain(BlockState state); - - @Override - public VoxelShape getShape(BlockState state, BlockGetter view, BlockPos pos, CollisionContext ePos) { - return SHAPE; - } - - @Override - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - BlockState down = world.getBlockState(pos.below()); - BlockState up = world.getBlockState(pos.above()); - return (isTerrain(down) || down.getBlock() == this) && (isSupport(up, world, pos) || up.getBlock() == this); - } - - protected boolean isSupport(BlockState state, LevelReader world, BlockPos pos) { - return canSupportCenter(world, pos.above(), Direction.UP); - } - - @Override - public BlockState updateShape(BlockState state, Direction facing, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (!canSurvive(state, world, pos)) { - return Blocks.AIR.defaultBlockState(); - } - else { - return state; - } - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - ItemStack tool = builder.getParameter(LootContextParams.TOOL); - if (tool != null && tool.getItem().is(FabricToolTags.SHEARS) || EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) > 0) { - return Lists.newArrayList(new ItemStack(this)); - } - else { - return Lists.newArrayList(); - } - } - - @Override - public BCLRenderLayer getRenderLayer() { - return BCLRenderLayer.CUTOUT; - } - - @Override - public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, BlockEntity blockEntity, ItemStack stack) { - super.playerDestroy(world, player, pos, state, blockEntity, stack); - world.neighborChanged(pos, Blocks.AIR, pos.below()); - } -} diff --git a/src/main/java/ru/bclib/blocks/WallMushroomBlock.java b/src/main/java/ru/bclib/blocks/WallMushroomBlock.java deleted file mode 100644 index 5e910e70..00000000 --- a/src/main/java/ru/bclib/blocks/WallMushroomBlock.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.bclib.blocks; - -import java.util.List; - -import com.google.common.collect.Lists; - -import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.Material; -import net.minecraft.world.level.storage.loot.LootContext; - -public abstract class WallMushroomBlock extends BaseWallPlantBlock { - public WallMushroomBlock(int light) { - super(FabricBlockSettings.of(Material.PLANT) - .breakByTool(FabricToolTags.AXES) - .breakByHand(true) - .luminance(light) - .hardness(0.2F) - .sound(SoundType.GRASS) - .sound(SoundType.WOOD) - .noCollission()); - } - - @Override - public List getDrops(BlockState state, LootContext.Builder builder) { - return Lists.newArrayList(new ItemStack(this)); - } - - @Override - public boolean isSupport(LevelReader world, BlockPos pos, BlockState blockState, Direction direction) { - return blockState.getMaterial().isSolid() && blockState.isFaceSturdy(world, pos, direction); - } -} diff --git a/src/main/java/ru/bclib/blocks/WoodenPressurePlateBlock.java b/src/main/java/ru/bclib/blocks/WoodenPressurePlateBlock.java deleted file mode 100644 index 03b2a860..00000000 --- a/src/main/java/ru/bclib/blocks/WoodenPressurePlateBlock.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.bclib.blocks; - -import net.minecraft.world.level.block.Block; - -public class WoodenPressurePlateBlock extends BasePressurePlateBlock { - public WoodenPressurePlateBlock(Block source) { - super(Sensitivity.EVERYTHING, source); - } -} diff --git a/src/main/java/ru/bclib/client/BCLibClient.java b/src/main/java/ru/bclib/client/BCLibClient.java deleted file mode 100644 index 5b6249d6..00000000 --- a/src/main/java/ru/bclib/client/BCLibClient.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.bclib.client; - -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.core.Registry; -import ru.bclib.api.ModIntegrationAPI; -import ru.bclib.client.render.BCLRenderLayer; -import ru.bclib.interfaces.IRenderTyped; -import ru.bclib.registry.BaseBlockEntityRenders; - -public class BCLibClient implements ClientModInitializer { - @Override - public void onInitializeClient() { - ModIntegrationAPI.registerAll(); - BaseBlockEntityRenders.register(); - registerRenderLayers(); - } - - private void registerRenderLayers() { - RenderType cutout = RenderType.cutout(); - RenderType translucent = RenderType.translucent(); - Registry.BLOCK.forEach(block -> { - if (block instanceof IRenderTyped) { - BCLRenderLayer layer = ((IRenderTyped) block).getRenderLayer(); - if (layer == BCLRenderLayer.CUTOUT) - BlockRenderLayerMap.INSTANCE.putBlock(block, cutout); - else if (layer == BCLRenderLayer.TRANSLUCENT) - BlockRenderLayerMap.INSTANCE.putBlock(block, translucent); - } - }); - } -} diff --git a/src/main/java/ru/bclib/client/gui/BlockSignEditScreen.java b/src/main/java/ru/bclib/client/gui/BlockSignEditScreen.java deleted file mode 100644 index 25be05c9..00000000 --- a/src/main/java/ru/bclib/client/gui/BlockSignEditScreen.java +++ /dev/null @@ -1,228 +0,0 @@ -package ru.bclib.client.gui; - -import java.util.Arrays; - -import com.mojang.blaze3d.platform.GlStateManager; -import com.mojang.blaze3d.platform.Lighting; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.BufferUploader; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.Tesselator; -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.math.Matrix4f; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.Util; -import net.minecraft.client.gui.GuiComponent; -import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.font.TextFieldHelper; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.multiplayer.ClientPacketListener; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.blockentity.SignRenderer.SignModel; -import net.minecraft.client.renderer.texture.OverlayTexture; -import net.minecraft.network.chat.CommonComponents; -import net.minecraft.network.chat.TextComponent; -import net.minecraft.network.chat.TranslatableComponent; -import net.minecraft.network.protocol.game.ServerboundSignUpdatePacket; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.blockentities.BaseSignBlockEntity; -import ru.bclib.blocks.BaseSignBlock; -import ru.bclib.client.render.BaseSignBlockEntityRenderer; - -@Environment(EnvType.CLIENT) -public class BlockSignEditScreen extends Screen { - private final SignModel model = new SignModel(); - private final BaseSignBlockEntity sign; - private int ticksSinceOpened; - private int currentRow; - private TextFieldHelper selectionManager; - private final String[] text = (String[]) Util.make(new String[4], (strings) -> { - Arrays.fill(strings, ""); - }); - - public BlockSignEditScreen(BaseSignBlockEntity sign) { - super(new TranslatableComponent("sign.edit")); - this.sign = sign; - } - - protected void init() { - minecraft.keyboardHandler.setSendRepeatsToGui(true); - this.addButton(new Button(this.width / 2 - 100, this.height / 4 + 120, 200, 20, CommonComponents.GUI_DONE, - (buttonWidget) -> { - this.finishEditing(); - })); - this.sign.setEditable(false); - this.selectionManager = new TextFieldHelper(() -> { - return this.text[this.currentRow]; - }, (string) -> { - this.text[this.currentRow] = string; - this.sign.setMessage(this.currentRow, new TextComponent(string)); - }, TextFieldHelper.createClipboardGetter(this.minecraft), TextFieldHelper.createClipboardSetter(this.minecraft), - (string) -> { - return this.minecraft.font.width(string) <= 90; - }); - } - - public void removed() { - minecraft.keyboardHandler.setSendRepeatsToGui(false); - ClientPacketListener clientPlayNetworkHandler = this.minecraft.getConnection(); - if (clientPlayNetworkHandler != null) { - clientPlayNetworkHandler.send(new ServerboundSignUpdatePacket(this.sign.getBlockPos(), this.text[0], this.text[1], - this.text[2], this.text[3])); - } - - this.sign.setEditable(true); - } - - public void tick() { - ++this.ticksSinceOpened; - if (!this.sign.getType().isValid(this.sign.getBlockState().getBlock())) { - this.finishEditing(); - } - } - - private void finishEditing() { - this.sign.setChanged(); - this.minecraft.setScreen((Screen) null); - } - - public boolean charTyped(char chr, int keyCode) { - this.selectionManager.charTyped(chr); - return true; - } - - public void onClose() { - this.finishEditing(); - } - - public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (keyCode == 265) { - this.currentRow = this.currentRow - 1 & 3; - this.selectionManager.setCursorToEnd(); - return true; - } else if (keyCode != 264 && keyCode != 257 && keyCode != 335) { - return selectionManager.keyPressed(keyCode) || super.keyPressed(keyCode, scanCode, modifiers); - } else { - this.currentRow = this.currentRow + 1 & 3; - this.selectionManager.setCursorToEnd(); - return true; - } - } - - public void render(PoseStack matrices, int mouseX, int mouseY, float delta) { - Lighting.setupForFlatItems(); - this.renderBackground(matrices); - GuiComponent.drawCenteredString(matrices, this.font, this.title, this.width / 2, 40, 16777215); - matrices.pushPose(); - matrices.translate((double) (this.width / 2), 0.0D, 50.0D); - - matrices.scale(93.75F, -93.75F, 93.75F); - matrices.translate(0.0D, -1.3125D, 0.0D); - BlockState blockState = this.sign.getBlockState(); - boolean bl = blockState.getValue(BaseSignBlock.FLOOR); - - if (!bl) { - matrices.translate(0.0D, -0.3125D, 0.0D); - } - - boolean bl2 = this.ticksSinceOpened / 6 % 2 == 0; - - matrices.pushPose(); - matrices.scale(0.6666667F, -0.6666667F, -0.6666667F); - MultiBufferSource.BufferSource immediate = minecraft.renderBuffers().bufferSource(); - VertexConsumer vertexConsumer = BaseSignBlockEntityRenderer.getConsumer(immediate, blockState.getBlock()); - model.sign.render(matrices, vertexConsumer, 15728880, OverlayTexture.NO_OVERLAY); - - if (bl) { - model.stick.render(matrices, vertexConsumer, 15728880, OverlayTexture.NO_OVERLAY); - } - - matrices.popPose(); - - matrices.translate(0.0D, 0.3333333432674408D, 0.046666666865348816D); - matrices.scale(0.010416667F, -0.010416667F, 0.010416667F); - int i = this.sign.getColor().getTextColor(); - int j = this.selectionManager.getCursorPos(); - int k = this.selectionManager.getSelectionPos(); - int l = this.currentRow * 10 - this.text.length * 5; - Matrix4f matrix4f = matrices.last().pose(); - - int m; - String string2; - int s; - int t; - for (m = 0; m < this.text.length; ++m) { - string2 = this.text[m]; - if (string2 != null) { - if (this.font.isBidirectional()) { - string2 = this.font.bidirectionalShaping(string2); - } - - float n = (float) (-this.minecraft.font.width(string2) / 2); - this.minecraft.font.drawInBatch(string2, n, (float) (m * 10 - this.text.length * 5), i, false, matrix4f, - immediate, false, 0, 15728880, false); - if (m == this.currentRow && j >= 0 && bl2) { - s = this.minecraft.font - .width(string2.substring(0, Math.max(Math.min(j, string2.length()), 0))); - t = s - this.minecraft.font.width(string2) / 2; - if (j >= string2.length()) { - this.minecraft.font.drawInBatch("_", (float) t, (float) l, i, false, matrix4f, immediate, false, - 0, 15728880, false); - } - } - } - } - - immediate.endBatch(); - - for (m = 0; m < this.text.length; ++m) { - string2 = this.text[m]; - if (string2 != null && m == this.currentRow && j >= 0) { - int r = this.minecraft.font - .width(string2.substring(0, Math.max(Math.min(j, string2.length()), 0))); - s = r - this.minecraft.font.width(string2) / 2; - if (bl2 && j < string2.length()) { - int var31 = l - 1; - int var10003 = s + 1; - this.minecraft.font.getClass(); - fill(matrices, s, var31, var10003, l + 9, -16777216 | i); - } - - if (k != j) { - t = Math.min(j, k); - int u = Math.max(j, k); - int v = this.minecraft.font.width(string2.substring(0, t)) - - this.minecraft.font.width(string2) / 2; - int w = this.minecraft.font.width(string2.substring(0, u)) - - this.minecraft.font.width(string2) / 2; - int x = Math.min(v, w); - int y = Math.max(v, w); - Tesselator tessellator = Tesselator.getInstance(); - BufferBuilder bufferBuilder = tessellator.getBuilder(); - RenderSystem.disableTexture(); - RenderSystem.enableColorLogicOp(); - RenderSystem.logicOp(GlStateManager.LogicOp.OR_REVERSE); - bufferBuilder.begin(7, DefaultVertexFormat.POSITION_COLOR); - float var32 = (float) x; - bufferBuilder.vertex(matrix4f, var32, (float) (l + 9), 0.0F).color(0, 0, 255, 255).endVertex(); - var32 = (float) y; - bufferBuilder.vertex(matrix4f, var32, (float) (l + 9), 0.0F).color(0, 0, 255, 255).endVertex(); - bufferBuilder.vertex(matrix4f, (float) y, (float) l, 0.0F).color(0, 0, 255, 255).endVertex(); - bufferBuilder.vertex(matrix4f, (float) x, (float) l, 0.0F).color(0, 0, 255, 255).endVertex(); - bufferBuilder.end(); - BufferUploader.end(bufferBuilder); - RenderSystem.disableColorLogicOp(); - RenderSystem.enableTexture(); - } - } - } - - matrices.popPose(); - Lighting.setupFor3DItems(); - super.render(matrices, mouseX, mouseY, delta); - } -} diff --git a/src/main/java/ru/bclib/client/models/BasePatterns.java b/src/main/java/ru/bclib/client/models/BasePatterns.java deleted file mode 100644 index 575e3b27..00000000 --- a/src/main/java/ru/bclib/client/models/BasePatterns.java +++ /dev/null @@ -1,61 +0,0 @@ -package ru.bclib.client.models; - -import net.minecraft.resources.ResourceLocation; -import ru.bclib.BCLib; - -public class BasePatterns { - //Block Models - public final static ResourceLocation BLOCK_EMPTY = BCLib.makeID("patterns/block/empty.json"); - public final static ResourceLocation BLOCK_BASE = BCLib.makeID("patterns/block/block.json"); - public final static ResourceLocation BLOCK_SIDED = BCLib.makeID("patterns/block/block_sided.json"); - public final static ResourceLocation BLOCK_BOTTOM_TOP = BCLib.makeID("patterns/block/block_bottom_top.json"); - public final static ResourceLocation BLOCK_SLAB = BCLib.makeID("patterns/block/slab.json"); - public final static ResourceLocation BLOCK_STAIR = BCLib.makeID("patterns/block/stairs.json"); - public final static ResourceLocation BLOCK_STAIR_INNER = BCLib.makeID("patterns/block/stairs_inner.json"); - public final static ResourceLocation BLOCK_STAIR_OUTER = BCLib.makeID("patterns/block/stairs_outer.json"); - public final static ResourceLocation BLOCK_WALL_POST = BCLib.makeID("patterns/block/wall_post.json"); - public final static ResourceLocation BLOCK_WALL_SIDE = BCLib.makeID("patterns/block/wall_side.json"); - public final static ResourceLocation BLOCK_WALL_SIDE_TALL = BCLib.makeID("patterns/block/wall_side_tall.json"); - public final static ResourceLocation BLOCK_FENCE_POST = BCLib.makeID("patterns/block/fence_post.json"); - public final static ResourceLocation BLOCK_FENCE_SIDE = BCLib.makeID("patterns/block/fence_side.json"); - public final static ResourceLocation BLOCK_BUTTON = BCLib.makeID("patterns/block/button.json"); - public final static ResourceLocation BLOCK_BUTTON_PRESSED = BCLib.makeID("patterns/block/button_pressed.json"); - public final static ResourceLocation BLOCK_PILLAR = BCLib.makeID("patterns/block/pillar.json"); - public final static ResourceLocation BLOCK_PLATE_UP = BCLib.makeID("patterns/block/pressure_plate_up.json"); - public final static ResourceLocation BLOCK_PLATE_DOWN = BCLib.makeID("patterns/block/pressure_plate_down.json"); - public final static ResourceLocation BLOCK_DOOR_TOP = BCLib.makeID("patterns/block/door_top.json"); - public final static ResourceLocation BLOCK_DOOR_TOP_HINGE = BCLib.makeID("patterns/block/door_top_hinge.json"); - public final static ResourceLocation BLOCK_DOOR_BOTTOM = BCLib.makeID("patterns/block/door_bottom.json"); - public final static ResourceLocation BLOCK_DOOR_BOTTOM_HINGE = BCLib.makeID("patterns/block/door_bottom_hinge.json"); - public final static ResourceLocation BLOCK_CROSS = BCLib.makeID("patterns/block/cross.json"); - public final static ResourceLocation BLOCK_CROSS_SHADED = BCLib.makeID("patterns/block/cross_shaded.json"); - public final static ResourceLocation BLOCK_GATE_CLOSED = BCLib.makeID("patterns/block/fence_gate_closed.json"); - public final static ResourceLocation BLOCK_GATE_CLOSED_WALL = BCLib.makeID("patterns/block/wall_gate_closed.json"); - public final static ResourceLocation BLOCK_GATE_OPEN = BCLib.makeID("patterns/block/fence_gate_open.json"); - public final static ResourceLocation BLOCK_GATE_OPEN_WALL = BCLib.makeID("patterns/block/wall_gate_open.json"); - public final static ResourceLocation BLOCK_TRAPDOOR = BCLib.makeID("patterns/block/trapdoor.json"); - public final static ResourceLocation BLOCK_LADDER = BCLib.makeID("patterns/block/ladder.json"); - public final static ResourceLocation BLOCK_BARREL_OPEN = BCLib.makeID("patterns/block/barrel_open.json"); - public final static ResourceLocation BLOCK_BOOKSHELF = BCLib.makeID("patterns/block/bookshelf.json"); - public final static ResourceLocation BLOCK_COMPOSTER = BCLib.makeID("patterns/block/composter.json"); - public final static ResourceLocation BLOCK_COLORED = BCLib.makeID("patterns/block/block_colored.json"); - public final static ResourceLocation BLOCK_BARS_POST = BCLib.makeID("patterns/block/bars_post.json"); - public final static ResourceLocation BLOCK_BARS_SIDE = BCLib.makeID("patterns/block/bars_side.json"); - public final static ResourceLocation BLOCK_ANVIL = BCLib.makeID("patterns/block/anvil.json"); - public final static ResourceLocation BLOCK_CHAIN = BCLib.makeID("patterns/block/chain.json"); - public final static ResourceLocation BLOCK_FURNACE = BCLib.makeID("patterns/block/furnace.json"); - public final static ResourceLocation BLOCK_FURNACE_LIT = BCLib.makeID("patterns/block/furnace_glow.json"); - public final static ResourceLocation BLOCK_TOP_SIDE_BOTTOM = BCLib.makeID("patterns/block/top_side_bottom.json"); - public final static ResourceLocation BLOCK_PATH = BCLib.makeID("patterns/block/path.json"); - - //Item Models - public final static ResourceLocation ITEM_WALL = BCLib.makeID("patterns/item/pattern_wall.json"); - public final static ResourceLocation ITEM_FENCE = BCLib.makeID("patterns/item/pattern_fence.json"); - public final static ResourceLocation ITEM_BUTTON = BCLib.makeID("patterns/item/pattern_button.json"); - public final static ResourceLocation ITEM_CHEST = BCLib.makeID("patterns/item/pattern_chest.json"); - public final static ResourceLocation ITEM_BLOCK = BCLib.makeID("patterns/item/pattern_block_item.json"); - public final static ResourceLocation ITEM_GENERATED = BCLib.makeID("patterns/item/pattern_item_generated.json"); - public final static ResourceLocation ITEM_HANDHELD = BCLib.makeID("patterns/item/pattern_item_handheld.json"); - public final static ResourceLocation ITEM_SPAWN_EGG = BCLib.makeID("patterns/item/pattern_item_spawn_egg.json"); - -} diff --git a/src/main/java/ru/bclib/client/models/BlockModelProvider.java b/src/main/java/ru/bclib/client/models/BlockModelProvider.java deleted file mode 100644 index e139a923..00000000 --- a/src/main/java/ru/bclib/client/models/BlockModelProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.bclib.client.models; - -import static net.minecraft.client.resources.model.ModelBakery.MISSING_MODEL_LOCATION; - -import java.util.Map; -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.BCLib; - -public interface BlockModelProvider extends ItemModelProvider { - @Environment(EnvType.CLIENT) - default @Nullable BlockModel getBlockModel(ResourceLocation resourceLocation, BlockState blockState) { - Optional pattern = PatternsHelper.createBlockSimple(resourceLocation); - return ModelsHelper.fromPattern(pattern); - } - - @Environment(EnvType.CLIENT) - default UnbakedModel getModelVariant(ResourceLocation stateId, BlockState blockState, Map modelCache) { - ResourceLocation modelId = new ResourceLocation(stateId.getNamespace(), "block/" + stateId.getPath()); - registerBlockModel(stateId, modelId, blockState, modelCache); - return ModelsHelper.createBlockSimple(modelId); - } - - @Environment(EnvType.CLIENT) - default void registerBlockModel(ResourceLocation stateId, ResourceLocation modelId, BlockState blockState, Map modelCache) { - if (!modelCache.containsKey(modelId)) { - BlockModel model = getBlockModel(stateId, blockState); - if (model != null) { - model.name = modelId.toString(); - modelCache.put(modelId, model); - } else { - BCLib.LOGGER.warning("Error loading model: {}", modelId); - modelCache.put(modelId, modelCache.get(MISSING_MODEL_LOCATION)); - } - } - } -} diff --git a/src/main/java/ru/bclib/client/models/ItemModelProvider.java b/src/main/java/ru/bclib/client/models/ItemModelProvider.java deleted file mode 100644 index 50930e6c..00000000 --- a/src/main/java/ru/bclib/client/models/ItemModelProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.bclib.client.models; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; - -public interface ItemModelProvider { - @Environment(EnvType.CLIENT) - default BlockModel getItemModel(ResourceLocation resourceLocation) { - return ModelsHelper.createItemModel(resourceLocation); - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/client/models/ModelsHelper.java b/src/main/java/ru/bclib/client/models/ModelsHelper.java deleted file mode 100644 index f7bdec4d..00000000 --- a/src/main/java/ru/bclib/client/models/ModelsHelper.java +++ /dev/null @@ -1,147 +0,0 @@ -package ru.bclib.client.models; - -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -import com.google.common.collect.Lists; -import com.mojang.math.Transformation; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.MultiVariant; -import net.minecraft.client.renderer.block.model.Variant; -import net.minecraft.client.renderer.block.model.multipart.Condition; -import net.minecraft.client.renderer.block.model.multipart.MultiPart; -import net.minecraft.client.renderer.block.model.multipart.Selector; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; - -@Environment(EnvType.CLIENT) -public class ModelsHelper { - public static BlockModel fromPattern(Optional pattern) { - return pattern.map(BlockModel::fromString).orElse(null); - } - - public static BlockModel createItemModel(ResourceLocation resourceLocation) { - return fromPattern(PatternsHelper.createItemGenerated(resourceLocation)); - } - - public static BlockModel createHandheldItem(ResourceLocation resourceLocation) { - return fromPattern(PatternsHelper.createItemHandheld(resourceLocation)); - } - - public static BlockModel createBlockItem(ResourceLocation resourceLocation) { - Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_BLOCK, resourceLocation); - return fromPattern(pattern); - } - - public static BlockModel createBlockEmpty(ResourceLocation resourceLocation) { - Optional pattern = PatternsHelper.createJson(BasePatterns.BLOCK_EMPTY, resourceLocation); - return fromPattern(pattern); - } - - public static MultiVariant createMultiVariant(ResourceLocation resourceLocation, Transformation transform, boolean uvLock) { - Variant variant = new Variant(resourceLocation, transform, uvLock, 1); - return new MultiVariant(Lists.newArrayList(variant)); - } - - public static MultiVariant createBlockSimple(ResourceLocation resourceLocation) { - return createMultiVariant(resourceLocation, Transformation.identity(), false); - } - - public static MultiVariant createFacingModel(ResourceLocation resourceLocation, Direction facing, boolean uvLock, boolean inverted) { - if (inverted) { - facing = facing.getOpposite(); - } - BlockModelRotation rotation = BlockModelRotation.by(0, (int) facing.toYRot()); - return createMultiVariant(resourceLocation, rotation.getRotation(), uvLock); - } - - public static MultiVariant createRotatedModel(ResourceLocation resourceLocation, Direction.Axis axis) { - BlockModelRotation rotation = BlockModelRotation.X0_Y0; - switch (axis) { - case X: rotation = BlockModelRotation.X90_Y90; break; - case Z: rotation = BlockModelRotation.X90_Y0; break; - default: break; - } - return createMultiVariant(resourceLocation, rotation.getRotation(), false); - } - - public static MultiVariant createRandomTopModel(ResourceLocation resourceLocation) { - return new MultiVariant(Lists.newArrayList( - new Variant(resourceLocation, Transformation.identity(), false, 1), - new Variant(resourceLocation, BlockModelRotation.X0_Y90.getRotation(), false, 1), - new Variant(resourceLocation, BlockModelRotation.X0_Y180.getRotation(), false, 1), - new Variant(resourceLocation, BlockModelRotation.X0_Y270.getRotation(), false, 1) - )); - } - - public static class MultiPartBuilder { - - private final static MultiPartBuilder BUILDER = new MultiPartBuilder(); - - public static MultiPartBuilder create(StateDefinition stateDefinition) { - BUILDER.stateDefinition = stateDefinition; - BUILDER.modelParts.clear(); - return BUILDER; - } - - private final List modelParts = Lists.newArrayList(); - private StateDefinition stateDefinition; - - private MultiPartBuilder() {} - - public ModelPart part(ResourceLocation modelId) { - return new ModelPart(modelId); - } - - public MultiPart build() { - if (modelParts.size() > 0) { - List selectors = Lists.newArrayList(); - modelParts.forEach(modelPart -> { - MultiVariant variant = createMultiVariant(modelPart.modelId, modelPart.transform, modelPart.uvLock); - selectors.add(new Selector(modelPart.condition, variant)); - }); - modelParts.clear(); - return new MultiPart(stateDefinition, selectors); - } - throw new IllegalStateException("At least one model part need to be created."); - } - - public class ModelPart { - private final ResourceLocation modelId; - private Transformation transform = Transformation.identity(); - private Condition condition = Condition.TRUE; - private boolean uvLock = false; - - private ModelPart(ResourceLocation modelId) { - this.modelId = modelId; - } - - public ModelPart setCondition(Function condition) { - this.condition = stateDefinition -> condition::apply; - return this; - } - - public ModelPart setTransformation(Transformation transform) { - this.transform = transform; - return this; - } - - public ModelPart setUVLock(boolean value) { - this.uvLock = value; - return this; - } - - public void add() { - modelParts.add(this); - } - } - } -} diff --git a/src/main/java/ru/bclib/client/models/PatternsHelper.java b/src/main/java/ru/bclib/client/models/PatternsHelper.java deleted file mode 100644 index 771b7d80..00000000 --- a/src/main/java/ru/bclib/client/models/PatternsHelper.java +++ /dev/null @@ -1,66 +0,0 @@ -package ru.bclib.client.models; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -import com.google.common.collect.Maps; - -import net.minecraft.client.Minecraft; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; - -public class PatternsHelper { - public static Optional createItemGenerated(ResourceLocation itemId) { - return createJson(BasePatterns.ITEM_GENERATED, itemId); - } - - public static Optional createItemHandheld(ResourceLocation itemId) { - return createJson(BasePatterns.ITEM_HANDHELD, itemId); - } - - public static Optional createBlockSimple(ResourceLocation blockId) { - return createJson(BasePatterns.BLOCK_BASE, blockId); - } - - public static Optional createBlockEmpty(ResourceLocation blockId) { - return createJson(BasePatterns.BLOCK_EMPTY, blockId); - } - - public static Optional createBlockPillar(ResourceLocation blockId) { - return createJson(BasePatterns.BLOCK_PILLAR, blockId); - } - - public static Optional createBlockBottomTop(ResourceLocation blockId) { - return createJson(BasePatterns.BLOCK_BOTTOM_TOP, blockId); - } - - public static Optional createBlockColored(ResourceLocation blockId) { - return createJson(BasePatterns.BLOCK_COLORED, blockId); - } - - public static Optional createJson(ResourceLocation patternId, ResourceLocation blockId) { - Map textures = Maps.newHashMap(); - textures.put("%modid%", blockId.getNamespace()); - textures.put("%texture%", blockId.getPath()); - return createJson(patternId, textures); - } - - public static Optional createJson(ResourceLocation patternId, Map textures) { - ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); - try (InputStream input = resourceManager.getResource(patternId).getInputStream()) { - String json = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)) - .lines().collect(Collectors.joining()); - for (Map.Entry texture : textures.entrySet()) { - json = json.replace(texture.getKey(), texture.getValue()); - } - return Optional.of(json); - } catch (Exception ex) { - return Optional.empty(); - } - } -} diff --git a/src/main/java/ru/bclib/client/render/BCLRenderLayer.java b/src/main/java/ru/bclib/client/render/BCLRenderLayer.java deleted file mode 100644 index 3846d7d6..00000000 --- a/src/main/java/ru/bclib/client/render/BCLRenderLayer.java +++ /dev/null @@ -1,6 +0,0 @@ -package ru.bclib.client.render; - -public enum BCLRenderLayer { - CUTOUT, - TRANSLUCENT; -} diff --git a/src/main/java/ru/bclib/client/render/BaseChestBlockEntityRenderer.java b/src/main/java/ru/bclib/client/render/BaseChestBlockEntityRenderer.java deleted file mode 100644 index 3626e902..00000000 --- a/src/main/java/ru/bclib/client/render/BaseChestBlockEntityRenderer.java +++ /dev/null @@ -1,175 +0,0 @@ -package ru.bclib.client.render; - -import java.util.HashMap; - -import com.google.common.collect.Maps; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.math.Vector3f; - -import it.unimi.dsi.fastutil.floats.Float2FloatFunction; -import it.unimi.dsi.fastutil.ints.Int2IntFunction; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.model.geom.ModelPart; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher; -import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; -import net.minecraft.client.renderer.blockentity.BrightnessCombiner; -import net.minecraft.core.Direction; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.AbstractChestBlock; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.ChestBlock; -import net.minecraft.world.level.block.DoubleBlockCombiner; -import net.minecraft.world.level.block.DoubleBlockCombiner.NeighborCombineResult; -import net.minecraft.world.level.block.entity.ChestBlockEntity; -import net.minecraft.world.level.block.entity.LidBlockEntity; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.ChestType; -import ru.bclib.blockentities.BaseChestBlockEntity; - -@Environment(EnvType.CLIENT) -public class BaseChestBlockEntityRenderer extends BlockEntityRenderer { - private static final HashMap LAYERS = Maps.newHashMap(); - private static final RenderType[] defaultLayer; - - private static final int ID_NORMAL = 0; - private static final int ID_LEFT = 1; - private static final int ID_RIGHT = 2; - - private final ModelPart partA; - private final ModelPart partC; - private final ModelPart partB; - private final ModelPart partRightA; - private final ModelPart partRightC; - private final ModelPart partRightB; - private final ModelPart partLeftA; - private final ModelPart partLeftC; - private final ModelPart partLeftB; - - public BaseChestBlockEntityRenderer(BlockEntityRenderDispatcher blockEntityRenderDispatcher) { - super(blockEntityRenderDispatcher); - - this.partC = new ModelPart(64, 64, 0, 19); - this.partC.addBox(1.0F, 0.0F, 1.0F, 14.0F, 9.0F, 14.0F, 0.0F); - this.partA = new ModelPart(64, 64, 0, 0); - this.partA.addBox(1.0F, 0.0F, 0.0F, 14.0F, 5.0F, 14.0F, 0.0F); - this.partA.y = 9.0F; - this.partA.z = 1.0F; - this.partB = new ModelPart(64, 64, 0, 0); - this.partB.addBox(7.0F, -1.0F, 15.0F, 2.0F, 4.0F, 1.0F, 0.0F); - this.partB.y = 8.0F; - this.partRightC = new ModelPart(64, 64, 0, 19); - this.partRightC.addBox(1.0F, 0.0F, 1.0F, 15.0F, 9.0F, 14.0F, 0.0F); - this.partRightA = new ModelPart(64, 64, 0, 0); - this.partRightA.addBox(1.0F, 0.0F, 0.0F, 15.0F, 5.0F, 14.0F, 0.0F); - this.partRightA.y = 9.0F; - this.partRightA.z = 1.0F; - this.partRightB = new ModelPart(64, 64, 0, 0); - this.partRightB.addBox(15.0F, -1.0F, 15.0F, 1.0F, 4.0F, 1.0F, 0.0F); - this.partRightB.y = 8.0F; - this.partLeftC = new ModelPart(64, 64, 0, 19); - this.partLeftC.addBox(0.0F, 0.0F, 1.0F, 15.0F, 9.0F, 14.0F, 0.0F); - this.partLeftA = new ModelPart(64, 64, 0, 0); - this.partLeftA.addBox(0.0F, 0.0F, 0.0F, 15.0F, 5.0F, 14.0F, 0.0F); - this.partLeftA.y = 9.0F; - this.partLeftA.z = 1.0F; - this.partLeftB = new ModelPart(64, 64, 0, 0); - this.partLeftB.addBox(0.0F, -1.0F, 15.0F, 1.0F, 4.0F, 1.0F, 0.0F); - this.partLeftB.y = 8.0F; - } - - public void render(BaseChestBlockEntity entity, float tickDelta, PoseStack matrices, MultiBufferSource vertexConsumers, int light, int overlay) { - Level world = entity.getLevel(); - boolean worldExists = world != null; - BlockState blockState = worldExists ? entity.getBlockState() : (BlockState) Blocks.CHEST.defaultBlockState().setValue(ChestBlock.FACING, Direction.SOUTH); - ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) ? (ChestType) blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; - Block block = blockState.getBlock(); - if (block instanceof AbstractChestBlock) { - AbstractChestBlock abstractChestBlock = (AbstractChestBlock) block; - boolean isDouble = chestType != ChestType.SINGLE; - float f = ((Direction) blockState.getValue(ChestBlock.FACING)).toYRot(); - NeighborCombineResult propertySource; - - matrices.pushPose(); - matrices.translate(0.5D, 0.5D, 0.5D); - matrices.mulPose(Vector3f.YP.rotationDegrees(-f)); - matrices.translate(-0.5D, -0.5D, -0.5D); - - if (worldExists) { - propertySource = abstractChestBlock.combine(blockState, world, entity.getBlockPos(), true); - } else { - propertySource = DoubleBlockCombiner.Combiner::acceptNone; - } - - float pitch = ((Float2FloatFunction) propertySource.apply(ChestBlock.opennessCombiner((LidBlockEntity) entity))).get(tickDelta); - pitch = 1.0F - pitch; - pitch = 1.0F - pitch * pitch * pitch; - @SuppressWarnings({ "unchecked", "rawtypes" }) - int blockLight = ((Int2IntFunction) propertySource.apply(new BrightnessCombiner())).applyAsInt(light); - - VertexConsumer vertexConsumer = getConsumer(vertexConsumers, block, chestType); - - if (isDouble) { - if (chestType == ChestType.LEFT) { - renderParts(matrices, vertexConsumer, this.partLeftA, this.partLeftB, this.partLeftC, pitch, blockLight, overlay); - } else { - renderParts(matrices, vertexConsumer, this.partRightA, this.partRightB, this.partRightC, pitch, blockLight, overlay); - } - } else { - renderParts(matrices, vertexConsumer, this.partA, this.partB, this.partC, pitch, blockLight, overlay); - } - - matrices.popPose(); - } - } - - private void renderParts(PoseStack matrices, VertexConsumer vertices, ModelPart modelPart, ModelPart modelPart2, ModelPart modelPart3, float pitch, int light, int overlay) { - modelPart.xRot = -(pitch * 1.5707964F); - modelPart2.xRot = modelPart.xRot; - modelPart.render(matrices, vertices, light, overlay); - modelPart2.render(matrices, vertices, light, overlay); - modelPart3.render(matrices, vertices, light, overlay); - } - - private static RenderType getChestTexture(ChestType type, RenderType[] layers) { - switch (type) { - case LEFT: - return layers[ID_LEFT]; - case RIGHT: - return layers[ID_RIGHT]; - case SINGLE: - default: - return layers[ID_NORMAL]; - } - } - - public static VertexConsumer getConsumer(MultiBufferSource provider, Block block, ChestType chestType) { - RenderType[] layers = LAYERS.getOrDefault(block, defaultLayer); - return provider.getBuffer(getChestTexture(chestType, layers)); - } - - public static void registerRenderLayer(Block block) { - ResourceLocation blockId = Registry.BLOCK.getKey(block); - String modId = blockId.getNamespace(); - String path = blockId.getPath(); - LAYERS.put(block, new RenderType[] { - RenderType.entityCutout(new ResourceLocation(modId, "textures/entity/chest/" + path + ".png")), - RenderType.entityCutout(new ResourceLocation(modId, "textures/entity/chest/" + path + "_left.png")), - RenderType.entityCutout(new ResourceLocation(modId, "textures/entity/chest/" + path + "_right.png")) - }); - } - - static { - defaultLayer = new RenderType[] { - RenderType.entityCutout(new ResourceLocation("textures/entity/chest/normal.png")), - RenderType.entityCutout(new ResourceLocation("textures/entity/chest/normal_left.png")), - RenderType.entityCutout(new ResourceLocation("textures/entity/chest/normal_right.png")) - }; - } -} diff --git a/src/main/java/ru/bclib/client/render/BaseSignBlockEntityRenderer.java b/src/main/java/ru/bclib/client/render/BaseSignBlockEntityRenderer.java deleted file mode 100644 index 6ef7e419..00000000 --- a/src/main/java/ru/bclib/client/render/BaseSignBlockEntityRenderer.java +++ /dev/null @@ -1,113 +0,0 @@ -package ru.bclib.client.render; - -import java.util.HashMap; -import java.util.List; - -import com.google.common.collect.Maps; -import com.mojang.blaze3d.platform.NativeImage; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import com.mojang.math.Vector3f; - -import net.minecraft.client.gui.Font; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher; -import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; -import net.minecraft.client.renderer.blockentity.SignRenderer; -import net.minecraft.client.renderer.blockentity.SignRenderer.SignModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.FormattedCharSequence; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.SignBlock; -import net.minecraft.world.level.block.StandingSignBlock; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.WoodType; -import ru.bclib.blockentities.BaseSignBlockEntity; -import ru.bclib.blocks.BaseSignBlock; - -public class BaseSignBlockEntityRenderer extends BlockEntityRenderer { - private static final HashMap LAYERS = Maps.newHashMap(); - private static final RenderType defaultLayer; - private final SignModel model = new SignRenderer.SignModel(); - - public BaseSignBlockEntityRenderer(BlockEntityRenderDispatcher dispatcher) { - super(dispatcher); - } - - public void render(BaseSignBlockEntity signBlockEntity, float tickDelta, PoseStack matrixStack, - MultiBufferSource provider, int light, int overlay) { - BlockState state = signBlockEntity.getBlockState(); - matrixStack.pushPose(); - - matrixStack.translate(0.5D, 0.5D, 0.5D); - float angle = -((float) (state.getValue(StandingSignBlock.ROTATION) * 360) / 16.0F); - - BlockState blockState = signBlockEntity.getBlockState(); - if (blockState.getValue(BaseSignBlock.FLOOR)) { - matrixStack.mulPose(Vector3f.YP.rotationDegrees(angle)); - this.model.stick.visible = true; - } else { - matrixStack.mulPose(Vector3f.YP.rotationDegrees(angle + 180)); - matrixStack.translate(0.0D, -0.3125D, -0.4375D); - this.model.stick.visible = false; - } - - matrixStack.pushPose(); - matrixStack.scale(0.6666667F, -0.6666667F, -0.6666667F); - VertexConsumer vertexConsumer = getConsumer(provider, state.getBlock()); - model.sign.render(matrixStack, vertexConsumer, light, overlay); - model.stick.render(matrixStack, vertexConsumer, light, overlay); - matrixStack.popPose(); - Font textRenderer = renderer.getFont(); - matrixStack.translate(0.0D, 0.3333333432674408D, 0.046666666865348816D); - matrixStack.scale(0.010416667F, -0.010416667F, 0.010416667F); - int m = signBlockEntity.getColor().getTextColor(); - int n = (int) (NativeImage.getR(m) * 0.4D); - int o = (int) (NativeImage.getG(m) * 0.4D); - int p = (int) (NativeImage.getB(m) * 0.4D); - int q = NativeImage.combine(0, p, o, n); - - for (int s = 0; s < 4; ++s) { - FormattedCharSequence orderedText = signBlockEntity.getRenderMessage(s, (text) -> { - List list = textRenderer.split(text, 90); - return list.isEmpty() ? FormattedCharSequence.EMPTY : list.get(0); - }); - if (orderedText != null) { - float t = (float) (-textRenderer.width(orderedText) / 2); - textRenderer.drawInBatch(orderedText, t, (float) (s * 10 - 20), q, false, matrixStack.last().pose(), provider, false, 0, light); - } - } - - matrixStack.popPose(); - } - - public static Material getModelTexture(Block block) { - WoodType signType2; - if (block instanceof SignBlock) { - signType2 = ((SignBlock) block).type(); - } else { - signType2 = WoodType.OAK; - } - - return Sheets.signTexture(signType2); - } - - public static VertexConsumer getConsumer(MultiBufferSource provider, Block block) { - return provider.getBuffer(LAYERS.getOrDefault(block, defaultLayer)); - } - - public static void registerRenderLayer(Block block) { - ResourceLocation blockId = Registry.BLOCK.getKey(block); - RenderType layer = RenderType.entitySolid(new ResourceLocation(blockId.getNamespace(), - "textures/entity/sign/" + blockId.getPath() + ".png")); - LAYERS.put(block, layer); - } - - static { - defaultLayer = RenderType.entitySolid(new ResourceLocation("textures/entity/signs/oak.png")); - } -} diff --git a/src/main/java/ru/bclib/client/sound/BlockSounds.java b/src/main/java/ru/bclib/client/sound/BlockSounds.java deleted file mode 100644 index 97c2cd20..00000000 --- a/src/main/java/ru/bclib/client/sound/BlockSounds.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.bclib.client.sound; - -import net.minecraft.sounds.SoundEvents; -import net.minecraft.world.level.block.SoundType; - -public class BlockSounds { - public static final SoundType TERRAIN_SOUND = new SoundType(1.0F, 1.0F, - SoundEvents.STONE_BREAK, - SoundEvents.WART_BLOCK_STEP, - SoundEvents.STONE_PLACE, - SoundEvents.STONE_HIT, - SoundEvents.STONE_FALL); -} diff --git a/src/main/java/ru/bclib/config/CategoryConfig.java b/src/main/java/ru/bclib/config/CategoryConfig.java deleted file mode 100644 index ae2fb2e2..00000000 --- a/src/main/java/ru/bclib/config/CategoryConfig.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.bclib.config; - -public class CategoryConfig extends IdConfig { - - public CategoryConfig(String modID, String group) { - super(modID, group, (id, category) -> { - return new ConfigKey(id.getPath(), id.getNamespace(), category); - }); - } -} diff --git a/src/main/java/ru/bclib/config/Config.java b/src/main/java/ru/bclib/config/Config.java deleted file mode 100644 index 968c45d7..00000000 --- a/src/main/java/ru/bclib/config/Config.java +++ /dev/null @@ -1,153 +0,0 @@ -package ru.bclib.config; - -import org.jetbrains.annotations.Nullable; - -import ru.bclib.BCLib; -import ru.bclib.config.ConfigKeeper.BooleanEntry; -import ru.bclib.config.ConfigKeeper.Entry; -import ru.bclib.config.ConfigKeeper.FloatEntry; -import ru.bclib.config.ConfigKeeper.IntegerEntry; -import ru.bclib.config.ConfigKeeper.RangeEntry; -import ru.bclib.config.ConfigKeeper.StringEntry; - -public abstract class Config { - protected final ConfigKeeper keeper; - - protected abstract void registerEntries(); - - public Config(String modID, String group) { - this.keeper = new ConfigKeeper(modID, group); - this.registerEntries(); - } - - public void saveChanges() { - this.keeper.save(); - } - - @Nullable - public > E getEntry(ConfigKey key, Class type) { - return this.keeper.getEntry(key, type); - } - - @Nullable - public > T getDefault(ConfigKey key, Class type) { - Entry entry = keeper.getEntry(key, type); - return entry != null ? entry.getDefault() : null; - } - - protected String getString(ConfigKey key, String defaultValue) { - String str = keeper.getValue(key, StringEntry.class); - if (str == null) { - StringEntry entry = keeper.registerEntry(key, new StringEntry(defaultValue)); - return entry.getValue(); - } - return str != null ? str : defaultValue; - } - - protected String getString(ConfigKey key) { - String str = keeper.getValue(key, StringEntry.class); - return str != null ? str : ""; - } - - protected boolean setString(ConfigKey key, String value) { - try { - StringEntry entry = keeper.getEntry(key, StringEntry.class); - if (entry == null) return false; - entry.setValue(value); - return true; - } catch (NullPointerException ex) { - BCLib.LOGGER.catching(ex); - } - return false; - } - - protected int getInt(ConfigKey key, int defaultValue) { - Integer val = keeper.getValue(key, IntegerEntry.class); - if (val == null) { - IntegerEntry entry = keeper.registerEntry(key, new IntegerEntry(defaultValue)); - return entry.getValue(); - } - return val != null ? val : defaultValue; - } - - protected int getInt(ConfigKey key) { - Integer val = keeper.getValue(key, IntegerEntry.class); - return val != null ? val : 0; - } - - protected boolean setInt(ConfigKey key, int value) { - try { - IntegerEntry entry = keeper.getEntry(key, IntegerEntry.class); - if (entry == null) return false; - entry.setValue(value); - return true; - } catch (NullPointerException ex) { - BCLib.LOGGER.catching(ex); - } - return false; - } - - protected , RE extends RangeEntry> boolean setRanged(ConfigKey key, T value, Class type) { - try { - RangeEntry entry = keeper.getEntry(key, type); - if (entry == null) return false; - entry.setValue(value); - return true; - } catch (NullPointerException | ClassCastException ex) { - BCLib.LOGGER.catching(ex); - } - return false; - } - - protected float getFloat(ConfigKey key, float defaultValue) { - Float val = keeper.getValue(key, FloatEntry.class); - if (val == null) { - FloatEntry entry = keeper.registerEntry(key, new FloatEntry(defaultValue)); - return entry.getValue(); - } - return val; - } - - protected float getFloat(ConfigKey key) { - Float val = keeper.getValue(key, FloatEntry.class); - return val != null ? val : 0.0F; - } - - protected boolean setFloat(ConfigKey key, float value) { - try { - FloatEntry entry = keeper.getEntry(key, FloatEntry.class); - if (entry == null) return false; - entry.setValue(value); - return true; - } catch (NullPointerException ex) { - BCLib.LOGGER.catching(ex); - } - return false; - } - - protected boolean getBoolean(ConfigKey key, boolean defaultValue) { - Boolean val = keeper.getValue(key, BooleanEntry.class); - if (val == null) { - BooleanEntry entry = keeper.registerEntry(key, new BooleanEntry(defaultValue)); - return entry.getValue(); - } - return val; - } - - protected boolean getBoolean(ConfigKey key) { - Boolean val = keeper.getValue(key, BooleanEntry.class); - return val != null ? val : false; - } - - protected boolean setBoolean(ConfigKey key, boolean value) { - try { - BooleanEntry entry = keeper.getEntry(key, BooleanEntry.class); - if (entry == null) return false; - entry.setValue(value); - return true; - } catch (NullPointerException ex) { - BCLib.LOGGER.catching(ex); - } - return false; - } -} diff --git a/src/main/java/ru/bclib/config/ConfigKeeper.java b/src/main/java/ru/bclib/config/ConfigKeeper.java deleted file mode 100644 index 0fc7f049..00000000 --- a/src/main/java/ru/bclib/config/ConfigKeeper.java +++ /dev/null @@ -1,320 +0,0 @@ -package ru.bclib.config; - -import java.lang.reflect.Type; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import org.jetbrains.annotations.Nullable; - -import com.google.common.collect.Maps; -import com.google.common.reflect.TypeToken; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import net.minecraft.util.GsonHelper; -import ru.bclib.util.JsonFactory; - -public final class ConfigKeeper { - private final Map> configEntries = Maps.newHashMap(); - private final JsonObject configObject; - private final ConfigWriter writer; - - private boolean changed = false; - - public ConfigKeeper(String modID, String group) { - this.writer = new ConfigWriter(modID, group); - this.configObject = writer.load(); - } - - public void save() { - if (!changed) return; - this.writer.save(); - this.changed = false; - } - - private > void initializeEntry(ConfigKey key, E entry) { - if (configObject == null) { - return; - } - String[] path = key.getPath(); - JsonObject obj = configObject; - - if (!key.isRoot()) { - for (String group : path) { - JsonElement element = obj.get(group); - if (element == null || !element.isJsonObject()) { - element = new JsonObject(); - obj.add(group, element); - } - obj = element.getAsJsonObject(); - } - } - - String paramKey = key.getEntry(); - paramKey += " [default: " + entry.getDefault() + "]"; - - this.changed |= entry.setLocation(obj, paramKey); - } - - private > void storeValue(E entry, T value) { - if (configObject == null) { - return; - } - T val = entry.getValue(); - if (value.equals(val)) return; - entry.toJson(value); - this.changed = true; - } - - private > T getValue(E entry) { - if (!entry.hasLocation()) { - return entry.getDefault(); - } - return entry.fromJson(); - } - - @Nullable - public > E getEntry(ConfigKey key, Class type) { - Entry entry = this.configEntries.get(key); - if (type.isInstance(entry)) { - return type.cast(entry); - } - return null; - } - - @Nullable - public > T getValue(ConfigKey key, Class type) { - Entry entry = this.getEntry(key, type); - if (entry == null) { - return null; - } - return entry.getValue(); - } - - public > E registerEntry(ConfigKey key, E entry) { - entry.setWriter(value -> this.storeValue(entry, value)); - entry.setReader(() -> { - return this.getValue(entry); - }); - this.initializeEntry(key, entry); - this.configEntries.put(key, entry); - return entry; - } - - public static class BooleanEntry extends Entry { - - public BooleanEntry(Boolean defaultValue) { - super(defaultValue); - } - - @Override - public Boolean fromJson() { - return GsonHelper.getAsBoolean(location, key, defaultValue); - } - - @Override - public void toJson(Boolean value) { - this.location.addProperty(key, value); - } - } - - public static class FloatEntry extends Entry { - - public FloatEntry(Float defaultValue) { - super(defaultValue); - } - - @Override - public Float fromJson() { - return GsonHelper.getAsFloat(location, key, defaultValue); - } - - @Override - public void toJson(Float value) { - this.location.addProperty(key, value); - } - } - - public static class FloatRange extends RangeEntry { - - public FloatRange(Float defaultValue, float minVal, float maxVal) { - super(defaultValue, minVal, maxVal); - } - - @Override - public Float fromJson() { - return GsonHelper.getAsFloat(location, key, defaultValue); - } - - @Override - public void toJson(Float value) { - this.location.addProperty(key, value); - } - } - - public static class IntegerEntry extends Entry { - - public IntegerEntry(Integer defaultValue) { - super(defaultValue); - } - - @Override - public Integer getDefault() { - return this.defaultValue; - } - - @Override - public Integer fromJson() { - return GsonHelper.getAsInt(location, key, defaultValue); - } - - @Override - public void toJson(Integer value) { - this.location.addProperty(key, value); - } - } - - public static class IntegerRange extends RangeEntry { - - public IntegerRange(Integer defaultValue, int minVal, int maxVal) { - super(defaultValue, minVal, maxVal); - } - - @Override - public Integer fromJson() { - return GsonHelper.getAsInt(location, key, defaultValue); - } - - @Override - public void toJson(Integer value) { - this.location.addProperty(key, value); - } - } - - public static class StringEntry extends Entry { - - public StringEntry(String defaultValue) { - super(defaultValue); - } - - @Override - public String fromJson() { - return GsonHelper.getAsString(location, key, defaultValue); - } - - @Override - public void toJson(String value) { - this.location.addProperty(key, value); - } - - } - - public static class EnumEntry> extends Entry { - - private final Type type; - - public EnumEntry(T defaultValue) { - super(defaultValue); - TypeToken token = new TypeToken() { - private static final long serialVersionUID = 1L; - }; - this.type = token.getType(); - } - - @Override - public T getDefault() { - return this.defaultValue; - } - - @Override - public T fromJson() { - return JsonFactory.GSON.fromJson(location.get(key), type); - } - - @Override - public void toJson(T value) { - location.addProperty(key, JsonFactory.GSON.toJson(value, type)); - } - } - - public static abstract class RangeEntry> extends Entry { - - private final T min, max; - - public RangeEntry(T defaultValue, T minVal, T maxVal) { - super(defaultValue); - this.min = minVal; - this.max = maxVal; - } - - @Override - public void setValue(T value) { - super.setValue(value.compareTo(min) < 0 ? min : value.compareTo(max) > 0 ? max : value); - } - - public T minValue() { - return this.min; - } - - public T maxValue() { - return this.max; - } - } - - public static abstract class Entry { - - protected final T defaultValue; - protected Consumer writer; - protected Supplier reader; - protected JsonObject location; - protected String key; - - public abstract T fromJson(); - - public abstract void toJson(T value); - - public Entry(T defaultValue) { - this.defaultValue = defaultValue; - } - - protected void setWriter(Consumer writer) { - this.writer = writer; - } - - protected void setReader(Supplier reader) { - this.reader = reader; - } - - protected boolean setLocation(JsonObject location, String key) { - this.location = location; - this.key = key; - if (!location.has(key)) { - this.toJson(defaultValue); - return true; - } - return false; - } - - protected boolean hasLocation() { - return this.location != null && - this.key != null; - } - - public T getValue() { - return this.reader.get(); - } - - public void setValue(T value) { - this.writer.accept(value); - } - - public T getDefault() { - return this.defaultValue; - } - - public void setDefault() { - this.setValue(defaultValue); - } - } -} diff --git a/src/main/java/ru/bclib/config/ConfigKey.java b/src/main/java/ru/bclib/config/ConfigKey.java deleted file mode 100644 index ed8b570d..00000000 --- a/src/main/java/ru/bclib/config/ConfigKey.java +++ /dev/null @@ -1,85 +0,0 @@ -package ru.bclib.config; - -import net.minecraft.resources.ResourceLocation; - -public class ConfigKey { - private final String path[]; - private final String entry; - private final boolean root; - - public ConfigKey(String entry, String... path) { - this.validate(entry); - this.path = path; - this.entry = entry; - this.root = path.length == 0 || (path.length == 1 && path[0].isEmpty()); - } - - public ConfigKey(String entry, ResourceLocation path) { - this(entry, path.getNamespace(), path.getPath()); - } - - public String[] getPath() { - return path; - } - - public String getEntry() { - return entry; - } - - public boolean isRoot() { - return root; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + path.hashCode(); - result = prime * result + entry.hashCode(); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof ConfigKey)) { - return false; - } - ConfigKey other = (ConfigKey) obj; - if (other.path.length != path.length) { - return false; - } - for (int i = 0; i < path.length; i++) { - if (!path[i].equals(other.path[i])) { - return false; - } - } - if (!entry.equals(other.entry)) { - return false; - } - return true; - } - - @Override - public String toString() { - if (root) { - return String.format("[root]:%s", entry); - } - String p = path[0]; - for (int i = 1; i < path.length; i++) { - p += "." + path[i]; - } - return String.format("%s:%s", p, entry); - } - - private void validate(String entry) { - if (entry == null) { - throw new NullPointerException("Config key must be not null!"); - } - if (entry.isEmpty()) { - throw new IndexOutOfBoundsException("Config key must be not empty!"); - } - } -} diff --git a/src/main/java/ru/bclib/config/ConfigWriter.java b/src/main/java/ru/bclib/config/ConfigWriter.java deleted file mode 100644 index ed4e125b..00000000 --- a/src/main/java/ru/bclib/config/ConfigWriter.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.bclib.config; - -import java.io.File; -import java.nio.file.Path; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import net.fabricmc.loader.api.FabricLoader; -import ru.bclib.util.JsonFactory; - -public class ConfigWriter { - private final static Path GAME_CONFIG_DIR = FabricLoader.getInstance().getConfigDir(); - - private final File configFile; - private JsonObject configObject; - - public ConfigWriter(String modID, String configFile) { - this.configFile = new File(new File(GAME_CONFIG_DIR.toFile(), modID), configFile + ".json"); - File parent = this.configFile.getParentFile(); - if (!parent.exists()) { - parent.mkdirs(); - } - this.load(); - } - - public JsonObject getConfig() { - return configObject; - } - - public void save() { - if (configObject == null) { - return; - } - save(configFile, configObject); - } - - public JsonObject load() { - if (configObject == null) { - configObject = load(configFile); - } - return configObject; - } - - public void save(JsonElement config) { - this.configObject = config.getAsJsonObject(); - save(configFile, config); - } - - public static JsonObject load(File configFile) { - return JsonFactory.getJsonObject(configFile); - } - - public static void save(File configFile, JsonElement config) { - JsonFactory.storeJson(configFile, config); - } - - public static String scrubFileName(String input) { - input = input.replaceAll("[/\\ ]+", "_"); - input = input.replaceAll("[,:&\"\\|\\<\\>\\?\\*]", "_"); - return input; - } -} diff --git a/src/main/java/ru/bclib/config/Configs.java b/src/main/java/ru/bclib/config/Configs.java deleted file mode 100644 index c847a620..00000000 --- a/src/main/java/ru/bclib/config/Configs.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.bclib.config; - -import ru.bclib.BCLib; - -public class Configs { - public static final PathConfig RECIPE_CONFIG = new PathConfig(BCLib.MOD_ID, "recipes"); - - public static void save() { - RECIPE_CONFIG.saveChanges(); - } -} diff --git a/src/main/java/ru/bclib/config/EntryConfig.java b/src/main/java/ru/bclib/config/EntryConfig.java deleted file mode 100644 index 0b22de86..00000000 --- a/src/main/java/ru/bclib/config/EntryConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.bclib.config; - -public class EntryConfig extends IdConfig { - public EntryConfig(String modID, String group) { - super(modID, group, (id, entry) -> { - return new ConfigKey(entry, id); - }); - } -} diff --git a/src/main/java/ru/bclib/config/IdConfig.java b/src/main/java/ru/bclib/config/IdConfig.java deleted file mode 100644 index 379d8551..00000000 --- a/src/main/java/ru/bclib/config/IdConfig.java +++ /dev/null @@ -1,92 +0,0 @@ -package ru.bclib.config; - -import java.util.function.BiFunction; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.resources.ResourceLocation; -import ru.bclib.config.ConfigKeeper.Entry; -import ru.bclib.config.ConfigKeeper.FloatRange; -import ru.bclib.config.ConfigKeeper.IntegerRange; - -public class IdConfig extends Config { - protected final BiFunction keyFactory; - - public IdConfig(String modID, String group, BiFunction keyFactory) { - super(modID, group); - this.keyFactory = keyFactory; - } - - @Override - protected void registerEntries() {} - - protected ConfigKey createKey(ResourceLocation id, String key) { - return this.keyFactory.apply(id, key); - } - - @Nullable - public > E getEntry(ResourceLocation id, String key, Class type) { - return this.getEntry(createKey(id, key), type); - } - - @Nullable - public > T getDefault(ResourceLocation id, String key, Class type) { - return this.getDefault(createKey(id, key), type); - } - - public String getString(ResourceLocation id, String key, String defaultValue) { - return this.getString(createKey(id, key), defaultValue); - } - - public String getString(ResourceLocation id, String key) { - return this.getString(createKey(id, key)); - } - - public boolean setString(ResourceLocation id, String key, String value) { - return this.setString(createKey(id, key), value); - } - - public int getInt(ResourceLocation id, String key, int defaultValue) { - return this.getInt(createKey(id, key), defaultValue); - } - - public int getInt(ResourceLocation id, String key) { - return this.getInt(createKey(id, key)); - } - - public boolean setInt(ResourceLocation id, String key, int value) { - return this.setInt(createKey(id, key), value); - } - - public boolean setRangedInt(ResourceLocation id, String key, int value) { - return this.setRanged(createKey(id, key), value, IntegerRange.class); - } - - public boolean setRangedFloat(ResourceLocation id, String key, float value) { - return this.setRanged(createKey(id, key), value, FloatRange.class); - } - - public float getFloat(ResourceLocation id, String key, float defaultValue) { - return this.getFloat(createKey(id, key), defaultValue); - } - - public float getFloat(ResourceLocation id, String key) { - return this.getFloat(createKey(id, key)); - } - - public boolean setFloat(ResourceLocation id, String key, float value) { - return this.setFloat(createKey(id, key), value); - } - - public boolean getBoolean(ResourceLocation id, String key, boolean defaultValue) { - return this.getBoolean(createKey(id, key), defaultValue); - } - - public boolean getBoolean(ResourceLocation id, String key) { - return this.getBoolean(createKey(id, key)); - } - - public boolean setBoolean(ResourceLocation id, String key, boolean value) { - return this.setBoolean(createKey(id, key), value); - } -} diff --git a/src/main/java/ru/bclib/config/PathConfig.java b/src/main/java/ru/bclib/config/PathConfig.java deleted file mode 100644 index 3f7bc3e9..00000000 --- a/src/main/java/ru/bclib/config/PathConfig.java +++ /dev/null @@ -1,149 +0,0 @@ -package ru.bclib.config; - -import org.jetbrains.annotations.Nullable; - -import ru.bclib.config.ConfigKeeper.Entry; -import ru.bclib.config.ConfigKeeper.FloatRange; -import ru.bclib.config.ConfigKeeper.IntegerRange; - -public class PathConfig extends Config { - - public PathConfig(String modID, String group) { - super(modID, group); - } - - @Override - protected void registerEntries() {} - - protected ConfigKey createKey(String category, String key) { - return new ConfigKey(key, category.split("\\.")); - } - - protected ConfigKey createKey(String key) { - return createKey("", key); - } - - @Nullable - public > E getEntry(String category, String key, Class type) { - return this.getEntry(createKey(category, key), type); - } - - @Nullable - public > T getDefault(String category, String key, Class type) { - return this.getDefault(createKey(category, key), type); - } - - public String getString(String category, String key, String defaultValue) { - return this.getString(createKey(category, key), defaultValue); - } - - public String getString(String category, String key) { - return this.getString(createKey(category, key)); - } - - public boolean setString(String category, String key, String value) { - return this.setString(createKey(category, key), value); - } - - public int getInt(String category, String key, int defaultValue) { - return this.getInt(createKey(category, key), defaultValue); - } - - public int getInt(String category, String key) { - return this.getInt(createKey(category, key)); - } - - public boolean setInt(String category, String key, int value) { - return this.setInt(createKey(category, key), value); - } - - public boolean setRangedInt(String category, String key, int value) { - return this.setRanged(createKey(category, key), value, IntegerRange.class); - } - - public boolean setRangedFloat(String category, String key, float value) { - return this.setRanged(createKey(category, key), value, FloatRange.class); - } - - public float getFloat(String category, String key, float defaultValue) { - return this.getFloat(createKey(category, key), defaultValue); - } - - public float getFloat(String category, String key) { - return this.getFloat(createKey(category, key)); - } - - public boolean setFloat(String category, String key, float value) { - return this.setFloat(createKey(category, key), value); - } - - public boolean getBoolean(String category, String key, boolean defaultValue) { - return this.getBoolean(createKey(category, key), defaultValue); - } - - public boolean getBoolean(String category, String key) { - return this.getBoolean(createKey(category, key)); - } - - public boolean setBoolean(String category, String key, boolean value) { - return this.setBoolean(createKey(category, key), value); - } - - // From Root - - public String getStringRoot(String key, String defaultValue) { - return this.getString(createKey(key), defaultValue); - } - - public String getStringRoot(String key) { - return this.getString(createKey(key)); - } - - public boolean setStringRoot(String key, String value) { - return this.setString(createKey(key), value); - } - - public int getIntRoot(String key, int defaultValue) { - return this.getInt(createKey(key), defaultValue); - } - - public int getIntRoot(String key) { - return this.getInt(createKey(key)); - } - - public boolean setIntRoot(String key, int value) { - return this.setInt(createKey(key), value); - } - - public boolean setRangedIntRoot(String key, int value) { - return this.setRanged(createKey(key), value, IntegerRange.class); - } - - public boolean setRangedFloatRoot(String key, float value) { - return this.setRanged(createKey(key), value, FloatRange.class); - } - - public float getFloatRoot(String key, float defaultValue) { - return this.getFloat(createKey(key), defaultValue); - } - - public float getFloatRoot(String key) { - return this.getFloat(createKey(key)); - } - - public boolean setFloatRoot(String key, float value) { - return this.setFloat(createKey(key), value); - } - - public boolean getBooleanRoot(String key, boolean defaultValue) { - return this.getBoolean(createKey(key), defaultValue); - } - - public boolean getBooleanRoot(String key) { - return this.getBoolean(createKey(key)); - } - - public boolean setBooleanRoot(String key, boolean value) { - return this.setBoolean(createKey(key), value); - } -} diff --git a/src/main/java/ru/bclib/integration/ModIntegration.java b/src/main/java/ru/bclib/integration/ModIntegration.java deleted file mode 100644 index 69859df4..00000000 --- a/src/main/java/ru/bclib/integration/ModIntegration.java +++ /dev/null @@ -1,209 +0,0 @@ -package ru.bclib.integration; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import net.fabricmc.fabric.api.tag.TagRegistry; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.core.Registry; -import net.minecraft.data.BuiltinRegistries; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.BlockTags; -import net.minecraft.tags.ItemTags; -import net.minecraft.tags.Tag; -import net.minecraft.tags.Tag.Named; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.biome.Biome; -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.feature.ConfiguredFeature; -import net.minecraft.world.level.levelgen.feature.Feature; -import ru.bclib.BCLib; -import ru.bclib.world.features.BCLFeature; - -public abstract class ModIntegration { - private final String modID; - - public void init() {} - - public ModIntegration(String modID) { - this.modID = modID; - } - - public ResourceLocation getID(String name) { - return new ResourceLocation(modID, name); - } - - public Block getBlock(String name) { - return Registry.BLOCK.get(getID(name)); - } - - public Item getItem(String name) { - return Registry.ITEM.get(getID(name)); - } - - public BlockState getDefaultState(String name) { - return getBlock(name).defaultBlockState(); - } - - public ResourceKey getKey(String name) { - return ResourceKey.create(Registry.BIOME_REGISTRY, getID(name)); - } - - public boolean modIsInstalled() { - return FabricLoader.getInstance().isModLoaded(modID); - } - - public BCLFeature getFeature(String featureID, String configuredFeatureID, GenerationStep.Decoration featureStep) { - Feature feature = Registry.FEATURE.get(getID(featureID)); - ConfiguredFeature featureConfigured = BuiltinRegistries.CONFIGURED_FEATURE.get(getID(configuredFeatureID)); - return new BCLFeature(feature, featureConfigured, featureStep); - } - - public BCLFeature getFeature(String name, GenerationStep.Decoration featureStep) { - return getFeature(name, name, featureStep); - } - - public ConfiguredFeature getConfiguredFeature(String name) { - return BuiltinRegistries.CONFIGURED_FEATURE.get(getID(name)); - } - - public Biome getBiome(String name) { - return BuiltinRegistries.BIOME.get(getID(name)); - } - - public Class getClass(String path) { - Class cl = null; - try { - cl = Class.forName(path); - } - catch (ClassNotFoundException e) { - BCLib.LOGGER.error(e.getMessage()); - if (BCLib.isDevEnvironment()) { - e.printStackTrace(); - } - } - return cl; - } - - @SuppressWarnings("unchecked") - public T getStaticFieldValue(Class cl, String name) { - if (cl != null) { - try { - Field field = cl.getDeclaredField(name); - if (field != null) { - return (T) field.get(null); - } - } - catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { - e.printStackTrace(); - } - } - return null; - } - - public Object getFieldValue(Class cl, String name, Object classInstance) { - if (cl != null) { - try { - Field field = cl.getDeclaredField(name); - if (field != null) { - return field.get(classInstance); - } - } - catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { - e.printStackTrace(); - } - } - return null; - } - - public Method getMethod(Class cl, String functionName, Class... args) { - if (cl != null) { - try { - return cl.getMethod(functionName, args); - } - catch (NoSuchMethodException | SecurityException e) { - BCLib.LOGGER.error(e.getMessage()); - if (BCLib.isDevEnvironment()) { - e.printStackTrace(); - } - } - } - return null; - } - - public Object executeMethod(Object instance, Method method, Object... args) { - if (method != null) { - try { - return method.invoke(instance, args); - } - catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - BCLib.LOGGER.error(e.getMessage()); - if (BCLib.isDevEnvironment()) { - e.printStackTrace(); - } - } - } - return null; - } - - public Object getAndExecuteStatic(Class cl, String functionName, Object... args) { - if (cl != null) { - Class[] classes = new Class[args.length]; - for (int i = 0; i < args.length; i++) { - classes[i] = args[i].getClass(); - } - Method method = getMethod(cl, functionName, classes); - return executeMethod(null, method, args); - } - return null; - } - - @SuppressWarnings("unchecked") - public T getAndExecuteRuntime(Class cl, Object instance, String functionName, Object... args) { - if (instance != null) { - Class[] classes = new Class[args.length]; - for (int i = 0; i < args.length; i++) { - classes[i] = args[i].getClass(); - } - Method method = getMethod(cl, functionName, classes); - return (T) executeMethod(instance, method, args); - } - return null; - } - - public Object newInstance(Class cl, Object... args) { - if (cl != null) { - for (Constructor constructor: cl.getConstructors()) { - if (constructor.getParameterCount() == args.length) { - try { - return constructor.newInstance(args); - } - catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - BCLib.LOGGER.error(e.getMessage()); - if (BCLib.isDevEnvironment()) { - e.printStackTrace(); - } - } - } - } - } - return null; - } - - public Tag.Named getItemTag(String name) { - ResourceLocation id = getID(name); - Tag tag = ItemTags.getAllTags().getTag(id); - return tag == null ? (Named) TagRegistry.item(id) : (Named) tag; - } - - public Tag.Named getBlockTag(String name) { - ResourceLocation id = getID(name); - Tag tag = BlockTags.getAllTags().getTag(id); - return tag == null ? (Named) TagRegistry.block(id) : (Named) tag; - } -} diff --git a/src/main/java/ru/bclib/interfaces/IColorProvider.java b/src/main/java/ru/bclib/interfaces/IColorProvider.java deleted file mode 100644 index c9f2f8b6..00000000 --- a/src/main/java/ru/bclib/interfaces/IColorProvider.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.bclib.interfaces; - -import net.minecraft.client.color.block.BlockColor; -import net.minecraft.client.color.item.ItemColor; - -public interface IColorProvider { - BlockColor getProvider(); - - ItemColor getItemProvider(); -} diff --git a/src/main/java/ru/bclib/interfaces/IRenderTyped.java b/src/main/java/ru/bclib/interfaces/IRenderTyped.java deleted file mode 100644 index 445f9e6b..00000000 --- a/src/main/java/ru/bclib/interfaces/IRenderTyped.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.bclib.interfaces; - -import ru.bclib.client.render.BCLRenderLayer; - -public interface IRenderTyped { - BCLRenderLayer getRenderLayer(); -} diff --git a/src/main/java/ru/bclib/interfaces/ISpetialItem.java b/src/main/java/ru/bclib/interfaces/ISpetialItem.java deleted file mode 100644 index 33112bf7..00000000 --- a/src/main/java/ru/bclib/interfaces/ISpetialItem.java +++ /dev/null @@ -1,6 +0,0 @@ -package ru.bclib.interfaces; - -public interface ISpetialItem { - boolean canPlaceOnWater(); - int getStackSize(); -} diff --git a/src/main/java/ru/bclib/items/BaseAnvilItem.java b/src/main/java/ru/bclib/items/BaseAnvilItem.java deleted file mode 100644 index d4c51755..00000000 --- a/src/main/java/ru/bclib/items/BaseAnvilItem.java +++ /dev/null @@ -1,58 +0,0 @@ -package ru.bclib.items; - -import java.util.List; - -import org.jetbrains.annotations.Nullable; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.core.Registry; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.TranslatableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.BlockItem; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.blocks.BaseAnvilBlock; -import ru.bclib.client.models.ItemModelProvider; - -public class BaseAnvilItem extends BlockItem implements ItemModelProvider { - - public final static String DESTRUCTION = "destruction"; - - public BaseAnvilItem(Block block, Properties properties) { - super(block, properties); - } - - @Override - protected BlockState getPlacementState(BlockPlaceContext blockPlaceContext) { - BlockState blockState = super.getPlacementState(blockPlaceContext); - ItemStack stack = blockPlaceContext.getItemInHand(); - int destruction = stack.getOrCreateTag().getInt(DESTRUCTION); - blockState = blockState.setValue(BaseAnvilBlock.DESTRUCTION, destruction); - return blockState; - } - - @Override - @Environment(EnvType.CLIENT) - public void appendHoverText(ItemStack itemStack, @Nullable Level level, List list, TooltipFlag tooltipFlag) { - super.appendHoverText(itemStack, level, list, tooltipFlag); - int l = itemStack.getOrCreateTag().getInt(DESTRUCTION); - if (l > 0) { - list.add(new TranslatableComponent("message.bclib.anvil_damage").append(": " + l)); - } - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - Block anvilBlock = getBlock(); - ResourceLocation blockId = Registry.BLOCK.getKey(anvilBlock); - return ((ItemModelProvider) anvilBlock).getItemModel(blockId); - } -} diff --git a/src/main/java/ru/bclib/items/BaseArmorItem.java b/src/main/java/ru/bclib/items/BaseArmorItem.java deleted file mode 100644 index 805c2128..00000000 --- a/src/main/java/ru/bclib/items/BaseArmorItem.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.bclib.items; - -import java.util.UUID; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; - -import net.minecraft.world.entity.EquipmentSlot; -import net.minecraft.world.entity.ai.attributes.Attribute; -import net.minecraft.world.entity.ai.attributes.AttributeModifier; -import net.minecraft.world.entity.ai.attributes.Attributes; -import net.minecraft.world.item.ArmorItem; -import net.minecraft.world.item.ArmorMaterial; -import ru.bclib.client.models.ItemModelProvider; - -public class BaseArmorItem extends ArmorItem implements ItemModelProvider { - - protected static final UUID[] ARMOR_MODIFIER_UUID_PER_SLOT = new UUID[] { - UUID.fromString("845DB27C-C624-495F-8C9F-6020A9A58B6B"), - UUID.fromString("D8499B04-0E66-4726-AB29-64469D734E0D"), - UUID.fromString("9F3D476D-C118-4544-8365-64846904B48E"), - UUID.fromString("2AD3F246-FEE1-4E67-B886-69FD380BB150") - }; - - protected final Multimap defaultModifiers; - - public BaseArmorItem(ArmorMaterial material, EquipmentSlot equipmentSlot, Properties settings) { - super(material, equipmentSlot, settings); - this.defaultModifiers = HashMultimap.create(); - UUID uuid = ARMOR_MODIFIER_UUID_PER_SLOT[equipmentSlot.getIndex()]; - addAttributeModifier(Attributes.ARMOR, new AttributeModifier(uuid, "Armor modifier", getDefense(), AttributeModifier.Operation.ADDITION)); - addAttributeModifier(Attributes.ARMOR_TOUGHNESS, new AttributeModifier(uuid, "Armor toughness", getToughness(), AttributeModifier.Operation.ADDITION)); - if (knockbackResistance > 0.0F) { - addAttributeModifier(Attributes.KNOCKBACK_RESISTANCE, new AttributeModifier(uuid, "Armor knockback resistance", knockbackResistance, AttributeModifier.Operation.ADDITION)); - } - } - - @Override - public Multimap getDefaultAttributeModifiers(EquipmentSlot equipmentSlot) { - return equipmentSlot == slot ? defaultModifiers : super.getDefaultAttributeModifiers(equipmentSlot); - } - - protected void addAttributeModifier(Attribute attribute, AttributeModifier modifier) { - if (defaultModifiers.containsKey(attribute)) { - defaultModifiers.removeAll(attribute); - } - defaultModifiers.put(attribute, modifier); - } -} diff --git a/src/main/java/ru/bclib/items/BaseAttribute.java b/src/main/java/ru/bclib/items/BaseAttribute.java deleted file mode 100644 index 69060830..00000000 --- a/src/main/java/ru/bclib/items/BaseAttribute.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.bclib.items; - -import net.minecraft.world.entity.ai.attributes.Attribute; - -public class BaseAttribute extends Attribute { - public BaseAttribute(String description, double value) { - super(description, value); - } -} diff --git a/src/main/java/ru/bclib/items/BaseBucketItem.java b/src/main/java/ru/bclib/items/BaseBucketItem.java deleted file mode 100644 index e33ac83a..00000000 --- a/src/main/java/ru/bclib/items/BaseBucketItem.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.bclib.items; - -import net.fabricmc.fabric.api.item.v1.FabricItemSettings; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.item.FishBucketItem; -import net.minecraft.world.level.material.Fluids; -import ru.bclib.client.models.ItemModelProvider; - -public class BaseBucketItem extends FishBucketItem implements ItemModelProvider { - public BaseBucketItem(EntityType type, FabricItemSettings settings) { - super(type, Fluids.WATER, settings.stacksTo(1)); - } -} diff --git a/src/main/java/ru/bclib/items/BaseDiscItem.java b/src/main/java/ru/bclib/items/BaseDiscItem.java deleted file mode 100644 index 3b6a8e46..00000000 --- a/src/main/java/ru/bclib/items/BaseDiscItem.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.bclib.items; - -import net.minecraft.sounds.SoundEvent; -import net.minecraft.world.item.RecordItem; -import ru.bclib.client.models.ItemModelProvider; - -public class BaseDiscItem extends RecordItem implements ItemModelProvider { - public BaseDiscItem(int comparatorOutput, SoundEvent sound, Properties settings) { - super(comparatorOutput, sound, settings); - } -} diff --git a/src/main/java/ru/bclib/items/BaseDrinkItem.java b/src/main/java/ru/bclib/items/BaseDrinkItem.java deleted file mode 100644 index cf4b520d..00000000 --- a/src/main/java/ru/bclib/items/BaseDrinkItem.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.bclib.items; - -import net.minecraft.advancements.CriteriaTriggers; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.stats.Stats; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResultHolder; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.ItemUtils; -import net.minecraft.world.item.Items; -import net.minecraft.world.item.UseAnim; -import net.minecraft.world.level.Level; - -public class BaseDrinkItem extends ModelProviderItem { - public BaseDrinkItem(Properties settings) { - super(settings); - } - - @Override - public int getUseDuration(ItemStack stack) { - return 32; - } - - @Override - public UseAnim getUseAnimation(ItemStack stack) { - return UseAnim.DRINK; - } - - @Override - public InteractionResultHolder use(Level world, Player user, InteractionHand hand) { - return ItemUtils.useDrink(world, user, hand); - } - - @Override - public ItemStack finishUsingItem(ItemStack stack, Level level, LivingEntity user) { - if (this.isEdible()) { - int count = stack.getCount(); - user.eat(level, stack); - stack.setCount(count); - } - - if (user instanceof ServerPlayer) { - ServerPlayer serverPlayerEntity = (ServerPlayer) user; - CriteriaTriggers.CONSUME_ITEM.trigger(serverPlayerEntity, stack); - serverPlayerEntity.awardStat(Stats.ITEM_USED.get(this)); - } - - if (user instanceof Player && !((Player) user).abilities.instabuild) { - stack.shrink(1); - } - - if (!level.isClientSide) { - user.removeAllEffects(); - } - - return stack.isEmpty() ? new ItemStack(Items.GLASS_BOTTLE) : stack; - } -} diff --git a/src/main/java/ru/bclib/items/BaseSpawnEggItem.java b/src/main/java/ru/bclib/items/BaseSpawnEggItem.java deleted file mode 100644 index ec7b63f3..00000000 --- a/src/main/java/ru/bclib/items/BaseSpawnEggItem.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.bclib.items; - -import java.util.Optional; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.item.SpawnEggItem; -import ru.bclib.client.models.BasePatterns; -import ru.bclib.client.models.ItemModelProvider; -import ru.bclib.client.models.ModelsHelper; -import ru.bclib.client.models.PatternsHelper; - -public class BaseSpawnEggItem extends SpawnEggItem implements ItemModelProvider { - public BaseSpawnEggItem(EntityType type, int primaryColor, int secondaryColor, Properties settings) { - super(type, primaryColor, secondaryColor, settings); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - Optional pattern = PatternsHelper.createJson(BasePatterns.ITEM_SPAWN_EGG, resourceLocation); - return ModelsHelper.fromPattern(pattern); - } -} diff --git a/src/main/java/ru/bclib/items/ModelProviderItem.java b/src/main/java/ru/bclib/items/ModelProviderItem.java deleted file mode 100644 index 4fbd6a00..00000000 --- a/src/main/java/ru/bclib/items/ModelProviderItem.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.bclib.items; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.Item; -import ru.bclib.client.models.ItemModelProvider; -import ru.bclib.client.models.ModelsHelper; - -public class ModelProviderItem extends Item implements ItemModelProvider { - public ModelProviderItem(Properties settings) { - super(settings); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return ModelsHelper.createItemModel(resourceLocation); - } -} diff --git a/src/main/java/ru/bclib/items/tool/BaseAxeItem.java b/src/main/java/ru/bclib/items/tool/BaseAxeItem.java deleted file mode 100644 index 3836f161..00000000 --- a/src/main/java/ru/bclib/items/tool/BaseAxeItem.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.bclib.items.tool; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.tool.attribute.v1.DynamicAttributeTool; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.Tag; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.AxeItem; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Tier; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.client.models.ItemModelProvider; -import ru.bclib.client.models.ModelsHelper; - -public class BaseAxeItem extends AxeItem implements DynamicAttributeTool, ItemModelProvider { - public BaseAxeItem(Tier material, float attackDamage, float attackSpeed, Properties settings) { - super(material, attackDamage, attackSpeed, settings); - } - - @Override - public int getMiningLevel(Tag tag, BlockState state, ItemStack stack, LivingEntity user) { - if (tag.equals(FabricToolTags.AXES)) { - return this.getTier().getLevel(); - } - return 0; - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return ModelsHelper.createHandheldItem(resourceLocation); - } -} diff --git a/src/main/java/ru/bclib/items/tool/BaseHoeItem.java b/src/main/java/ru/bclib/items/tool/BaseHoeItem.java deleted file mode 100644 index 6e1aeab4..00000000 --- a/src/main/java/ru/bclib/items/tool/BaseHoeItem.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.bclib.items.tool; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.HoeItem; -import net.minecraft.world.item.Tier; -import ru.bclib.client.models.ItemModelProvider; -import ru.bclib.client.models.ModelsHelper; - -public class BaseHoeItem extends HoeItem implements ItemModelProvider { - public BaseHoeItem(Tier material, int attackDamage, float attackSpeed, Properties settings) { - super(material, attackDamage, attackSpeed, settings); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return ModelsHelper.createHandheldItem(resourceLocation); - } -} diff --git a/src/main/java/ru/bclib/items/tool/BasePickaxeItem.java b/src/main/java/ru/bclib/items/tool/BasePickaxeItem.java deleted file mode 100644 index 213c530b..00000000 --- a/src/main/java/ru/bclib/items/tool/BasePickaxeItem.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.bclib.items.tool; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.tool.attribute.v1.DynamicAttributeTool; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.fabricmc.fabric.impl.tool.attribute.ToolManagerImpl; -import net.fabricmc.fabric.impl.tool.attribute.ToolManagerImpl.Entry; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.Tag; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.PickaxeItem; -import net.minecraft.world.item.Tier; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.client.models.ItemModelProvider; -import ru.bclib.client.models.ModelsHelper; - -public class BasePickaxeItem extends PickaxeItem implements DynamicAttributeTool, ItemModelProvider { - public BasePickaxeItem(Tier material, int attackDamage, float attackSpeed, Properties settings) { - super(material, attackDamage, attackSpeed, settings); - } - - @Override - public int getMiningLevel(Tag tag, BlockState state, ItemStack stack, LivingEntity user) { - if (tag.equals(FabricToolTags.PICKAXES)) { - return getTier().getLevel(); - } - return 0; - } - - @Override - public float getDestroySpeed(ItemStack stack, BlockState state) { - Entry entry = ToolManagerImpl.entryNullable(state.getBlock()); - return (entry != null && entry.getMiningLevel(FabricToolTags.PICKAXES) >= 0) ? speed : super.getDestroySpeed(stack, state); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return ModelsHelper.createHandheldItem(resourceLocation); - } -} diff --git a/src/main/java/ru/bclib/items/tool/BaseShovelItem.java b/src/main/java/ru/bclib/items/tool/BaseShovelItem.java deleted file mode 100644 index 1a84ee98..00000000 --- a/src/main/java/ru/bclib/items/tool/BaseShovelItem.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.bclib.items.tool; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.tool.attribute.v1.DynamicAttributeTool; -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.fabricmc.fabric.impl.tool.attribute.ToolManagerImpl; -import net.fabricmc.fabric.impl.tool.attribute.ToolManagerImpl.Entry; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.Tag; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.ShovelItem; -import net.minecraft.world.item.Tier; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.client.models.ItemModelProvider; -import ru.bclib.client.models.ModelsHelper; - -public class BaseShovelItem extends ShovelItem implements DynamicAttributeTool, ItemModelProvider { - public BaseShovelItem(Tier material, float attackDamage, float attackSpeed, Properties settings) { - super(material, attackDamage, attackSpeed, settings); - } - - @Override - public int getMiningLevel(Tag tag, BlockState state, ItemStack stack, LivingEntity user) { - if (tag.equals(FabricToolTags.SHOVELS)) { - return this.getTier().getLevel(); - } - return 0; - } - - @Override - public float getDestroySpeed(ItemStack stack, BlockState state) { - Entry entry = ToolManagerImpl.entryNullable(state.getBlock()); - return (entry != null && entry.getMiningLevel(FabricToolTags.SHOVELS) >= 0) ? speed : super.getDestroySpeed(stack, state); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return ModelsHelper.createHandheldItem(resourceLocation); - } -} diff --git a/src/main/java/ru/bclib/items/tool/BaseSwordItem.java b/src/main/java/ru/bclib/items/tool/BaseSwordItem.java deleted file mode 100644 index f78098a3..00000000 --- a/src/main/java/ru/bclib/items/tool/BaseSwordItem.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.bclib.items.tool; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.tool.attribute.v1.DynamicAttributeTool; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.SwordItem; -import net.minecraft.world.item.Tier; -import ru.bclib.client.models.ItemModelProvider; -import ru.bclib.client.models.ModelsHelper; - -public class BaseSwordItem extends SwordItem implements DynamicAttributeTool, ItemModelProvider { - public BaseSwordItem(Tier material, int attackDamage, float attackSpeed, Properties settings) { - super(material, attackDamage, attackSpeed, settings); - } - - @Override - @Environment(EnvType.CLIENT) - public BlockModel getItemModel(ResourceLocation resourceLocation) { - return ModelsHelper.createHandheldItem(resourceLocation); - } -} diff --git a/src/main/java/ru/bclib/mixin/client/BackgroundRendererMixin.java b/src/main/java/ru/bclib/mixin/client/BackgroundRendererMixin.java deleted file mode 100644 index ca1076be..00000000 --- a/src/main/java/ru/bclib/mixin/client/BackgroundRendererMixin.java +++ /dev/null @@ -1,143 +0,0 @@ -package ru.bclib.mixin.client; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import com.mojang.blaze3d.platform.GlStateManager; -import com.mojang.blaze3d.systems.RenderSystem; - -import net.minecraft.client.Camera; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.client.renderer.FogRenderer; -import net.minecraft.core.BlockPos.MutableBlockPos; -import net.minecraft.util.Mth; -import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.effect.MobEffects; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.biome.Biome; -import net.minecraft.world.level.material.FluidState; -import ru.bclib.api.BiomeAPI; -import ru.bclib.util.BackgroundInfo; -import ru.bclib.util.MHelper; -import ru.bclib.world.biomes.BCLBiome; - -@Mixin(FogRenderer.class) -public class BackgroundRendererMixin { - private static final MutableBlockPos BCL_LAST_POS = new MutableBlockPos(0, -100, 0); - private static final MutableBlockPos BCL_MUT_POS = new MutableBlockPos(); - private static final float[] BCL_FOG_DENSITY = new float[8]; - - @Shadow - private static float fogRed; - @Shadow - private static float fogGreen; - @Shadow - private static float fogBlue; - - @Inject(method = "setupColor", at = @At("RETURN")) - private static void bcl_onRender(Camera camera, float tickDelta, ClientLevel world, int i, float f, CallbackInfo info) { - FluidState fluidState = camera.getFluidInCamera(); - if (fluidState.isEmpty() && world.dimension().equals(Level.END)) { - Entity entity = camera.getEntity(); - boolean skip = false; - if (entity instanceof LivingEntity) { - MobEffectInstance effect = ((LivingEntity) entity).getEffect(MobEffects.NIGHT_VISION); - skip = effect != null && effect.getDuration() > 0; - } - if (!skip) { - fogRed *= 4; - fogGreen *= 4; - fogBlue *= 4; - } - } - - BackgroundInfo.fogColorRed = fogRed; - BackgroundInfo.fogColorGreen = fogGreen; - BackgroundInfo.fogColorBlue = fogBlue; - } - - @Inject(method = "setupFog", at = @At("HEAD"), cancellable = true) - private static void bcl_fogDensity(Camera camera, FogRenderer.FogMode fogType, float viewDistance, boolean thickFog, CallbackInfo info) { - Entity entity = camera.getEntity(); - FluidState fluidState = camera.getFluidInCamera(); - if (fluidState.isEmpty()) { - float fog = bcl_getFogDensity(entity.level, entity.getX(), entity.getEyeY(), entity.getZ()); - BackgroundInfo.fogDensity = fog; - float start = viewDistance * 0.75F / fog; - float end = viewDistance / fog; - - if (entity instanceof LivingEntity) { - LivingEntity le = (LivingEntity) entity; - MobEffectInstance effect = le.getEffect(MobEffects.BLINDNESS); - if (effect != null) { - int duration = effect.getDuration(); - if (duration > 20) { - start = 0; - end *= 0.03F; - BackgroundInfo.blindness = 1; - } - else { - float delta = (float) duration / 20F; - BackgroundInfo.blindness = delta; - start = Mth.lerp(delta, start, 0); - end = Mth.lerp(delta, end, end * 0.03F); - } - } - else { - BackgroundInfo.blindness = 0; - } - } - - RenderSystem.fogStart(start); - RenderSystem.fogEnd(end); - RenderSystem.fogMode(GlStateManager.FogMode.LINEAR); - RenderSystem.setupNvFogDistance(); - info.cancel(); - } - } - - private static float bcl_getFogDensityI(Level level, int x, int y, int z) { - Biome biome = level.getBiome(BCL_MUT_POS.set(x, y, z)); - BCLBiome renderBiome = BiomeAPI.getRenderBiome(biome); - return renderBiome.getFogDensity(); - } - - private static float bcl_getFogDensity(Level level, double x, double y, double z) { - int x1 = (MHelper.floor(x) >> 3) << 3; - int y1 = (MHelper.floor(y) >> 3) << 3; - int z1 = (MHelper.floor(z) >> 3) << 3; - float dx = (float) (x - x1) / 8F; - float dy = (float) (y - y1) / 8F; - float dz = (float) (z - z1) / 8F; - - if (BCL_LAST_POS.getX() != x1 || BCL_LAST_POS.getY() != y1 || BCL_LAST_POS.getZ() != z1) { - int x2 = x1 + 8; - int y2 = y1 + 8; - int z2 = z1 + 8; - BCL_LAST_POS.set(x1, y1, z1); - BCL_FOG_DENSITY[0] = bcl_getFogDensityI(level, x1, y1, z1); - BCL_FOG_DENSITY[1] = bcl_getFogDensityI(level, x2, y1, z1); - BCL_FOG_DENSITY[2] = bcl_getFogDensityI(level, x1, y2, z1); - BCL_FOG_DENSITY[3] = bcl_getFogDensityI(level, x2, y2, z1); - BCL_FOG_DENSITY[4] = bcl_getFogDensityI(level, x1, y1, z2); - BCL_FOG_DENSITY[5] = bcl_getFogDensityI(level, x2, y1, z2); - BCL_FOG_DENSITY[6] = bcl_getFogDensityI(level, x1, y2, z2); - BCL_FOG_DENSITY[7] = bcl_getFogDensityI(level, x2, y2, z2); - } - - float a = Mth.lerp(dx, BCL_FOG_DENSITY[0], BCL_FOG_DENSITY[1]); - float b = Mth.lerp(dx, BCL_FOG_DENSITY[2], BCL_FOG_DENSITY[3]); - float c = Mth.lerp(dx, BCL_FOG_DENSITY[4], BCL_FOG_DENSITY[5]); - float d = Mth.lerp(dx, BCL_FOG_DENSITY[6], BCL_FOG_DENSITY[7]); - - a = Mth.lerp(dy, a, b); - b = Mth.lerp(dy, c, d); - - return Mth.lerp(dz, a, b); - } -} diff --git a/src/main/java/ru/bclib/mixin/client/EnchantingTableBlockMixin.java b/src/main/java/ru/bclib/mixin/client/EnchantingTableBlockMixin.java deleted file mode 100644 index a78291c5..00000000 --- a/src/main/java/ru/bclib/mixin/client/EnchantingTableBlockMixin.java +++ /dev/null @@ -1,46 +0,0 @@ -package ru.bclib.mixin.client; - -import java.util.Random; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.particles.ParticleTypes; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.EnchantmentTableBlock; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.api.TagAPI; - -@Mixin(EnchantmentTableBlock.class) -public abstract class EnchantingTableBlockMixin extends Block { - public EnchantingTableBlockMixin(Properties settings) { - super(settings); - } - - @Inject(method = "animateTick", at = @At(value = "TAIL")) - private void be_onRandomDisplayTick(BlockState state, Level world, BlockPos pos, Random random, CallbackInfo info) { - for (int px = -2; px <= 2; ++px) { - for (int pz = -2; pz <= 2; ++pz) { - if (px > -2 && px < 2 && pz == -1) { - pz = 2; - } - if (random.nextInt(16) == 0) { - for (int py = 0; py <= 1; ++py) { - BlockPos blockPos = pos.offset(px, py, pz); - if (world.getBlockState(blockPos).is(TagAPI.BOOKSHELVES)) { - if (!world.isEmptyBlock(pos.offset(px / 2, 0, pz / 2))) { - break; - } - world.addParticle(ParticleTypes.ENCHANT, pos.getX() + 0.5, pos.getY() + 2.0, pos.getZ() + 0.5, px + random.nextFloat() - 0.5, py - random.nextFloat() - 1.0, pz + random.nextFloat() - 0.5); - } - } - } - } - } - - } -} diff --git a/src/main/java/ru/bclib/mixin/client/ModelBakeryMixin.java b/src/main/java/ru/bclib/mixin/client/ModelBakeryMixin.java deleted file mode 100644 index bcad7100..00000000 --- a/src/main/java/ru/bclib/mixin/client/ModelBakeryMixin.java +++ /dev/null @@ -1,106 +0,0 @@ -package ru.bclib.mixin.client; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import net.minecraft.client.renderer.block.BlockModelShaper; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.multipart.MultiPart; -import net.minecraft.client.resources.model.ModelBakery; -import net.minecraft.client.resources.model.ModelResourceLocation; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraft.world.item.BlockItem; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.BCLib; -import ru.bclib.client.models.BlockModelProvider; -import ru.bclib.client.models.ItemModelProvider; - -@Mixin(ModelBakery.class) -public abstract class ModelBakeryMixin { - @Final - @Shadow - private ResourceManager resourceManager; - @Final - @Shadow - private Map unbakedCache; - - @Shadow - protected abstract void cacheAndQueueDependencies(ResourceLocation resourceLocation, UnbakedModel unbakedModel); - - @Inject(method = "loadModel", at = @At("HEAD"), cancellable = true) - private void bclib_loadModels(ResourceLocation resourceLocation, CallbackInfo info) { - if (resourceLocation instanceof ModelResourceLocation) { - String modId = resourceLocation.getNamespace(); - String path = resourceLocation.getPath(); - ResourceLocation clearLoc = new ResourceLocation(modId, path); - ModelResourceLocation modelId = (ModelResourceLocation) resourceLocation; - if ("inventory".equals(modelId.getVariant())) { - ResourceLocation itemLoc = new ResourceLocation(modId, "item/" + path); - ResourceLocation itemModelLoc = new ResourceLocation(modId, "models/" + itemLoc.getPath() + ".json"); - if (!resourceManager.hasResource(itemModelLoc)) { - Item item = Registry.ITEM.get(clearLoc); - ItemModelProvider modelProvider = null; - if (item instanceof ItemModelProvider) { - modelProvider = (ItemModelProvider) item; - } else if (item instanceof BlockItem) { - Block block = Registry.BLOCK.get(clearLoc); - if (block instanceof ItemModelProvider) { - modelProvider = (ItemModelProvider) block; - } - } - if (modelProvider != null) { - BlockModel model = modelProvider.getItemModel(clearLoc); - if (model != null) { - model.name = itemLoc.toString(); - cacheAndQueueDependencies(modelId, model); - unbakedCache.put(itemLoc, model); - } else { - BCLib.LOGGER.warning("Error loading model: {}", itemLoc); - } - info.cancel(); - } - } - } else { - ResourceLocation stateLoc = new ResourceLocation(modId, "blockstates/" + path + ".json"); - if (!resourceManager.hasResource(stateLoc)) { - Block block = Registry.BLOCK.get(clearLoc); - if (block instanceof BlockModelProvider) { - List possibleStates = block.getStateDefinition().getPossibleStates(); - Optional possibleState = possibleStates.stream() - .filter(state -> modelId.equals(BlockModelShaper.stateToModelLocation(clearLoc, state))) - .findFirst(); - if (possibleState.isPresent()) { - UnbakedModel modelVariant = ((BlockModelProvider) block).getModelVariant(modelId, possibleState.get(), unbakedCache); - if (modelVariant != null) { - if (modelVariant instanceof MultiPart) { - possibleStates.forEach(state -> { - ResourceLocation stateId = BlockModelShaper.stateToModelLocation(clearLoc, state); - cacheAndQueueDependencies(stateId, modelVariant); - }); - } else { - cacheAndQueueDependencies(modelId, modelVariant); - } - } else { - BCLib.LOGGER.warning("Error loading variant: {}", modelId); - } - info.cancel(); - } - } - } - } - } - } -} diff --git a/src/main/java/ru/bclib/mixin/common/BoneMealItemMixin.java b/src/main/java/ru/bclib/mixin/common/BoneMealItemMixin.java deleted file mode 100644 index 30ca8ef0..00000000 --- a/src/main/java/ru/bclib/mixin/common/BoneMealItemMixin.java +++ /dev/null @@ -1,149 +0,0 @@ -package ru.bclib.mixin.common; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.BlockPos.MutableBlockPos; -import net.minecraft.core.Vec3i; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.item.BoneMealItem; -import net.minecraft.world.item.context.UseOnContext; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.biome.Biome.BiomeCategory; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.api.BiomeAPI; -import ru.bclib.api.BonemealAPI; -import ru.bclib.api.TagAPI; -import ru.bclib.util.BlocksHelper; -import ru.bclib.util.MHelper; - -@Mixin(BoneMealItem.class) -public class BoneMealItemMixin { - private static final MutableBlockPos bclib_BLOCK_POS = new MutableBlockPos(); - - @Inject(method = "useOn", at = @At("HEAD"), cancellable = true) - private void bclib_onUse(UseOnContext context, CallbackInfoReturnable info) { - Level world = context.getLevel(); - BlockPos blockPos = context.getClickedPos(); - if (!world.isClientSide) { - BlockPos offseted = blockPos.relative(context.getClickedFace()); - boolean endBiome = world.getBiome(offseted).getBiomeCategory() == BiomeCategory.THEEND; - - if (world.getBlockState(blockPos).is(TagAPI.END_GROUND)) { - boolean consume = false; - if (world.getBlockState(blockPos).is(Blocks.END_STONE)) { - BlockState nylium = bclib_getNylium(world, blockPos); - if (nylium != null) { - BlocksHelper.setWithoutUpdate(world, blockPos, nylium); - consume = true; - } - } - else { - if (!world.getFluidState(offseted).isEmpty() && endBiome) { - if (world.getBlockState(offseted).getBlock().equals(Blocks.WATER)) { - consume = bclib_growWaterGrass(world, blockPos); - } - } - else { - consume = bclib_growLandGrass(world, blockPos); - } - } - if (consume) { - if (!context.getPlayer().isCreative()) { - context.getItemInHand().shrink(1); - } - world.levelEvent(2005, blockPos, 0); - info.setReturnValue(InteractionResult.SUCCESS); - info.cancel(); - } - } - else if (!world.getFluidState(offseted).isEmpty() && endBiome) { - if (world.getBlockState(offseted).getBlock().equals(Blocks.WATER)) { - info.setReturnValue(InteractionResult.FAIL); - info.cancel(); - } - } - } - } - - private boolean bclib_growLandGrass(Level world, BlockPos pos) { - int y1 = pos.getY() + 3; - int y2 = pos.getY() - 3; - boolean result = false; - for (int i = 0; i < 64; i++) { - int x = (int) (pos.getX() + world.random.nextGaussian() * 2); - int z = (int) (pos.getZ() + world.random.nextGaussian() * 2); - bclib_BLOCK_POS.setX(x); - bclib_BLOCK_POS.setZ(z); - for (int y = y1; y >= y2; y--) { - bclib_BLOCK_POS.setY(y); - BlockPos down = bclib_BLOCK_POS.below(); - if (world.isEmptyBlock(bclib_BLOCK_POS) && !world.isEmptyBlock(down)) { - BlockState grass = bclib_getLandGrassState(world, down); - if (grass != null) { - BlocksHelper.setWithoutUpdate(world, bclib_BLOCK_POS, grass); - result = true; - } - break; - } - } - } - return result; - } - - private boolean bclib_growWaterGrass(Level world, BlockPos pos) { - int y1 = pos.getY() + 3; - int y2 = pos.getY() - 3; - boolean result = false; - for (int i = 0; i < 64; i++) { - int x = (int) (pos.getX() + world.random.nextGaussian() * 2); - int z = (int) (pos.getZ() + world.random.nextGaussian() * 2); - bclib_BLOCK_POS.setX(x); - bclib_BLOCK_POS.setZ(z); - for (int y = y1; y >= y2; y--) { - bclib_BLOCK_POS.setY(y); - BlockPos down = bclib_BLOCK_POS.below(); - if (BlocksHelper.isFluid(world.getBlockState(bclib_BLOCK_POS)) && !BlocksHelper.isFluid(world.getBlockState(down))) { - BlockState grass = bclib_getWaterGrassState(world, down); - if (grass != null) { - BlocksHelper.setWithoutUpdate(world, bclib_BLOCK_POS, grass); - result = true; - } - break; - } - } - } - return result; - } - - private BlockState bclib_getLandGrassState(Level world, BlockPos pos) { - BlockState state = world.getBlockState(pos); - Block block = state.getBlock(); - block = BonemealAPI.getLandGrass(BiomeAPI.getBiomeID(world.getBiome(pos)), block, world.getRandom()); - return block == null ? null : block.defaultBlockState(); - } - - private BlockState bclib_getWaterGrassState(Level world, BlockPos pos) { - BlockState state = world.getBlockState(pos); - Block block = state.getBlock(); - block = BonemealAPI.getLandGrass(BiomeAPI.getBiomeID(world.getBiome(pos)), block, world.getRandom()); - return block == null ? null : block.defaultBlockState(); - } - - private BlockState bclib_getNylium(Level world, BlockPos pos) { - Vec3i[] offsets = MHelper.getOffsets(world.getRandom()); - for (Vec3i dir : offsets) { - BlockPos p = pos.offset(dir); - BlockState state = world.getBlockState(p); - if (BonemealAPI.isSpreadable(state.getBlock())) { - return state; - } - } - return null; - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/mixin/common/EnchantmentMenuMixin.java b/src/main/java/ru/bclib/mixin/common/EnchantmentMenuMixin.java deleted file mode 100644 index d0af1dd8..00000000 --- a/src/main/java/ru/bclib/mixin/common/EnchantmentMenuMixin.java +++ /dev/null @@ -1,140 +0,0 @@ -package ru.bclib.mixin.common; - -import java.util.List; -import java.util.Random; - -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import net.minecraft.core.Registry; -import net.minecraft.world.Container; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.ContainerLevelAccess; -import net.minecraft.world.inventory.DataSlot; -import net.minecraft.world.inventory.EnchantmentMenu; -import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.EnchantmentInstance; -import ru.bclib.api.TagAPI; - -@Mixin(EnchantmentMenu.class) -public abstract class EnchantmentMenuMixin extends AbstractContainerMenu { - @Final - @Shadow - private Container enchantSlots; - - @Final - @Shadow - private ContainerLevelAccess access; - - @Final - @Shadow - private Random random; - - @Final - @Shadow - private DataSlot enchantmentSeed; - - @Shadow - @Final - public int[] costs; - - @Shadow - @Final - public int[] enchantClue; - - @Shadow - @Final - public int[] levelClue; - - protected EnchantmentMenuMixin(MenuType type, int syncId) { - super(type, syncId); - } - - @Inject(method = "slotsChanged", at = @At("HEAD"), cancellable = true) - private void be_slotsChanged(Container inventory, CallbackInfo info) { - if (inventory == this.enchantSlots) { - ItemStack itemStack = inventory.getItem(0); - if (!itemStack.isEmpty() && itemStack.isEnchantable()) { - this.access.execute((world, blockPos) -> { - int i = 0; - - int j; - for (j = -1; j <= 1; ++j) { - for (int k = -1; k <= 1; ++k) { - if ((j != 0 || k != 0) && world.isEmptyBlock(blockPos.offset(k, 0, j)) && world.isEmptyBlock(blockPos.offset(k, 1, j))) { - if (world.getBlockState(blockPos.offset(k * 2, 0, j * 2)).is(TagAPI.BOOKSHELVES)) { - ++i; - } - - if (world.getBlockState(blockPos.offset(k * 2, 1, j * 2)).is(TagAPI.BOOKSHELVES)) { - ++i; - } - - if (k != 0 && j != 0) { - if (world.getBlockState(blockPos.offset(k * 2, 0, j)).is(TagAPI.BOOKSHELVES)) { - ++i; - } - - if (world.getBlockState(blockPos.offset(k * 2, 1, j)).is(TagAPI.BOOKSHELVES)) { - ++i; - } - - if (world.getBlockState(blockPos.offset(k, 0, j * 2)).is(TagAPI.BOOKSHELVES)) { - ++i; - } - - if (world.getBlockState(blockPos.offset(k, 1, j * 2)).is(TagAPI.BOOKSHELVES)) { - ++i; - } - } - } - } - } - - random.setSeed(enchantmentSeed.get()); - - for (j = 0; j < 3; ++j) { - costs[j] = EnchantmentHelper.getEnchantmentCost(this.random, j, i, itemStack); - enchantClue[j] = -1; - levelClue[j] = -1; - if (costs[j] < j + 1) { - costs[j] = 0; - } - } - - for (j = 0; j < 3; ++j) { - if (this.costs[j] > 0) { - List list = this.getEnchantmentList(itemStack, j, this.costs[j]); - if (list != null && !list.isEmpty()) { - EnchantmentInstance enchantmentLevelEntry = (EnchantmentInstance) list.get(this.random.nextInt(list.size())); - enchantClue[j] = Registry.ENCHANTMENT.getId(enchantmentLevelEntry.enchantment); - levelClue[j] = enchantmentLevelEntry.level; - } - } - } - - broadcastChanges(); - }); - } - else { - for (int i = 0; i < 3; ++i) { - costs[i] = 0; - enchantClue[i] = -1; - levelClue[i] = -1; - } - } - info.cancel(); - } - } - - @Shadow - private List getEnchantmentList(ItemStack stack, int slot, int level) { - return null; - } -} diff --git a/src/main/java/ru/bclib/mixin/common/MinecraftServerMixin.java b/src/main/java/ru/bclib/mixin/common/MinecraftServerMixin.java deleted file mode 100644 index 1cf79d29..00000000 --- a/src/main/java/ru/bclib/mixin/common/MinecraftServerMixin.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.bclib.mixin.common; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.ServerResources; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.storage.WorldData; -import ru.bclib.api.BiomeAPI; -import ru.bclib.recipes.BCLRecipeManager; - -@Mixin(MinecraftServer.class) -public class MinecraftServerMixin { - @Shadow - private ServerResources resources; - - @Final - @Shadow - private Map, ServerLevel> levels; - - @Final - @Shadow - protected WorldData worldData; - - @Inject(method = "reloadResources", at = @At(value = "RETURN"), cancellable = true) - private void bcl_reloadResources(Collection collection, CallbackInfoReturnable> info) { - bcl_injectRecipes(); - } - - @Inject(method = "loadLevel", at = @At(value = "RETURN"), cancellable = true) - private void bcl_loadLevel(CallbackInfo info) { - bcl_injectRecipes(); - BiomeAPI.initRegistry(MinecraftServer.class.cast(this)); - } - - private void bcl_injectRecipes() { - if (FabricLoader.getInstance().isModLoaded("kubejs")) { - RecipeManagerAccessor accessor = (RecipeManagerAccessor) resources.getRecipeManager(); - accessor.bcl_setRecipes(BCLRecipeManager.getMap(accessor.bcl_getRecipes())); - } - } -} diff --git a/src/main/java/ru/bclib/mixin/common/RecipeManagerAccessor.java b/src/main/java/ru/bclib/mixin/common/RecipeManagerAccessor.java deleted file mode 100644 index e3dece33..00000000 --- a/src/main/java/ru/bclib/mixin/common/RecipeManagerAccessor.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.bclib.mixin.common; - -import java.util.Map; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.world.item.crafting.RecipeManager; -import net.minecraft.world.item.crafting.RecipeType; - -@Mixin(RecipeManager.class) -public interface RecipeManagerAccessor { - @Accessor("recipes") - Map, Map>> bcl_getRecipes(); - - @Accessor("recipes") - void bcl_setRecipes(Map, Map>> recipes); -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/mixin/common/RecipeManagerMixin.java b/src/main/java/ru/bclib/mixin/common/RecipeManagerMixin.java deleted file mode 100644 index 5c2092f6..00000000 --- a/src/main/java/ru/bclib/mixin/common/RecipeManagerMixin.java +++ /dev/null @@ -1,64 +0,0 @@ -package ru.bclib.mixin.common; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import com.google.gson.JsonElement; - -import net.minecraft.Util; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraft.util.profiling.ProfilerFiller; -import net.minecraft.world.Container; -import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.world.item.crafting.RecipeManager; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraft.world.level.Level; -import ru.bclib.recipes.BCLRecipeManager; - -@Mixin(RecipeManager.class) -public abstract class RecipeManagerMixin { - @Shadow - private Map, Map>> recipes; - - @Inject(method = "apply", at = @At(value = "RETURN")) - private void be_apply(Map map, ResourceManager resourceManager, ProfilerFiller profiler, CallbackInfo info) { - recipes = BCLRecipeManager.getMap(recipes); - } - - @Shadow - private > Map> byType(RecipeType type) { - return null; - } - - /** - * @author paulevs - * @reason Remove conflicts with vanilla tags - * Change recipe order to show mod recipes first, helps when block have vanilla tag - * (example - mod stone with vanilla tags and furnace from that stone) - */ - @Overwrite - public > Optional getRecipeFor(RecipeType type, C inventory, Level world) { - Collection> values = byType(type).values(); - List> list = new ArrayList>(values); - list.sort((v1, v2) -> { - boolean b1 = v1.getId().getNamespace().equals("minecraft"); - boolean b2 = v2.getId().getNamespace().equals("minecraft"); - return b1 ^ b2 ? (b1 ? 1 : -1) : 0; - }); - - return list.stream().flatMap((recipe) -> { - return Util.toStream(type.tryMatch(recipe, world, inventory)); - }).findFirst(); - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java b/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java deleted file mode 100644 index ef881e4d..00000000 --- a/src/main/java/ru/bclib/mixin/common/ServerLevelMixin.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.bclib.mixin.common; - -import java.io.File; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.function.Supplier; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -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.util.profiling.ProfilerFiller; -import net.minecraft.world.level.CustomSpawner; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.chunk.ChunkGenerator; -import net.minecraft.world.level.dimension.DimensionType; -import net.minecraft.world.level.storage.LevelStorageSource; -import net.minecraft.world.level.storage.ServerLevelData; -import net.minecraft.world.level.storage.WritableLevelData; -import ru.bclib.api.BiomeAPI; -import ru.bclib.api.DataFixerAPI; -import ru.bclib.api.WorldDataAPI; - -@Mixin(ServerLevel.class) -public abstract class ServerLevelMixin extends Level { - private static String bcl_lastWorld = null; - - protected ServerLevelMixin(WritableLevelData writableLevelData, ResourceKey resourceKey, DimensionType dimensionType, Supplier supplier, boolean bl, boolean bl2, long l) { - super(writableLevelData, resourceKey, dimensionType, supplier, bl, bl2, l); - } - - @Inject(method = "*", at = @At("TAIL")) - private void bcl_onServerWorldInit(MinecraftServer server, Executor workerExecutor, LevelStorageSource.LevelStorageAccess session, ServerLevelData properties, ResourceKey registryKey, DimensionType dimensionType, ChunkProgressListener worldGenerationProgressListener, ChunkGenerator chunkGenerator, boolean debugWorld, long l, List list, boolean bl, CallbackInfo info) { - BiomeAPI.initRegistry(server); - - if (bcl_lastWorld != null && bcl_lastWorld.equals(session.getLevelId())) { - return; - } - - bcl_lastWorld = session.getLevelId(); - - ServerLevel world = ServerLevel.class.cast(this); - File dir = session.getDimensionPath(world.dimension()); - if (!new File(dir, "level.dat").exists()) { - dir = dir.getParentFile(); - } - - DataFixerAPI.fixData(dir); - WorldDataAPI.load(new File(dir, "data")); - } -} diff --git a/src/main/java/ru/bclib/mixin/common/TagLoaderMixin.java b/src/main/java/ru/bclib/mixin/common/TagLoaderMixin.java deleted file mode 100644 index 743a59f5..00000000 --- a/src/main/java/ru/bclib/mixin/common/TagLoaderMixin.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.bclib.mixin.common; - -import java.util.Map; -import java.util.concurrent.Executor; -import java.util.function.Supplier; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyArg; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.Tag; -import net.minecraft.tags.TagLoader; -import ru.bclib.util.TagHelper; - -@Mixin(TagLoader.class) -public class TagLoaderMixin { - @Shadow - private String name; - - @ModifyArg(method = "prepare", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;")) - public Supplier> be_modifyTags(Supplier> supplier, Executor executor) { - return () -> TagHelper.apply(name, supplier.get()); - } -} diff --git a/src/main/java/ru/bclib/noise/OpenSimplexNoise.java b/src/main/java/ru/bclib/noise/OpenSimplexNoise.java deleted file mode 100644 index d002334f..00000000 --- a/src/main/java/ru/bclib/noise/OpenSimplexNoise.java +++ /dev/null @@ -1,2185 +0,0 @@ -package ru.bclib.noise; - -/* - * OpenSimplex Noise in Java. - * by Kurt Spencer - * - * v1.1 (October 5, 2014) - * - Added 2D and 4D implementations. - * - Proper gradient sets for all dimensions, from a - * dimensionally-generalizable scheme with an actual - * rhyme and reason behind it. - * - Removed default permutation array in favor of - * default seed. - * - Changed seed-based constructor to be independent - * of any particular randomization library, so results - * will be the same when ported to other languages. - */ - -public class OpenSimplexNoise { - private static final double STRETCH_CONSTANT_2D = -0.211324865405187; // (1/Math.sqrt(2+1)-1)/2; - private static final double SQUISH_CONSTANT_2D = 0.366025403784439; // (Math.sqrt(2+1)-1)/2; - private static final double STRETCH_CONSTANT_3D = -1.0 / 6; // (1/Math.sqrt(3+1)-1)/3; - private static final double SQUISH_CONSTANT_3D = 1.0 / 3; // (Math.sqrt(3+1)-1)/3; - private static final double STRETCH_CONSTANT_4D = -0.138196601125011; // (1/Math.sqrt(4+1)-1)/4; - private static final double SQUISH_CONSTANT_4D = 0.309016994374947; // (Math.sqrt(4+1)-1)/4; - - private static final double NORM_CONSTANT_2D = 47; - private static final double NORM_CONSTANT_3D = 103; - private static final double NORM_CONSTANT_4D = 30; - - private static final long DEFAULT_SEED = 0; - - private short[] perm; - private short[] permGradIndex3D; - - public OpenSimplexNoise() { - this(DEFAULT_SEED); - } - - public OpenSimplexNoise(short[] perm) { - this.perm = perm; - permGradIndex3D = new short[256]; - - for (int i = 0; i < 256; i++) { - // Since 3D has 24 gradients, simple bitmask won't work, so - // precompute modulo array. - permGradIndex3D[i] = (short) ((perm[i] % (gradients3D.length / 3)) * 3); - } - } - - // Initializes the class using a permutation array generated from a 64-bit - // seed. - // Generates a proper permutation (i.e. doesn't merely perform N successive - // pair swaps on a base array) - // Uses a simple 64-bit LCG. - public OpenSimplexNoise(long seed) { - perm = new short[256]; - permGradIndex3D = new short[256]; - short[] source = new short[256]; - for (short i = 0; i < 256; i++) - source[i] = i; - seed = seed * 6364136223846793005l + 1442695040888963407l; - seed = seed * 6364136223846793005l + 1442695040888963407l; - seed = seed * 6364136223846793005l + 1442695040888963407l; - for (int i = 255; i >= 0; i--) { - seed = seed * 6364136223846793005l + 1442695040888963407l; - int r = (int) ((seed + 31) % (i + 1)); - if (r < 0) - r += (i + 1); - perm[i] = source[r]; - permGradIndex3D[i] = (short) ((perm[i] % (gradients3D.length / 3)) * 3); - source[r] = source[i]; - } - } - - // 2D OpenSimplex Noise. - public double eval(double x, double y) { - - // Place input coordinates onto grid. - double stretchOffset = (x + y) * STRETCH_CONSTANT_2D; - double xs = x + stretchOffset; - double ys = y + stretchOffset; - - // Floor to get grid coordinates of rhombus (stretched square) - // super-cell origin. - int xsb = fastFloor(xs); - int ysb = fastFloor(ys); - - // Skew out to get actual coordinates of rhombus origin. We'll need - // these later. - double squishOffset = (xsb + ysb) * SQUISH_CONSTANT_2D; - double xb = xsb + squishOffset; - double yb = ysb + squishOffset; - - // Compute grid coordinates relative to rhombus origin. - double xins = xs - xsb; - double yins = ys - ysb; - - // Sum those together to get a value that determines which region we're - // in. - double inSum = xins + yins; - - // Positions relative to origin point. - double dx0 = x - xb; - double dy0 = y - yb; - - // We'll be defining these inside the next block and using them - // afterwards. - double dx_ext, dy_ext; - int xsv_ext, ysv_ext; - - double value = 0; - - // Contribution (1,0) - double dx1 = dx0 - 1 - SQUISH_CONSTANT_2D; - double dy1 = dy0 - 0 - SQUISH_CONSTANT_2D; - double attn1 = 2 - dx1 * dx1 - dy1 * dy1; - if (attn1 > 0) { - attn1 *= attn1; - value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, dx1, dy1); - } - - // Contribution (0,1) - double dx2 = dx0 - 0 - SQUISH_CONSTANT_2D; - double dy2 = dy0 - 1 - SQUISH_CONSTANT_2D; - double attn2 = 2 - dx2 * dx2 - dy2 * dy2; - if (attn2 > 0) { - attn2 *= attn2; - value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, dx2, dy2); - } - - if (inSum <= 1) { // We're inside the triangle (2-Simplex) at (0,0) - double zins = 1 - inSum; - if (zins > xins || zins > yins) { // (0,0) is one of the closest two - // triangular vertices - if (xins > yins) { - xsv_ext = xsb + 1; - ysv_ext = ysb - 1; - dx_ext = dx0 - 1; - dy_ext = dy0 + 1; - } else { - xsv_ext = xsb - 1; - ysv_ext = ysb + 1; - dx_ext = dx0 + 1; - dy_ext = dy0 - 1; - } - } else { // (1,0) and (0,1) are the closest two vertices. - xsv_ext = xsb + 1; - ysv_ext = ysb + 1; - dx_ext = dx0 - 1 - 2 * SQUISH_CONSTANT_2D; - dy_ext = dy0 - 1 - 2 * SQUISH_CONSTANT_2D; - } - } else { // We're inside the triangle (2-Simplex) at (1,1) - double zins = 2 - inSum; - if (zins < xins || zins < yins) { // (0,0) is one of the closest two - // triangular vertices - if (xins > yins) { - xsv_ext = xsb + 2; - ysv_ext = ysb + 0; - dx_ext = dx0 - 2 - 2 * SQUISH_CONSTANT_2D; - dy_ext = dy0 + 0 - 2 * SQUISH_CONSTANT_2D; - } else { - xsv_ext = xsb + 0; - ysv_ext = ysb + 2; - dx_ext = dx0 + 0 - 2 * SQUISH_CONSTANT_2D; - dy_ext = dy0 - 2 - 2 * SQUISH_CONSTANT_2D; - } - } else { // (1,0) and (0,1) are the closest two vertices. - dx_ext = dx0; - dy_ext = dy0; - xsv_ext = xsb; - ysv_ext = ysb; - } - xsb += 1; - ysb += 1; - dx0 = dx0 - 1 - 2 * SQUISH_CONSTANT_2D; - dy0 = dy0 - 1 - 2 * SQUISH_CONSTANT_2D; - } - - // Contribution (0,0) or (1,1) - double attn0 = 2 - dx0 * dx0 - dy0 * dy0; - if (attn0 > 0) { - attn0 *= attn0; - value += attn0 * attn0 * extrapolate(xsb, ysb, dx0, dy0); - } - - // Extra Vertex - double attn_ext = 2 - dx_ext * dx_ext - dy_ext * dy_ext; - if (attn_ext > 0) { - attn_ext *= attn_ext; - value += attn_ext * attn_ext * extrapolate(xsv_ext, ysv_ext, dx_ext, dy_ext); - } - - return value / NORM_CONSTANT_2D; - } - - // 3D OpenSimplex Noise. - public double eval(double x, double y, double z) { - - // Place input coordinates on simplectic honeycomb. - double stretchOffset = (x + y + z) * STRETCH_CONSTANT_3D; - double xs = x + stretchOffset; - double ys = y + stretchOffset; - double zs = z + stretchOffset; - - // Floor to get simplectic honeycomb coordinates of rhombohedron - // (stretched cube) super-cell origin. - int xsb = fastFloor(xs); - int ysb = fastFloor(ys); - int zsb = fastFloor(zs); - - // Skew out to get actual coordinates of rhombohedron origin. We'll need - // these later. - double squishOffset = (xsb + ysb + zsb) * SQUISH_CONSTANT_3D; - double xb = xsb + squishOffset; - double yb = ysb + squishOffset; - double zb = zsb + squishOffset; - - // Compute simplectic honeycomb coordinates relative to rhombohedral - // origin. - double xins = xs - xsb; - double yins = ys - ysb; - double zins = zs - zsb; - - // Sum those together to get a value that determines which region we're - // in. - double inSum = xins + yins + zins; - - // Positions relative to origin point. - double dx0 = x - xb; - double dy0 = y - yb; - double dz0 = z - zb; - - // We'll be defining these inside the next block and using them - // afterwards. - double dx_ext0, dy_ext0, dz_ext0; - double dx_ext1, dy_ext1, dz_ext1; - int xsv_ext0, ysv_ext0, zsv_ext0; - int xsv_ext1, ysv_ext1, zsv_ext1; - - double value = 0; - if (inSum <= 1) { // We're inside the tetrahedron (3-Simplex) at (0,0,0) - - // Determine which two of (0,0,1), (0,1,0), (1,0,0) are closest. - byte aPoint = 0x01; - double aScore = xins; - byte bPoint = 0x02; - double bScore = yins; - if (aScore >= bScore && zins > bScore) { - bScore = zins; - bPoint = 0x04; - } else if (aScore < bScore && zins > aScore) { - aScore = zins; - aPoint = 0x04; - } - - // Now we determine the two lattice points not part of the - // tetrahedron that may contribute. - // This depends on the closest two tetrahedral vertices, including - // (0,0,0) - double wins = 1 - inSum; - if (wins > aScore || wins > bScore) { // (0,0,0) is one of the - // closest two tetrahedral - // vertices. - byte c = (bScore > aScore ? bPoint : aPoint); // Our other - // closest - // vertex is the - // closest out - // of a and b. - - if ((c & 0x01) == 0) { - xsv_ext0 = xsb - 1; - xsv_ext1 = xsb; - dx_ext0 = dx0 + 1; - dx_ext1 = dx0; - } else { - xsv_ext0 = xsv_ext1 = xsb + 1; - dx_ext0 = dx_ext1 = dx0 - 1; - } - - if ((c & 0x02) == 0) { - ysv_ext0 = ysv_ext1 = ysb; - dy_ext0 = dy_ext1 = dy0; - if ((c & 0x01) == 0) { - ysv_ext1 -= 1; - dy_ext1 += 1; - } else { - ysv_ext0 -= 1; - dy_ext0 += 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysb + 1; - dy_ext0 = dy_ext1 = dy0 - 1; - } - - if ((c & 0x04) == 0) { - zsv_ext0 = zsb; - zsv_ext1 = zsb - 1; - dz_ext0 = dz0; - dz_ext1 = dz0 + 1; - } else { - zsv_ext0 = zsv_ext1 = zsb + 1; - dz_ext0 = dz_ext1 = dz0 - 1; - } - } else { // (0,0,0) is not one of the closest two tetrahedral - // vertices. - byte c = (byte) (aPoint | bPoint); // Our two extra vertices are - // determined by the closest - // two. - - if ((c & 0x01) == 0) { - xsv_ext0 = xsb; - xsv_ext1 = xsb - 1; - dx_ext0 = dx0 - 2 * SQUISH_CONSTANT_3D; - dx_ext1 = dx0 + 1 - SQUISH_CONSTANT_3D; - } else { - xsv_ext0 = xsv_ext1 = xsb + 1; - dx_ext0 = dx0 - 1 - 2 * SQUISH_CONSTANT_3D; - dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_3D; - } - - if ((c & 0x02) == 0) { - ysv_ext0 = ysb; - ysv_ext1 = ysb - 1; - dy_ext0 = dy0 - 2 * SQUISH_CONSTANT_3D; - dy_ext1 = dy0 + 1 - SQUISH_CONSTANT_3D; - } else { - ysv_ext0 = ysv_ext1 = ysb + 1; - dy_ext0 = dy0 - 1 - 2 * SQUISH_CONSTANT_3D; - dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_3D; - } - - if ((c & 0x04) == 0) { - zsv_ext0 = zsb; - zsv_ext1 = zsb - 1; - dz_ext0 = dz0 - 2 * SQUISH_CONSTANT_3D; - dz_ext1 = dz0 + 1 - SQUISH_CONSTANT_3D; - } else { - zsv_ext0 = zsv_ext1 = zsb + 1; - dz_ext0 = dz0 - 1 - 2 * SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_3D; - } - } - - // Contribution (0,0,0) - double attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0; - if (attn0 > 0) { - attn0 *= attn0; - value += attn0 * attn0 * extrapolate(xsb + 0, ysb + 0, zsb + 0, dx0, dy0, dz0); - } - - // Contribution (1,0,0) - double dx1 = dx0 - 1 - SQUISH_CONSTANT_3D; - double dy1 = dy0 - 0 - SQUISH_CONSTANT_3D; - double dz1 = dz0 - 0 - SQUISH_CONSTANT_3D; - double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1; - if (attn1 > 0) { - attn1 *= attn1; - value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, dx1, dy1, dz1); - } - - // Contribution (0,1,0) - double dx2 = dx0 - 0 - SQUISH_CONSTANT_3D; - double dy2 = dy0 - 1 - SQUISH_CONSTANT_3D; - double dz2 = dz1; - double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2; - if (attn2 > 0) { - attn2 *= attn2; - value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, dx2, dy2, dz2); - } - - // Contribution (0,0,1) - double dx3 = dx2; - double dy3 = dy1; - double dz3 = dz0 - 1 - SQUISH_CONSTANT_3D; - double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3; - if (attn3 > 0) { - attn3 *= attn3; - value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, dx3, dy3, dz3); - } - } else if (inSum >= 2) { // We're inside the tetrahedron (3-Simplex) at - // (1,1,1) - - // Determine which two tetrahedral vertices are the closest, out of - // (1,1,0), (1,0,1), (0,1,1) but not (1,1,1). - byte aPoint = 0x06; - double aScore = xins; - byte bPoint = 0x05; - double bScore = yins; - if (aScore <= bScore && zins < bScore) { - bScore = zins; - bPoint = 0x03; - } else if (aScore > bScore && zins < aScore) { - aScore = zins; - aPoint = 0x03; - } - - // Now we determine the two lattice points not part of the - // tetrahedron that may contribute. - // This depends on the closest two tetrahedral vertices, including - // (1,1,1) - double wins = 3 - inSum; - if (wins < aScore || wins < bScore) { // (1,1,1) is one of the - // closest two tetrahedral - // vertices. - byte c = (bScore < aScore ? bPoint : aPoint); // Our other - // closest - // vertex is the - // closest out - // of a and b. - - if ((c & 0x01) != 0) { - xsv_ext0 = xsb + 2; - xsv_ext1 = xsb + 1; - dx_ext0 = dx0 - 2 - 3 * SQUISH_CONSTANT_3D; - dx_ext1 = dx0 - 1 - 3 * SQUISH_CONSTANT_3D; - } else { - xsv_ext0 = xsv_ext1 = xsb; - dx_ext0 = dx_ext1 = dx0 - 3 * SQUISH_CONSTANT_3D; - } - - if ((c & 0x02) != 0) { - ysv_ext0 = ysv_ext1 = ysb + 1; - dy_ext0 = dy_ext1 = dy0 - 1 - 3 * SQUISH_CONSTANT_3D; - if ((c & 0x01) != 0) { - ysv_ext1 += 1; - dy_ext1 -= 1; - } else { - ysv_ext0 += 1; - dy_ext0 -= 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysb; - dy_ext0 = dy_ext1 = dy0 - 3 * SQUISH_CONSTANT_3D; - } - - if ((c & 0x04) != 0) { - zsv_ext0 = zsb + 1; - zsv_ext1 = zsb + 2; - dz_ext0 = dz0 - 1 - 3 * SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 2 - 3 * SQUISH_CONSTANT_3D; - } else { - zsv_ext0 = zsv_ext1 = zsb; - dz_ext0 = dz_ext1 = dz0 - 3 * SQUISH_CONSTANT_3D; - } - } else { // (1,1,1) is not one of the closest two tetrahedral - // vertices. - byte c = (byte) (aPoint & bPoint); // Our two extra vertices are - // determined by the closest - // two. - - if ((c & 0x01) != 0) { - xsv_ext0 = xsb + 1; - xsv_ext1 = xsb + 2; - dx_ext0 = dx0 - 1 - SQUISH_CONSTANT_3D; - dx_ext1 = dx0 - 2 - 2 * SQUISH_CONSTANT_3D; - } else { - xsv_ext0 = xsv_ext1 = xsb; - dx_ext0 = dx0 - SQUISH_CONSTANT_3D; - dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D; - } - - if ((c & 0x02) != 0) { - ysv_ext0 = ysb + 1; - ysv_ext1 = ysb + 2; - dy_ext0 = dy0 - 1 - SQUISH_CONSTANT_3D; - dy_ext1 = dy0 - 2 - 2 * SQUISH_CONSTANT_3D; - } else { - ysv_ext0 = ysv_ext1 = ysb; - dy_ext0 = dy0 - SQUISH_CONSTANT_3D; - dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D; - } - - if ((c & 0x04) != 0) { - zsv_ext0 = zsb + 1; - zsv_ext1 = zsb + 2; - dz_ext0 = dz0 - 1 - SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 2 - 2 * SQUISH_CONSTANT_3D; - } else { - zsv_ext0 = zsv_ext1 = zsb; - dz_ext0 = dz0 - SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D; - } - } - - // Contribution (1,1,0) - double dx3 = dx0 - 1 - 2 * SQUISH_CONSTANT_3D; - double dy3 = dy0 - 1 - 2 * SQUISH_CONSTANT_3D; - double dz3 = dz0 - 0 - 2 * SQUISH_CONSTANT_3D; - double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3; - if (attn3 > 0) { - attn3 *= attn3; - value += attn3 * attn3 * extrapolate(xsb + 1, ysb + 1, zsb + 0, dx3, dy3, dz3); - } - - // Contribution (1,0,1) - double dx2 = dx3; - double dy2 = dy0 - 0 - 2 * SQUISH_CONSTANT_3D; - double dz2 = dz0 - 1 - 2 * SQUISH_CONSTANT_3D; - double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2; - if (attn2 > 0) { - attn2 *= attn2; - value += attn2 * attn2 * extrapolate(xsb + 1, ysb + 0, zsb + 1, dx2, dy2, dz2); - } - - // Contribution (0,1,1) - double dx1 = dx0 - 0 - 2 * SQUISH_CONSTANT_3D; - double dy1 = dy3; - double dz1 = dz2; - double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1; - if (attn1 > 0) { - attn1 *= attn1; - value += attn1 * attn1 * extrapolate(xsb + 0, ysb + 1, zsb + 1, dx1, dy1, dz1); - } - - // Contribution (1,1,1) - dx0 = dx0 - 1 - 3 * SQUISH_CONSTANT_3D; - dy0 = dy0 - 1 - 3 * SQUISH_CONSTANT_3D; - dz0 = dz0 - 1 - 3 * SQUISH_CONSTANT_3D; - double attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0; - if (attn0 > 0) { - attn0 *= attn0; - value += attn0 * attn0 * extrapolate(xsb + 1, ysb + 1, zsb + 1, dx0, dy0, dz0); - } - } else { // We're inside the octahedron (Rectified 3-Simplex) in - // between. - double aScore; - byte aPoint; - boolean aIsFurtherSide; - double bScore; - byte bPoint; - boolean bIsFurtherSide; - - // Decide between point (0,0,1) and (1,1,0) as closest - double p1 = xins + yins; - if (p1 > 1) { - aScore = p1 - 1; - aPoint = 0x03; - aIsFurtherSide = true; - } else { - aScore = 1 - p1; - aPoint = 0x04; - aIsFurtherSide = false; - } - - // Decide between point (0,1,0) and (1,0,1) as closest - double p2 = xins + zins; - if (p2 > 1) { - bScore = p2 - 1; - bPoint = 0x05; - bIsFurtherSide = true; - } else { - bScore = 1 - p2; - bPoint = 0x02; - bIsFurtherSide = false; - } - - // The closest out of the two (1,0,0) and (0,1,1) will replace the - // furthest out of the two decided above, if closer. - double p3 = yins + zins; - if (p3 > 1) { - double score = p3 - 1; - if (aScore <= bScore && aScore < score) { - aScore = score; - aPoint = 0x06; - aIsFurtherSide = true; - } else if (aScore > bScore && bScore < score) { - bScore = score; - bPoint = 0x06; - bIsFurtherSide = true; - } - } else { - double score = 1 - p3; - if (aScore <= bScore && aScore < score) { - aScore = score; - aPoint = 0x01; - aIsFurtherSide = false; - } else if (aScore > bScore && bScore < score) { - bScore = score; - bPoint = 0x01; - bIsFurtherSide = false; - } - } - - // Where each of the two closest points are determines how the extra - // two vertices are calculated. - if (aIsFurtherSide == bIsFurtherSide) { - if (aIsFurtherSide) { // Both closest points on (1,1,1) side - - // One of the two extra points is (1,1,1) - dx_ext0 = dx0 - 1 - 3 * SQUISH_CONSTANT_3D; - dy_ext0 = dy0 - 1 - 3 * SQUISH_CONSTANT_3D; - dz_ext0 = dz0 - 1 - 3 * SQUISH_CONSTANT_3D; - xsv_ext0 = xsb + 1; - ysv_ext0 = ysb + 1; - zsv_ext0 = zsb + 1; - - // Other extra point is based on the shared axis. - byte c = (byte) (aPoint & bPoint); - if ((c & 0x01) != 0) { - dx_ext1 = dx0 - 2 - 2 * SQUISH_CONSTANT_3D; - dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D; - xsv_ext1 = xsb + 2; - ysv_ext1 = ysb; - zsv_ext1 = zsb; - } else if ((c & 0x02) != 0) { - dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D; - dy_ext1 = dy0 - 2 - 2 * SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D; - xsv_ext1 = xsb; - ysv_ext1 = ysb + 2; - zsv_ext1 = zsb; - } else { - dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D; - dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 2 - 2 * SQUISH_CONSTANT_3D; - xsv_ext1 = xsb; - ysv_ext1 = ysb; - zsv_ext1 = zsb + 2; - } - } else {// Both closest points on (0,0,0) side - - // One of the two extra points is (0,0,0) - dx_ext0 = dx0; - dy_ext0 = dy0; - dz_ext0 = dz0; - xsv_ext0 = xsb; - ysv_ext0 = ysb; - zsv_ext0 = zsb; - - // Other extra point is based on the omitted axis. - byte c = (byte) (aPoint | bPoint); - if ((c & 0x01) == 0) { - dx_ext1 = dx0 + 1 - SQUISH_CONSTANT_3D; - dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_3D; - xsv_ext1 = xsb - 1; - ysv_ext1 = ysb + 1; - zsv_ext1 = zsb + 1; - } else if ((c & 0x02) == 0) { - dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_3D; - dy_ext1 = dy0 + 1 - SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_3D; - xsv_ext1 = xsb + 1; - ysv_ext1 = ysb - 1; - zsv_ext1 = zsb + 1; - } else { - dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_3D; - dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_3D; - dz_ext1 = dz0 + 1 - SQUISH_CONSTANT_3D; - xsv_ext1 = xsb + 1; - ysv_ext1 = ysb + 1; - zsv_ext1 = zsb - 1; - } - } - } else { // One point on (0,0,0) side, one point on (1,1,1) side - byte c1, c2; - if (aIsFurtherSide) { - c1 = aPoint; - c2 = bPoint; - } else { - c1 = bPoint; - c2 = aPoint; - } - - // One contribution is a permutation of (1,1,-1) - if ((c1 & 0x01) == 0) { - dx_ext0 = dx0 + 1 - SQUISH_CONSTANT_3D; - dy_ext0 = dy0 - 1 - SQUISH_CONSTANT_3D; - dz_ext0 = dz0 - 1 - SQUISH_CONSTANT_3D; - xsv_ext0 = xsb - 1; - ysv_ext0 = ysb + 1; - zsv_ext0 = zsb + 1; - } else if ((c1 & 0x02) == 0) { - dx_ext0 = dx0 - 1 - SQUISH_CONSTANT_3D; - dy_ext0 = dy0 + 1 - SQUISH_CONSTANT_3D; - dz_ext0 = dz0 - 1 - SQUISH_CONSTANT_3D; - xsv_ext0 = xsb + 1; - ysv_ext0 = ysb - 1; - zsv_ext0 = zsb + 1; - } else { - dx_ext0 = dx0 - 1 - SQUISH_CONSTANT_3D; - dy_ext0 = dy0 - 1 - SQUISH_CONSTANT_3D; - dz_ext0 = dz0 + 1 - SQUISH_CONSTANT_3D; - xsv_ext0 = xsb + 1; - ysv_ext0 = ysb + 1; - zsv_ext0 = zsb - 1; - } - - // One contribution is a permutation of (0,0,2) - dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D; - dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D; - dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D; - xsv_ext1 = xsb; - ysv_ext1 = ysb; - zsv_ext1 = zsb; - if ((c2 & 0x01) != 0) { - dx_ext1 -= 2; - xsv_ext1 += 2; - } else if ((c2 & 0x02) != 0) { - dy_ext1 -= 2; - ysv_ext1 += 2; - } else { - dz_ext1 -= 2; - zsv_ext1 += 2; - } - } - - // Contribution (1,0,0) - double dx1 = dx0 - 1 - SQUISH_CONSTANT_3D; - double dy1 = dy0 - 0 - SQUISH_CONSTANT_3D; - double dz1 = dz0 - 0 - SQUISH_CONSTANT_3D; - double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1; - if (attn1 > 0) { - attn1 *= attn1; - value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, dx1, dy1, dz1); - } - - // Contribution (0,1,0) - double dx2 = dx0 - 0 - SQUISH_CONSTANT_3D; - double dy2 = dy0 - 1 - SQUISH_CONSTANT_3D; - double dz2 = dz1; - double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2; - if (attn2 > 0) { - attn2 *= attn2; - value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, dx2, dy2, dz2); - } - - // Contribution (0,0,1) - double dx3 = dx2; - double dy3 = dy1; - double dz3 = dz0 - 1 - SQUISH_CONSTANT_3D; - double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3; - if (attn3 > 0) { - attn3 *= attn3; - value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, dx3, dy3, dz3); - } - - // Contribution (1,1,0) - double dx4 = dx0 - 1 - 2 * SQUISH_CONSTANT_3D; - double dy4 = dy0 - 1 - 2 * SQUISH_CONSTANT_3D; - double dz4 = dz0 - 0 - 2 * SQUISH_CONSTANT_3D; - double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4; - if (attn4 > 0) { - attn4 *= attn4; - value += attn4 * attn4 * extrapolate(xsb + 1, ysb + 1, zsb + 0, dx4, dy4, dz4); - } - - // Contribution (1,0,1) - double dx5 = dx4; - double dy5 = dy0 - 0 - 2 * SQUISH_CONSTANT_3D; - double dz5 = dz0 - 1 - 2 * SQUISH_CONSTANT_3D; - double attn5 = 2 - dx5 * dx5 - dy5 * dy5 - dz5 * dz5; - if (attn5 > 0) { - attn5 *= attn5; - value += attn5 * attn5 * extrapolate(xsb + 1, ysb + 0, zsb + 1, dx5, dy5, dz5); - } - - // Contribution (0,1,1) - double dx6 = dx0 - 0 - 2 * SQUISH_CONSTANT_3D; - double dy6 = dy4; - double dz6 = dz5; - double attn6 = 2 - dx6 * dx6 - dy6 * dy6 - dz6 * dz6; - if (attn6 > 0) { - attn6 *= attn6; - value += attn6 * attn6 * extrapolate(xsb + 0, ysb + 1, zsb + 1, dx6, dy6, dz6); - } - } - - // First extra vertex - double attn_ext0 = 2 - dx_ext0 * dx_ext0 - dy_ext0 * dy_ext0 - dz_ext0 * dz_ext0; - if (attn_ext0 > 0) { - attn_ext0 *= attn_ext0; - value += attn_ext0 * attn_ext0 * extrapolate(xsv_ext0, ysv_ext0, zsv_ext0, dx_ext0, dy_ext0, dz_ext0); - } - - // Second extra vertex - double attn_ext1 = 2 - dx_ext1 * dx_ext1 - dy_ext1 * dy_ext1 - dz_ext1 * dz_ext1; - if (attn_ext1 > 0) { - attn_ext1 *= attn_ext1; - value += attn_ext1 * attn_ext1 * extrapolate(xsv_ext1, ysv_ext1, zsv_ext1, dx_ext1, dy_ext1, dz_ext1); - } - - return value / NORM_CONSTANT_3D; - } - - // 4D OpenSimplex Noise. - public double eval(double x, double y, double z, double w) { - - // Place input coordinates on simplectic honeycomb. - double stretchOffset = (x + y + z + w) * STRETCH_CONSTANT_4D; - double xs = x + stretchOffset; - double ys = y + stretchOffset; - double zs = z + stretchOffset; - double ws = w + stretchOffset; - - // Floor to get simplectic honeycomb coordinates of rhombo-hypercube - // super-cell origin. - int xsb = fastFloor(xs); - int ysb = fastFloor(ys); - int zsb = fastFloor(zs); - int wsb = fastFloor(ws); - - // Skew out to get actual coordinates of stretched rhombo-hypercube - // origin. We'll need these later. - double squishOffset = (xsb + ysb + zsb + wsb) * SQUISH_CONSTANT_4D; - double xb = xsb + squishOffset; - double yb = ysb + squishOffset; - double zb = zsb + squishOffset; - double wb = wsb + squishOffset; - - // Compute simplectic honeycomb coordinates relative to rhombo-hypercube - // origin. - double xins = xs - xsb; - double yins = ys - ysb; - double zins = zs - zsb; - double wins = ws - wsb; - - // Sum those together to get a value that determines which region we're - // in. - double inSum = xins + yins + zins + wins; - - // Positions relative to origin point. - double dx0 = x - xb; - double dy0 = y - yb; - double dz0 = z - zb; - double dw0 = w - wb; - - // We'll be defining these inside the next block and using them - // afterwards. - double dx_ext0, dy_ext0, dz_ext0, dw_ext0; - double dx_ext1, dy_ext1, dz_ext1, dw_ext1; - double dx_ext2, dy_ext2, dz_ext2, dw_ext2; - int xsv_ext0, ysv_ext0, zsv_ext0, wsv_ext0; - int xsv_ext1, ysv_ext1, zsv_ext1, wsv_ext1; - int xsv_ext2, ysv_ext2, zsv_ext2, wsv_ext2; - - double value = 0; - if (inSum <= 1) { // We're inside the pentachoron (4-Simplex) at - // (0,0,0,0) - - // Determine which two of (0,0,0,1), (0,0,1,0), (0,1,0,0), (1,0,0,0) - // are closest. - byte aPoint = 0x01; - double aScore = xins; - byte bPoint = 0x02; - double bScore = yins; - if (aScore >= bScore && zins > bScore) { - bScore = zins; - bPoint = 0x04; - } else if (aScore < bScore && zins > aScore) { - aScore = zins; - aPoint = 0x04; - } - if (aScore >= bScore && wins > bScore) { - bScore = wins; - bPoint = 0x08; - } else if (aScore < bScore && wins > aScore) { - aScore = wins; - aPoint = 0x08; - } - - // Now we determine the three lattice points not part of the - // pentachoron that may contribute. - // This depends on the closest two pentachoron vertices, including - // (0,0,0,0) - double uins = 1 - inSum; - if (uins > aScore || uins > bScore) { // (0,0,0,0) is one of the - // closest two pentachoron - // vertices. - byte c = (bScore > aScore ? bPoint : aPoint); // Our other - // closest - // vertex is the - // closest out - // of a and b. - if ((c & 0x01) == 0) { - xsv_ext0 = xsb - 1; - xsv_ext1 = xsv_ext2 = xsb; - dx_ext0 = dx0 + 1; - dx_ext1 = dx_ext2 = dx0; - } else { - xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb + 1; - dx_ext0 = dx_ext1 = dx_ext2 = dx0 - 1; - } - - if ((c & 0x02) == 0) { - ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb; - dy_ext0 = dy_ext1 = dy_ext2 = dy0; - if ((c & 0x01) == 0x01) { - ysv_ext0 -= 1; - dy_ext0 += 1; - } else { - ysv_ext1 -= 1; - dy_ext1 += 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1; - dy_ext0 = dy_ext1 = dy_ext2 = dy0 - 1; - } - - if ((c & 0x04) == 0) { - zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb; - dz_ext0 = dz_ext1 = dz_ext2 = dz0; - if ((c & 0x03) != 0) { - if ((c & 0x03) == 0x03) { - zsv_ext0 -= 1; - dz_ext0 += 1; - } else { - zsv_ext1 -= 1; - dz_ext1 += 1; - } - } else { - zsv_ext2 -= 1; - dz_ext2 += 1; - } - } else { - zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1; - dz_ext0 = dz_ext1 = dz_ext2 = dz0 - 1; - } - - if ((c & 0x08) == 0) { - wsv_ext0 = wsv_ext1 = wsb; - wsv_ext2 = wsb - 1; - dw_ext0 = dw_ext1 = dw0; - dw_ext2 = dw0 + 1; - } else { - wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb + 1; - dw_ext0 = dw_ext1 = dw_ext2 = dw0 - 1; - } - } else { // (0,0,0,0) is not one of the closest two pentachoron - // vertices. - byte c = (byte) (aPoint | bPoint); // Our three extra vertices - // are determined by the - // closest two. - - if ((c & 0x01) == 0) { - xsv_ext0 = xsv_ext2 = xsb; - xsv_ext1 = xsb - 1; - dx_ext0 = dx0 - 2 * SQUISH_CONSTANT_4D; - dx_ext1 = dx0 + 1 - SQUISH_CONSTANT_4D; - dx_ext2 = dx0 - SQUISH_CONSTANT_4D; - } else { - xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb + 1; - dx_ext0 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - dx_ext1 = dx_ext2 = dx0 - 1 - SQUISH_CONSTANT_4D; - } - - if ((c & 0x02) == 0) { - ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb; - dy_ext0 = dy0 - 2 * SQUISH_CONSTANT_4D; - dy_ext1 = dy_ext2 = dy0 - SQUISH_CONSTANT_4D; - if ((c & 0x01) == 0x01) { - ysv_ext1 -= 1; - dy_ext1 += 1; - } else { - ysv_ext2 -= 1; - dy_ext2 += 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1; - dy_ext0 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - dy_ext1 = dy_ext2 = dy0 - 1 - SQUISH_CONSTANT_4D; - } - - if ((c & 0x04) == 0) { - zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb; - dz_ext0 = dz0 - 2 * SQUISH_CONSTANT_4D; - dz_ext1 = dz_ext2 = dz0 - SQUISH_CONSTANT_4D; - if ((c & 0x03) == 0x03) { - zsv_ext1 -= 1; - dz_ext1 += 1; - } else { - zsv_ext2 -= 1; - dz_ext2 += 1; - } - } else { - zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1; - dz_ext0 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - dz_ext1 = dz_ext2 = dz0 - 1 - SQUISH_CONSTANT_4D; - } - - if ((c & 0x08) == 0) { - wsv_ext0 = wsv_ext1 = wsb; - wsv_ext2 = wsb - 1; - dw_ext0 = dw0 - 2 * SQUISH_CONSTANT_4D; - dw_ext1 = dw0 - SQUISH_CONSTANT_4D; - dw_ext2 = dw0 + 1 - SQUISH_CONSTANT_4D; - } else { - wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb + 1; - dw_ext0 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - dw_ext1 = dw_ext2 = dw0 - 1 - SQUISH_CONSTANT_4D; - } - } - - // Contribution (0,0,0,0) - double attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0 - dw0 * dw0; - if (attn0 > 0) { - attn0 *= attn0; - value += attn0 * attn0 * extrapolate(xsb + 0, ysb + 0, zsb + 0, wsb + 0, dx0, dy0, dz0, dw0); - } - - // Contribution (1,0,0,0) - double dx1 = dx0 - 1 - SQUISH_CONSTANT_4D; - double dy1 = dy0 - 0 - SQUISH_CONSTANT_4D; - double dz1 = dz0 - 0 - SQUISH_CONSTANT_4D; - double dw1 = dw0 - 0 - SQUISH_CONSTANT_4D; - double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1; - if (attn1 > 0) { - attn1 *= attn1; - value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 0, dx1, dy1, dz1, dw1); - } - - // Contribution (0,1,0,0) - double dx2 = dx0 - 0 - SQUISH_CONSTANT_4D; - double dy2 = dy0 - 1 - SQUISH_CONSTANT_4D; - double dz2 = dz1; - double dw2 = dw1; - double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2; - if (attn2 > 0) { - attn2 *= attn2; - value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 0, dx2, dy2, dz2, dw2); - } - - // Contribution (0,0,1,0) - double dx3 = dx2; - double dy3 = dy1; - double dz3 = dz0 - 1 - SQUISH_CONSTANT_4D; - double dw3 = dw1; - double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3; - if (attn3 > 0) { - attn3 *= attn3; - value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 0, dx3, dy3, dz3, dw3); - } - - // Contribution (0,0,0,1) - double dx4 = dx2; - double dy4 = dy1; - double dz4 = dz1; - double dw4 = dw0 - 1 - SQUISH_CONSTANT_4D; - double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4; - if (attn4 > 0) { - attn4 *= attn4; - value += attn4 * attn4 * extrapolate(xsb + 0, ysb + 0, zsb + 0, wsb + 1, dx4, dy4, dz4, dw4); - } - } else if (inSum >= 3) { // We're inside the pentachoron (4-Simplex) at - // (1,1,1,1) - // Determine which two of (1,1,1,0), - // (1,1,0,1), (1,0,1,1), (0,1,1,1) - // are closest. - byte aPoint = 0x0E; - double aScore = xins; - byte bPoint = 0x0D; - double bScore = yins; - if (aScore <= bScore && zins < bScore) { - bScore = zins; - bPoint = 0x0B; - } else if (aScore > bScore && zins < aScore) { - aScore = zins; - aPoint = 0x0B; - } - if (aScore <= bScore && wins < bScore) { - bScore = wins; - bPoint = 0x07; - } else if (aScore > bScore && wins < aScore) { - aScore = wins; - aPoint = 0x07; - } - - // Now we determine the three lattice points not part of the - // pentachoron that may contribute. - // This depends on the closest two pentachoron vertices, including - // (0,0,0,0) - double uins = 4 - inSum; - if (uins < aScore || uins < bScore) { // (1,1,1,1) is one of the - // closest two pentachoron - // vertices. - byte c = (bScore < aScore ? bPoint : aPoint); // Our other - // closest - // vertex is the - // closest out - // of a and b. - - if ((c & 0x01) != 0) { - xsv_ext0 = xsb + 2; - xsv_ext1 = xsv_ext2 = xsb + 1; - dx_ext0 = dx0 - 2 - 4 * SQUISH_CONSTANT_4D; - dx_ext1 = dx_ext2 = dx0 - 1 - 4 * SQUISH_CONSTANT_4D; - } else { - xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb; - dx_ext0 = dx_ext1 = dx_ext2 = dx0 - 4 * SQUISH_CONSTANT_4D; - } - - if ((c & 0x02) != 0) { - ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1; - dy_ext0 = dy_ext1 = dy_ext2 = dy0 - 1 - 4 * SQUISH_CONSTANT_4D; - if ((c & 0x01) != 0) { - ysv_ext1 += 1; - dy_ext1 -= 1; - } else { - ysv_ext0 += 1; - dy_ext0 -= 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb; - dy_ext0 = dy_ext1 = dy_ext2 = dy0 - 4 * SQUISH_CONSTANT_4D; - } - - if ((c & 0x04) != 0) { - zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1; - dz_ext0 = dz_ext1 = dz_ext2 = dz0 - 1 - 4 * SQUISH_CONSTANT_4D; - if ((c & 0x03) != 0x03) { - if ((c & 0x03) == 0) { - zsv_ext0 += 1; - dz_ext0 -= 1; - } else { - zsv_ext1 += 1; - dz_ext1 -= 1; - } - } else { - zsv_ext2 += 1; - dz_ext2 -= 1; - } - } else { - zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb; - dz_ext0 = dz_ext1 = dz_ext2 = dz0 - 4 * SQUISH_CONSTANT_4D; - } - - if ((c & 0x08) != 0) { - wsv_ext0 = wsv_ext1 = wsb + 1; - wsv_ext2 = wsb + 2; - dw_ext0 = dw_ext1 = dw0 - 1 - 4 * SQUISH_CONSTANT_4D; - dw_ext2 = dw0 - 2 - 4 * SQUISH_CONSTANT_4D; - } else { - wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb; - dw_ext0 = dw_ext1 = dw_ext2 = dw0 - 4 * SQUISH_CONSTANT_4D; - } - } else { // (1,1,1,1) is not one of the closest two pentachoron - // vertices. - byte c = (byte) (aPoint & bPoint); // Our three extra vertices - // are determined by the - // closest two. - - if ((c & 0x01) != 0) { - xsv_ext0 = xsv_ext2 = xsb + 1; - xsv_ext1 = xsb + 2; - dx_ext0 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - dx_ext1 = dx0 - 2 - 3 * SQUISH_CONSTANT_4D; - dx_ext2 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; - } else { - xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb; - dx_ext0 = dx0 - 2 * SQUISH_CONSTANT_4D; - dx_ext1 = dx_ext2 = dx0 - 3 * SQUISH_CONSTANT_4D; - } - - if ((c & 0x02) != 0) { - ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1; - dy_ext0 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - dy_ext1 = dy_ext2 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; - if ((c & 0x01) != 0) { - ysv_ext2 += 1; - dy_ext2 -= 1; - } else { - ysv_ext1 += 1; - dy_ext1 -= 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb; - dy_ext0 = dy0 - 2 * SQUISH_CONSTANT_4D; - dy_ext1 = dy_ext2 = dy0 - 3 * SQUISH_CONSTANT_4D; - } - - if ((c & 0x04) != 0) { - zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1; - dz_ext0 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - dz_ext1 = dz_ext2 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; - if ((c & 0x03) != 0) { - zsv_ext2 += 1; - dz_ext2 -= 1; - } else { - zsv_ext1 += 1; - dz_ext1 -= 1; - } - } else { - zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb; - dz_ext0 = dz0 - 2 * SQUISH_CONSTANT_4D; - dz_ext1 = dz_ext2 = dz0 - 3 * SQUISH_CONSTANT_4D; - } - - if ((c & 0x08) != 0) { - wsv_ext0 = wsv_ext1 = wsb + 1; - wsv_ext2 = wsb + 2; - dw_ext0 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - dw_ext1 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; - dw_ext2 = dw0 - 2 - 3 * SQUISH_CONSTANT_4D; - } else { - wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb; - dw_ext0 = dw0 - 2 * SQUISH_CONSTANT_4D; - dw_ext1 = dw_ext2 = dw0 - 3 * SQUISH_CONSTANT_4D; - } - } - - // Contribution (1,1,1,0) - double dx4 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; - double dy4 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; - double dz4 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; - double dw4 = dw0 - 3 * SQUISH_CONSTANT_4D; - double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4; - if (attn4 > 0) { - attn4 *= attn4; - value += attn4 * attn4 * extrapolate(xsb + 1, ysb + 1, zsb + 1, wsb + 0, dx4, dy4, dz4, dw4); - } - - // Contribution (1,1,0,1) - double dx3 = dx4; - double dy3 = dy4; - double dz3 = dz0 - 3 * SQUISH_CONSTANT_4D; - double dw3 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; - double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3; - if (attn3 > 0) { - attn3 *= attn3; - value += attn3 * attn3 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 1, dx3, dy3, dz3, dw3); - } - - // Contribution (1,0,1,1) - double dx2 = dx4; - double dy2 = dy0 - 3 * SQUISH_CONSTANT_4D; - double dz2 = dz4; - double dw2 = dw3; - double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2; - if (attn2 > 0) { - attn2 *= attn2; - value += attn2 * attn2 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 1, dx2, dy2, dz2, dw2); - } - - // Contribution (0,1,1,1) - double dx1 = dx0 - 3 * SQUISH_CONSTANT_4D; - double dz1 = dz4; - double dy1 = dy4; - double dw1 = dw3; - double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1; - if (attn1 > 0) { - attn1 *= attn1; - value += attn1 * attn1 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 1, dx1, dy1, dz1, dw1); - } - - // Contribution (1,1,1,1) - dx0 = dx0 - 1 - 4 * SQUISH_CONSTANT_4D; - dy0 = dy0 - 1 - 4 * SQUISH_CONSTANT_4D; - dz0 = dz0 - 1 - 4 * SQUISH_CONSTANT_4D; - dw0 = dw0 - 1 - 4 * SQUISH_CONSTANT_4D; - double attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0 - dw0 * dw0; - if (attn0 > 0) { - attn0 *= attn0; - value += attn0 * attn0 * extrapolate(xsb + 1, ysb + 1, zsb + 1, wsb + 1, dx0, dy0, dz0, dw0); - } - } else if (inSum <= 2) { // We're inside the first dispentachoron - // (Rectified 4-Simplex) - double aScore; - byte aPoint; - boolean aIsBiggerSide = true; - double bScore; - byte bPoint; - boolean bIsBiggerSide = true; - - // Decide between (1,1,0,0) and (0,0,1,1) - if (xins + yins > zins + wins) { - aScore = xins + yins; - aPoint = 0x03; - } else { - aScore = zins + wins; - aPoint = 0x0C; - } - - // Decide between (1,0,1,0) and (0,1,0,1) - if (xins + zins > yins + wins) { - bScore = xins + zins; - bPoint = 0x05; - } else { - bScore = yins + wins; - bPoint = 0x0A; - } - - // Closer between (1,0,0,1) and (0,1,1,0) will replace the further - // of a and b, if closer. - if (xins + wins > yins + zins) { - double score = xins + wins; - if (aScore >= bScore && score > bScore) { - bScore = score; - bPoint = 0x09; - } else if (aScore < bScore && score > aScore) { - aScore = score; - aPoint = 0x09; - } - } else { - double score = yins + zins; - if (aScore >= bScore && score > bScore) { - bScore = score; - bPoint = 0x06; - } else if (aScore < bScore && score > aScore) { - aScore = score; - aPoint = 0x06; - } - } - - // Decide if (1,0,0,0) is closer. - double p1 = 2 - inSum + xins; - if (aScore >= bScore && p1 > bScore) { - bScore = p1; - bPoint = 0x01; - bIsBiggerSide = false; - } else if (aScore < bScore && p1 > aScore) { - aScore = p1; - aPoint = 0x01; - aIsBiggerSide = false; - } - - // Decide if (0,1,0,0) is closer. - double p2 = 2 - inSum + yins; - if (aScore >= bScore && p2 > bScore) { - bScore = p2; - bPoint = 0x02; - bIsBiggerSide = false; - } else if (aScore < bScore && p2 > aScore) { - aScore = p2; - aPoint = 0x02; - aIsBiggerSide = false; - } - - // Decide if (0,0,1,0) is closer. - double p3 = 2 - inSum + zins; - if (aScore >= bScore && p3 > bScore) { - bScore = p3; - bPoint = 0x04; - bIsBiggerSide = false; - } else if (aScore < bScore && p3 > aScore) { - aScore = p3; - aPoint = 0x04; - aIsBiggerSide = false; - } - - // Decide if (0,0,0,1) is closer. - double p4 = 2 - inSum + wins; - if (aScore >= bScore && p4 > bScore) { - bScore = p4; - bPoint = 0x08; - bIsBiggerSide = false; - } else if (aScore < bScore && p4 > aScore) { - aScore = p4; - aPoint = 0x08; - aIsBiggerSide = false; - } - - // Where each of the two closest points are determines how the extra - // three vertices are calculated. - if (aIsBiggerSide == bIsBiggerSide) { - if (aIsBiggerSide) { // Both closest points on the bigger side - byte c1 = (byte) (aPoint | bPoint); - byte c2 = (byte) (aPoint & bPoint); - if ((c1 & 0x01) == 0) { - xsv_ext0 = xsb; - xsv_ext1 = xsb - 1; - dx_ext0 = dx0 - 3 * SQUISH_CONSTANT_4D; - dx_ext1 = dx0 + 1 - 2 * SQUISH_CONSTANT_4D; - } else { - xsv_ext0 = xsv_ext1 = xsb + 1; - dx_ext0 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; - dx_ext1 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - } - - if ((c1 & 0x02) == 0) { - ysv_ext0 = ysb; - ysv_ext1 = ysb - 1; - dy_ext0 = dy0 - 3 * SQUISH_CONSTANT_4D; - dy_ext1 = dy0 + 1 - 2 * SQUISH_CONSTANT_4D; - } else { - ysv_ext0 = ysv_ext1 = ysb + 1; - dy_ext0 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; - dy_ext1 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - } - - if ((c1 & 0x04) == 0) { - zsv_ext0 = zsb; - zsv_ext1 = zsb - 1; - dz_ext0 = dz0 - 3 * SQUISH_CONSTANT_4D; - dz_ext1 = dz0 + 1 - 2 * SQUISH_CONSTANT_4D; - } else { - zsv_ext0 = zsv_ext1 = zsb + 1; - dz_ext0 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; - dz_ext1 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - } - - if ((c1 & 0x08) == 0) { - wsv_ext0 = wsb; - wsv_ext1 = wsb - 1; - dw_ext0 = dw0 - 3 * SQUISH_CONSTANT_4D; - dw_ext1 = dw0 + 1 - 2 * SQUISH_CONSTANT_4D; - } else { - wsv_ext0 = wsv_ext1 = wsb + 1; - dw_ext0 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; - dw_ext1 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - } - - // One combination is a permutation of (0,0,0,2) based on c2 - xsv_ext2 = xsb; - ysv_ext2 = ysb; - zsv_ext2 = zsb; - wsv_ext2 = wsb; - dx_ext2 = dx0 - 2 * SQUISH_CONSTANT_4D; - dy_ext2 = dy0 - 2 * SQUISH_CONSTANT_4D; - dz_ext2 = dz0 - 2 * SQUISH_CONSTANT_4D; - dw_ext2 = dw0 - 2 * SQUISH_CONSTANT_4D; - if ((c2 & 0x01) != 0) { - xsv_ext2 += 2; - dx_ext2 -= 2; - } else if ((c2 & 0x02) != 0) { - ysv_ext2 += 2; - dy_ext2 -= 2; - } else if ((c2 & 0x04) != 0) { - zsv_ext2 += 2; - dz_ext2 -= 2; - } else { - wsv_ext2 += 2; - dw_ext2 -= 2; - } - - } else { // Both closest points on the smaller side - // One of the two extra points is (0,0,0,0) - xsv_ext2 = xsb; - ysv_ext2 = ysb; - zsv_ext2 = zsb; - wsv_ext2 = wsb; - dx_ext2 = dx0; - dy_ext2 = dy0; - dz_ext2 = dz0; - dw_ext2 = dw0; - - // Other two points are based on the omitted axes. - byte c = (byte) (aPoint | bPoint); - - if ((c & 0x01) == 0) { - xsv_ext0 = xsb - 1; - xsv_ext1 = xsb; - dx_ext0 = dx0 + 1 - SQUISH_CONSTANT_4D; - dx_ext1 = dx0 - SQUISH_CONSTANT_4D; - } else { - xsv_ext0 = xsv_ext1 = xsb + 1; - dx_ext0 = dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_4D; - } - - if ((c & 0x02) == 0) { - ysv_ext0 = ysv_ext1 = ysb; - dy_ext0 = dy_ext1 = dy0 - SQUISH_CONSTANT_4D; - if ((c & 0x01) == 0x01) { - ysv_ext0 -= 1; - dy_ext0 += 1; - } else { - ysv_ext1 -= 1; - dy_ext1 += 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysb + 1; - dy_ext0 = dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_4D; - } - - if ((c & 0x04) == 0) { - zsv_ext0 = zsv_ext1 = zsb; - dz_ext0 = dz_ext1 = dz0 - SQUISH_CONSTANT_4D; - if ((c & 0x03) == 0x03) { - zsv_ext0 -= 1; - dz_ext0 += 1; - } else { - zsv_ext1 -= 1; - dz_ext1 += 1; - } - } else { - zsv_ext0 = zsv_ext1 = zsb + 1; - dz_ext0 = dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_4D; - } - - if ((c & 0x08) == 0) { - wsv_ext0 = wsb; - wsv_ext1 = wsb - 1; - dw_ext0 = dw0 - SQUISH_CONSTANT_4D; - dw_ext1 = dw0 + 1 - SQUISH_CONSTANT_4D; - } else { - wsv_ext0 = wsv_ext1 = wsb + 1; - dw_ext0 = dw_ext1 = dw0 - 1 - SQUISH_CONSTANT_4D; - } - - } - } else { // One point on each "side" - byte c1, c2; - if (aIsBiggerSide) { - c1 = aPoint; - c2 = bPoint; - } else { - c1 = bPoint; - c2 = aPoint; - } - - // Two contributions are the bigger-sided point with each 0 - // replaced with -1. - if ((c1 & 0x01) == 0) { - xsv_ext0 = xsb - 1; - xsv_ext1 = xsb; - dx_ext0 = dx0 + 1 - SQUISH_CONSTANT_4D; - dx_ext1 = dx0 - SQUISH_CONSTANT_4D; - } else { - xsv_ext0 = xsv_ext1 = xsb + 1; - dx_ext0 = dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_4D; - } - - if ((c1 & 0x02) == 0) { - ysv_ext0 = ysv_ext1 = ysb; - dy_ext0 = dy_ext1 = dy0 - SQUISH_CONSTANT_4D; - if ((c1 & 0x01) == 0x01) { - ysv_ext0 -= 1; - dy_ext0 += 1; - } else { - ysv_ext1 -= 1; - dy_ext1 += 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysb + 1; - dy_ext0 = dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_4D; - } - - if ((c1 & 0x04) == 0) { - zsv_ext0 = zsv_ext1 = zsb; - dz_ext0 = dz_ext1 = dz0 - SQUISH_CONSTANT_4D; - if ((c1 & 0x03) == 0x03) { - zsv_ext0 -= 1; - dz_ext0 += 1; - } else { - zsv_ext1 -= 1; - dz_ext1 += 1; - } - } else { - zsv_ext0 = zsv_ext1 = zsb + 1; - dz_ext0 = dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_4D; - } - - if ((c1 & 0x08) == 0) { - wsv_ext0 = wsb; - wsv_ext1 = wsb - 1; - dw_ext0 = dw0 - SQUISH_CONSTANT_4D; - dw_ext1 = dw0 + 1 - SQUISH_CONSTANT_4D; - } else { - wsv_ext0 = wsv_ext1 = wsb + 1; - dw_ext0 = dw_ext1 = dw0 - 1 - SQUISH_CONSTANT_4D; - } - - // One contribution is a permutation of (0,0,0,2) based on the - // smaller-sided point - xsv_ext2 = xsb; - ysv_ext2 = ysb; - zsv_ext2 = zsb; - wsv_ext2 = wsb; - dx_ext2 = dx0 - 2 * SQUISH_CONSTANT_4D; - dy_ext2 = dy0 - 2 * SQUISH_CONSTANT_4D; - dz_ext2 = dz0 - 2 * SQUISH_CONSTANT_4D; - dw_ext2 = dw0 - 2 * SQUISH_CONSTANT_4D; - if ((c2 & 0x01) != 0) { - xsv_ext2 += 2; - dx_ext2 -= 2; - } else if ((c2 & 0x02) != 0) { - ysv_ext2 += 2; - dy_ext2 -= 2; - } else if ((c2 & 0x04) != 0) { - zsv_ext2 += 2; - dz_ext2 -= 2; - } else { - wsv_ext2 += 2; - dw_ext2 -= 2; - } - } - - // Contribution (1,0,0,0) - double dx1 = dx0 - 1 - SQUISH_CONSTANT_4D; - double dy1 = dy0 - 0 - SQUISH_CONSTANT_4D; - double dz1 = dz0 - 0 - SQUISH_CONSTANT_4D; - double dw1 = dw0 - 0 - SQUISH_CONSTANT_4D; - double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1; - if (attn1 > 0) { - attn1 *= attn1; - value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 0, dx1, dy1, dz1, dw1); - } - - // Contribution (0,1,0,0) - double dx2 = dx0 - 0 - SQUISH_CONSTANT_4D; - double dy2 = dy0 - 1 - SQUISH_CONSTANT_4D; - double dz2 = dz1; - double dw2 = dw1; - double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2; - if (attn2 > 0) { - attn2 *= attn2; - value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 0, dx2, dy2, dz2, dw2); - } - - // Contribution (0,0,1,0) - double dx3 = dx2; - double dy3 = dy1; - double dz3 = dz0 - 1 - SQUISH_CONSTANT_4D; - double dw3 = dw1; - double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3; - if (attn3 > 0) { - attn3 *= attn3; - value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 0, dx3, dy3, dz3, dw3); - } - - // Contribution (0,0,0,1) - double dx4 = dx2; - double dy4 = dy1; - double dz4 = dz1; - double dw4 = dw0 - 1 - SQUISH_CONSTANT_4D; - double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4; - if (attn4 > 0) { - attn4 *= attn4; - value += attn4 * attn4 * extrapolate(xsb + 0, ysb + 0, zsb + 0, wsb + 1, dx4, dy4, dz4, dw4); - } - - // Contribution (1,1,0,0) - double dx5 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dy5 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dz5 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dw5 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; - double attn5 = 2 - dx5 * dx5 - dy5 * dy5 - dz5 * dz5 - dw5 * dw5; - if (attn5 > 0) { - attn5 *= attn5; - value += attn5 * attn5 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 0, dx5, dy5, dz5, dw5); - } - - // Contribution (1,0,1,0) - double dx6 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dy6 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dz6 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dw6 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; - double attn6 = 2 - dx6 * dx6 - dy6 * dy6 - dz6 * dz6 - dw6 * dw6; - if (attn6 > 0) { - attn6 *= attn6; - value += attn6 * attn6 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 0, dx6, dy6, dz6, dw6); - } - - // Contribution (1,0,0,1) - double dx7 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dy7 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dz7 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dw7 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - double attn7 = 2 - dx7 * dx7 - dy7 * dy7 - dz7 * dz7 - dw7 * dw7; - if (attn7 > 0) { - attn7 *= attn7; - value += attn7 * attn7 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 1, dx7, dy7, dz7, dw7); - } - - // Contribution (0,1,1,0) - double dx8 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dy8 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dz8 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dw8 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; - double attn8 = 2 - dx8 * dx8 - dy8 * dy8 - dz8 * dz8 - dw8 * dw8; - if (attn8 > 0) { - attn8 *= attn8; - value += attn8 * attn8 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 0, dx8, dy8, dz8, dw8); - } - - // Contribution (0,1,0,1) - double dx9 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dy9 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dz9 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dw9 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - double attn9 = 2 - dx9 * dx9 - dy9 * dy9 - dz9 * dz9 - dw9 * dw9; - if (attn9 > 0) { - attn9 *= attn9; - value += attn9 * attn9 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 1, dx9, dy9, dz9, dw9); - } - - // Contribution (0,0,1,1) - double dx10 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dy10 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dz10 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dw10 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - double attn10 = 2 - dx10 * dx10 - dy10 * dy10 - dz10 * dz10 - dw10 * dw10; - if (attn10 > 0) { - attn10 *= attn10; - value += attn10 * attn10 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 1, dx10, dy10, dz10, dw10); - } - } else { // We're inside the second dispentachoron (Rectified 4-Simplex) - double aScore; - byte aPoint; - boolean aIsBiggerSide = true; - double bScore; - byte bPoint; - boolean bIsBiggerSide = true; - - // Decide between (0,0,1,1) and (1,1,0,0) - if (xins + yins < zins + wins) { - aScore = xins + yins; - aPoint = 0x0C; - } else { - aScore = zins + wins; - aPoint = 0x03; - } - - // Decide between (0,1,0,1) and (1,0,1,0) - if (xins + zins < yins + wins) { - bScore = xins + zins; - bPoint = 0x0A; - } else { - bScore = yins + wins; - bPoint = 0x05; - } - - // Closer between (0,1,1,0) and (1,0,0,1) will replace the further - // of a and b, if closer. - if (xins + wins < yins + zins) { - double score = xins + wins; - if (aScore <= bScore && score < bScore) { - bScore = score; - bPoint = 0x06; - } else if (aScore > bScore && score < aScore) { - aScore = score; - aPoint = 0x06; - } - } else { - double score = yins + zins; - if (aScore <= bScore && score < bScore) { - bScore = score; - bPoint = 0x09; - } else if (aScore > bScore && score < aScore) { - aScore = score; - aPoint = 0x09; - } - } - - // Decide if (0,1,1,1) is closer. - double p1 = 3 - inSum + xins; - if (aScore <= bScore && p1 < bScore) { - bScore = p1; - bPoint = 0x0E; - bIsBiggerSide = false; - } else if (aScore > bScore && p1 < aScore) { - aScore = p1; - aPoint = 0x0E; - aIsBiggerSide = false; - } - - // Decide if (1,0,1,1) is closer. - double p2 = 3 - inSum + yins; - if (aScore <= bScore && p2 < bScore) { - bScore = p2; - bPoint = 0x0D; - bIsBiggerSide = false; - } else if (aScore > bScore && p2 < aScore) { - aScore = p2; - aPoint = 0x0D; - aIsBiggerSide = false; - } - - // Decide if (1,1,0,1) is closer. - double p3 = 3 - inSum + zins; - if (aScore <= bScore && p3 < bScore) { - bScore = p3; - bPoint = 0x0B; - bIsBiggerSide = false; - } else if (aScore > bScore && p3 < aScore) { - aScore = p3; - aPoint = 0x0B; - aIsBiggerSide = false; - } - - // Decide if (1,1,1,0) is closer. - double p4 = 3 - inSum + wins; - if (aScore <= bScore && p4 < bScore) { - bScore = p4; - bPoint = 0x07; - bIsBiggerSide = false; - } else if (aScore > bScore && p4 < aScore) { - aScore = p4; - aPoint = 0x07; - aIsBiggerSide = false; - } - - // Where each of the two closest points are determines how the extra - // three vertices are calculated. - if (aIsBiggerSide == bIsBiggerSide) { - if (aIsBiggerSide) { // Both closest points on the bigger side - byte c1 = (byte) (aPoint & bPoint); - byte c2 = (byte) (aPoint | bPoint); - - // Two contributions are permutations of (0,0,0,1) and - // (0,0,0,2) based on c1 - xsv_ext0 = xsv_ext1 = xsb; - ysv_ext0 = ysv_ext1 = ysb; - zsv_ext0 = zsv_ext1 = zsb; - wsv_ext0 = wsv_ext1 = wsb; - dx_ext0 = dx0 - SQUISH_CONSTANT_4D; - dy_ext0 = dy0 - SQUISH_CONSTANT_4D; - dz_ext0 = dz0 - SQUISH_CONSTANT_4D; - dw_ext0 = dw0 - SQUISH_CONSTANT_4D; - dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_4D; - dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_4D; - dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_4D; - dw_ext1 = dw0 - 2 * SQUISH_CONSTANT_4D; - if ((c1 & 0x01) != 0) { - xsv_ext0 += 1; - dx_ext0 -= 1; - xsv_ext1 += 2; - dx_ext1 -= 2; - } else if ((c1 & 0x02) != 0) { - ysv_ext0 += 1; - dy_ext0 -= 1; - ysv_ext1 += 2; - dy_ext1 -= 2; - } else if ((c1 & 0x04) != 0) { - zsv_ext0 += 1; - dz_ext0 -= 1; - zsv_ext1 += 2; - dz_ext1 -= 2; - } else { - wsv_ext0 += 1; - dw_ext0 -= 1; - wsv_ext1 += 2; - dw_ext1 -= 2; - } - - // One contribution is a permutation of (1,1,1,-1) based on - // c2 - xsv_ext2 = xsb + 1; - ysv_ext2 = ysb + 1; - zsv_ext2 = zsb + 1; - wsv_ext2 = wsb + 1; - dx_ext2 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - dy_ext2 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - dz_ext2 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - dw_ext2 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - if ((c2 & 0x01) == 0) { - xsv_ext2 -= 2; - dx_ext2 += 2; - } else if ((c2 & 0x02) == 0) { - ysv_ext2 -= 2; - dy_ext2 += 2; - } else if ((c2 & 0x04) == 0) { - zsv_ext2 -= 2; - dz_ext2 += 2; - } else { - wsv_ext2 -= 2; - dw_ext2 += 2; - } - } else { // Both closest points on the smaller side - // One of the two extra points is (1,1,1,1) - xsv_ext2 = xsb + 1; - ysv_ext2 = ysb + 1; - zsv_ext2 = zsb + 1; - wsv_ext2 = wsb + 1; - dx_ext2 = dx0 - 1 - 4 * SQUISH_CONSTANT_4D; - dy_ext2 = dy0 - 1 - 4 * SQUISH_CONSTANT_4D; - dz_ext2 = dz0 - 1 - 4 * SQUISH_CONSTANT_4D; - dw_ext2 = dw0 - 1 - 4 * SQUISH_CONSTANT_4D; - - // Other two points are based on the shared axes. - byte c = (byte) (aPoint & bPoint); - - if ((c & 0x01) != 0) { - xsv_ext0 = xsb + 2; - xsv_ext1 = xsb + 1; - dx_ext0 = dx0 - 2 - 3 * SQUISH_CONSTANT_4D; - dx_ext1 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; - } else { - xsv_ext0 = xsv_ext1 = xsb; - dx_ext0 = dx_ext1 = dx0 - 3 * SQUISH_CONSTANT_4D; - } - - if ((c & 0x02) != 0) { - ysv_ext0 = ysv_ext1 = ysb + 1; - dy_ext0 = dy_ext1 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; - if ((c & 0x01) == 0) { - ysv_ext0 += 1; - dy_ext0 -= 1; - } else { - ysv_ext1 += 1; - dy_ext1 -= 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysb; - dy_ext0 = dy_ext1 = dy0 - 3 * SQUISH_CONSTANT_4D; - } - - if ((c & 0x04) != 0) { - zsv_ext0 = zsv_ext1 = zsb + 1; - dz_ext0 = dz_ext1 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; - if ((c & 0x03) == 0) { - zsv_ext0 += 1; - dz_ext0 -= 1; - } else { - zsv_ext1 += 1; - dz_ext1 -= 1; - } - } else { - zsv_ext0 = zsv_ext1 = zsb; - dz_ext0 = dz_ext1 = dz0 - 3 * SQUISH_CONSTANT_4D; - } - - if ((c & 0x08) != 0) { - wsv_ext0 = wsb + 1; - wsv_ext1 = wsb + 2; - dw_ext0 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; - dw_ext1 = dw0 - 2 - 3 * SQUISH_CONSTANT_4D; - } else { - wsv_ext0 = wsv_ext1 = wsb; - dw_ext0 = dw_ext1 = dw0 - 3 * SQUISH_CONSTANT_4D; - } - } - } else { // One point on each "side" - byte c1, c2; - if (aIsBiggerSide) { - c1 = aPoint; - c2 = bPoint; - } else { - c1 = bPoint; - c2 = aPoint; - } - - // Two contributions are the bigger-sided point with each 1 - // replaced with 2. - if ((c1 & 0x01) != 0) { - xsv_ext0 = xsb + 2; - xsv_ext1 = xsb + 1; - dx_ext0 = dx0 - 2 - 3 * SQUISH_CONSTANT_4D; - dx_ext1 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; - } else { - xsv_ext0 = xsv_ext1 = xsb; - dx_ext0 = dx_ext1 = dx0 - 3 * SQUISH_CONSTANT_4D; - } - - if ((c1 & 0x02) != 0) { - ysv_ext0 = ysv_ext1 = ysb + 1; - dy_ext0 = dy_ext1 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; - if ((c1 & 0x01) == 0) { - ysv_ext0 += 1; - dy_ext0 -= 1; - } else { - ysv_ext1 += 1; - dy_ext1 -= 1; - } - } else { - ysv_ext0 = ysv_ext1 = ysb; - dy_ext0 = dy_ext1 = dy0 - 3 * SQUISH_CONSTANT_4D; - } - - if ((c1 & 0x04) != 0) { - zsv_ext0 = zsv_ext1 = zsb + 1; - dz_ext0 = dz_ext1 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; - if ((c1 & 0x03) == 0) { - zsv_ext0 += 1; - dz_ext0 -= 1; - } else { - zsv_ext1 += 1; - dz_ext1 -= 1; - } - } else { - zsv_ext0 = zsv_ext1 = zsb; - dz_ext0 = dz_ext1 = dz0 - 3 * SQUISH_CONSTANT_4D; - } - - if ((c1 & 0x08) != 0) { - wsv_ext0 = wsb + 1; - wsv_ext1 = wsb + 2; - dw_ext0 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; - dw_ext1 = dw0 - 2 - 3 * SQUISH_CONSTANT_4D; - } else { - wsv_ext0 = wsv_ext1 = wsb; - dw_ext0 = dw_ext1 = dw0 - 3 * SQUISH_CONSTANT_4D; - } - - // One contribution is a permutation of (1,1,1,-1) based on the - // smaller-sided point - xsv_ext2 = xsb + 1; - ysv_ext2 = ysb + 1; - zsv_ext2 = zsb + 1; - wsv_ext2 = wsb + 1; - dx_ext2 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - dy_ext2 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - dz_ext2 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - dw_ext2 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - if ((c2 & 0x01) == 0) { - xsv_ext2 -= 2; - dx_ext2 += 2; - } else if ((c2 & 0x02) == 0) { - ysv_ext2 -= 2; - dy_ext2 += 2; - } else if ((c2 & 0x04) == 0) { - zsv_ext2 -= 2; - dz_ext2 += 2; - } else { - wsv_ext2 -= 2; - dw_ext2 += 2; - } - } - - // Contribution (1,1,1,0) - double dx4 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D; - double dy4 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D; - double dz4 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D; - double dw4 = dw0 - 3 * SQUISH_CONSTANT_4D; - double attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4; - if (attn4 > 0) { - attn4 *= attn4; - value += attn4 * attn4 * extrapolate(xsb + 1, ysb + 1, zsb + 1, wsb + 0, dx4, dy4, dz4, dw4); - } - - // Contribution (1,1,0,1) - double dx3 = dx4; - double dy3 = dy4; - double dz3 = dz0 - 3 * SQUISH_CONSTANT_4D; - double dw3 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D; - double attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3; - if (attn3 > 0) { - attn3 *= attn3; - value += attn3 * attn3 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 1, dx3, dy3, dz3, dw3); - } - - // Contribution (1,0,1,1) - double dx2 = dx4; - double dy2 = dy0 - 3 * SQUISH_CONSTANT_4D; - double dz2 = dz4; - double dw2 = dw3; - double attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2; - if (attn2 > 0) { - attn2 *= attn2; - value += attn2 * attn2 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 1, dx2, dy2, dz2, dw2); - } - - // Contribution (0,1,1,1) - double dx1 = dx0 - 3 * SQUISH_CONSTANT_4D; - double dz1 = dz4; - double dy1 = dy4; - double dw1 = dw3; - double attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1; - if (attn1 > 0) { - attn1 *= attn1; - value += attn1 * attn1 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 1, dx1, dy1, dz1, dw1); - } - - // Contribution (1,1,0,0) - double dx5 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dy5 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dz5 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dw5 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; - double attn5 = 2 - dx5 * dx5 - dy5 * dy5 - dz5 * dz5 - dw5 * dw5; - if (attn5 > 0) { - attn5 *= attn5; - value += attn5 * attn5 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 0, dx5, dy5, dz5, dw5); - } - - // Contribution (1,0,1,0) - double dx6 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dy6 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dz6 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dw6 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; - double attn6 = 2 - dx6 * dx6 - dy6 * dy6 - dz6 * dz6 - dw6 * dw6; - if (attn6 > 0) { - attn6 *= attn6; - value += attn6 * attn6 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 0, dx6, dy6, dz6, dw6); - } - - // Contribution (1,0,0,1) - double dx7 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dy7 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dz7 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dw7 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - double attn7 = 2 - dx7 * dx7 - dy7 * dy7 - dz7 * dz7 - dw7 * dw7; - if (attn7 > 0) { - attn7 *= attn7; - value += attn7 * attn7 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 1, dx7, dy7, dz7, dw7); - } - - // Contribution (0,1,1,0) - double dx8 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dy8 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dz8 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dw8 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D; - double attn8 = 2 - dx8 * dx8 - dy8 * dy8 - dz8 * dz8 - dw8 * dw8; - if (attn8 > 0) { - attn8 *= attn8; - value += attn8 * attn8 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 0, dx8, dy8, dz8, dw8); - } - - // Contribution (0,1,0,1) - double dx9 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dy9 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dz9 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dw9 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - double attn9 = 2 - dx9 * dx9 - dy9 * dy9 - dz9 * dz9 - dw9 * dw9; - if (attn9 > 0) { - attn9 *= attn9; - value += attn9 * attn9 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 1, dx9, dy9, dz9, dw9); - } - - // Contribution (0,0,1,1) - double dx10 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dy10 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D; - double dz10 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D; - double dw10 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D; - double attn10 = 2 - dx10 * dx10 - dy10 * dy10 - dz10 * dz10 - dw10 * dw10; - if (attn10 > 0) { - attn10 *= attn10; - value += attn10 * attn10 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 1, dx10, dy10, dz10, dw10); - } - } - - // First extra vertex - double attn_ext0 = 2 - dx_ext0 * dx_ext0 - dy_ext0 * dy_ext0 - dz_ext0 * dz_ext0 - dw_ext0 * dw_ext0; - if (attn_ext0 > 0) { - attn_ext0 *= attn_ext0; - value += attn_ext0 * attn_ext0 - * extrapolate(xsv_ext0, ysv_ext0, zsv_ext0, wsv_ext0, dx_ext0, dy_ext0, dz_ext0, dw_ext0); - } - - // Second extra vertex - double attn_ext1 = 2 - dx_ext1 * dx_ext1 - dy_ext1 * dy_ext1 - dz_ext1 * dz_ext1 - dw_ext1 * dw_ext1; - if (attn_ext1 > 0) { - attn_ext1 *= attn_ext1; - value += attn_ext1 * attn_ext1 - * extrapolate(xsv_ext1, ysv_ext1, zsv_ext1, wsv_ext1, dx_ext1, dy_ext1, dz_ext1, dw_ext1); - } - - // Third extra vertex - double attn_ext2 = 2 - dx_ext2 * dx_ext2 - dy_ext2 * dy_ext2 - dz_ext2 * dz_ext2 - dw_ext2 * dw_ext2; - if (attn_ext2 > 0) { - attn_ext2 *= attn_ext2; - value += attn_ext2 * attn_ext2 - * extrapolate(xsv_ext2, ysv_ext2, zsv_ext2, wsv_ext2, dx_ext2, dy_ext2, dz_ext2, dw_ext2); - } - - return value / NORM_CONSTANT_4D; - } - - private double extrapolate(int xsb, int ysb, double dx, double dy) { - int index = perm[(perm[xsb & 0xFF] + ysb) & 0xFF] & 0x0E; - return gradients2D[index] * dx + gradients2D[index + 1] * dy; - } - - private double extrapolate(int xsb, int ysb, int zsb, double dx, double dy, double dz) { - int index = permGradIndex3D[(perm[(perm[xsb & 0xFF] + ysb) & 0xFF] + zsb) & 0xFF]; - return gradients3D[index] * dx + gradients3D[index + 1] * dy + gradients3D[index + 2] * dz; - } - - private double extrapolate(int xsb, int ysb, int zsb, int wsb, double dx, double dy, double dz, double dw) { - int index = perm[(perm[(perm[(perm[xsb & 0xFF] + ysb) & 0xFF] + zsb) & 0xFF] + wsb) & 0xFF] & 0xFC; - return gradients4D[index] * dx + gradients4D[index + 1] * dy + gradients4D[index + 2] * dz - + gradients4D[index + 3] * dw; - } - - private static int fastFloor(double x) { - int xi = (int) x; - return x < xi ? xi - 1 : xi; - } - - // Gradients for 2D. They approximate the directions to the - // vertices of an octagon from the center. - private static byte[] gradients2D = new byte[] { 5, 2, 2, 5, -5, 2, -2, 5, 5, -2, 2, -5, -5, -2, -2, -5, }; - - // Gradients for 3D. They approximate the directions to the - // vertices of a rhombicuboctahedron from the center, skewed so - // that the triangular and square facets can be inscribed inside - // circles of the same radius. - private static byte[] gradients3D = new byte[] { -11, 4, 4, -4, 11, 4, -4, 4, 11, 11, 4, 4, 4, 11, 4, 4, 4, 11, -11, - -4, 4, -4, -11, 4, -4, -4, 11, 11, -4, 4, 4, -11, 4, 4, -4, 11, -11, 4, -4, -4, 11, -4, -4, 4, -11, 11, 4, - -4, 4, 11, -4, 4, 4, -11, -11, -4, -4, -4, -11, -4, -4, -4, -11, 11, -4, -4, 4, -11, -4, 4, -4, -11, }; - - // Gradients for 4D. They approximate the directions to the - // vertices of a disprismatotesseractihexadecachoron from the center, - // skewed so that the tetrahedral and cubic facets can be inscribed inside - // spheres of the same radius. - private static byte[] gradients4D = new byte[] { 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, -3, 1, 1, 1, -1, 3, - 1, 1, -1, 1, 3, 1, -1, 1, 1, 3, 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, 1, 1, -1, 1, 3, -3, -1, 1, 1, -1, -3, 1, - 1, -1, -1, 3, 1, -1, -1, 1, 3, 3, 1, -1, 1, 1, 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, -3, 1, -1, 1, -1, 3, -1, - 1, -1, 1, -3, 1, -1, 1, -1, 3, 3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, -3, -1, -1, 1, -1, - -3, -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, 3, 1, 1, -1, 1, 3, 1, -1, 1, 1, 3, -1, 1, 1, 1, -3, -3, 1, 1, -1, - -1, 3, 1, -1, -1, 1, 3, -1, -1, 1, 1, -3, 3, -1, 1, -1, 1, -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3, -3, -1, 1, - -1, -1, -3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, 3, 1, -1, -1, 1, 3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, -3, - 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, -1, -1, 1, -1, -3, 3, -1, -1, -1, 1, -3, -1, -1, 1, -1, -3, -1, 1, -1, - -1, -3, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, }; -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/noise/VoronoiNoise.java b/src/main/java/ru/bclib/noise/VoronoiNoise.java deleted file mode 100644 index 07bea100..00000000 --- a/src/main/java/ru/bclib/noise/VoronoiNoise.java +++ /dev/null @@ -1,140 +0,0 @@ -package ru.bclib.noise; - -import java.util.Random; - -import net.minecraft.core.BlockPos; -import ru.bclib.util.MHelper; - -public class VoronoiNoise { - private static final Random RANDOM = new Random(); - final int seed; - - public VoronoiNoise() { - this(0); - } - - public VoronoiNoise(int seed) { - this.seed = seed; - } - - private int getSeed(int x, int y, int z) { - int h = seed + x * 374761393 + y * 668265263 + z; - h = (h ^ (h >> 13)) * 1274126177; - return h ^ (h >> 16); - } - - public double sample(double x, double y, double z) { - int ix = MHelper.floor(x); - int iy = MHelper.floor(y); - int iz = MHelper.floor(z); - - float px = (float) (x - ix); - float py = (float) (y - iy); - float pz = (float) (z - iz); - - float d = 10; - - for (int pox = -1; pox < 2; pox++) { - for (int poy = -1; poy < 2; poy++) { - for (int poz = -1; poz < 2; poz++) { - RANDOM.setSeed(getSeed(pox + ix, poy + iy, poz + iz)); - float pointX = pox + RANDOM.nextFloat(); - float pointY = poy + RANDOM.nextFloat(); - float pointZ = poz + RANDOM.nextFloat(); - float d2 = MHelper.lengthSqr(pointX - px, pointY - py, pointZ - pz); - if (d2 < d) { - d = d2; - } - } - } - } - - return Math.sqrt(d); - } - - public Random getRandom(double x, double y, double z) { - int ix = MHelper.floor(x); - int iy = MHelper.floor(y); - int iz = MHelper.floor(z); - - float px = (float) (x - ix); - float py = (float) (y - iy); - float pz = (float) (z - iz); - - float d = 10; - - int posX = 0; - int posY = 0; - int posZ = 0; - - for (int pox = -1; pox < 2; pox++) { - for (int poy = -1; poy < 2; poy++) { - for (int poz = -1; poz < 2; poz++) { - RANDOM.setSeed(getSeed(pox + ix, poy + iy, poz + iz)); - float pointX = pox + RANDOM.nextFloat(); - float pointY = poy + RANDOM.nextFloat(); - float pointZ = poz + RANDOM.nextFloat(); - float d2 = MHelper.lengthSqr(pointX - px, pointY - py, pointZ - pz); - if (d2 < d) { - d = d2; - posX = pox; - posY = poy; - posZ = poz; - } - } - } - } - - posX += ix; - posY += iy; - posZ += iz; - - int seed = MHelper.getSeed(posY, posX, posZ); - RANDOM.setSeed(seed); - - return RANDOM; - } - - public BlockPos[] getPos(double x, double y, double z, double scale) { - int ix = MHelper.floor(x); - int iy = MHelper.floor(y); - int iz = MHelper.floor(z); - - float px = (float) (x - ix); - float py = (float) (y - iy); - float pz = (float) (z - iz); - - float d = 10; - float selX = 0; - float selY = 0; - float selZ = 0; - float selXPre = 0; - float selYPre = 0; - float selZPre = 0; - - for (int pox = -1; pox < 2; pox++) { - for (int poy = -1; poy < 2; poy++) { - for (int poz = -1; poz < 2; poz++) { - RANDOM.setSeed(getSeed(pox + ix, poy + iy, poz + iz)); - float pointX = pox + RANDOM.nextFloat(); - float pointY = poy + RANDOM.nextFloat(); - float pointZ = poz + RANDOM.nextFloat(); - float d2 = MHelper.lengthSqr(pointX - px, pointY - py, pointZ - pz); - if (d2 < d) { - d = d2; - selXPre = selX; - selYPre = selY; - selZPre = selZ; - selX = pointX; - selY = pointY; - selZ = pointZ; - } - } - } - } - - BlockPos p1 = new BlockPos((ix + (double) selX) * scale, (iy + (double) selY) * scale, (iz + (double) selZ) * scale); - BlockPos p2 = new BlockPos((ix + (double) selXPre) * scale, (iy + (double) selYPre) * scale, (iz + (double) selZPre) * scale); - return new BlockPos[] {p1, p2}; - } -} diff --git a/src/main/java/ru/bclib/recipes/BCLRecipeManager.java b/src/main/java/ru/bclib/recipes/BCLRecipeManager.java deleted file mode 100644 index 8e2f6e71..00000000 --- a/src/main/java/ru/bclib/recipes/BCLRecipeManager.java +++ /dev/null @@ -1,93 +0,0 @@ -package ru.bclib.recipes; - -import java.util.Map; -import java.util.Map.Entry; - -import com.google.common.collect.Maps; - -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.crafting.Recipe; -import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraft.world.level.ItemLike; -import net.minecraft.world.level.block.Block; - -public class BCLRecipeManager { - private static final Map, Map>> RECIPES = Maps.newHashMap(); - - public static void addRecipe(RecipeType type, Recipe recipe) { - Map> list = RECIPES.get(type); - if (list == null) { - list = Maps.newHashMap(); - RECIPES.put(type, list); - } - list.put(recipe.getId(), recipe); - } - - @SuppressWarnings("unchecked") - public static > T getRecipe(RecipeType type, ResourceLocation id) { - if (RECIPES.containsKey(type)) { - return (T) RECIPES.get(type).get(id); - } - return null; - } - - public static Map, Map>> getMap(Map, Map>> recipes) { - Map, Map>> result = Maps.newHashMap(); - - for (RecipeType type : recipes.keySet()) { - Map> typeList = Maps.newHashMap(); - typeList.putAll(recipes.get(type)); - result.put(type, typeList); - } - - for (RecipeType type : RECIPES.keySet()) { - Map> list = RECIPES.get(type); - if (list != null) { - Map> typeList = result.get(type); - if (typeList == null) { - typeList = Maps.newHashMap(); - result.put(type, typeList); - } - for (Entry> entry : list.entrySet()) { - ResourceLocation id = entry.getKey(); - if (!typeList.containsKey(id)) - typeList.put(id, entry.getValue()); - } - } - } - - return result; - } - - public static , T extends Recipe> S registerSerializer(String modID, String id, S serializer) { - return Registry.register(Registry.RECIPE_SERIALIZER, modID + ":" + id, serializer); - } - - public static > RecipeType registerType(String modID, String type) { - ResourceLocation recipeTypeId = new ResourceLocation(modID, type); - return Registry.register(Registry.RECIPE_TYPE, recipeTypeId, new RecipeType() { - public String toString() { - return type; - } - }); - } - - public static boolean exists(ItemLike item) { - if (item instanceof Block) { - return Registry.BLOCK.getKey((Block) item) != Registry.BLOCK.getDefaultKey(); - } else { - return Registry.ITEM.getKey(item.asItem()) != Registry.ITEM.getDefaultKey(); - } - } - - public static boolean exists(ItemLike... items) { - for (ItemLike item : items) { - if (!exists(item)) { - return false; - } - } - return true; - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/recipes/CraftingRecipes.java b/src/main/java/ru/bclib/recipes/CraftingRecipes.java deleted file mode 100644 index d28b5b97..00000000 --- a/src/main/java/ru/bclib/recipes/CraftingRecipes.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.bclib.recipes; - -import net.minecraft.tags.ItemTags; -import net.minecraft.world.item.Items; -import net.minecraft.world.level.block.Blocks; -import ru.bclib.BCLib; -import ru.bclib.api.TagAPI; -import ru.bclib.config.Configs; - -public class CraftingRecipes { - public static void init() { - GridRecipe.make(BCLib.MOD_ID, "tag_smith_table", Blocks.SMITHING_TABLE).setShape("II", "##", "##").addMaterial('#', ItemTags.PLANKS).addMaterial('I', TagAPI.IRON_INGOTS).checkConfig(Configs.RECIPE_CONFIG).build(); - GridRecipe.make(BCLib.MOD_ID, "tag_cauldron", Blocks.CAULDRON).setShape("I I", "I I", "III").addMaterial('I', TagAPI.IRON_INGOTS).checkConfig(Configs.RECIPE_CONFIG).build(); - GridRecipe.make(BCLib.MOD_ID, "tag_hopper", Blocks.HOPPER).setShape("I I", "ICI", " I ").addMaterial('I', TagAPI.IRON_INGOTS).addMaterial('C', TagAPI.ITEM_CHEST).checkConfig(Configs.RECIPE_CONFIG).build(); - GridRecipe.make(BCLib.MOD_ID, "tag_piston", Blocks.PISTON).setShape("WWW", "CIC", "CDC").addMaterial('I', TagAPI.IRON_INGOTS).addMaterial('D', Items.REDSTONE).addMaterial('C', Items.COBBLESTONE).addMaterial('W', ItemTags.PLANKS).checkConfig(Configs.RECIPE_CONFIG).build(); - GridRecipe.make(BCLib.MOD_ID, "tag_rail", Blocks.RAIL).setOutputCount(16).setShape("I I", "ISI", "I I").addMaterial('I', TagAPI.IRON_INGOTS).addMaterial('S', Items.STICK).checkConfig(Configs.RECIPE_CONFIG).build(); - GridRecipe.make(BCLib.MOD_ID, "tag_stonecutter", Blocks.STONECUTTER).setShape(" I ", "SSS").addMaterial('I', TagAPI.IRON_INGOTS).addMaterial('S', Items.STONE).checkConfig(Configs.RECIPE_CONFIG).build(); - GridRecipe.make(BCLib.MOD_ID, "tag_bucket", Items.BUCKET).setShape("I I", " I ").addMaterial('I', TagAPI.IRON_INGOTS).checkConfig(Configs.RECIPE_CONFIG).build(); - GridRecipe.make(BCLib.MOD_ID, "tag_compass", Items.COMPASS).setShape(" I ", "IDI", " I ").addMaterial('I', TagAPI.IRON_INGOTS).addMaterial('D', Items.REDSTONE).checkConfig(Configs.RECIPE_CONFIG).build(); - GridRecipe.make(BCLib.MOD_ID, "tag_minecart", Items.MINECART).setShape("I I", "III").addMaterial('I', TagAPI.IRON_INGOTS).checkConfig(Configs.RECIPE_CONFIG).build(); - GridRecipe.make(BCLib.MOD_ID, "tag_shield", Items.SHIELD).setShape("WIW", "WWW", " W ").addMaterial('I', TagAPI.IRON_INGOTS).addMaterial('W', ItemTags.PLANKS).checkConfig(Configs.RECIPE_CONFIG).build(); - - GridRecipe.make(BCLib.MOD_ID, "tag_hopper", Blocks.HOPPER) - .setShape("I I", "ICI", " I ") - .addMaterial('I', TagAPI.IRON_INGOTS) - .addMaterial('C', TagAPI.ITEM_CHEST) - .checkConfig(Configs.RECIPE_CONFIG) - .build(); - - GridRecipe.make(BCLib.MOD_ID, "tag_shulker_box", Blocks.SHULKER_BOX) - .setShape("S", "C", "S") - .addMaterial('S', Items.SHULKER_SHELL) - .addMaterial('C', TagAPI.ITEM_CHEST) - .checkConfig(Configs.RECIPE_CONFIG) - .build(); - } -} diff --git a/src/main/java/ru/bclib/recipes/FurnaceRecipe.java b/src/main/java/ru/bclib/recipes/FurnaceRecipe.java deleted file mode 100644 index 38d20a4b..00000000 --- a/src/main/java/ru/bclib/recipes/FurnaceRecipe.java +++ /dev/null @@ -1,102 +0,0 @@ -package ru.bclib.recipes; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.BlastingRecipe; -import net.minecraft.world.item.crafting.CampfireCookingRecipe; -import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraft.world.item.crafting.SmeltingRecipe; -import net.minecraft.world.item.crafting.SmokingRecipe; -import net.minecraft.world.level.ItemLike; -import ru.bclib.BCLib; -import ru.bclib.config.PathConfig; - -public class FurnaceRecipe { - private static final FurnaceRecipe INSTANCE = new FurnaceRecipe(); - - private ResourceLocation id; - private ItemLike input; - private ItemLike output; - private boolean exist; - private String group; - private int count; - private int time; - private float xp; - - private FurnaceRecipe() {} - - public static FurnaceRecipe make(String modID, String name, ItemLike input, ItemLike output) { - INSTANCE.id = new ResourceLocation(modID, name); - INSTANCE.group = ""; - INSTANCE.input = input; - INSTANCE.output = output; - INSTANCE.count = 1; - INSTANCE.time = 200; - INSTANCE.xp = 0; - INSTANCE.exist = BCLRecipeManager.exists(output) && BCLRecipeManager.exists(input); - return INSTANCE; - } - - public FurnaceRecipe checkConfig(PathConfig config) { - exist |= config.getBoolean("furnace", id.getPath(), true); - return this; - } - - public FurnaceRecipe setGroup(String group) { - this.group = group; - return this; - } - - public FurnaceRecipe setOutputCount(int count) { - this.count = count; - return this; - } - - public FurnaceRecipe setXP(float xp) { - this.xp = xp; - return this; - } - - public FurnaceRecipe setCookTime(int time) { - this.time = time; - return this; - } - - public void build() { - build(false, false, false); - } - - public void buildWithBlasting() { - build(true, false, false); - } - - public void buildFoodlike() { - build(false, true, true); - } - - public void build(boolean blasting, boolean campfire, boolean smoker) { - if (exist) { - SmeltingRecipe recipe = new SmeltingRecipe(id, group, Ingredient.of(input), new ItemStack(output, count), xp, time); - BCLRecipeManager.addRecipe(RecipeType.SMELTING, recipe); - - if (blasting) { - BlastingRecipe recipe2 = new BlastingRecipe(id, group, Ingredient.of(input), new ItemStack(output, count), xp, time / 2); - BCLRecipeManager.addRecipe(RecipeType.BLASTING, recipe2); - } - - if (campfire) { - CampfireCookingRecipe recipe2 = new CampfireCookingRecipe(id, group, Ingredient.of(input), new ItemStack(output, count), xp, time * 3); - BCLRecipeManager.addRecipe(RecipeType.CAMPFIRE_COOKING, recipe2); - } - - if (smoker) { - SmokingRecipe recipe2 = new SmokingRecipe(id, group, Ingredient.of(input), new ItemStack(output, count), xp, time / 2); - BCLRecipeManager.addRecipe(RecipeType.SMOKING, recipe2); - } - } - else { - BCLib.LOGGER.debug("Furnace recipe {} couldn't be added", id); - } - } -} diff --git a/src/main/java/ru/bclib/recipes/GridRecipe.java b/src/main/java/ru/bclib/recipes/GridRecipe.java deleted file mode 100644 index bfb9abe7..00000000 --- a/src/main/java/ru/bclib/recipes/GridRecipe.java +++ /dev/null @@ -1,126 +0,0 @@ -package ru.bclib.recipes; - -import java.util.Arrays; -import java.util.Map; - -import com.google.common.collect.Maps; - -import net.minecraft.core.NonNullList; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.Tag; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.CraftingRecipe; -import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraft.world.item.crafting.ShapedRecipe; -import net.minecraft.world.item.crafting.ShapelessRecipe; -import net.minecraft.world.level.ItemLike; -import ru.bclib.BCLib; -import ru.bclib.config.PathConfig; - -public class GridRecipe { - private static final GridRecipe INSTANCE = new GridRecipe(); - - private ResourceLocation id; - private ItemLike output; - - private String group; - private RecipeType type; - private boolean shaped; - private String[] shape; - private Map materialKeys = Maps.newHashMap(); - private int count; - private boolean exist = true; - - private GridRecipe() {} - - public static GridRecipe make(String modID, String name, ItemLike output) { - INSTANCE.id = new ResourceLocation(modID, name); - INSTANCE.output = output; - - INSTANCE.group = ""; - INSTANCE.type = RecipeType.CRAFTING; - INSTANCE.shaped = true; - INSTANCE.shape = new String[] {"#"}; - INSTANCE.materialKeys.clear(); - INSTANCE.count = 1; - - INSTANCE.exist = BCLRecipeManager.exists(output); - - return INSTANCE; - } - - public GridRecipe checkConfig(PathConfig config) { - exist |= config.getBoolean("grid", id.getPath(), true); - return this; - } - - public GridRecipe setGroup(String group) { - this.group = group; - return this; - } - - public GridRecipe setShape(String... shape) { - this.shape = shape; - return this; - } - - public GridRecipe setList(String shape) { - this.shape = new String[] { shape }; - this.shaped = false; - return this; - } - - public GridRecipe addMaterial(char key, Tag value) { - return addMaterial(key, Ingredient.of(value)); - } - - public GridRecipe addMaterial(char key, ItemStack... value) { - return addMaterial(key, Ingredient.of(Arrays.stream(value))); - } - - public GridRecipe addMaterial(char key, ItemLike... values) { - for (ItemLike item: values) { - exist &= BCLRecipeManager.exists(item); - } - return addMaterial(key, Ingredient.of(values)); - } - - private GridRecipe addMaterial(char key, Ingredient value) { - materialKeys.put(key, value); - return this; - } - - public GridRecipe setOutputCount(int count) { - this.count = count; - return this; - } - - private NonNullList getMaterials(int width, int height) { - NonNullList materials = NonNullList.withSize(width * height, Ingredient.EMPTY); - int pos = 0; - for (String line: shape) { - for (int i = 0; i < width; i++) { - char c = line.charAt(i); - Ingredient material = materialKeys.get(c); - materials.set(pos ++, material == null ? Ingredient.EMPTY : material); - } - } - return materials; - } - - public void build() { - if (exist) { - int height = shape.length; - int width = shape[0].length(); - ItemStack result = new ItemStack(output, count); - NonNullList materials = this.getMaterials(width, height); - - CraftingRecipe recipe = shaped ? new ShapedRecipe(id, group, width, height, materials, result) : new ShapelessRecipe(id, group, result, materials); - BCLRecipeManager.addRecipe(type, recipe); - } else { - BCLib.LOGGER.debug("Recipe {} couldn't be added", id); - } - } -} diff --git a/src/main/java/ru/bclib/recipes/SmithingTableRecipe.java b/src/main/java/ru/bclib/recipes/SmithingTableRecipe.java deleted file mode 100644 index bc6aa702..00000000 --- a/src/main/java/ru/bclib/recipes/SmithingTableRecipe.java +++ /dev/null @@ -1,106 +0,0 @@ -package ru.bclib.recipes; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.Tag; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.Ingredient; -import net.minecraft.world.item.crafting.RecipeType; -import net.minecraft.world.item.crafting.UpgradeRecipe; -import net.minecraft.world.level.ItemLike; -import ru.bclib.BCLib; -import ru.bclib.config.PathConfig; - -public class SmithingTableRecipe { - - private final static SmithingTableRecipe BUILDER = new SmithingTableRecipe(); - private final static RecipeType TYPE = RecipeType.SMITHING; - - public static SmithingTableRecipe create(String modID, String name) { - return create(new ResourceLocation(modID, name)); - } - - public static SmithingTableRecipe create(ResourceLocation id) { - BUILDER.id = id; - BUILDER.base = null; - BUILDER.addition = null; - BUILDER.result = null; - BUILDER.alright = true; - - return BUILDER; - } - - private ResourceLocation id; - private Ingredient base; - private Ingredient addition; - private ItemStack result; - private boolean alright; - private boolean exist; - - private SmithingTableRecipe() {} - - public SmithingTableRecipe checkConfig(PathConfig config) { - exist |= config.getBoolean("smithing", id.getPath(), true); - return this; - } - - public SmithingTableRecipe setResult(ItemLike item) { - return this.setResult(item, 1); - } - - public SmithingTableRecipe setResult(ItemLike item, int count) { - this.alright &= BCLRecipeManager.exists(item); - this.result = new ItemStack(item, count); - return this; - } - - public SmithingTableRecipe setBase(ItemLike... items) { - this.alright &= BCLRecipeManager.exists(items); - this.base = Ingredient.of(items); - return this; - } - - public SmithingTableRecipe setBase(Tag tag) { - this.base = (Ingredient.of(tag)); - return this; - } - - public SmithingTableRecipe setAddition(ItemLike... items) { - this.alright &= BCLRecipeManager.exists(items); - this.addition = Ingredient.of(items); - return this; - } - - public SmithingTableRecipe setAddition(Tag tag) { - this.addition = (Ingredient.of(tag)); - return this; - } - - public void build() { - if (!exist) { - return; - } - - if (base == null) { - BCLib.LOGGER.warning("Base input for Smithing recipe can't be 'null', recipe {} will be ignored!", id); - return; - } - if (addition == null) { - BCLib.LOGGER.warning("Addition input for Smithing recipe can't be 'null', recipe {} will be ignored!", id); - return; - } - if(result == null) { - BCLib.LOGGER.warning("Result for Smithing recipe can't be 'null', recipe {} will be ignored!", id); - return; - } - if (BCLRecipeManager.getRecipe(TYPE, id) != null) { - BCLib.LOGGER.warning("Can't add Smithing recipe! Id {} already exists!", id); - return; - } - if (!alright) { - BCLib.LOGGER.debug("Can't add Smithing recipe {}! Ingeredients or output not exists.", id); - return; - } - BCLRecipeManager.addRecipe(TYPE, new UpgradeRecipe(id, base, addition, result)); - } -} diff --git a/src/main/java/ru/bclib/registry/BaseBlockEntities.java b/src/main/java/ru/bclib/registry/BaseBlockEntities.java deleted file mode 100644 index 989e318a..00000000 --- a/src/main/java/ru/bclib/registry/BaseBlockEntities.java +++ /dev/null @@ -1,56 +0,0 @@ -package ru.bclib.registry; - -import java.util.function.Supplier; - -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.BlockItem; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import ru.bclib.BCLib; -import ru.bclib.blockentities.BaseBarrelBlockEntity; -import ru.bclib.blockentities.BaseChestBlockEntity; -import ru.bclib.blockentities.BaseFurnaceBlockEntity; -import ru.bclib.blockentities.BaseSignBlockEntity; -import ru.bclib.blockentities.DynamicBlockEntityType; -import ru.bclib.blocks.BaseBarrelBlock; -import ru.bclib.blocks.BaseChestBlock; -import ru.bclib.blocks.BaseFurnaceBlock; -import ru.bclib.blocks.BaseSignBlock; - -public class BaseBlockEntities { - public static final DynamicBlockEntityType CHEST = registerBlockEntityType(BCLib.makeID("chest"), BaseChestBlockEntity::new); - public static final DynamicBlockEntityType BARREL = registerBlockEntityType(BCLib.makeID("barrel"), BaseBarrelBlockEntity::new); - public static final DynamicBlockEntityType SIGN = registerBlockEntityType(BCLib.makeID("sign"), BaseSignBlockEntity::new); - public static final DynamicBlockEntityType FURNACE = registerBlockEntityType(BCLib.makeID("furnace"), BaseFurnaceBlockEntity::new); - - public static DynamicBlockEntityType registerBlockEntityType(ResourceLocation typeId, Supplier supplier) { - return Registry.register(Registry.BLOCK_ENTITY_TYPE, typeId, new DynamicBlockEntityType<>(supplier)); - } - - public static void register() {} - - public static Block[] getChests() { - return BaseRegistry.getRegisteredBlocks().values().stream() - .filter(item -> item instanceof BlockItem && ((BlockItem) item).getBlock() instanceof BaseChestBlock) - .map(item -> ((BlockItem) item).getBlock()).toArray(Block[]::new); - } - - public static Block[] getBarrels() { - return BaseRegistry.getRegisteredBlocks().values().stream() - .filter(item -> item instanceof BlockItem && ((BlockItem) item).getBlock() instanceof BaseBarrelBlock) - .map(item -> ((BlockItem) item).getBlock()).toArray(Block[]::new); - } - - public static Block[] getSigns() { - return BaseRegistry.getRegisteredBlocks().values().stream() - .filter(item -> item instanceof BlockItem && ((BlockItem) item).getBlock() instanceof BaseSignBlock) - .map(item -> ((BlockItem) item).getBlock()).toArray(Block[]::new); - } - - public static Block[] getFurnaces() { - return BaseRegistry.getRegisteredBlocks().values().stream() - .filter(item -> item instanceof BlockItem && ((BlockItem) item).getBlock() instanceof BaseFurnaceBlock) - .map(item -> ((BlockItem) item).getBlock()).toArray(Block[]::new); - } -} diff --git a/src/main/java/ru/bclib/registry/BaseBlockEntityRenders.java b/src/main/java/ru/bclib/registry/BaseBlockEntityRenders.java deleted file mode 100644 index 5b8050ba..00000000 --- a/src/main/java/ru/bclib/registry/BaseBlockEntityRenders.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.bclib.registry; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.client.rendereregistry.v1.BlockEntityRendererRegistry; -import ru.bclib.client.render.BaseChestBlockEntityRenderer; -import ru.bclib.client.render.BaseSignBlockEntityRenderer; - -@Environment(EnvType.CLIENT) -public class BaseBlockEntityRenders { - public static void register() { - BlockEntityRendererRegistry.INSTANCE.register(BaseBlockEntities.CHEST, BaseChestBlockEntityRenderer::new); - BlockEntityRendererRegistry.INSTANCE.register(BaseBlockEntities.SIGN, BaseSignBlockEntityRenderer::new); - } -} diff --git a/src/main/java/ru/bclib/registry/BaseRegistry.java b/src/main/java/ru/bclib/registry/BaseRegistry.java deleted file mode 100644 index cf9796b5..00000000 --- a/src/main/java/ru/bclib/registry/BaseRegistry.java +++ /dev/null @@ -1,83 +0,0 @@ -package ru.bclib.registry; - -import java.util.List; -import java.util.Map; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import net.fabricmc.fabric.api.item.v1.FabricItemSettings; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.Items; -import ru.bclib.BCLib; - -public abstract class BaseRegistry { - - private static final List> REGISTRIES = Lists.newArrayList(); - private static final Map> MOD_BLOCKS = Maps.newHashMap(); - private static final Map> MOD_ITEMS = Maps.newHashMap(); - - public static Map> getRegisteredBlocks() { - return MOD_BLOCKS; - } - - public static Map> getRegisteredItems() { - return MOD_ITEMS; - } - - public static List getModBlocks(String modId) { - if (MOD_BLOCKS.containsKey(modId)) { - return MOD_BLOCKS.get(modId); - } - List modBlocks = Lists.newArrayList(); - MOD_BLOCKS.put(modId, modBlocks); - return modBlocks; - } - - public static List getModItems(String modId) { - if (MOD_ITEMS.containsKey(modId)) { - return MOD_ITEMS.get(modId); - } - List modBlocks = Lists.newArrayList(); - MOD_ITEMS.put(modId, modBlocks); - return modBlocks; - } - - public static void register() { - REGISTRIES.forEach(BaseRegistry::registerInternal); - } - - protected final CreativeModeTab creativeTab; - - protected BaseRegistry(CreativeModeTab creativeTab) { - this.creativeTab = creativeTab; - REGISTRIES.add(this); - } - - public T register(String name, T obj) { - return register(createModId(name), obj); - } - - public abstract T register(ResourceLocation objId, T obj); - - public ResourceLocation createModId(String name) { - return BCLib.makeID(name); - } - - public void registerItem(ResourceLocation id, Item item, List registry) { - if (item != Items.AIR) { - Registry.register(Registry.ITEM, id, item); - registry.add(item); - } - } - - public FabricItemSettings makeItemSettings() { - FabricItemSettings properties = new FabricItemSettings(); - return (FabricItemSettings) properties.tab(creativeTab); - } - - private void registerInternal() {} -} diff --git a/src/main/java/ru/bclib/registry/BlocksRegistry.java b/src/main/java/ru/bclib/registry/BlocksRegistry.java deleted file mode 100644 index bd663f50..00000000 --- a/src/main/java/ru/bclib/registry/BlocksRegistry.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.bclib.registry; - -import net.fabricmc.fabric.api.registry.FlammableBlockRegistry; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.BlockItem; -import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.Item.Properties; -import net.minecraft.world.item.WaterLilyBlockItem; -import net.minecraft.world.level.block.Block; -import ru.bclib.interfaces.ISpetialItem; - -public abstract class BlocksRegistry extends BaseRegistry { - - protected BlocksRegistry(CreativeModeTab creativeTab) { - super(creativeTab); - } - - @Override - public Block register(ResourceLocation id, Block block) { - int maxCount = 64; - boolean placeOnWater = false; - if (block instanceof ISpetialItem) { - ISpetialItem item = (ISpetialItem) block; - maxCount = item.getStackSize(); - placeOnWater = item.canPlaceOnWater(); - } - Properties item = makeItemSettings().stacksTo(maxCount); - if (placeOnWater) { - registerBlockItem(id, new WaterLilyBlockItem(block, item)); - } else { - registerBlockItem(id, new BlockItem(block, item)); - } - if (block.defaultBlockState().getMaterial().isFlammable() && FlammableBlockRegistry.getDefaultInstance().get(block).getBurnChance() == 0) { - FlammableBlockRegistry.getDefaultInstance().add(block, 5, 5); - } - return Registry.register(Registry.BLOCK, id, block); - } - - public Block registerBlockOnly(String name, Block block) { - return Registry.register(Registry.BLOCK, createModId(name), block); - } - - public Item registerBlockItem(ResourceLocation id, Item item) { - registerItem(id, item, BaseRegistry.getModBlocks(id.getNamespace())); - return item; - } -} diff --git a/src/main/java/ru/bclib/registry/ItemsRegistry.java b/src/main/java/ru/bclib/registry/ItemsRegistry.java deleted file mode 100644 index 57d884bf..00000000 --- a/src/main/java/ru/bclib/registry/ItemsRegistry.java +++ /dev/null @@ -1,109 +0,0 @@ -package ru.bclib.registry; - -import net.fabricmc.fabric.api.tool.attribute.v1.FabricToolTags; -import net.minecraft.core.BlockSource; -import net.minecraft.core.Direction; -import net.minecraft.core.dispenser.DefaultDispenseItemBehavior; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.tags.Tag; -import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.MobSpawnType; -import net.minecraft.world.food.FoodProperties; -import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.ShovelItem; -import net.minecraft.world.item.SpawnEggItem; -import net.minecraft.world.item.SwordItem; -import net.minecraft.world.item.TieredItem; -import net.minecraft.world.level.block.DispenserBlock; -import ru.bclib.items.BaseDiscItem; -import ru.bclib.items.BaseDrinkItem; -import ru.bclib.items.BaseSpawnEggItem; -import ru.bclib.items.ModelProviderItem; -import ru.bclib.items.tool.BaseAxeItem; -import ru.bclib.items.tool.BaseHoeItem; -import ru.bclib.items.tool.BasePickaxeItem; -import ru.bclib.util.TagHelper; - -public abstract class ItemsRegistry extends BaseRegistry { - - protected ItemsRegistry(CreativeModeTab creativeTab) { - super(creativeTab); - } - - public Item registerDisc(String name, int power, SoundEvent sound) { - return register(name, new BaseDiscItem(power, sound, makeItemSettings().stacksTo(1))); - } - - public Item registerItem(String name) { - return register(name, new ModelProviderItem(makeItemSettings())); - } - - @Override - public Item register(ResourceLocation itemId, Item item) { - registerItem(itemId, item, BaseRegistry.getModItems(itemId.getNamespace())); - return item; - } - - public TieredItem registerTool(String name, TieredItem item) { - ResourceLocation id = createModId(name); - registerItem(id, item, BaseRegistry.getModItems(id.getNamespace())); - - if (item instanceof ShovelItem) { - TagHelper.addTag((Tag.Named) FabricToolTags.SHOVELS, item); - } else if (item instanceof SwordItem) { - TagHelper.addTag((Tag.Named) FabricToolTags.SWORDS, item); - } else if (item instanceof BasePickaxeItem) { - TagHelper.addTag((Tag.Named) FabricToolTags.PICKAXES, item); - } else if (item instanceof BaseAxeItem) { - TagHelper.addTag((Tag.Named) FabricToolTags.AXES, item); - } else if (item instanceof BaseHoeItem) { - TagHelper.addTag((Tag.Named) FabricToolTags.HOES, item); - } - - return item; - } - - public Item registerEgg(String name, EntityType type, int background, int dots) { - SpawnEggItem item = new BaseSpawnEggItem(type, background, dots, makeItemSettings()); - DefaultDispenseItemBehavior behavior = new DefaultDispenseItemBehavior() { - public ItemStack execute(BlockSource pointer, ItemStack stack) { - Direction direction = pointer.getBlockState().getValue(DispenserBlock.FACING); - EntityType entityType = ((SpawnEggItem) stack.getItem()).getType(stack.getTag()); - entityType.spawn(pointer.getLevel(), stack, null, pointer.getPos().relative(direction), MobSpawnType.DISPENSER, direction != Direction.UP, false); - stack.shrink(1); - return stack; - } - }; - DispenserBlock.registerBehavior(item, behavior); - return register(name, item); - } - - public Item registerFood(String name, int hunger, float saturation, MobEffectInstance... effects) { - FoodProperties.Builder builder = new FoodProperties.Builder().nutrition(hunger).saturationMod(saturation); - for (MobEffectInstance effect: effects) { - builder.effect(effect, 1F); - } - return registerFood(name, builder.build()); - } - - public Item registerFood(String name, FoodProperties foodComponent) { - return register(name, new ModelProviderItem(makeItemSettings().food(foodComponent))); - } - - public Item registerDrink(String name) { - return register(name, new BaseDrinkItem(makeItemSettings().stacksTo(1))); - } - - public Item registerDrink(String name, FoodProperties foodComponent) { - return register(name, new BaseDrinkItem(makeItemSettings().stacksTo(1).food(foodComponent))); - } - - public Item registerDrink(String name, int hunger, float saturation) { - FoodProperties.Builder builder = new FoodProperties.Builder().nutrition(hunger).saturationMod(saturation); - return registerDrink(name, builder.build()); - } -} diff --git a/src/main/java/ru/bclib/sdf/PosInfo.java b/src/main/java/ru/bclib/sdf/PosInfo.java deleted file mode 100644 index b8a64c4d..00000000 --- a/src/main/java/ru/bclib/sdf/PosInfo.java +++ /dev/null @@ -1,104 +0,0 @@ -package ru.bclib.sdf; - -import java.util.Map; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; - -public class PosInfo implements Comparable { - private static final BlockState AIR = Blocks.AIR.defaultBlockState(); - private final Map blocks; - private final Map add; - private final BlockPos pos; - private BlockState state; - - public static PosInfo create(Map blocks, Map add, BlockPos pos) { - return new PosInfo(blocks, add, pos); - } - - private PosInfo(Map blocks, Map add, BlockPos pos) { - this.blocks = blocks; - this.add = add; - this.pos = pos; - blocks.put(pos, this); - } - - public BlockState getState() { - return state; - } - - public BlockState getState(BlockPos pos) { - PosInfo info = blocks.get(pos); - if (info == null) { - info = add.get(pos); - return info == null ? AIR : info.getState(); - } - return info.getState(); - } - - public void setState(BlockState state) { - this.state = state; - } - - public void setState(BlockPos pos, BlockState state) { - PosInfo info = blocks.get(pos); - if (info != null) { - info.setState(state); - } - } - - public BlockState getState(Direction dir) { - PosInfo info = blocks.get(pos.relative(dir)); - if (info == null) { - info = add.get(pos.relative(dir)); - return info == null ? AIR : info.getState(); - } - return info.getState(); - } - - public BlockState getState(Direction dir, int distance) { - PosInfo info = blocks.get(pos.relative(dir, distance)); - if (info == null) { - return AIR; - } - return info.getState(); - } - - public BlockState getStateUp() { - return getState(Direction.UP); - } - - public BlockState getStateDown() { - return getState(Direction.DOWN); - } - - @Override - public int hashCode() { - return pos.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof PosInfo)) { - return false; - } - return pos.equals(((PosInfo) obj).pos); - } - - @Override - public int compareTo(PosInfo info) { - return this.pos.getY() - info.pos.getY(); - } - - public BlockPos getPos() { - return pos; - } - - public void setBlockPos(BlockPos pos, BlockState state) { - PosInfo info = new PosInfo(blocks, add, pos); - info.state = state; - add.put(pos, info); - } -} diff --git a/src/main/java/ru/bclib/sdf/SDF.java b/src/main/java/ru/bclib/sdf/SDF.java deleted file mode 100644 index 396850de..00000000 --- a/src/main/java/ru/bclib/sdf/SDF.java +++ /dev/null @@ -1,310 +0,0 @@ -package ru.bclib.sdf; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.BlockPos.MutableBlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.level.ServerLevelAccessor; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.AABB; -import ru.bclib.util.BlocksHelper; -import ru.bclib.world.structures.StructureWorld; - -public abstract class SDF { - private List> postProcesses = Lists.newArrayList(); - private Function canReplace = (state) -> { - return state.getMaterial().isReplaceable(); - }; - - public abstract float getDistance(float x, float y, float z); - - public abstract BlockState getBlockState(BlockPos pos); - - public SDF addPostProcess(Function postProcess) { - this.postProcesses.add(postProcess); - return this; - } - - public SDF setReplaceFunction(Function canReplace) { - this.canReplace = canReplace; - return this; - } - - public void fillRecursive(ServerLevelAccessor world, BlockPos start) { - Map mapWorld = Maps.newHashMap(); - Map addInfo = Maps.newHashMap(); - Set blocks = Sets.newHashSet(); - Set ends = Sets.newHashSet(); - Set add = Sets.newHashSet(); - ends.add(new BlockPos(0, 0, 0)); - boolean run = true; - - MutableBlockPos bPos = new MutableBlockPos(); - - while (run) { - for (BlockPos center: ends) { - for (Direction dir: Direction.values()) { - bPos.set(center).move(dir); - BlockPos wpos = bPos.offset(start); - - if (!blocks.contains(bPos) && canReplace.apply(world.getBlockState(wpos))) { - if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) { - BlockState state = getBlockState(wpos); - PosInfo.create(mapWorld, addInfo, wpos).setState(state); - add.add(bPos.immutable()); - } - } - } - } - - blocks.addAll(ends); - ends.clear(); - ends.addAll(add); - add.clear(); - - run &= !ends.isEmpty(); - } - - List infos = new ArrayList(mapWorld.values()); - if (infos.size() > 0) { - Collections.sort(infos); - postProcesses.forEach((postProcess) -> { - infos.forEach((info) -> { - info.setState(postProcess.apply(info)); - }); - }); - infos.forEach((info) -> { - BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); - }); - - infos.clear(); - infos.addAll(addInfo.values()); - Collections.sort(infos); - postProcesses.forEach((postProcess) -> { - infos.forEach((info) -> { - info.setState(postProcess.apply(info)); - }); - }); - infos.forEach((info) -> { - if (canReplace.apply(world.getBlockState(info.getPos()))) { - BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); - } - }); - } - } - - public void fillArea(ServerLevelAccessor world, BlockPos center, AABB box) { - Map mapWorld = Maps.newHashMap(); - Map addInfo = Maps.newHashMap(); - - MutableBlockPos mut = new MutableBlockPos(); - for (int y = (int) box.minY; y <= box.maxY; y++) { - mut.setY(y); - for (int x = (int) box.minX; x <= box.maxX; x++) { - mut.setX(x); - for (int z = (int) box.minZ; z <= box.maxZ; z++) { - mut.setZ(z); - if (canReplace.apply(world.getBlockState(mut))) { - BlockPos fpos = mut.subtract(center); - if (this.getDistance(fpos.getX(), fpos.getY(), fpos.getZ()) < 0) { - PosInfo.create(mapWorld, addInfo, mut.immutable()).setState(getBlockState(mut)); - } - } - } - } - } - - List infos = new ArrayList(mapWorld.values()); - if (infos.size() > 0) { - Collections.sort(infos); - postProcesses.forEach((postProcess) -> { - infos.forEach((info) -> { - info.setState(postProcess.apply(info)); - }); - }); - infos.forEach((info) -> { - BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); - }); - - infos.clear(); - infos.addAll(addInfo.values()); - Collections.sort(infos); - postProcesses.forEach((postProcess) -> { - infos.forEach((info) -> { - info.setState(postProcess.apply(info)); - }); - }); - infos.forEach((info) -> { - if (canReplace.apply(world.getBlockState(info.getPos()))) { - BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); - } - }); - } - } - - public void fillRecursiveIgnore(ServerLevelAccessor world, BlockPos start, Function ignore) { - Map mapWorld = Maps.newHashMap(); - Map addInfo = Maps.newHashMap(); - Set blocks = Sets.newHashSet(); - Set ends = Sets.newHashSet(); - Set add = Sets.newHashSet(); - ends.add(new BlockPos(0, 0, 0)); - boolean run = true; - - MutableBlockPos bPos = new MutableBlockPos(); - - while (run) { - for (BlockPos center: ends) { - for (Direction dir: Direction.values()) { - bPos.set(center).move(dir); - BlockPos wpos = bPos.offset(start); - BlockState state = world.getBlockState(wpos); - boolean ign = ignore.apply(state); - if (!blocks.contains(bPos) && (ign || canReplace.apply(state))) { - if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) { - PosInfo.create(mapWorld, addInfo, wpos).setState(ign ? state : getBlockState(bPos)); - add.add(bPos.immutable()); - } - } - } - } - - blocks.addAll(ends); - ends.clear(); - ends.addAll(add); - add.clear(); - - run &= !ends.isEmpty(); - } - - List infos = new ArrayList(mapWorld.values()); - if (infos.size() > 0) { - Collections.sort(infos); - postProcesses.forEach((postProcess) -> { - infos.forEach((info) -> { - info.setState(postProcess.apply(info)); - }); - }); - infos.forEach((info) -> { - BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); - }); - - infos.clear(); - infos.addAll(addInfo.values()); - Collections.sort(infos); - postProcesses.forEach((postProcess) -> { - infos.forEach((info) -> { - info.setState(postProcess.apply(info)); - }); - }); - infos.forEach((info) -> { - if (canReplace.apply(world.getBlockState(info.getPos()))) { - BlocksHelper.setWithoutUpdate(world, info.getPos(), info.getState()); - } - }); - } - } - - public void fillRecursive(StructureWorld world, BlockPos start) { - Map mapWorld = Maps.newHashMap(); - Map addInfo = Maps.newHashMap(); - Set blocks = Sets.newHashSet(); - Set ends = Sets.newHashSet(); - Set add = Sets.newHashSet(); - ends.add(new BlockPos(0, 0, 0)); - boolean run = true; - - MutableBlockPos bPos = new MutableBlockPos(); - - while (run) { - for (BlockPos center: ends) { - for (Direction dir: Direction.values()) { - bPos.set(center).move(dir); - BlockPos wpos = bPos.offset(start); - - if (!blocks.contains(bPos)) { - if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) { - BlockState state = getBlockState(wpos); - PosInfo.create(mapWorld, addInfo, wpos).setState(state); - add.add(bPos.immutable()); - } - } - } - } - - blocks.addAll(ends); - ends.clear(); - ends.addAll(add); - add.clear(); - - run &= !ends.isEmpty(); - } - - List infos = new ArrayList(mapWorld.values()); - Collections.sort(infos); - postProcesses.forEach((postProcess) -> { - infos.forEach((info) -> { - info.setState(postProcess.apply(info)); - }); - }); - infos.forEach((info) -> { - world.setBlock(info.getPos(), info.getState()); - }); - - infos.clear(); - infos.addAll(addInfo.values()); - Collections.sort(infos); - postProcesses.forEach((postProcess) -> { - infos.forEach((info) -> { - info.setState(postProcess.apply(info)); - }); - }); - infos.forEach((info) -> { - world.setBlock(info.getPos(), info.getState()); - }); - } - - public Set getPositions(ServerLevelAccessor world, BlockPos start) { - Set blocks = Sets.newHashSet(); - Set ends = Sets.newHashSet(); - Set add = Sets.newHashSet(); - ends.add(new BlockPos(0, 0, 0)); - boolean run = true; - - MutableBlockPos bPos = new MutableBlockPos(); - - while (run) { - for (BlockPos center: ends) { - for (Direction dir: Direction.values()) { - bPos.set(center).move(dir); - BlockPos wpos = bPos.offset(start); - BlockState state = world.getBlockState(wpos); - if (!blocks.contains(wpos) && canReplace.apply(state)) { - if (this.getDistance(bPos.getX(), bPos.getY(), bPos.getZ()) < 0) { - add.add(bPos.immutable()); - } - } - } - } - - ends.forEach((end) -> blocks.add(end.offset(start))); - ends.clear(); - ends.addAll(add); - add.clear(); - - run &= !ends.isEmpty(); - } - - return blocks; - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFBinary.java b/src/main/java/ru/bclib/sdf/operator/SDFBinary.java deleted file mode 100644 index 0e223e54..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFBinary.java +++ /dev/null @@ -1,34 +0,0 @@ -package ru.bclib.sdf.operator; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.sdf.SDF; - -public abstract class SDFBinary extends SDF { - protected SDF sourceA; - protected SDF sourceB; - protected boolean firstValue; - - public SDFBinary setSourceA(SDF sourceA) { - this.sourceA = sourceA; - return this; - } - - public SDFBinary setSourceB(SDF sourceB) { - this.sourceB = sourceB; - return this; - } - - protected void selectValue(float a, float b) { - firstValue = a < b; - } - - @Override - public BlockState getBlockState(BlockPos pos) { - if (firstValue) { - return sourceA.getBlockState(pos); - } else { - return sourceB.getBlockState(pos); - } - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFCoordModify.java b/src/main/java/ru/bclib/sdf/operator/SDFCoordModify.java deleted file mode 100644 index 1b096f5e..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFCoordModify.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.bclib.sdf.operator; - -import java.util.function.Consumer; - -import com.mojang.math.Vector3f; - -public class SDFCoordModify extends SDFUnary { - private static final Vector3f POS = new Vector3f(); - private Consumer function; - - public SDFCoordModify setFunction(Consumer function) { - this.function = function; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - POS.set(x, y, z); - function.accept(POS); - return this.source.getDistance(POS.x(), POS.y(), POS.z()); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFCopyRotate.java b/src/main/java/ru/bclib/sdf/operator/SDFCopyRotate.java deleted file mode 100644 index 2960f254..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFCopyRotate.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.bclib.sdf.operator; - -import ru.bclib.util.MHelper; - -public class SDFCopyRotate extends SDFUnary { - int count = 1; - - public SDFCopyRotate setCount(int count) { - this.count = count; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - float px = (float) Math.atan2(x, z); - float pz = MHelper.length(x, z); - return this.source.getDistance(px, y, pz); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFDisplacement.java b/src/main/java/ru/bclib/sdf/operator/SDFDisplacement.java deleted file mode 100644 index 5ca88dee..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFDisplacement.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.bclib.sdf.operator; - -import java.util.function.Function; - -import com.mojang.math.Vector3f; - -public class SDFDisplacement extends SDFUnary { - private static final Vector3f POS = new Vector3f(); - private Function displace; - - public SDFDisplacement setFunction(Function displace) { - this.displace = displace; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - POS.set(x, y, z); - return this.source.getDistance(x, y, z) + displace.apply(POS); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFFlatWave.java b/src/main/java/ru/bclib/sdf/operator/SDFFlatWave.java deleted file mode 100644 index d4d2174d..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFFlatWave.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.bclib.sdf.operator; - -public class SDFFlatWave extends SDFDisplacement { - private int rayCount = 1; - private float intensity; - private float angle; - - public SDFFlatWave() { - setFunction((pos) -> { - return (float) Math.cos(Math.atan2(pos.x(), pos.z()) * rayCount + angle) * intensity; - }); - } - - public SDFFlatWave setRaysCount(int count) { - this.rayCount = count; - return this; - } - - public SDFFlatWave setAngle(float angle) { - this.angle = angle; - return this; - } - - public SDFFlatWave setIntensity(float intensity) { - this.intensity = intensity; - return this; - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFHeightmap.java b/src/main/java/ru/bclib/sdf/operator/SDFHeightmap.java deleted file mode 100644 index 5629607a..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFHeightmap.java +++ /dev/null @@ -1,64 +0,0 @@ -package ru.bclib.sdf.operator; - -import com.mojang.blaze3d.platform.NativeImage; - -import net.minecraft.util.Mth; - -public class SDFHeightmap extends SDFDisplacement { - private float intensity = 1F; - private NativeImage map; - private float offsetX; - private float offsetZ; - private float scale; - private float cos = 1; - private float sin = 0; - - public SDFHeightmap() { - setFunction((pos) -> { - if (map == null) { - return 0F; - } - float px = Mth.clamp(pos.x() * scale + offsetX, 0, map.getWidth() - 2); - float pz = Mth.clamp(pos.z() * scale + offsetZ, 0, map.getHeight() - 2); - float dx = (px * cos - pz * sin); - float dz = (pz * cos + px * sin); - int x1 = Mth.floor(dx); - int z1 = Mth.floor(dz); - int x2 = x1 + 1; - int z2 = z1 + 1; - dx = dx - x1; - dz = dz - z1; - float a = (map.getPixelRGBA(x1, z1) & 255) / 255F; - float b = (map.getPixelRGBA(x2, z1) & 255) / 255F; - float c = (map.getPixelRGBA(x1, z2) & 255) / 255F; - float d = (map.getPixelRGBA(x2, z2) & 255) / 255F; - a = Mth.lerp(dx, a, b); - b = Mth.lerp(dx, c, d); - return -Mth.lerp(dz, a, b) * intensity; - }); - } - - public SDFHeightmap setMap(NativeImage map) { - this.map = map; - offsetX = map.getWidth() * 0.5F; - offsetZ = map.getHeight() * 0.5F; - scale = map.getWidth(); - return this; - } - - public SDFHeightmap setAngle(float angle) { - sin = Mth.sin(angle); - cos = Mth.cos(angle); - return this; - } - - public SDFHeightmap setScale(float scale) { - this.scale = map.getWidth() * scale; - return this; - } - - public SDFHeightmap setIntensity(float intensity) { - this.intensity = intensity; - return this; - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFIntersection.java b/src/main/java/ru/bclib/sdf/operator/SDFIntersection.java deleted file mode 100644 index d5ef436c..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFIntersection.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.bclib.sdf.operator; - -import ru.bclib.util.MHelper; - -public class SDFIntersection extends SDFBinary { - @Override - public float getDistance(float x, float y, float z) { - float a = this.sourceA.getDistance(x, y, z); - float b = this.sourceB.getDistance(x, y, z); - this.selectValue(a, b); - return MHelper.max(a, b); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFInvert.java b/src/main/java/ru/bclib/sdf/operator/SDFInvert.java deleted file mode 100644 index b6cb1be0..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFInvert.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.bclib.sdf.operator; - -public class SDFInvert extends SDFUnary { - @Override - public float getDistance(float x, float y, float z) { - return -this.source.getDistance(x, y, z); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFRadialNoiseMap.java b/src/main/java/ru/bclib/sdf/operator/SDFRadialNoiseMap.java deleted file mode 100644 index 921768db..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFRadialNoiseMap.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.bclib.sdf.operator; - -import net.minecraft.util.Mth; -import ru.bclib.noise.OpenSimplexNoise; -import ru.bclib.util.MHelper; - -public class SDFRadialNoiseMap extends SDFDisplacement { - private static final float SIN = Mth.sin(0.5F); - private static final float COS = Mth.cos(0.5F); - - private OpenSimplexNoise noise; - private float intensity = 1F; - private float radius = 1F; - private short offsetX; - private short offsetZ; - - public SDFRadialNoiseMap() { - setFunction((pos) -> { - if (intensity == 0) { - return 0F; - } - float px = pos.x() / radius; - float pz = pos.z() / radius; - float distance = MHelper.lengthSqr(px, pz); - if (distance > 1) { - return 0F; - } - distance = 1 - Mth.sqrt(distance); - float nx = px * COS - pz * SIN; - float nz = pz * COS + px * SIN; - distance *= getNoise(nx * 0.75 + offsetX, nz * 0.75 + offsetZ); - return distance * intensity; - }); - } - - private float getNoise(double x, double z) { - return (float) noise.eval(x, z) + (float) noise.eval(x * 3 + 1000, z * 3) * 0.5F + (float) noise.eval(x * 9 + 1000, z * 9) * 0.2F; - } - - public SDFRadialNoiseMap setSeed(long seed) { - noise = new OpenSimplexNoise(seed); - return this; - } - - public SDFRadialNoiseMap setIntensity(float intensity) { - this.intensity = intensity; - return this; - } - - public SDFRadialNoiseMap setRadius(float radius) { - this.radius = radius; - return this; - } - - public SDFRadialNoiseMap setOffset(int x, int z) { - offsetX = (short) (x & 32767); - offsetZ = (short) (z & 32767); - return this; - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFRotation.java b/src/main/java/ru/bclib/sdf/operator/SDFRotation.java deleted file mode 100644 index 67e65ebf..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFRotation.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.bclib.sdf.operator; - -import com.mojang.math.Quaternion; -import com.mojang.math.Vector3f; - -public class SDFRotation extends SDFUnary { - private static final Vector3f POS = new Vector3f(); - private Quaternion rotation; - - public SDFRotation setRotation(Vector3f axis, float rotationAngle) { - rotation = new Quaternion(axis, rotationAngle, false); - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - POS.set(x, y, z); - POS.transform(rotation); - return source.getDistance(POS.x(), POS.y(), POS.z()); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFRound.java b/src/main/java/ru/bclib/sdf/operator/SDFRound.java deleted file mode 100644 index fd5b0e51..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFRound.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.bclib.sdf.operator; - -public class SDFRound extends SDFUnary { - private float radius; - - public SDFRound setRadius(float radius) { - this.radius = radius; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - return this.source.getDistance(x, y, z) - radius; - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFScale.java b/src/main/java/ru/bclib/sdf/operator/SDFScale.java deleted file mode 100644 index 18ee0eaf..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFScale.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.bclib.sdf.operator; - -public class SDFScale extends SDFUnary { - private float scale; - - public SDFScale setScale(float scale) { - this.scale = scale; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - return source.getDistance(x / scale, y / scale, z / scale) * scale; - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFScale3D.java b/src/main/java/ru/bclib/sdf/operator/SDFScale3D.java deleted file mode 100644 index 3693d751..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFScale3D.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.bclib.sdf.operator; - -public class SDFScale3D extends SDFUnary { - private float x; - private float y; - private float z; - - public SDFScale3D setScale(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - return source.getDistance(x / this.x, y / this.y, z / this.z); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFSmoothIntersection.java b/src/main/java/ru/bclib/sdf/operator/SDFSmoothIntersection.java deleted file mode 100644 index 393d66d2..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFSmoothIntersection.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.bclib.sdf.operator; - -import net.minecraft.util.Mth; - -public class SDFSmoothIntersection extends SDFBinary { - private float radius; - - public SDFSmoothIntersection setRadius(float radius) { - this.radius = radius; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - float a = this.sourceA.getDistance(x, y, z); - float b = this.sourceB.getDistance(x, y, z); - this.selectValue(a, b); - float h = Mth.clamp(0.5F - 0.5F * (b - a) / radius, 0F, 1F); - return Mth.lerp(h, b, a) + radius * h * (1F - h); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFSmoothSubtraction.java b/src/main/java/ru/bclib/sdf/operator/SDFSmoothSubtraction.java deleted file mode 100644 index 020b8590..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFSmoothSubtraction.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.bclib.sdf.operator; - -import net.minecraft.util.Mth; - -public class SDFSmoothSubtraction extends SDFBinary { - private float radius; - - public SDFSmoothSubtraction setRadius(float radius) { - this.radius = radius; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - float a = this.sourceA.getDistance(x, y, z); - float b = this.sourceB.getDistance(x, y, z); - this.selectValue(a, b); - float h = Mth.clamp(0.5F - 0.5F * (b + a) / radius, 0F, 1F); - return Mth.lerp(h, b, -a) + radius * h * (1F - h); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFSmoothUnion.java b/src/main/java/ru/bclib/sdf/operator/SDFSmoothUnion.java deleted file mode 100644 index 493c1c9b..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFSmoothUnion.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.bclib.sdf.operator; - -import net.minecraft.util.Mth; - -public class SDFSmoothUnion extends SDFBinary { - private float radius; - - public SDFSmoothUnion setRadius(float radius) { - this.radius = radius; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - float a = this.sourceA.getDistance(x, y, z); - float b = this.sourceB.getDistance(x, y, z); - this.selectValue(a, b); - float h = Mth.clamp(0.5F + 0.5F * (b - a) / radius, 0F, 1F); - return Mth.lerp(h, b, a) - radius * h * (1F - h); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFSubtraction.java b/src/main/java/ru/bclib/sdf/operator/SDFSubtraction.java deleted file mode 100644 index d0356868..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFSubtraction.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.bclib.sdf.operator; - -import ru.bclib.util.MHelper; - -public class SDFSubtraction extends SDFBinary { - @Override - public float getDistance(float x, float y, float z) { - float a = this.sourceA.getDistance(x, y, z); - float b = this.sourceB.getDistance(x, y, z); - this.selectValue(a, b); - return MHelper.max(a, -b); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFTranslate.java b/src/main/java/ru/bclib/sdf/operator/SDFTranslate.java deleted file mode 100644 index 99168225..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFTranslate.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.bclib.sdf.operator; - -public class SDFTranslate extends SDFUnary { - float x; - float y; - float z; - - public SDFTranslate setTranslate(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - return source.getDistance(x - this.x, y - this.y, z - this.z); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFUnary.java b/src/main/java/ru/bclib/sdf/operator/SDFUnary.java deleted file mode 100644 index 83882752..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFUnary.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.bclib.sdf.operator; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.sdf.SDF; - -public abstract class SDFUnary extends SDF { - protected SDF source; - - public SDFUnary setSource(SDF source) { - this.source = source; - return this; - } - - @Override - public BlockState getBlockState(BlockPos pos) { - return source.getBlockState(pos); - } -} diff --git a/src/main/java/ru/bclib/sdf/operator/SDFUnion.java b/src/main/java/ru/bclib/sdf/operator/SDFUnion.java deleted file mode 100644 index b9db5ab0..00000000 --- a/src/main/java/ru/bclib/sdf/operator/SDFUnion.java +++ /dev/null @@ -1,13 +0,0 @@ -package ru.bclib.sdf.operator; - -import ru.bclib.util.MHelper; - -public class SDFUnion extends SDFBinary { - @Override - public float getDistance(float x, float y, float z) { - float a = this.sourceA.getDistance(x, y, z); - float b = this.sourceB.getDistance(x, y, z); - this.selectValue(a, b); - return MHelper.min(a, b); - } -} diff --git a/src/main/java/ru/bclib/sdf/primitive/SDFCappedCone.java b/src/main/java/ru/bclib/sdf/primitive/SDFCappedCone.java deleted file mode 100644 index 891253da..00000000 --- a/src/main/java/ru/bclib/sdf/primitive/SDFCappedCone.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.bclib.sdf.primitive; - -import net.minecraft.util.Mth; -import ru.bclib.util.MHelper; - -public class SDFCappedCone extends SDFPrimitive { - private float radius1; - private float radius2; - private float height; - - public SDFCappedCone setRadius1(float radius) { - this.radius1 = radius; - return this; - } - - public SDFCappedCone setRadius2(float radius) { - this.radius2 = radius; - return this; - } - - public SDFCappedCone setHeight(float height) { - this.height = height; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - float qx = MHelper.length(x, z); - float k2x = radius2 - radius1; - float k2y = 2 * height; - float cax = qx - MHelper.min(qx, (y < 0F) ? radius1 : radius2); - float cay = Math.abs(y) - height; - float mlt = Mth.clamp(MHelper.dot(radius2 - qx, height - y, k2x, k2y) / MHelper.dot(k2x, k2y, k2x, k2y), 0F, 1F); - float cbx = qx - radius2 + k2x * mlt; - float cby = y - height + k2y * mlt; - float s = (cbx < 0F && cay < 0F) ? -1F : 1F; - return s * (float) Math.sqrt(MHelper.min(MHelper.dot(cax, cay, cax, cay), MHelper.dot(cbx, cby, cbx, cby))); - } -} diff --git a/src/main/java/ru/bclib/sdf/primitive/SDFCapsule.java b/src/main/java/ru/bclib/sdf/primitive/SDFCapsule.java deleted file mode 100644 index a29f724e..00000000 --- a/src/main/java/ru/bclib/sdf/primitive/SDFCapsule.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.bclib.sdf.primitive; - -import net.minecraft.util.Mth; -import ru.bclib.util.MHelper; - -public class SDFCapsule extends SDFPrimitive { - private float radius; - private float height; - - public SDFCapsule setRadius(float radius) { - this.radius = radius; - return this; - } - - public SDFCapsule setHeight(float height) { - this.height = height; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - return MHelper.length(x, y - Mth.clamp(y, 0, height), z) - radius; - } -} diff --git a/src/main/java/ru/bclib/sdf/primitive/SDFFlatland.java b/src/main/java/ru/bclib/sdf/primitive/SDFFlatland.java deleted file mode 100644 index 1f06cbf5..00000000 --- a/src/main/java/ru/bclib/sdf/primitive/SDFFlatland.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.bclib.sdf.primitive; - -public class SDFFlatland extends SDFPrimitive { - @Override - public float getDistance(float x, float y, float z) { - return y; - } -} diff --git a/src/main/java/ru/bclib/sdf/primitive/SDFHexPrism.java b/src/main/java/ru/bclib/sdf/primitive/SDFHexPrism.java deleted file mode 100644 index 23a5b55c..00000000 --- a/src/main/java/ru/bclib/sdf/primitive/SDFHexPrism.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.bclib.sdf.primitive; - -import ru.bclib.util.MHelper; - -public class SDFHexPrism extends SDFPrimitive { - private float radius; - private float height; - - public SDFHexPrism setRadius(float radius) { - this.radius = radius; - return this; - } - - public SDFHexPrism setHeight(float height) { - this.height = height; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - float px = Math.abs(x); - float py = Math.abs(y); - float pz = Math.abs(z); - return MHelper.max(py - height, MHelper.max((px * 0.866025F + pz * 0.5F), pz) - radius); - } -} diff --git a/src/main/java/ru/bclib/sdf/primitive/SDFLine.java b/src/main/java/ru/bclib/sdf/primitive/SDFLine.java deleted file mode 100644 index 9fd316d7..00000000 --- a/src/main/java/ru/bclib/sdf/primitive/SDFLine.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.bclib.sdf.primitive; - -import net.minecraft.util.Mth; -import ru.bclib.util.MHelper; - -public class SDFLine extends SDFPrimitive { - private float radius; - private float x1; - private float y1; - private float z1; - private float x2; - private float y2; - private float z2; - - public SDFLine setRadius(float radius) { - this.radius = radius; - return this; - } - - public SDFLine setStart(float x, float y, float z) { - this.x1 = x; - this.y1 = y; - this.z1 = z; - return this; - } - - public SDFLine setEnd(float x, float y, float z) { - this.x2 = x; - this.y2 = y; - this.z2 = z; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - float pax = x - x1; - float pay = y - y1; - float paz = z - z1; - - float bax = x2 - x1; - float bay = y2 - y1; - float baz = z2 - z1; - - float dpb = MHelper.dot(pax, pay, paz, bax, bay, baz); - float dbb = MHelper.dot(bax, bay, baz, bax, bay, baz); - float h = Mth.clamp(dpb / dbb, 0F, 1F); - return MHelper.length(pax - bax * h, pay - bay * h, paz - baz * h) - radius; - } -} diff --git a/src/main/java/ru/bclib/sdf/primitive/SDFPie.java b/src/main/java/ru/bclib/sdf/primitive/SDFPie.java deleted file mode 100644 index 0ad180f4..00000000 --- a/src/main/java/ru/bclib/sdf/primitive/SDFPie.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.bclib.sdf.primitive; - -import net.minecraft.util.Mth; -import ru.bclib.util.MHelper; - -public class SDFPie extends SDFPrimitive { - private float sin; - private float cos; - private float radius; - - public SDFPie setAngle(float angle) { - this.sin = (float) Math.sin(angle); - this.cos = (float) Math.cos(angle); - return this; - } - - public SDFPie setRadius(float radius) { - this.radius = radius; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - float px = Math.abs(x); - float l = MHelper.length(px, y, z) - radius; - float m = MHelper.dot(px, z, sin, cos); - m = Mth.clamp(m, 0, radius); - m = MHelper.length(px - sin * m, z - cos * m); - return MHelper.max(l, m * (float) Math.signum(cos * px - sin * z)); - } -} diff --git a/src/main/java/ru/bclib/sdf/primitive/SDFPrimitive.java b/src/main/java/ru/bclib/sdf/primitive/SDFPrimitive.java deleted file mode 100644 index e40b61ab..00000000 --- a/src/main/java/ru/bclib/sdf/primitive/SDFPrimitive.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.bclib.sdf.primitive; - -import java.util.function.Function; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.sdf.SDF; - -public abstract class SDFPrimitive extends SDF { - protected Function placerFunction; - - public SDFPrimitive setBlock(Function placerFunction) { - this.placerFunction = placerFunction; - return this; - } - - public SDFPrimitive setBlock(BlockState state) { - this.placerFunction = (pos) -> { - return state; - }; - return this; - } - - public SDFPrimitive setBlock(Block block) { - this.placerFunction = (pos) -> { - return block.defaultBlockState(); - }; - return this; - } - - public BlockState getBlockState(BlockPos pos) { - return placerFunction.apply(pos); - } - - /*public abstract CompoundTag toNBT(CompoundTag root) { - - }*/ -} diff --git a/src/main/java/ru/bclib/sdf/primitive/SDFSphere.java b/src/main/java/ru/bclib/sdf/primitive/SDFSphere.java deleted file mode 100644 index c8ff092b..00000000 --- a/src/main/java/ru/bclib/sdf/primitive/SDFSphere.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.bclib.sdf.primitive; - -import ru.bclib.util.MHelper; - -public class SDFSphere extends SDFPrimitive { - private float radius; - - public SDFSphere setRadius(float radius) { - this.radius = radius; - return this; - } - - @Override - public float getDistance(float x, float y, float z) { - return MHelper.length(x, y, z) - radius; - } -} diff --git a/src/main/java/ru/bclib/server/BCLibServer.java b/src/main/java/ru/bclib/server/BCLibServer.java deleted file mode 100644 index 2fbc8c0b..00000000 --- a/src/main/java/ru/bclib/server/BCLibServer.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.bclib.server; - -import net.fabricmc.api.DedicatedServerModInitializer; -import ru.bclib.api.ModIntegrationAPI; - -public class BCLibServer implements DedicatedServerModInitializer { - @Override - public void onInitializeServer() { - ModIntegrationAPI.registerAll(); - } -} diff --git a/src/main/java/ru/bclib/util/BackgroundInfo.java b/src/main/java/ru/bclib/util/BackgroundInfo.java deleted file mode 100644 index 34c2c347..00000000 --- a/src/main/java/ru/bclib/util/BackgroundInfo.java +++ /dev/null @@ -1,9 +0,0 @@ -package ru.bclib.util; - -public class BackgroundInfo { - public static float fogColorRed; - public static float fogColorGreen; - public static float fogColorBlue; - public static float fogDensity = 1; - public static float blindness; -} diff --git a/src/main/java/ru/bclib/util/BlocksHelper.java b/src/main/java/ru/bclib/util/BlocksHelper.java deleted file mode 100644 index 21b974f9..00000000 --- a/src/main/java/ru/bclib/util/BlocksHelper.java +++ /dev/null @@ -1,192 +0,0 @@ -package ru.bclib.util; - -import java.util.Map; -import java.util.Random; - -import com.google.common.collect.Maps; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.BlockPos.MutableBlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.ClipContext.Fluid; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.Property; - -public class BlocksHelper { - private static final Map COLOR_BY_BLOCK = Maps.newHashMap(); - - public static final int FLAG_UPDATE_BLOCK = 1; - public static final int FLAG_SEND_CLIENT_CHANGES = 2; - public static final int FLAG_NO_RERENDER = 4; - public static final int FORSE_RERENDER = 8; - public static final int FLAG_IGNORE_OBSERVERS = 16; - - public static final int SET_SILENT = FLAG_UPDATE_BLOCK | FLAG_IGNORE_OBSERVERS | FLAG_SEND_CLIENT_CHANGES; - public static final int SET_OBSERV = FLAG_UPDATE_BLOCK | FLAG_SEND_CLIENT_CHANGES; - public static final Direction[] HORIZONTAL = makeHorizontal(); - public static final Direction[] DIRECTIONS = Direction.values(); - - private static final MutableBlockPos POS = new MutableBlockPos(); - protected static final BlockState AIR = Blocks.AIR.defaultBlockState(); - protected static final BlockState WATER = Blocks.WATER.defaultBlockState(); - - public static void addBlockColor(Block block, int color) { - COLOR_BY_BLOCK.put(block, color); - } - - public static int getBlockColor(Block block) { - return COLOR_BY_BLOCK.getOrDefault(block, 0xFF000000); - } - - public static void setWithoutUpdate(LevelAccessor world, BlockPos pos, BlockState state) { - world.setBlock(pos, state, SET_SILENT); - } - - public static void setWithoutUpdate(LevelAccessor world, BlockPos pos, Block block) { - world.setBlock(pos, block.defaultBlockState(), SET_SILENT); - } - - public static void setWithUpdate(LevelAccessor world, BlockPos pos, BlockState state) { - world.setBlock(pos, state, SET_OBSERV); - } - - public static void setWithUpdate(LevelAccessor world, BlockPos pos, Block block) { - world.setBlock(pos, block.defaultBlockState(), SET_OBSERV); - } - - public static int upRay(LevelAccessor world, BlockPos pos, int maxDist) { - int length = 0; - for (int j = 1; j < maxDist && (world.isEmptyBlock(pos.above(j))); j++) { - length++; - } - return length; - } - - public static int downRay(LevelAccessor world, BlockPos pos, int maxDist) { - int length = 0; - for (int j = 1; j < maxDist && (world.isEmptyBlock(pos.below(j))); j++) { - length++; - } - return length; - } - - public static int downRayRep(LevelAccessor world, BlockPos pos, int maxDist) { - POS.set(pos); - for (int j = 1; j < maxDist && (world.getBlockState(POS)).getMaterial().isReplaceable(); j++) { - POS.setY(POS.getY() - 1); - } - return pos.getY() - POS.getY(); - } - - public static int raycastSqr(LevelAccessor world, BlockPos pos, int dx, int dy, int dz, int maxDist) { - POS.set(pos); - for (int j = 1; j < maxDist && (world.getBlockState(POS)).getMaterial().isReplaceable(); j++) { - POS.move(dx, dy, dz); - } - return (int) pos.distSqr(POS); - } - - /** - * Rotates {@link BlockState} horizontally. Used in block classes with {@link Direction} {@link Property} in rotate function. - * @param state - {@link BlockState} to mirror; - * @param rotation - {@link Rotation}; - * @param facing - Block {@link Direction} {@link Property}. - * @return Rotated {@link BlockState}. - */ - public static BlockState rotateHorizontal(BlockState state, Rotation rotation, Property facing) { - return state.setValue(facing, rotation.rotate(state.getValue(facing))); - } - - /** - * Mirrors {@link BlockState} horizontally. Used in block classes with {@link Direction} {@link Property} in mirror function. - * @param state - {@link BlockState} to mirror; - * @param mirror - {@link Mirror}; - * @param facing - Block {@link Direction} {@link Property}. - * @return Mirrored {@link BlockState}. - */ - public static BlockState mirrorHorizontal(BlockState state, Mirror mirror, Property facing) { - return state.rotate(mirror.getRotation(state.getValue(facing))); - } - - /** - * Counts the amount of same block down. - * @param world - {@link LevelAccessor} world; - * @param pos - {@link BlockPos} start position; - * @param block - {@link Block} to count. - * @return Integer amount of blocks. - */ - public static int getLengthDown(LevelAccessor world, BlockPos pos, Block block) { - int count = 1; - while (world.getBlockState(pos.below(count)).getBlock() == block) { - count++; - } - return count; - } - - /** - * Creates a new {@link Direction} array with clockwise order: - * NORTH, EAST, SOUTH, WEST - * @return Array of {@link Direction}. - */ - public static Direction[] makeHorizontal() { - return new Direction[] { Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST }; - } - - /** - * Get any random horizontal {@link Direction}. - * @param random - {@link Random}. - * @return {@link Direction}. - */ - public static Direction randomHorizontal(Random random) { - return HORIZONTAL[random.nextInt(4)]; - } - - /** - * Get any random {@link Direction} including vertical and horizontal. - * @param random - {@link Random}. - * @return {@link Direction}. - */ - public static Direction randomDirection(Random random) { - return DIRECTIONS[random.nextInt(6)]; - } - - /** - * Check if block is {@link Fluid} or not. - * @param state - {@link BlockState} to check. - * @return {@code true} if block is fluid and {@code false} if not. - */ - public static boolean isFluid(BlockState state) { - return !state.getFluidState().isEmpty(); - } - - /** - * Check if block is "invulnerable" like Bedrock. - * @param state - {@link BlockState} to check; - * @param world - {@link BlockGetter} world where BlockState exist; - * @param pos - {@link BlockPos} where BlockState is. - * @return {@code true} if block is "invulnerable" and {@code false} if not. - */ - public static boolean isInvulnerable(BlockState state, BlockGetter world, BlockPos pos) { - return state.getDestroySpeed(world, pos) < 0; - } - - /** - * Check if block is "invulnerable" like Bedrock. Unlike safe function will pass world and position parameters as {@code null}. - * @param state - {@link BlockState} to check. - * @return {@code true} if block is "invulnerable" and {@code false} if not. - */ - public static boolean isInvulnerableUnsafe(BlockState state) { - try { - return isInvulnerable(state, null, null); - } - catch (Exception e) { - return false; - } - } -} diff --git a/src/main/java/ru/bclib/util/ColorExtractor.java b/src/main/java/ru/bclib/util/ColorExtractor.java deleted file mode 100644 index 9614345d..00000000 --- a/src/main/java/ru/bclib/util/ColorExtractor.java +++ /dev/null @@ -1,148 +0,0 @@ -package ru.bclib.util; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Random; - -public class ColorExtractor { - private List

centers = new ArrayList<>(); - private List colors; - private Integer result; - - public ColorExtractor(List colors) { - this.colors = colors; - Random rnd = new Random(); - int size = colors.size(); - for (int i = 0; i < 4; i++) { - int color = colors.get(rnd.nextInt(size)); - this.centers.add(new Center(color)); - } - } - - public int analize() { - boolean moved = true; - while (moved) { - this.remap(); - moved = false; - for (Center center : centers) { - if (center.move()) { - moved = true; - } - } - } - List
toClear = new ArrayList<>(); - this.centers.forEach(center -> { - if (center.colors.isEmpty()) { - toClear.add(center); - } - }); - if (toClear.size() > 0) { - toClear.forEach(clear -> centers.remove(clear)); - } - this.centers.sort(Center.COMPARATOR); - - return this.getResult(); - } - - public int getResult() { - if (result == null) { - double weights = 0; - double alpha = 0; - double red = 0; - double green = 0; - double blue = 0; - for (Center center : centers) { - double weight = (double) center.colors.size() / colors.size(); - weights += weight; - alpha += center.a * weight; - red += center.r * weight; - green += center.g * weight; - blue += center.b * weight; - } ; - - int a = (int) Math.round(alpha / weights); - int r = (int) Math.round(red / weights); - int g = (int) Math.round(green / weights); - int b = (int) Math.round(blue / weights); - - this.result = a << 24 | r << 16 | g << 8 | b; - } - - return this.result; - } - - private void remap() { - this.centers.forEach(entry -> entry.colors.clear()); - this.colors.forEach(color -> { - int id = 0; - int base = centers.get(0).getColor(); - int dst = ColorUtil.colorDistance(color, base); - for (Center center : centers) { - base = center.getColor(); - int dst1 = ColorUtil.colorDistance(color, base); - if (dst1 < dst) { - dst = dst1; - id = centers.indexOf(center); - } - } - this.centers.get(id).colors.add(color); - }); - } - - private static class Center { - static final Comparator
COMPARATOR = new Comparator
() { - @Override - public int compare(Center c1, Center c2) { - return Integer.compare(c1.getColor(), c2.getColor()); - } - }; - - List colors = new ArrayList<>(); - double a, r, g, b; - - Center(int color) { - this.a = (color >> 24) & 255; - this.r = (color >> 16) & 255; - this.g = (color >> 8) & 255; - this.b = color & 255; - } - - private void update(double a, double r, double g, double b) { - this.a = a; - this.r = r; - this.g = g; - this.b = b; - } - - public int getColor() { - int a = (int) Math.round(this.a); - int r = (int) Math.round(this.r); - int g = (int) Math.round(this.g); - int b = (int) Math.round(this.b); - return a << 24 | r << 16 | g << 8 | b; - } - - public boolean move() { - double or = r; - double og = g; - double ob = b; - double a = 0, r = 0, g = 0, b = 0; - int size = this.colors.size(); - for (int col : colors) { - a += (col >> 24) & 255; - r += (col >> 16) & 255; - g += (col >> 8) & 255; - b += col & 255; - } - a /= size; - r /= size; - g /= size; - b /= size; - - this.update(a, r, g, b); - - return Math.abs(r - or) > 0.1 || Math.abs(g - og) > 0.1 || Math.abs(b - ob) > 0.1; - } - } -} diff --git a/src/main/java/ru/bclib/util/ColorUtil.java b/src/main/java/ru/bclib/util/ColorUtil.java deleted file mode 100644 index 179dff9e..00000000 --- a/src/main/java/ru/bclib/util/ColorUtil.java +++ /dev/null @@ -1,260 +0,0 @@ -package ru.bclib.util; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.google.common.collect.Maps; -import com.mojang.blaze3d.platform.NativeImage; - -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.impl.client.indigo.renderer.helper.ColorHelper; -import net.minecraft.client.Minecraft; -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.Resource; -import net.minecraft.server.packs.resources.ResourceManager; -import net.minecraft.util.Mth; -import net.minecraft.world.item.BlockItem; -import net.minecraft.world.item.Item; -import ru.bclib.BCLib; - -public class ColorUtil { - private static final float[] FLOAT_BUFFER = new float[4]; - private static final int ALPHA = 255 << 24; - - public static int color(int r, int g, int b) { - return ALPHA | (r << 16) | (g << 8) | b; - } - - public static int color(String hex) { - int r = Integer.parseInt(hex.substring(0, 2), 16); - int g = Integer.parseInt(hex.substring(2, 4), 16); - int b = Integer.parseInt(hex.substring(4, 6), 16); - return color(r, g, b); - } - - public static int[] toIntArray(int color) { - return new int[] { - (color >> 24) & 255, - (color >> 16) & 255, - (color >> 8) & 255, - color & 255 - }; - } - - public static float[] toFloatArray(int color) { - FLOAT_BUFFER[0] = ((color >> 16 & 255) / 255.0F); - FLOAT_BUFFER[1] = ((color >> 8 & 255) / 255.0F); - FLOAT_BUFFER[2] = ((color & 255) / 255.0F); - FLOAT_BUFFER[3] = ((color >> 24 & 255) / 255.0F); - - return FLOAT_BUFFER; - } - - public static float[] RGBtoHSB(int r, int g, int b, float[] hsbvals) { - float hue, saturation, brightness; - if (hsbvals == null) { - hsbvals = FLOAT_BUFFER; - } - int cmax = (r > g) ? r : g; - if (b > cmax) cmax = b; - int cmin = (r < g) ? r : g; - if (b < cmin) cmin = b; - - brightness = ((float) cmax) / 255.0F; - if (cmax != 0) - saturation = ((float) (cmax - cmin)) / ((float) cmax); - else - saturation = 0; - if (saturation == 0) - hue = 0; - else { - float redc = ((float) (cmax - r)) / ((float) (cmax - cmin)); - float greenc = ((float) (cmax - g)) / ((float) (cmax - cmin)); - float bluec = ((float) (cmax - b)) / ((float) (cmax - cmin)); - if (r == cmax) - hue = bluec - greenc; - else if (g == cmax) - hue = 2.0F + redc - bluec; - else - hue = 4.0F + greenc - redc; - hue = hue / 6.0F; - if (hue < 0) - hue = hue + 1.0F; - } - hsbvals[0] = hue; - hsbvals[1] = saturation; - hsbvals[2] = brightness; - return hsbvals; - } - - public static int HSBtoRGB(float hue, float saturation, float brightness) { - int r = 0, g = 0, b = 0; - if (saturation == 0) { - r = g = b = (int) (brightness * 255.0F + 0.5F); - } - else { - float h = (hue - (float) Math.floor(hue)) * 6.0F; - float f = h - (float) java.lang.Math.floor(h); - float p = brightness * (1.0F - saturation); - float q = brightness * (1.0F - saturation * f); - float t = brightness * (1.0F - (saturation * (1.0F - f))); - switch ((int) h) { - case 0: - r = (int) (brightness * 255.0F + 0.5F); - g = (int) (t * 255.0F + 0.5F); - b = (int) (p * 255.0F + 0.5F); - break; - case 1: - r = (int) (q * 255.0F + 0.5F); - g = (int) (brightness * 255.0F + 0.5F); - b = (int) (p * 255.0F + 0.5F); - break; - case 2: - r = (int) (p * 255.0F + 0.5F); - g = (int) (brightness * 255.0F + 0.5F); - b = (int) (t * 255.0F + 0.5F); - break; - case 3: - r = (int) (p * 255.0F + 0.5F); - g = (int) (q * 255.0F + 0.5F); - b = (int) (brightness * 255.0F + 0.5F); - break; - case 4: - r = (int) (t * 255.0F + 0.5F); - g = (int) (p * 255.0F + 0.5F); - b = (int) (brightness * 255.0F + 0.5F); - break; - case 5: - r = (int) (brightness * 255.0F + 0.5F); - g = (int) (p * 255.0F + 0.5F); - b = (int) (q * 255.0F + 0.5F); - break; - } - } - return 0xFF000000 | (r << 16) | (g << 8) | (b << 0); - } - - public static int parseHex(String hexColor) { - int len = hexColor.length(); - if (len < 6 || len > 8 || len % 2 > 0) { - return -1; - } - - int color, shift; - if (len == 6) { - color = 0xFF000000; - shift = 16; - } - else { - color = 0; - shift = 24; - } - - try { - String[] splited = hexColor.split("(?<=\\G.{2})"); - for (String digit : splited) { - color |= Integer.valueOf(digit, 16) << shift; - shift -= 8; - } - } - catch (NumberFormatException ex) { - BCLib.LOGGER.catching(ex); - return -1; - } - - return color; - } - - public static int toABGR(int color) { - int r = (color >> 16) & 255; - int g = (color >> 8) & 255; - int b = color & 255; - return 0xFF000000 | b << 16 | g << 8 | r; - } - - public static int ABGRtoARGB(int color) { - int a = (color >> 24) & 255; - int b = (color >> 16) & 255; - int g = (color >> 8) & 255; - int r = color & 255; - return a << 24 | r << 16 | g << 8 | b; - } - - public static int colorBrigtness(int color, float val) { - RGBtoHSB((color >> 16) & 255, (color >> 8) & 255, color & 255, FLOAT_BUFFER); - FLOAT_BUFFER[2] += val / 10.0F; - FLOAT_BUFFER[2] = Mth.clamp(FLOAT_BUFFER[2], 0.0F, 1.0F); - return HSBtoRGB(FLOAT_BUFFER[0], FLOAT_BUFFER[1], FLOAT_BUFFER[2]); - } - - public static int applyTint(int color, int tint) { - return colorBrigtness(ColorHelper.multiplyColor(color, tint), 1.5F); - } - - public static int colorDistance(int color1, int color2) { - int r1 = (color1 >> 16) & 255; - int g1 = (color1 >> 8) & 255; - int b1 = color1 & 255; - int r2 = (color2 >> 16) & 255; - int g2 = (color2 >> 8) & 255; - int b2 = color2 & 255; - return MHelper.sqr(r1 - r2) + MHelper.sqr(g1 - g2) + MHelper.sqr(b1 - b2); - } - - private static Map colorPalette = Maps.newHashMap(); - - @Environment(EnvType.CLIENT) - public static int extractColor(Item item) { - ResourceLocation id = Registry.ITEM.getKey(item); - if (id.equals(Registry.ITEM.getDefaultKey())) return -1; - if (colorPalette.containsKey(id)) { - return colorPalette.get(id); - } - ResourceLocation texture; - if (item instanceof BlockItem) { - texture = new ResourceLocation(id.getNamespace(), "textures/block/" + id.getPath() + ".png"); - } - else { - texture = new ResourceLocation(id.getNamespace(), "textures/item/" + id.getPath() + ".png"); - } - NativeImage image = loadImage(texture, 16, 16); - List colors = new ArrayList<>(); - for (int i = 0; i < image.getWidth(); i++) { - for (int j = 0; j < 16; j++) { - int col = image.getPixelRGBA(i, j); - if (((col >> 24) & 255) > 0) { - colors.add(ABGRtoARGB(col)); - } - } - } - image.close(); - - if (colors.size() == 0) return -1; - - ColorExtractor extractor = new ColorExtractor(colors); - int color = extractor.analize(); - colorPalette.put(id, color); - - return color; - } - - @Environment(EnvType.CLIENT) - public static NativeImage loadImage(ResourceLocation image, int w, int h) { - Minecraft minecraft = Minecraft.getInstance(); - ResourceManager resourceManager = minecraft.getResourceManager(); - if (resourceManager.hasResource(image)) { - try (Resource resource = resourceManager.getResource(image)) { - return NativeImage.read(resource.getInputStream()); - } - catch (IOException e) { - BCLib.LOGGER.warning("Can't load texture image: {}. Will be created empty image.", image); - BCLib.LOGGER.warning("Cause: {}.", e.getMessage()); - } - } - return new NativeImage(w, h, false); - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/util/JsonFactory.java b/src/main/java/ru/bclib/util/JsonFactory.java deleted file mode 100644 index 9397f32c..00000000 --- a/src/main/java/ru/bclib/util/JsonFactory.java +++ /dev/null @@ -1,93 +0,0 @@ -package ru.bclib.util; - -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import ru.bclib.BCLib; - -public class JsonFactory { - public final static Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - - public static JsonObject getJsonObject(InputStream stream) { - try { - Reader reader = new InputStreamReader(stream); - JsonElement json = loadJson(reader); - if (json != null && json.isJsonObject()) { - JsonObject jsonObject = json.getAsJsonObject(); - return jsonObject != null ? jsonObject : new JsonObject(); - } - } - catch (Exception ex) { - BCLib.LOGGER.catching(ex); - } - return new JsonObject(); - } - - public static JsonObject getJsonObject(File jsonFile) { - if (jsonFile.exists()) { - JsonElement json = loadJson(jsonFile); - if (json != null && json.isJsonObject()) { - JsonObject jsonObject = json.getAsJsonObject(); - return jsonObject != null ? jsonObject : new JsonObject(); - } - } - return new JsonObject(); - } - - public static JsonElement loadJson(File jsonFile) { - if (jsonFile.exists()) { - try (Reader reader = new FileReader(jsonFile)) { - return loadJson(reader); - } - catch (Exception ex) { - BCLib.LOGGER.catching(ex); - } - } - return null; - } - - public static JsonElement loadJson(Reader reader) { - return GSON.fromJson(reader, JsonElement.class); - } - - public static void storeJson(File jsonFile, JsonElement jsonObject) { - try (FileWriter writer = new FileWriter(jsonFile)) { - String json = GSON.toJson(jsonObject); - writer.write(json); - writer.flush(); - } - catch (IOException ex) { - BCLib.LOGGER.catching(ex); - } - } - - public static int getInt(JsonObject object, String member, int def) { - JsonElement elem = object.get(member); - return elem == null ? def : elem.getAsInt(); - } - - public static float getFloat(JsonObject object, String member, float def) { - JsonElement elem = object.get(member); - return elem == null ? def : elem.getAsFloat(); - } - - public static boolean getBoolean(JsonObject object, String member, boolean def) { - JsonElement elem = object.get(member); - return elem == null ? def : elem.getAsBoolean(); - } - - public static String getString(JsonObject object, String member, String def) { - JsonElement elem = object.get(member); - return elem == null ? def : elem.getAsString(); - } -} diff --git a/src/main/java/ru/bclib/util/Logger.java b/src/main/java/ru/bclib/util/Logger.java deleted file mode 100644 index 52721bac..00000000 --- a/src/main/java/ru/bclib/util/Logger.java +++ /dev/null @@ -1,62 +0,0 @@ -package ru.bclib.util; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; - -public final class Logger { - private static final org.apache.logging.log4j.Logger LOGGER = LogManager.getLogger(); - private final String modPref; - - public Logger(String modID) { - this.modPref = "[" + modID + "] "; - } - - public void log(Level level, String message) { - LOGGER.log(level, modPref + message); - } - - public void log(Level level, String message, Object... params) { - LOGGER.log(level, modPref + message, params); - } - - public void debug(Object message) { - this.log(Level.DEBUG, message.toString()); - } - - public void debug(Object message, Object... params) { - this.log(Level.DEBUG, message.toString(), params); - } - - public void catching(Throwable ex) { - this.error(ex.getLocalizedMessage()); - LOGGER.catching(ex); - } - - public void info(String message) { - this.log(Level.INFO, message); - } - - public void info(String message, Object... params) { - this.log(Level.INFO, message, params); - } - - public void warning(String message, Object... params) { - this.log(Level.WARN, message, params); - } - - public void warning(String message, Object obj, Exception ex) { - LOGGER.warn(modPref + message, obj, ex); - } - - public void error(String message) { - this.log(Level.ERROR, message); - } - - public void error(String message, Object obj, Exception ex) { - LOGGER.error(modPref + message, obj, ex); - } - - public void error(String message, Exception ex) { - LOGGER.error(modPref + message, ex); - } -} diff --git a/src/main/java/ru/bclib/util/MHelper.java b/src/main/java/ru/bclib/util/MHelper.java deleted file mode 100644 index b7d53913..00000000 --- a/src/main/java/ru/bclib/util/MHelper.java +++ /dev/null @@ -1,224 +0,0 @@ -package ru.bclib.util; - -import java.util.Random; - -import com.mojang.math.Vector3f; - -import net.minecraft.core.Vec3i; - -public class MHelper { - private static final Vec3i[] RANDOM_OFFSETS = new Vec3i[3 * 3 * 3 - 1]; - private static final float RAD_TO_DEG = 57.295779513082320876798154814105F; - public static final float PHI = (float) (Math.PI * (3 - Math.sqrt(5))); - public static final float PI2 = (float) (Math.PI * 2); - public static final Random RANDOM = new Random(); - - public static int randRange(int min, int max, Random random) { - return min + random.nextInt(max - min + 1); - } - - public static double randRange(double min, double max, Random random) { - return min + random.nextDouble() * (max - min); - } - - public static float randRange(float min, float max, Random random) { - return min + random.nextFloat() * (max - min); - } - - public static byte setBit(byte source, int pos, boolean value) { - return value ? setBitTrue(source, pos) : setBitFalse(source, pos); - } - - public static byte setBitTrue(byte source, int pos) { - source |= 1 << pos; - return source; - } - - public static byte setBitFalse(byte source, int pos) { - source &= ~(1 << pos); - return source; - } - - public static boolean getBit(byte source, int pos) { - return ((source >> pos) & 1) == 1; - } - - public static float wrap(float x, float side) { - return x - floor(x / side) * side; - } - - public static int floor(double x) { - return x < 0 ? (int) (x - 1) : (int) x; - } - - public static int min(int a, int b) { - return a < b ? a : b; - } - - public static int min(int a, int b, int c) { - return min(a, min(b, c)); - } - - public static int max(int a, int b) { - return a > b ? a : b; - } - - public static float min(float a, float b) { - return a < b ? a : b; - } - - public static float max(float a, float b) { - return a > b ? a : b; - } - - public static float max(float a, float b, float c) { - return max(a, max(b, c)); - } - - public static int max(int a, int b, int c) { - return max(a, max(b, c)); - } - - public static boolean isEven(int num) { - return (num & 1) == 0; - } - - public static float lengthSqr(float x, float y, float z) { - return x * x + y * y + z * z; - } - - public static double lengthSqr(double x, double y, double z) { - return x * x + y * y + z * z; - } - - public static float length(float x, float y, float z) { - return (float) Math.sqrt(lengthSqr(x, y, z)); - } - - public static double length(double x, double y, double z) { - return Math.sqrt(lengthSqr(x, y, z)); - } - - public static float lengthSqr(float x, float y) { - return x * x + y * y; - } - - public static double lengthSqr(double x, double y) { - return x * x + y * y; - } - - public static float length(float x, float y) { - return (float) Math.sqrt(lengthSqr(x, y)); - } - - public static double length(double x, double y) { - return Math.sqrt(lengthSqr(x, y)); - } - - public static float dot(float x1, float y1, float z1, float x2, float y2, float z2) { - return x1 * x2 + y1 * y2 + z1 * z2; - } - - public static float dot(float x1, float y1, float x2, float y2) { - return x1 * x2 + y1 * y2; - } - - public static int getRandom(int x, int z) { - int h = x * 374761393 + z * 668265263; - h = (h ^ (h >> 13)) * 1274126177; - return h ^ (h >> 16); - } - - public static int getSeed(int seed, int x, int y) { - int h = seed + x * 374761393 + y * 668265263; - h = (h ^ (h >> 13)) * 1274126177; - return h ^ (h >> 16); - } - - public static int getSeed(int seed, int x, int y, int z) { - int h = seed + x * 374761393 + y * 668265263 + z; - h = (h ^ (h >> 13)) * 1274126177; - return h ^ (h >> 16); - } - - public static void shuffle(T[] array, Random random) { - for (int i = 0; i < array.length; i++) { - int i2 = random.nextInt(array.length); - T element = array[i]; - array[i] = array[i2]; - array[i2] = element; - } - } - - public static int sqr(int i) { - return i * i; - } - - public static float sqr(float f) { - return f * f; - } - - public static double sqr(double d) { - return d * d; - } - - public static final float radiansToDegrees(float value) { - return value * RAD_TO_DEG; - } - - public static final float degreesToRadians(float value) { - return value / RAD_TO_DEG; - } - - public static Vector3f cross(Vector3f vec1, Vector3f vec2) - { - float cx = vec1.y() * vec2.z() - vec1.z() * vec2.y(); - float cy = vec1.z() * vec2.x() - vec1.x() * vec2.z(); - float cz = vec1.x() * vec2.y() - vec1.y() * vec2.x(); - return new Vector3f(cx, cy, cz); - } - - public static Vector3f normalize(Vector3f vec) { - float length = lengthSqr(vec.x(), vec.y(), vec.z()); - if (length > 0) { - length = (float) Math.sqrt(length); - float x = vec.x() / length; - float y = vec.y() / length; - float z = vec.z() / length; - vec.set(x, y, z); - } - return vec; - } - - public static float angle(Vector3f vec1, Vector3f vec2) { - float dot = vec1.x() * vec2.x() + vec1.y() * vec2.y() + vec1.z() * vec2.z(); - float length1 = lengthSqr(vec1.x(), vec1.y(), vec1.z()); - float length2 = lengthSqr(vec2.x(), vec2.y(), vec2.z()); - return (float) Math.acos(dot / Math.sqrt(length1 * length2)); - } - - public static Vector3f randomHorizontal(Random random) { - float angleY = randRange(0, PI2, random); - float vx = (float) Math.sin(angleY); - float vz = (float) Math.cos(angleY); - return new Vector3f(vx, 0, vz); - } - - public static Vec3i[] getOffsets(Random random) { - shuffle(RANDOM_OFFSETS, random); - return RANDOM_OFFSETS; - } - - static { - int index = 0; - for (int x = -1; x <= 1; x++) { - for (int y = -1; y <= 1; y++) { - for (int z = -1; z <= 1; z++) { - if (x != 0 || y != 0 || z != 0) { - RANDOM_OFFSETS[index++] = new Vec3i(x, y, z); - } - } - } - } - } -} diff --git a/src/main/java/ru/bclib/util/SplineHelper.java b/src/main/java/ru/bclib/util/SplineHelper.java deleted file mode 100644 index 0b1e8a75..00000000 --- a/src/main/java/ru/bclib/util/SplineHelper.java +++ /dev/null @@ -1,344 +0,0 @@ -package ru.bclib.util; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.function.Function; - -import com.google.common.collect.Lists; -import com.mojang.math.Vector3f; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.BlockPos.MutableBlockPos; -import net.minecraft.util.Mth; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.block.state.BlockState; -import ru.bclib.sdf.SDF; -import ru.bclib.sdf.operator.SDFUnion; -import ru.bclib.sdf.primitive.SDFLine; - -public class SplineHelper { - public static List makeSpline(float x1, float y1, float z1, float x2, float y2, float z2, int points) { - List spline = Lists.newArrayList(); - spline.add(new Vector3f(x1, y1, z1)); - int count = points - 1; - for (int i = 1; i < count; i++) { - float delta = (float) i / (float) count; - float x = Mth.lerp(delta, x1, x2); - float y = Mth.lerp(delta, y1, y2); - float z = Mth.lerp(delta, z1, z2); - spline.add(new Vector3f(x, y, z)); - } - spline.add(new Vector3f(x2, y2, z2)); - return spline; - } - - public static List smoothSpline(List spline, int segmentPoints) { - List result = Lists.newArrayList(); - Vector3f start = spline.get(0); - for (int i = 1; i < spline.size(); i++) { - Vector3f end = spline.get(i); - for (int j = 0; j < segmentPoints; j++) { - float delta = (float) j / segmentPoints; - delta = 0.5F - 0.5F * Mth.cos(delta * 3.14159F); - result.add(lerp(start, end, delta)); - } - start = end; - } - result.add(start); - return result; - } - - private static Vector3f lerp(Vector3f start, Vector3f end, float delta) { - float x = Mth.lerp(delta, start.x(), end.x()); - float y = Mth.lerp(delta, start.y(), end.y()); - float z = Mth.lerp(delta, start.z(), end.z()); - return new Vector3f(x, y, z); - } - - public static void offsetParts(List spline, Random random, float dx, float dy, float dz) { - int count = spline.size(); - for (int i = 1; i < count; i++) { - Vector3f pos = spline.get(i); - float x = pos.x() + (float) random.nextGaussian() * dx; - float y = pos.y() + (float) random.nextGaussian() * dy; - float z = pos.z() + (float) random.nextGaussian() * dz; - pos.set(x, y, z); - } - } - - public static void powerOffset(List spline, float distance, float power) { - int count = spline.size(); - float max = count + 1; - for (int i = 1; i < count; i++) { - Vector3f pos = spline.get(i); - float x = (float) i / max; - float y = pos.y() + (float) Math.pow(x, power) * distance; - pos.set(pos.x(), y, pos.z()); - } - } - - public static SDF buildSDF(List spline, float radius1, float radius2, Function placerFunction) { - int count = spline.size(); - float max = count - 2; - SDF result = null; - Vector3f start = spline.get(0); - for (int i = 1; i < count; i++) { - Vector3f pos = spline.get(i); - float delta = (float) (i - 1) / max; - SDF line = new SDFLine() - .setRadius(Mth.lerp(delta, radius1, radius2)) - .setStart(start.x(), start.y(), start.z()) - .setEnd(pos.x(), pos.y(), pos.z()) - .setBlock(placerFunction); - result = result == null ? line : new SDFUnion().setSourceA(result).setSourceB(line); - start = pos; - } - return result; - } - - public static SDF buildSDF(List spline, Function radiusFunction, Function placerFunction) { - int count = spline.size(); - float max = count - 2; - SDF result = null; - Vector3f start = spline.get(0); - for (int i = 1; i < count; i++) { - Vector3f pos = spline.get(i); - float delta = (float) (i - 1) / max; - SDF line = new SDFLine() - .setRadius(radiusFunction.apply(delta)) - .setStart(start.x(), start.y(), start.z()) - .setEnd(pos.x(), pos.y(), pos.z()) - .setBlock(placerFunction); - result = result == null ? line : new SDFUnion().setSourceA(result).setSourceB(line); - start = pos; - } - return result; - } - - public static boolean fillSpline(List spline, WorldGenLevel world, BlockState state, BlockPos pos, Function replace) { - Vector3f startPos = spline.get(0); - for (int i = 1; i < spline.size(); i++) { - Vector3f endPos = spline.get(i); - if (!(fillLine(startPos, endPos, world, state, pos, replace))) { - return false; - } - startPos = endPos; - } - - return true; - } - - public static void fillSplineForce(List spline, WorldGenLevel world, BlockState state, BlockPos pos, Function replace) { - Vector3f startPos = spline.get(0); - for (int i = 1; i < spline.size(); i++) { - Vector3f endPos = spline.get(i); - fillLineForce(startPos, endPos, world, state, pos, replace); - startPos = endPos; - } - } - - public static boolean fillLine(Vector3f start, Vector3f end, WorldGenLevel world, BlockState state, BlockPos pos, Function replace) { - float dx = end.x() - start.x(); - float dy = end.y() - start.y(); - float dz = end.z() - start.z(); - float max = MHelper.max(Math.abs(dx), Math.abs(dy), Math.abs(dz)); - int count = MHelper.floor(max + 1); - dx /= max; - dy /= max; - dz /= max; - float x = start.x(); - float y = start.y(); - float z = start.z(); - boolean down = Math.abs(dy) > 0.2; - - BlockState bState; - MutableBlockPos bPos = new MutableBlockPos(); - for (int i = 0; i < count; i++) { - bPos.set(x + pos.getX(), y + pos.getY(), z + pos.getZ()); - bState = world.getBlockState(bPos); - if (bState.equals(state) || replace.apply(bState)) { - BlocksHelper.setWithoutUpdate(world, bPos, state); - bPos.setY(bPos.getY() - 1); - bState = world.getBlockState(bPos); - if (down && bState.equals(state) || replace.apply(bState)) { - BlocksHelper.setWithoutUpdate(world, bPos, state); - } - } - else { - return false; - } - x += dx; - y += dy; - z += dz; - } - bPos.set(end.x() + pos.getX(), end.y() + pos.getY(), end.z() + pos.getZ()); - bState = world.getBlockState(bPos); - if (bState.equals(state) || replace.apply(bState)) { - BlocksHelper.setWithoutUpdate(world, bPos, state); - bPos.setY(bPos.getY() - 1); - bState = world.getBlockState(bPos); - if (down && bState.equals(state) || replace.apply(bState)) { - BlocksHelper.setWithoutUpdate(world, bPos, state); - } - return true; - } - else { - return false; - } - } - - public static void fillLineForce(Vector3f start, Vector3f end, WorldGenLevel world, BlockState state, BlockPos pos, Function replace) { - float dx = end.x() - start.x(); - float dy = end.y() - start.y(); - float dz = end.z() - start.z(); - float max = MHelper.max(Math.abs(dx), Math.abs(dy), Math.abs(dz)); - int count = MHelper.floor(max + 1); - dx /= max; - dy /= max; - dz /= max; - float x = start.x(); - float y = start.y(); - float z = start.z(); - boolean down = Math.abs(dy) > 0.2; - - BlockState bState; - MutableBlockPos bPos = new MutableBlockPos(); - for (int i = 0; i < count; i++) { - bPos.set(x + pos.getX(), y + pos.getY(), z + pos.getZ()); - bState = world.getBlockState(bPos); - if (replace.apply(bState)) { - BlocksHelper.setWithoutUpdate(world, bPos, state); - bPos.setY(bPos.getY() - 1); - bState = world.getBlockState(bPos); - if (down && replace.apply(bState)) { - BlocksHelper.setWithoutUpdate(world, bPos, state); - } - } - x += dx; - y += dy; - z += dz; - } - bPos.set(end.x() + pos.getX(), end.y() + pos.getY(), end.z() + pos.getZ()); - bState = world.getBlockState(bPos); - if (replace.apply(bState)) { - BlocksHelper.setWithoutUpdate(world, bPos, state); - bPos.setY(bPos.getY() - 1); - bState = world.getBlockState(bPos); - if (down && replace.apply(bState)) { - BlocksHelper.setWithoutUpdate(world, bPos, state); - } - } - } - - public static boolean canGenerate(List spline, float scale, BlockPos start, WorldGenLevel world, Function canReplace) { - int count = spline.size(); - Vector3f vec = spline.get(0); - MutableBlockPos mut = new MutableBlockPos(); - float x1 = start.getX() + vec.x() * scale; - float y1 = start.getY() + vec.y() * scale; - float z1 = start.getZ() + vec.z() * scale; - for (int i = 1; i < count; i++) { - vec = spline.get(i); - float x2 = start.getX() + vec.x() * scale; - float y2 = start.getY() + vec.y() * scale; - float z2 = start.getZ() + vec.z() * scale; - - for (float py = y1; py < y2; py += 3) { - if (py - start.getY() < 10) continue; - float lerp = (py - y1) / (y2 - y1); - float x = Mth.lerp(lerp, x1, x2); - float z = Mth.lerp(lerp, z1, z2); - mut.set(x, py, z); - if (!canReplace.apply(world.getBlockState(mut))) { - return false; - } - } - - x1 = x2; - y1 = y2; - z1 = z2; - } - return true; - } - - public static boolean canGenerate(List spline, BlockPos start, WorldGenLevel world, Function canReplace) { - int count = spline.size(); - Vector3f vec = spline.get(0); - MutableBlockPos mut = new MutableBlockPos(); - float x1 = start.getX() + vec.x(); - float y1 = start.getY() + vec.y(); - float z1 = start.getZ() + vec.z(); - for (int i = 1; i < count; i++) { - vec = spline.get(i); - float x2 = start.getX() + vec.x(); - float y2 = start.getY() + vec.y(); - float z2 = start.getZ() + vec.z(); - - for (float py = y1; py < y2; py += 3) { - if (py - start.getY() < 10) continue; - float lerp = (py - y1) / (y2 - y1); - float x = Mth.lerp(lerp, x1, x2); - float z = Mth.lerp(lerp, z1, z2); - mut.set(x, py, z); - if (!canReplace.apply(world.getBlockState(mut))) { - return false; - } - } - - x1 = x2; - y1 = y2; - z1 = z2; - } - return true; - } - - public static Vector3f getPos(List spline, float index) { - int i = (int) index; - int last = spline.size() - 1; - if (i >= last) { - return spline.get(last); - } - float delta = index - i; - Vector3f p1 = spline.get(i); - Vector3f p2 = spline.get(i + 1); - float x = Mth.lerp(delta, p1.x(), p2.x()); - float y = Mth.lerp(delta, p1.y(), p2.y()); - float z = Mth.lerp(delta, p1.z(), p2.z()); - return new Vector3f(x, y, z); - } - - public static void rotateSpline(List spline, float angle) { - for (Vector3f v: spline) { - float sin = (float) Math.sin(angle); - float cos = (float) Math.cos(angle); - float x = v.x() * cos + v.z() * sin; - float z = v.x() * sin + v.z() * cos; - v.set(x, v.y(), z); - } - } - - public static List copySpline(List spline) { - List result = new ArrayList(spline.size()); - for (Vector3f v: spline) { - result.add(new Vector3f(v.x(), v.y(), v.z())); - } - return result; - } - - public static void scale(List spline, float scale) { - scale(spline, scale, scale, scale); - } - - public static void scale(List spline, float x, float y, float z) { - for (Vector3f v: spline) { - v.set(v.x() * x, v.y() * y, v.z() * z); - } - } - - public static void offset(List spline, Vector3f offset) { - for (Vector3f v: spline) { - v.set(offset.x() + v.x(), offset.y() + v.y(), offset.z() + v.z()); - } - } -} diff --git a/src/main/java/ru/bclib/util/StructureHelper.java b/src/main/java/ru/bclib/util/StructureHelper.java deleted file mode 100644 index 179159e3..00000000 --- a/src/main/java/ru/bclib/util/StructureHelper.java +++ /dev/null @@ -1,373 +0,0 @@ -package ru.bclib.util; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Enumeration; -import java.util.Random; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import com.google.common.collect.Sets; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.BlockPos.MutableBlockPos; -import net.minecraft.core.Direction; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.BlockTags; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.levelgen.structure.BoundingBox; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; -import net.minecraft.world.level.material.Material; -import ru.bclib.api.TagAPI; - -public class StructureHelper { - private static final Direction[] DIR = BlocksHelper.makeHorizontal(); - - public static StructureTemplate readStructure(ResourceLocation resource) { - String ns = resource.getNamespace(); - String nm = resource.getPath(); - return readStructure("/data/" + ns + "/structures/" + nm + ".nbt"); - } - - public static StructureTemplate readStructure(File datapack, String path) { - if (datapack.isDirectory()) { - return readStructure(datapack.toString() + "/" + path); - } - else if (datapack.isFile() && datapack.getName().endsWith(".zip")) { - try { - ZipFile zipFile = new ZipFile(datapack); - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - String name = entry.getName(); - long compressedSize = entry.getCompressedSize(); - long normalSize = entry.getSize(); - String type = entry.isDirectory() ? "DIR" : "FILE"; - - System.out.println(name); - System.out.format("\t %s - %d - %d\n", type, compressedSize, normalSize); - } - zipFile.close(); - } - catch (IOException e) { - e.printStackTrace(); - } - } - return null; - } - - public static StructureTemplate readStructure(String path) { - try { - InputStream inputstream = StructureHelper.class.getResourceAsStream(path); - return readStructureFromStream(inputstream); - } - catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - private static StructureTemplate readStructureFromStream(InputStream stream) throws IOException { - CompoundTag nbttagcompound = NbtIo.readCompressed(stream); - - StructureTemplate template = new StructureTemplate(); - template.load(nbttagcompound); - - return template; - } - - public static BlockPos offsetPos(BlockPos pos, StructureTemplate structure, Rotation rotation, Mirror mirror) { - BlockPos offset = StructureTemplate.transform(structure.getSize(), mirror, rotation, BlockPos.ZERO); - return pos.offset(-offset.getX() * 0.5, 0, -offset.getZ() * 0.5); - } - - public static void placeCenteredBottom(WorldGenLevel world, BlockPos pos, StructureTemplate structure, Rotation rotation, Mirror mirror, Random random) { - placeCenteredBottom(world, pos, structure, rotation, mirror, makeBox(pos), random); - } - - public static void placeCenteredBottom(WorldGenLevel world, BlockPos pos, StructureTemplate structure, Rotation rotation, Mirror mirror, BoundingBox bounds, Random random) { - BlockPos offset = offsetPos(pos, structure, rotation, mirror); - StructurePlaceSettings placementData = new StructurePlaceSettings().setRotation(rotation).setMirror(mirror).setBoundingBox(bounds); - structure.placeInWorldChunk(world, offset, placementData, random); - } - - private static BoundingBox makeBox(BlockPos pos) { - int sx = ((pos.getX() >> 4) << 4) - 16; - int sz = ((pos.getZ() >> 4) << 4) - 16; - int ex = sx + 47; - int ez = sz + 47; - return BoundingBox.createProper(sx, 0, sz, ex, 255, ez); - } - - public static BoundingBox getStructureBounds(BlockPos pos, StructureTemplate structure, Rotation rotation, Mirror mirror) { - BlockPos max = structure.getSize(); - BlockPos min = StructureTemplate.transform(structure.getSize(), mirror, rotation, BlockPos.ZERO); - max = max.subtract(min); - return new BoundingBox(min.offset(pos), max.offset(pos)); - } - - public static BoundingBox intersectBoxes(BoundingBox box1, BoundingBox box2) { - int x1 = MHelper.max(box1.x0, box2.x0); - int y1 = MHelper.max(box1.y0, box2.y0); - int z1 = MHelper.max(box1.z0, box2.z0); - - int x2 = MHelper.min(box1.x1, box2.x1); - int y2 = MHelper.min(box1.y1, box2.y1); - int z2 = MHelper.min(box1.z1, box2.z1); - - return BoundingBox.createProper(x1, y1, z1, x2, y2, z2); - } - - public static void erode(WorldGenLevel world, BoundingBox bounds, int iterations, Random random) { - MutableBlockPos mut = new MutableBlockPos(); - boolean canDestruct = true; - for (int i = 0; i < iterations; i++) { - for (int x = bounds.x0; x <= bounds.x1; x++) { - mut.setX(x); - for (int z = bounds.z0; z <= bounds.z1; z++) { - mut.setZ(z); - for (int y = bounds.y1; y >= bounds.y0; y--) { - mut.setY(y); - BlockState state = world.getBlockState(mut); - boolean ignore = ignore(state, world, mut); - if (canDestruct && BlocksHelper.isInvulnerable(state, world, mut) && random.nextInt(8) == 0 && world.isEmptyBlock(mut.below(2))) { - int r = MHelper.randRange(1, 4, random); - int cx = mut.getX(); - int cy = mut.getY(); - int cz = mut.getZ(); - int x1 = cx - r; - int y1 = cy - r; - int z1 = cz - r; - int x2 = cx + r; - int y2 = cy + r; - int z2 = cz + r; - for (int px = x1; px <= x2; px++) { - int dx = px - cx; - dx *= dx; - mut.setX(px); - for (int py = y1; py <= y2; py++) { - int dy = py - cy; - dy *= dy; - mut.setY(py); - for (int pz = z1; pz <= z2; pz++) { - int dz = pz - cz; - dz *= dz; - mut.setZ(pz); - if (dx + dy + dz <= r && BlocksHelper.isInvulnerable(world.getBlockState(mut), world, mut)) { - BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); - } - } - } - } - mut.setX(cx); - mut.setY(cy); - mut.setZ(cz); - canDestruct = false; - continue; - } - else if (ignore) { - continue; - } - if (!state.isAir() && random.nextBoolean()) { - MHelper.shuffle(DIR, random); - for (Direction dir: DIR) { - if (world.isEmptyBlock(mut.relative(dir)) && world.isEmptyBlock(mut.below().relative(dir))) { - BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); - mut.move(dir).move(Direction.DOWN); - for (int py = mut.getY(); y >= bounds.y0 - 10; y--) { - mut.setY(py - 1); - if (!world.isEmptyBlock(mut)) { - mut.setY(py); - BlocksHelper.setWithoutUpdate(world, mut, state); - break; - } - } - } - } - break; - } - else if (random.nextInt(8) == 0 && !BlocksHelper.isInvulnerable(world.getBlockState(mut.above()), world, mut)) { - BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); - } - } - } - } - } - for (int x = bounds.x0; x <= bounds.x1; x++) { - mut.setX(x); - for (int z = bounds.z0; z <= bounds.z1; z++) { - mut.setZ(z); - for (int y = bounds.y1; y >= bounds.y0; y--) { - mut.setY(y); - BlockState state = world.getBlockState(mut); - if (!ignore(state, world, mut) && world.isEmptyBlock(mut.below())) { - BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); - for (int py = mut.getY(); py >= bounds.y0 - 10; py--) { - mut.setY(py - 1); - if (!world.isEmptyBlock(mut)) { - mut.setY(py); - BlocksHelper.setWithoutUpdate(world, mut, state); - break; - } - } - } - } - } - } - } - - public static void erodeIntense(WorldGenLevel world, BoundingBox bounds, Random random) { - MutableBlockPos mut = new MutableBlockPos(); - MutableBlockPos mut2 = new MutableBlockPos(); - int minY = bounds.y0 - 10; - for (int x = bounds.x0; x <= bounds.x1; x++) { - mut.setX(x); - for (int z = bounds.z0; z <= bounds.z1; z++) { - mut.setZ(z); - for (int y = bounds.y1; y >= bounds.y0; y--) { - mut.setY(y); - BlockState state = world.getBlockState(mut); - if (!ignore(state, world, mut)) { - if (random.nextInt(6) == 0) { - BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); - if (random.nextBoolean()) { - int px = MHelper.floor(random.nextGaussian() * 2 + x + 0.5); - int pz = MHelper.floor(random.nextGaussian() * 2 + z + 0.5); - mut2.set(px, y, pz); - while (world.getBlockState(mut2).getMaterial().isReplaceable() && mut2.getY() > minY) { - mut2.setY(mut2.getY() - 1); - } - if (!world.getBlockState(mut2).isAir() && state.canSurvive(world, mut2)) { - mut2.setY(mut2.getY() + 1); - BlocksHelper.setWithoutUpdate(world, mut2, state); - } - } - } - else if (random.nextInt(8) == 0) { - BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); - } - } - } - } - } - - drop(world, bounds); - } - - private static boolean isTerrainNear(WorldGenLevel world, BlockPos pos) { - for (Direction dir: BlocksHelper.DIRECTIONS) { - if (world.getBlockState(pos.relative(dir)).is(TagAPI.GEN_TERRAIN)) { - return true; - } - } - return false; - } - - private static void drop(WorldGenLevel world, BoundingBox bounds) { - MutableBlockPos mut = new MutableBlockPos(); - - Set blocks = Sets.newHashSet(); - Set edge = Sets.newHashSet(); - Set add = Sets.newHashSet(); - - for (int x = bounds.x0; x <= bounds.x1; x++) { - mut.setX(x); - for (int z = bounds.z0; z <= bounds.z1; z++) { - mut.setZ(z); - for (int y = bounds.y0; y <= bounds.y1; y++) { - mut.setY(y); - BlockState state = world.getBlockState(mut); - if (!ignore(state, world, mut) && isTerrainNear(world, mut)) { - edge.add(mut.immutable()); - } - } - } - } - - if (edge.isEmpty()) { - return; - } - - while (!edge.isEmpty()) { - for (BlockPos center: edge) { - for (Direction dir: BlocksHelper.DIRECTIONS) { - BlockState state = world.getBlockState(center); - if (state.isCollisionShapeFullBlock(world, center)) { - mut.set(center).move(dir); - if (bounds.isInside(mut)) { - state = world.getBlockState(mut); - if (!ignore(state, world, mut) && !blocks.contains(mut)) { - add.add(mut.immutable()); - } - } - } - } - } - - blocks.addAll(edge); - edge.clear(); - edge.addAll(add); - add.clear(); - } - - int minY = bounds.y0 - 10; - for (int x = bounds.x0; x <= bounds.x1; x++) { - mut.setX(x); - for (int z = bounds.z0; z <= bounds.z1; z++) { - mut.setZ(z); - for (int y = bounds.y0; y <= bounds.y1; y++) { - mut.setY(y); - BlockState state = world.getBlockState(mut); - if (!ignore(state, world, mut) && !blocks.contains(mut)) { - BlocksHelper.setWithoutUpdate(world, mut, Blocks.AIR); - while (world.getBlockState(mut).getMaterial().isReplaceable() && mut.getY() > minY) { - mut.setY(mut.getY() - 1); - } - if (mut.getY() > minY) { - mut.setY(mut.getY() + 1); - BlocksHelper.setWithoutUpdate(world, mut, state); - } - } - } - } - } - } - - private static boolean ignore(BlockState state, WorldGenLevel world, BlockPos pos) { - return state.getMaterial().isReplaceable() || - !state.getFluidState().isEmpty() || - state.is(TagAPI.END_GROUND) || - state.is(BlockTags.LOGS) || - state.is(BlockTags.LEAVES) || - state.getMaterial().equals(Material.PLANT) || - state.getMaterial().equals(Material.LEAVES) || - BlocksHelper.isInvulnerable(state, world, pos); - } - - public static void cover(WorldGenLevel world, BoundingBox bounds, Random random) { - MutableBlockPos mut = new MutableBlockPos(); - for (int x = bounds.x0; x <= bounds.x1; x++) { - mut.setX(x); - for (int z = bounds.z0; z <= bounds.z1; z++) { - mut.setZ(z); - BlockState top = world.getBiome(mut).getGenerationSettings().getSurfaceBuilderConfig().getTopMaterial(); - for (int y = bounds.y1; y >= bounds.y0; y--) { - mut.setY(y); - BlockState state = world.getBlockState(mut); - if (state.is(TagAPI.END_GROUND) && !world.getBlockState(mut.above()).getMaterial().isSolidBlocking()) { - BlocksHelper.setWithoutUpdate(world, mut, top); - } - } - } - } - } -} diff --git a/src/main/java/ru/bclib/util/TagHelper.java b/src/main/java/ru/bclib/util/TagHelper.java deleted file mode 100644 index f5dc604d..00000000 --- a/src/main/java/ru/bclib/util/TagHelper.java +++ /dev/null @@ -1,73 +0,0 @@ -package ru.bclib.util; - -import java.util.Map; -import java.util.Set; - -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.Tag; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.ItemLike; -import net.minecraft.world.level.block.Block; - -public class TagHelper { - private static final Map> TAGS_BLOCK = Maps.newConcurrentMap(); - private static final Map> TAGS_ITEM = Maps.newConcurrentMap(); - - public static void addTag(Tag.Named tag, Block... blocks) { - ResourceLocation tagID = tag.getName(); - Set set = TAGS_BLOCK.computeIfAbsent(tagID, k -> Sets.newHashSet()); - for (Block block: blocks) { - ResourceLocation id = Registry.BLOCK.getKey(block); - if (id != Registry.BLOCK.getDefaultKey()) { - set.add(id); - } - } - } - - public static void addTag(Tag.Named tag, ItemLike... items) { - ResourceLocation tagID = tag.getName(); - Set set = TAGS_ITEM.computeIfAbsent(tagID, k -> Sets.newHashSet()); - for (ItemLike item: items) { - ResourceLocation id = Registry.ITEM.getKey(item.asItem()); - if (id != Registry.ITEM.getDefaultKey()) { - set.add(id); - } - } - } - - @SafeVarargs - public static void addTags(ItemLike item, Tag.Named... tags) { - for (Tag.Named tag: tags) { - addTag(tag, item); - } - } - - @SafeVarargs - public static void addTags(Block block, Tag.Named... tags) { - for (Tag.Named tag: tags) { - addTag(tag, block); - } - } - - public static Tag.Builder apply(Tag.Builder builder, Set ids) { - ids.forEach(value -> builder.addElement(value, "Better End Code")); - return builder; - } - - public static Map apply(String entry, Map tagsMap) { - Map> endTags = null; - if (entry.equals("block")) { - endTags = TAGS_BLOCK; - } else if (entry.equals("item")) { - endTags = TAGS_ITEM; - } - if (endTags != null) { - endTags.forEach((id, ids) -> apply(tagsMap.computeIfAbsent(id, key -> Tag.Builder.tag()), ids)); - } - return tagsMap; - } -} diff --git a/src/main/java/ru/bclib/util/TranslationHelper.java b/src/main/java/ru/bclib/util/TranslationHelper.java deleted file mode 100644 index 68ad5412..00000000 --- a/src/main/java/ru/bclib/util/TranslationHelper.java +++ /dev/null @@ -1,119 +0,0 @@ -package ru.bclib.util; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Collections; -import java.util.List; - -import com.google.common.collect.Lists; -import com.google.gson.Gson; -import com.google.gson.JsonObject; - -import net.minecraft.core.Registry; -import net.minecraft.data.BuiltinRegistries; -import net.minecraft.resources.ResourceLocation; - -public class TranslationHelper { - public static void printMissingNames(String modID) { - List missingNamesEn = Lists.newArrayList(); - List missingNamesRu = Lists.newArrayList(); - - Gson gson = new Gson(); - InputStream streamEn = TranslationHelper.class.getResourceAsStream("/assets/" + modID + "/lang/en_us.json"); - InputStream streamRu = TranslationHelper.class.getResourceAsStream("/assets/" + modID + "/lang/ru_ru.json"); - JsonObject translationEn = gson.fromJson(new InputStreamReader(streamEn), JsonObject.class); - JsonObject translationRu = gson.fromJson(new InputStreamReader(streamRu), JsonObject.class); - - Registry.BLOCK.forEach(block -> { - if (Registry.BLOCK.getKey(block).getNamespace().equals(modID)) { - String name = block.getName().getString(); - if (!translationEn.has(name)) { - missingNamesEn.add(name); - } - if (!translationRu.has(name)) { - missingNamesRu.add(name); - } - } - }); - - Registry.ITEM.forEach(item -> { - if (Registry.ITEM.getKey(item).getNamespace().equals(modID)) { - String name = item.getDescription().getString(); - if (!translationEn.has(name)) { - missingNamesEn.add(name); - } - if (!translationRu.has(name)) { - missingNamesRu.add(name); - } - } - }); - - BuiltinRegistries.BIOME.forEach(biome -> { - ResourceLocation id = BuiltinRegistries.BIOME.getKey(biome); - if (id.getNamespace().equals(modID)) { - String name = "biome." + modID + "." + id.getPath(); - if (!translationEn.has(name)) { - missingNamesEn.add(name); - } - if (!translationRu.has(name)) { - missingNamesRu.add(name); - } - } - }); - - Registry.ENTITY_TYPE.forEach((entity) -> { - ResourceLocation id = Registry.ENTITY_TYPE.getKey(entity); - if (id.getNamespace().equals(modID)) { - String name = "entity." + modID + "." + id.getPath(); - if (!translationEn.has(name)) { - missingNamesEn.add(name); - } - if (!translationRu.has(name)) { - missingNamesRu.add(name); - } - } - }); - - if (!missingNamesEn.isEmpty() || !missingNamesRu.isEmpty()) { - - System.out.println("========================================"); - System.out.println(" MISSING NAMES LIST"); - - if (!missingNamesEn.isEmpty()) { - Collections.sort(missingNamesEn); - System.out.println("========================================"); - System.out.println(" ENGLISH"); - System.out.println("========================================"); - missingNamesEn.forEach((name) -> { - System.out.println(" \"" + name + "\": \"" + fastTranslateEn(name) + "\","); - }); - } - - if (!missingNamesRu.isEmpty()) { - Collections.sort(missingNamesRu); - System.out.println("========================================"); - System.out.println(" RUSSIAN"); - System.out.println("========================================"); - missingNamesRu.forEach((name) -> { - System.out.println(" \"" + name + "\": \"\","); - }); - } - - System.out.println("========================================"); - } - } - - public static String fastTranslateEn(String text) { - String[] words = text.substring(text.lastIndexOf('.') + 1).split("_"); - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < words.length; i++) { - String word = words[i]; - builder.append(Character.toUpperCase(word.charAt(0))); - builder.append(word, 1, word.length()); - if (i < words.length - 1) { - builder.append(' '); - } - } - return builder.toString(); - } -} diff --git a/src/main/java/ru/bclib/util/WeighTree.java b/src/main/java/ru/bclib/util/WeighTree.java deleted file mode 100644 index 096747c4..00000000 --- a/src/main/java/ru/bclib/util/WeighTree.java +++ /dev/null @@ -1,90 +0,0 @@ -package ru.bclib.util; - -import java.util.Locale; -import java.util.Random; - -public class WeighTree { - private final float maxWeight; - private final Node root; - - public WeighTree(WeightedList list) { - maxWeight = list.getMaxWeight(); - root = getNode(list); - } - - /** - * Get eandom value from tree. - * @param random - {@link Random}. - * @return {@link T} value. - */ - public T get(Random random) { - return root.get(random.nextFloat() * maxWeight); - } - - private Node getNode(WeightedList list) { - int size = list.size(); - if (size == 1) { - return new Leaf(list.get(0)); - } - else if (size == 2) { - T first = list.get(0); - return new Branch(list.getWeight(0), new Leaf(first), new Leaf(list.get(1))); - } - else { - int index = size >> 1; - float separator = list.getWeight(index); - Node a = getNode(list.subList(0, index + 1)); - Node b = getNode(list.subList(index, size)); - return new Branch(separator, a, b); - } - } - - private abstract class Node { - abstract T get(float value); - } - - private class Branch extends Node { - final float separator; - final Node min; - final Node max; - - public Branch(float separator, Node min, Node max) { - this.separator = separator; - this.min = min; - this.max = max; - } - - @Override - T get(float value) { - return value < separator ? min.get(value) : max.get(value); - } - - @Override - public String toString() { - return String.format(Locale.ROOT, "[%f, %s, %s]", separator, min.toString(), max.toString()); - } - } - - private class Leaf extends Node { - final T biome; - - Leaf(T value) { - this.biome = value; - } - - @Override - T get(float value) { - return biome; - } - - @Override - public String toString() { - return String.format(Locale.ROOT, "[%s]", biome.toString()); - } - } - - @Override - public String toString() { - return root.toString(); - } -} diff --git a/src/main/java/ru/bclib/util/WeightedList.java b/src/main/java/ru/bclib/util/WeightedList.java deleted file mode 100644 index 5455dd39..00000000 --- a/src/main/java/ru/bclib/util/WeightedList.java +++ /dev/null @@ -1,115 +0,0 @@ -package ru.bclib.util; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.function.Consumer; - -public class WeightedList { - private final List weights = new ArrayList(); - private final List values = new ArrayList(); - private float maxWeight; - - /** - * Adds value with specified weight to the list - * @param value - * @param weight - */ - public void add(T value, float weight) { - maxWeight += weight; - weights.add(maxWeight); - values.add(value); - } - - /** - * Get random value. - * @param random - {@link Random}. - * @return {@link T} value. - */ - public T get(Random random) { - if (maxWeight < 1) { - return null; - } - float weight = random.nextFloat() * maxWeight; - for (int i = 0; i < weights.size(); i++) { - if (weight <= weights.get(i)) { - return values.get(i); - } - } - return null; - } - - /** - * Get value by index. - * @param index - {@code int} index. - * @return {@link T} value. - */ - public T get(int index) { - return values.get(index); - } - - /** - * Get value weight. Weight is summed with all previous values weights. - * @param index - {@code int} index. - * @return {@code float} weight. - */ - public float getWeight(int index) { - return weights.get(index); - } - - /** - * Chech if the list is empty. - * @return {@code true} if list is empty and {@code false} if not. - */ - public boolean isEmpty() { - return maxWeight == 0; - } - - /** - * Get the list size. - * @return {@code int} list size. - */ - public int size() { - return values.size(); - } - - /** - * Makes a sublist of this list with same weights. Used only in {@link WeighTree} - * @param start - {@code int} start index (inclusive). - * @param end - {@code int} end index (exclusive). - * @return {@link WeightedList}. - */ - protected WeightedList subList(int start, int end) { - WeightedList list = new WeightedList(); - for (int i = start; i < end; i++) { - list.weights.add(weights.get(i)); - list.values.add(values.get(i)); - } - return list; - } - - /** - * Check if list contains certain value. - * @param value - {@link T} value. - * @return {@code true} if value is in list and {@code false} if not. - */ - public boolean contains(T value) { - return values.contains(value); - } - - /** - * Applies {@link Consumer} to all values in list. - * @param function - {@link Consumer}. - */ - public void forEach(Consumer function) { - values.forEach(function); - } - - /** - * Get the maximum weight of the tree. - * @return {@code float} maximum weight. - */ - public float getMaxWeight() { - return maxWeight; - } -} diff --git a/src/main/java/ru/bclib/world/biomes/BCLBiome.java b/src/main/java/ru/bclib/world/biomes/BCLBiome.java deleted file mode 100644 index d6326ebe..00000000 --- a/src/main/java/ru/bclib/world/biomes/BCLBiome.java +++ /dev/null @@ -1,197 +0,0 @@ -package ru.bclib.world.biomes; - -import java.io.InputStream; -import java.util.List; -import java.util.Map; -import java.util.Random; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; - -import net.minecraft.core.Registry; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.biome.Biome; -import ru.bclib.util.JsonFactory; -import ru.bclib.util.StructureHelper; -import ru.bclib.util.WeightedList; -import ru.bclib.world.features.BCLFeature; -import ru.bclib.world.features.ListFeature; -import ru.bclib.world.features.ListFeature.StructureInfo; -import ru.bclib.world.features.NBTStructureFeature.TerrainMerge; - -public class BCLBiome { - protected WeightedList subbiomes = new WeightedList(); - - protected final Biome biome; - protected final ResourceLocation mcID; - protected BCLBiome edge; - protected int edgeSize; - - protected BCLBiome biomeParent; - protected float maxSubBiomeChance = 1; - protected final float genChance; - - private final Map customData; - private final float fogDensity; - private BCLFeature structuresFeature; - private Biome actualBiome; - - public BCLBiome(BCLBiomeDef definition) { - this.mcID = definition.getID(); - this.readStructureList(); - if (structuresFeature != null) { - definition.addFeature(structuresFeature); - } - this.biome = definition.build(); - this.genChance = definition.getGenChance(); - this.fogDensity = definition.getFodDensity(); - this.customData = definition.getCustomData(); - } - - public BCLBiome(ResourceLocation id, Biome biome, float fogDensity, float genChance) { - this.mcID = id; - this.biome = biome; - this.genChance = genChance; - this.fogDensity = fogDensity; - this.readStructureList(); - this.customData = Maps.newHashMap(); - } - - public BCLBiome getEdge() { - return edge == null ? this : edge; - } - - public void setEdge(BCLBiome edge) { - this.edge = edge; - edge.biomeParent = this; - } - - public int getEdgeSize() { - return edgeSize; - } - - public void setEdgeSize(int size) { - edgeSize = size; - } - - public void addSubBiome(BCLBiome biome) { - biome.biomeParent = this; - subbiomes.add(biome, biome.getGenChance()); - } - - public boolean containsSubBiome(BCLBiome biome) { - return subbiomes.contains(biome); - } - - public BCLBiome getSubBiome(Random random) { - BCLBiome biome = subbiomes.get(random); - return biome == null ? this : biome; - } - - public BCLBiome getParentBiome() { - return this.biomeParent; - } - - public boolean hasEdge() { - return edge != null; - } - - public boolean hasParentBiome() { - return biomeParent != null; - } - - public boolean isSame(BCLBiome biome) { - return biome == this || (biome.hasParentBiome() && biome.getParentBiome() == this); - } - - public Biome getBiome() { - return biome; - } - - @Override - public String toString() { - return mcID.toString(); - } - - public ResourceLocation getID() { - return mcID; - } - - public float getFogDensity() { - return fogDensity; - } - - protected void readStructureList() { - String ns = mcID.getNamespace(); - String nm = mcID.getPath(); - - String path = "/data/" + ns + "/structures/biome/" + nm + "/"; - InputStream inputstream = StructureHelper.class.getResourceAsStream(path + "structures.json"); - if (inputstream != null) { - JsonObject obj = JsonFactory.getJsonObject(inputstream); - JsonArray enties = obj.getAsJsonArray("structures"); - if (enties != null) { - List list = Lists.newArrayList(); - enties.forEach((entry) -> { - JsonObject e = entry.getAsJsonObject(); - String structure = path + e.get("nbt").getAsString() + ".nbt"; - TerrainMerge terrainMerge = TerrainMerge.getFromString(e.get("terrainMerge").getAsString()); - int offsetY = e.get("offsetY").getAsInt(); - list.add(new StructureInfo(structure, offsetY, terrainMerge)); - }); - if (!list.isEmpty()) { - structuresFeature = BCLFeature.makeChansedFeature(new ResourceLocation(ns, nm + "_structures"), new ListFeature(list), 10); - } - } - } - } - - public BCLFeature getStructuresFeature() { - return structuresFeature; - } - - public Biome getActualBiome() { - return this.actualBiome; - } - - public float getGenChance() { - return this.genChance; - } - - public void updateActualBiomes(Registry biomeRegistry) { - subbiomes.forEach((sub) -> { - if (sub != this) { - sub.updateActualBiomes(biomeRegistry); - } - }); - if (edge != null && edge != this) { - edge.updateActualBiomes(biomeRegistry); - } - this.actualBiome = biomeRegistry.get(mcID); - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - BCLBiome biome = (BCLBiome) obj; - return biome == null ? false : biome.mcID.equals(mcID); - } - - @Override - public int hashCode() { - return mcID.hashCode(); - } - - @SuppressWarnings("unchecked") - public T getCustomData(String name, T defaultValue) { - return (T) customData.getOrDefault(name, defaultValue); - } - - public void addCustomData(String name, Object obj) { - customData.put(name, obj); - } -} diff --git a/src/main/java/ru/bclib/world/biomes/BCLBiomeDef.java b/src/main/java/ru/bclib/world/biomes/BCLBiomeDef.java deleted file mode 100644 index 30f77701..00000000 --- a/src/main/java/ru/bclib/world/biomes/BCLBiomeDef.java +++ /dev/null @@ -1,388 +0,0 @@ -package ru.bclib.world.biomes; - -import java.util.List; -import java.util.Map; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import net.minecraft.core.Registry; -import net.minecraft.core.particles.ParticleOptions; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.sounds.Music; -import net.minecraft.sounds.Musics; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.level.biome.AmbientAdditionsSettings; -import net.minecraft.world.level.biome.AmbientMoodSettings; -import net.minecraft.world.level.biome.AmbientParticleSettings; -import net.minecraft.world.level.biome.Biome; -import net.minecraft.world.level.biome.Biome.BiomeCategory; -import net.minecraft.world.level.biome.Biome.Precipitation; -import net.minecraft.world.level.biome.BiomeGenerationSettings; -import net.minecraft.world.level.biome.BiomeSpecialEffects.Builder; -import net.minecraft.world.level.biome.MobSpawnSettings; -import net.minecraft.world.level.biome.MobSpawnSettings.SpawnerData; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.levelgen.GenerationStep.Carving; -import net.minecraft.world.level.levelgen.GenerationStep.Decoration; -import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver; -import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; -import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature; -import net.minecraft.world.level.levelgen.feature.configurations.ProbabilityFeatureConfiguration; -import net.minecraft.world.level.levelgen.surfacebuilders.ConfiguredSurfaceBuilder; -import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilder; -import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilderBaseConfiguration; -import ru.bclib.config.IdConfig; -import ru.bclib.util.ColorUtil; -import ru.bclib.world.features.BCLFeature; -import ru.bclib.world.structures.BCLStructureFeature; -import ru.bclib.world.surface.DoubleBlockSurfaceBuilder; - -public class BCLBiomeDef { - public static final int DEF_FOLIAGE_OVERWORLD = ColorUtil.color(110, 143, 64); - public static final int DEF_FOLIAGE_NETHER = ColorUtil.color(117, 10, 10); - public static final int DEF_FOLIAGE_END = ColorUtil.color(197, 210, 112); - - private final List> structures = Lists.newArrayList(); - private final List features = Lists.newArrayList(); - private final List carvers = Lists.newArrayList(); - private final List mobs = Lists.newArrayList(); - private final List spawns = Lists.newArrayList(); - private final Map customData = Maps.newHashMap(); - - private final ResourceLocation id; - - private AmbientParticleSettings particleConfig; - private AmbientAdditionsSettings additions; - private AmbientMoodSettings mood; - private SoundEvent music; - private SoundEvent loop; - - private int foliageColor = DEF_FOLIAGE_OVERWORLD; - private int grassColor = DEF_FOLIAGE_OVERWORLD; - private int waterFogColor = 329011; - private int waterColor = 4159204; - private int fogColor = 10518688; - private float fogDensity = 1F; - private float depth = 0.1F; - - private Precipitation precipitation = Precipitation.NONE; - private BiomeCategory category = BiomeCategory.NONE; - private float temperature = 1F; - private float genChance = 1F; - private float downfall = 0F; - private int edgeSize = 32; - - private ConfiguredSurfaceBuilder surface; - - /** - * Custom biome definition. Can be extended with new parameters. - * @param id - Biome {@link ResourceLocation} (identifier). - */ - public BCLBiomeDef(ResourceLocation id) { - this.id = id; - } - - /** - * Create default definition for The Nether biome. - * @return {@link BCLBiomeDef}. - */ - public BCLBiomeDef netherBiome() { - this.foliageColor = DEF_FOLIAGE_NETHER; - this.grassColor = DEF_FOLIAGE_NETHER; - this.setCategory(BiomeCategory.NETHER); - return this; - } - - /** - * Create default definition for The End biome. - * @return {@link BCLBiomeDef}. - */ - public BCLBiomeDef endBiome() { - this.foliageColor = DEF_FOLIAGE_END; - this.grassColor = DEF_FOLIAGE_END; - this.setCategory(BiomeCategory.THEEND); - return this; - } - - /** - * Used to load biome settings from config. - * @param config - {@link IdConfig}. - * @return this {@link BCLBiomeDef}. - */ - public BCLBiomeDef loadConfigValues(IdConfig config) { - this.fogDensity = config.getFloat(id, "fog_density", this.fogDensity); - this.genChance = config.getFloat(id, "generation_chance", this.genChance); - this.edgeSize = config.getInt(id, "edge_size", this.edgeSize); - return this; - } - - /** - * Set category of the biome. - * @param category - {@link BiomeCategory}. - * @return this {@link BCLBiomeDef}. - */ - public BCLBiomeDef setCategory(BiomeCategory category) { - this.category = category; - return this; - } - - public BCLBiomeDef setPrecipitation(Precipitation precipitation) { - this.precipitation = precipitation; - return this; - } - - public BCLBiomeDef setSurface(Block block) { - setSurface(SurfaceBuilder.DEFAULT.configured(new SurfaceBuilderBaseConfiguration( - block.defaultBlockState(), - Blocks.END_STONE.defaultBlockState(), - Blocks.END_STONE.defaultBlockState() - ))); - return this; - } - - public BCLBiomeDef setSurface(Block block1, Block block2) { - setSurface(DoubleBlockSurfaceBuilder.register("bclib_" + id.getPath() + "_surface").setBlock1(block1).setBlock2(block2).configured()); - return this; - } - - public BCLBiomeDef setSurface(ConfiguredSurfaceBuilder builder) { - this.surface = builder; - return this; - } - - public BCLBiomeDef setParticles(ParticleOptions particle, float probability) { - this.particleConfig = new AmbientParticleSettings(particle, probability); - return this; - } - - public BCLBiomeDef setGenChance(float genChance) { - this.genChance = genChance; - return this; - } - - public BCLBiomeDef setDepth(float depth) { - this.depth = depth; - return this; - } - - public BCLBiomeDef setTemperature(float temperature) { - this.temperature = temperature; - return this; - } - - public BCLBiomeDef setDownfall(float downfall) { - this.downfall = downfall; - return this; - } - - public BCLBiomeDef setEdgeSize(int edgeSize) { - this.edgeSize = edgeSize; - return this; - } - - public BCLBiomeDef addMobSpawn(EntityType type, int weight, int minGroupSize, int maxGroupSize) { - ResourceLocation eID = Registry.ENTITY_TYPE.getKey(type); - if (eID != Registry.ENTITY_TYPE.getDefaultKey()) { - SpawnInfo info = new SpawnInfo(); - info.type = type; - info.weight = weight; - info.minGroupSize = minGroupSize; - info.maxGroupSize = maxGroupSize; - mobs.add(info); - } - return this; - } - - public BCLBiomeDef addMobSpawn(SpawnerData entry) { - spawns.add(entry); - return this; - } - - public BCLBiomeDef addStructureFeature(ConfiguredStructureFeature feature) { - structures.add(feature); - return this; - } - - public BCLBiomeDef addStructureFeature(BCLStructureFeature feature) { - structures.add(feature.getFeatureConfigured()); - return this; - } - - public BCLBiomeDef addFeature(BCLFeature feature) { - FeatureInfo info = new FeatureInfo(); - info.featureStep = feature.getFeatureStep(); - info.feature = feature.getFeatureConfigured(); - features.add(info); - return this; - } - - public BCLBiomeDef addFeature(Decoration featureStep, ConfiguredFeature feature) { - FeatureInfo info = new FeatureInfo(); - info.featureStep = featureStep; - info.feature = feature; - features.add(info); - return this; - } - - private int getColor(int r, int g, int b) { - r = Mth.clamp(r, 0, 255); - g = Mth.clamp(g, 0, 255); - b = Mth.clamp(b, 0, 255); - return ColorUtil.color(r, g, b); - } - - public BCLBiomeDef setFogColor(int r, int g, int b) { - this.fogColor = getColor(r, g, b); - return this; - } - - public BCLBiomeDef setFogDensity(float density) { - this.fogDensity = density; - return this; - } - - public BCLBiomeDef setWaterColor(int r, int g, int b) { - this.waterColor = getColor(r, g, b); - return this; - } - - public BCLBiomeDef setWaterFogColor(int r, int g, int b) { - this.waterFogColor = getColor(r, g, b); - return this; - } - - public BCLBiomeDef setWaterAndFogColor(int r, int g, int b) { - return setWaterColor(r, g, b).setWaterFogColor(r, g, b); - } - - public BCLBiomeDef setFoliageColor(int r, int g, int b) { - this.foliageColor = getColor(r, g, b); - return this; - } - - public BCLBiomeDef setGrassColor(int r, int g, int b) { - this.grassColor = getColor(r, g, b); - return this; - } - - public BCLBiomeDef setPlantsColor(int r, int g, int b) { - return this.setFoliageColor(r, g, b).setGrassColor(r, g, b); - } - - public BCLBiomeDef setLoop(SoundEvent loop) { - this.loop = loop; - return this; - } - - public BCLBiomeDef setMood(SoundEvent mood) { - this.mood = new AmbientMoodSettings(mood, 6000, 8, 2.0D); - return this; - } - - public BCLBiomeDef setAdditions(SoundEvent additions) { - this.additions = new AmbientAdditionsSettings(additions, 0.0111); - return this; - } - - public BCLBiomeDef setMusic(SoundEvent music) { - this.music = music; - return this; - } - - public Biome build() { - MobSpawnSettings.Builder spawnSettings = new MobSpawnSettings.Builder(); - BiomeGenerationSettings.Builder generationSettings = new BiomeGenerationSettings.Builder(); - Builder effects = new Builder(); - - mobs.forEach((spawn) -> { - spawnSettings.addSpawn(spawn.type.getCategory(), new MobSpawnSettings.SpawnerData(spawn.type, spawn.weight, spawn.minGroupSize, spawn.maxGroupSize)); - }); - - spawns.forEach((entry) -> { - spawnSettings.addSpawn(entry.type.getCategory(), entry); - }); - - generationSettings.surfaceBuilder(surface == null ? net.minecraft.data.worldgen.SurfaceBuilders.END : surface); - structures.forEach((structure) -> generationSettings.addStructureStart(structure)); - features.forEach((info) -> generationSettings.addFeature(info.featureStep, info.feature)); - carvers.forEach((info) -> generationSettings.addCarver(info.carverStep, info.carver)); - - effects.skyColor(0).waterColor(waterColor).waterFogColor(waterFogColor).fogColor(fogColor).foliageColorOverride(foliageColor).grassColorOverride(grassColor); - if (loop != null) effects.ambientLoopSound(loop); - if (mood != null) effects.ambientMoodSound(mood); - if (additions != null) effects.ambientAdditionsSound(additions); - if (particleConfig != null) effects.ambientParticle(particleConfig); - effects.backgroundMusic(music != null ? new Music(music, 600, 2400, true) : Musics.END); - - return new Biome.BiomeBuilder() - .precipitation(precipitation) - .biomeCategory(category) - .depth(depth) - .scale(0.2F) - .temperature(temperature) - .downfall(downfall) - .specialEffects(effects.build()) - .mobSpawnSettings(spawnSettings.build()) - .generationSettings(generationSettings.build()) - .build(); - } - - private static final class SpawnInfo { - EntityType type; - int weight; - int minGroupSize; - int maxGroupSize; - } - - private static final class FeatureInfo { - Decoration featureStep; - ConfiguredFeature feature; - } - - private static final class CarverInfo { - Carving carverStep; - ConfiguredWorldCarver carver; - } - - public ResourceLocation getID() { - return id; - } - - public float getFodDensity() { - return fogDensity; - } - - public float getGenChance() { - return genChance; - } - - public int getEdgeSize() { - return edgeSize; - } - - public BCLBiomeDef addCarver(Carving carverStep, ConfiguredWorldCarver carver) { - CarverInfo info = new CarverInfo(); - info.carverStep = carverStep; - info.carver = carver; - carvers.add(info); - return this; - } - - public BCLBiomeDef addCustomData(String name, Object value) { - customData.put(name, value); - return this; - } - - @SuppressWarnings("unchecked") - public T getCustomData(String name, Object defaultValue) { - return (T) customData.getOrDefault(name, defaultValue); - } - - protected Map getCustomData() { - return customData; - } -} \ No newline at end of file diff --git a/src/main/java/ru/bclib/world/features/BCLFeature.java b/src/main/java/ru/bclib/world/features/BCLFeature.java deleted file mode 100644 index d8d3483e..00000000 --- a/src/main/java/ru/bclib/world/features/BCLFeature.java +++ /dev/null @@ -1,94 +0,0 @@ -package ru.bclib.world.features; - -import net.minecraft.core.Registry; -import net.minecraft.data.BuiltinRegistries; -import net.minecraft.data.worldgen.Features; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.levelgen.GenerationStep; -import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; -import net.minecraft.world.level.levelgen.feature.Feature; -import net.minecraft.world.level.levelgen.feature.configurations.CountConfiguration; -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.OreConfiguration; -import net.minecraft.world.level.levelgen.feature.configurations.RangeDecoratorConfiguration; -import net.minecraft.world.level.levelgen.placement.ChanceDecoratorConfiguration; -import net.minecraft.world.level.levelgen.placement.FeatureDecorator; -import net.minecraft.world.level.levelgen.structure.templatesystem.BlockMatchTest; - -public class BCLFeature { - private Feature feature; - private ConfiguredFeature featureConfigured; - private GenerationStep.Decoration featureStep; - - public BCLFeature(Feature feature, ConfiguredFeature configuredFeature, GenerationStep.Decoration featureStep) { - this.featureConfigured = configuredFeature; - this.featureStep = featureStep; - this.feature = feature; - } - - public BCLFeature(ResourceLocation id, Feature feature, GenerationStep.Decoration featureStep, ConfiguredFeature configuredFeature) { - this.featureConfigured = Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, id, configuredFeature); - this.feature = Registry.register(Registry.FEATURE, id, feature); - this.featureStep = featureStep; - } - - public static BCLFeature makeVegetationFeature(ResourceLocation id, Feature feature, int density) { - ConfiguredFeature configured = feature.configured(FeatureConfiguration.NONE).decorated(Features.Decorators.HEIGHTMAP_SQUARE).countRandom(density); - return new BCLFeature(id, feature, GenerationStep.Decoration.VEGETAL_DECORATION, configured); - } - - public static BCLFeature makeRawGenFeature(ResourceLocation id, Feature feature, int chance) { - ConfiguredFeature configured = feature.configured(FeatureConfiguration.NONE).decorated(FeatureDecorator.CHANCE.configured(new ChanceDecoratorConfiguration(chance))); - return new BCLFeature(id, feature, GenerationStep.Decoration.RAW_GENERATION, configured); - } - - public static BCLFeature makeLakeFeature(ResourceLocation id, Feature feature, int chance) { - ConfiguredFeature configured = feature.configured(FeatureConfiguration.NONE).decorated(FeatureDecorator.WATER_LAKE.configured(new ChanceDecoratorConfiguration(chance))); - return new BCLFeature(id, feature, GenerationStep.Decoration.LAKES, configured); - } - - public static BCLFeature makeOreFeature(ResourceLocation id, Block blockOre, int veins, int veinSize, int offset, int minY, int maxY) { - OreConfiguration featureConfig = new OreConfiguration(new BlockMatchTest(Blocks.END_STONE), blockOre.defaultBlockState(), veinSize); - RangeDecoratorConfiguration rangeDecorator = new RangeDecoratorConfiguration(offset, minY, maxY); - ConfiguredFeature oreFeature = Feature.ORE.configured(featureConfig) - .decorated(FeatureDecorator.RANGE.configured(rangeDecorator)) - .squared() - .count(veins); - return new BCLFeature(Feature.ORE, Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, id, oreFeature), GenerationStep.Decoration.UNDERGROUND_ORES); - } - - public static BCLFeature makeChunkFeature(ResourceLocation id, Feature feature) { - ConfiguredFeature configured = feature.configured(FeatureConfiguration.NONE).decorated(FeatureDecorator.COUNT.configured(new CountConfiguration(1))); - return new BCLFeature(id, feature, GenerationStep.Decoration.LOCAL_MODIFICATIONS, configured); - } - - public static BCLFeature makeChansedFeature(ResourceLocation id, Feature feature, int chance) { - ConfiguredFeature configured = feature.configured(FeatureConfiguration.NONE).decorated(FeatureDecorator.CHANCE.configured(new ChanceDecoratorConfiguration(chance))); - return new BCLFeature(id, feature, GenerationStep.Decoration.SURFACE_STRUCTURES, configured); - } - - public static BCLFeature makeCountRawFeature(ResourceLocation id, Feature feature, int chance) { - ConfiguredFeature configured = feature.configured(FeatureConfiguration.NONE).decorated(FeatureDecorator.COUNT.configured(new CountConfiguration(chance))); - return new BCLFeature(id, feature, GenerationStep.Decoration.RAW_GENERATION, configured); - } - - public static BCLFeature makeFeatureConfigured(ResourceLocation id, Feature feature) { - ConfiguredFeature configured = feature.configured(FeatureConfiguration.NONE); - return new BCLFeature(id, feature, GenerationStep.Decoration.RAW_GENERATION, configured); - } - - public Feature getFeature() { - return feature; - } - - public ConfiguredFeature getFeatureConfigured() { - return featureConfigured; - } - - public GenerationStep.Decoration getFeatureStep() { - return featureStep; - } -} diff --git a/src/main/java/ru/bclib/world/features/DefaultFeature.java b/src/main/java/ru/bclib/world/features/DefaultFeature.java deleted file mode 100644 index 30553274..00000000 --- a/src/main/java/ru/bclib/world/features/DefaultFeature.java +++ /dev/null @@ -1,44 +0,0 @@ -package ru.bclib.world.features; - -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; -import ru.bclib.util.BlocksHelper; - -public abstract class DefaultFeature extends Feature { - protected static final BlockState AIR = Blocks.AIR.defaultBlockState(); - protected 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); - } -} diff --git a/src/main/java/ru/bclib/world/features/ListFeature.java b/src/main/java/ru/bclib/world/features/ListFeature.java deleted file mode 100644 index 8f00c087..00000000 --- a/src/main/java/ru/bclib/world/features/ListFeature.java +++ /dev/null @@ -1,78 +0,0 @@ -package ru.bclib.world.features; - -import java.util.List; -import java.util.Random; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; -import ru.bclib.util.StructureHelper; - -public class ListFeature extends NBTStructureFeature { - private final List list; - private StructureInfo selected; - - public ListFeature(List list) { - this.list = list; - } - - @Override - protected StructureTemplate getStructure(WorldGenLevel world, BlockPos pos, Random random) { - selected = list.get(random.nextInt(list.size())); - return selected.getStructure(); - } - - @Override - protected boolean canSpawn(WorldGenLevel world, BlockPos pos, Random random) { - int cx = pos.getX() >> 4; - int cz = pos.getZ() >> 4; - return ((cx + cz) & 1) == 0 && pos.getY() > 58;// && world.getBlockState(pos.below()).is(EndTags.GEN_TERRAIN); - } - - @Override - protected Rotation getRotation(WorldGenLevel world, BlockPos pos, Random random) { - return Rotation.getRandom(random); - } - - @Override - protected Mirror getMirror(WorldGenLevel world, BlockPos pos, Random random) { - return Mirror.values()[random.nextInt(3)]; - } - - @Override - protected int getYOffset(StructureTemplate structure, WorldGenLevel world, BlockPos pos, Random random) { - return selected.offsetY; - } - - @Override - protected TerrainMerge getTerrainMerge(WorldGenLevel world, BlockPos pos, Random random) { - return selected.terrainMerge; - } - - @Override - protected void addStructureData(StructurePlaceSettings data) {} - - public static final class StructureInfo { - public final TerrainMerge terrainMerge; - public final String structurePath; - public final int offsetY; - - private StructureTemplate structure; - - public StructureInfo(String structurePath, int offsetY, TerrainMerge terrainMerge) { - this.terrainMerge = terrainMerge; - this.structurePath = structurePath; - this.offsetY = offsetY; - } - - public StructureTemplate getStructure() { - if (structure == null) { - structure = StructureHelper.readStructure(structurePath); - } - return structure; - } - } -} diff --git a/src/main/java/ru/bclib/world/features/NBTStructureFeature.java b/src/main/java/ru/bclib/world/features/NBTStructureFeature.java deleted file mode 100644 index 80d94729..00000000 --- a/src/main/java/ru/bclib/world/features/NBTStructureFeature.java +++ /dev/null @@ -1,213 +0,0 @@ -package ru.bclib.world.features; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Random; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.BlockPos.MutableBlockPos; -import net.minecraft.core.Direction; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.MinecraftServer; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.biome.Biome; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.ChunkGenerator; -import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; -import net.minecraft.world.level.levelgen.structure.BoundingBox; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate; -import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilderConfiguration; -import ru.bclib.api.BiomeAPI; -import ru.bclib.api.TagAPI; -import ru.bclib.util.BlocksHelper; -import ru.bclib.world.processors.DestructionStructureProcessor; - -public abstract class NBTStructureFeature extends DefaultFeature { - protected static final DestructionStructureProcessor DESTRUCTION = new DestructionStructureProcessor(); - - protected abstract StructureTemplate getStructure(WorldGenLevel world, BlockPos pos, Random random); - - protected abstract boolean canSpawn(WorldGenLevel world, BlockPos pos, Random random); - - protected abstract Rotation getRotation(WorldGenLevel world, BlockPos pos, Random random); - - protected abstract Mirror getMirror(WorldGenLevel world, BlockPos pos, Random random); - - protected abstract int getYOffset(StructureTemplate structure, WorldGenLevel world, BlockPos pos, Random random); - - protected abstract TerrainMerge getTerrainMerge(WorldGenLevel world, BlockPos pos, Random random); - - protected abstract void addStructureData(StructurePlaceSettings data); - - protected BlockPos getGround(WorldGenLevel world, BlockPos center) { - Biome biome = world.getBiome(center); - ResourceLocation id = BiomeAPI.getBiomeID(biome); - if (id.getNamespace().contains("moutain") || id.getNamespace().contains("lake")) { - int y = getAverageY(world, center); - return new BlockPos(center.getX(), y, center.getZ()); - } - else { - int y = getAverageYWG(world, center); - return new BlockPos(center.getX(), y, center.getZ()); - } - } - - protected int getAverageY(WorldGenLevel world, BlockPos center) { - int y = getYOnSurface(world, center.getX(), center.getZ()); - y += getYOnSurface(world, center.getX() - 2, center.getZ() - 2); - y += getYOnSurface(world, center.getX() + 2, center.getZ() - 2); - y += getYOnSurface(world, center.getX() - 2, center.getZ() + 2); - y += getYOnSurface(world, center.getX() + 2, center.getZ() + 2); - return y / 5; - } - - protected int getAverageYWG(WorldGenLevel world, BlockPos center) { - int y = getYOnSurfaceWG(world, center.getX(), center.getZ()); - y += getYOnSurfaceWG(world, center.getX() - 2, center.getZ() - 2); - y += getYOnSurfaceWG(world, center.getX() + 2, center.getZ() - 2); - y += getYOnSurfaceWG(world, center.getX() - 2, center.getZ() + 2); - y += getYOnSurfaceWG(world, center.getX() + 2, center.getZ() + 2); - return y / 5; - } - - @Override - public boolean place(WorldGenLevel world, ChunkGenerator chunkGenerator, Random random, BlockPos center, NoneFeatureConfiguration featureConfig) { - center = new BlockPos(((center.getX() >> 4) << 4) | 8, 128, ((center.getZ() >> 4) << 4) | 8); - center = getGround(world, center); - - if (!canSpawn(world, center, random)) { - return false; - } - - int posY = center.getY() + 1; - StructureTemplate structure = getStructure(world, center, random); - Rotation rotation = getRotation(world, center, random); - Mirror mirror = getMirror(world, center, random); - BlockPos offset = StructureTemplate.transform(structure.getSize(), mirror, rotation, BlockPos.ZERO); - center = center.offset(0, getYOffset(structure, world, center, random) + 0.5, 0); - - BoundingBox bounds = makeBox(center); - StructurePlaceSettings placementData = new StructurePlaceSettings().setRotation(rotation).setMirror(mirror).setBoundingBox(bounds); - addStructureData(placementData); - center = center.offset(-offset.getX() * 0.5, 0, -offset.getZ() * 0.5); - structure.placeInWorldChunk(world, center, placementData, random); - - TerrainMerge merge = getTerrainMerge(world, center, random); - int x1 = center.getX(); - int z1 = center.getZ(); - int x2 = x1 + offset.getX(); - int z2 = z1 + offset.getZ(); - if (merge != TerrainMerge.NONE) { - MutableBlockPos mut = new MutableBlockPos(); - - if (x2 < x1) { - int a = x1; - x1 = x2; - x2 = a; - } - - if (z2 < z1) { - int a = z1; - z1 = z2; - z2 = a; - } - - int surfMax = posY - 1; - for (int x = x1; x <= x2; x++) { - mut.setX(x); - for (int z = z1; z <= z2; z++) { - mut.setZ(z); - mut.setY(surfMax); - BlockState state = world.getBlockState(mut); - if (!state.is(TagAPI.GEN_TERRAIN) && state.isFaceSturdy(world, mut, Direction.DOWN)) { - for (int i = 0; i < 10; i++) { - mut.setY(mut.getY() - 1); - BlockState stateSt = world.getBlockState(mut); - if (!stateSt.is(TagAPI.GEN_TERRAIN)) { - if (merge == TerrainMerge.SURFACE) { - SurfaceBuilderConfiguration config = world.getBiome(mut).getGenerationSettings().getSurfaceBuilderConfig(); - boolean isTop = mut.getY() == surfMax && state.getMaterial().isSolidBlocking(); - BlockState top = isTop ? config.getTopMaterial() : config.getUnderMaterial(); - BlocksHelper.setWithoutUpdate(world, mut, top); - } - else { - BlocksHelper.setWithoutUpdate(world, mut, state); - } - } - else { - if (stateSt.is(TagAPI.END_GROUND) && state.getMaterial().isSolidBlocking()) { - if (merge == TerrainMerge.SURFACE) { - SurfaceBuilderConfiguration config = world.getBiome(mut).getGenerationSettings() - .getSurfaceBuilderConfig(); - BlocksHelper.setWithoutUpdate(world, mut, config.getUnderMaterial()); - } - else { - BlocksHelper.setWithoutUpdate(world, mut, state); - } - } - break; - } - } - } - } - } - } - //BlocksHelper.fixBlocks(world, new BlockPos(x1, center.getY(), z1), new BlockPos(x2, center.getY() + offset.getY(), z2)); - - return true; - } - - protected BoundingBox makeBox(BlockPos pos) { - int sx = ((pos.getX() >> 4) << 4) - 16; - int sz = ((pos.getZ() >> 4) << 4) - 16; - int ex = sx + 47; - int ez = sz + 47; - return BoundingBox.createProper(sx, 0, sz, ex, 255, ez); - } - - protected static StructureTemplate readStructure(ResourceLocation resource) { - String ns = resource.getNamespace(); - String nm = resource.getPath(); - - try { - InputStream inputstream = MinecraftServer.class - .getResourceAsStream("/data/" + ns + "/structures/" + nm + ".nbt"); - return readStructureFromStream(inputstream); - } - catch (IOException e) { - e.printStackTrace(); - } - - return null; - } - - private static StructureTemplate readStructureFromStream(InputStream stream) throws IOException { - CompoundTag nbttagcompound = NbtIo.readCompressed(stream); - - StructureTemplate template = new StructureTemplate(); - template.load(nbttagcompound); - - return template; - } - - public static enum TerrainMerge { - NONE, SURFACE, OBJECT; - - public static TerrainMerge getFromString(String type) { - if (type.equals("surface")) { - return SURFACE; - } - else if (type.equals("object")) { - return OBJECT; - } - else { - return NONE; - } - } - } -} diff --git a/src/main/java/ru/bclib/world/generator/BiomeChunk.java b/src/main/java/ru/bclib/world/generator/BiomeChunk.java deleted file mode 100644 index 3fdb6358..00000000 --- a/src/main/java/ru/bclib/world/generator/BiomeChunk.java +++ /dev/null @@ -1,35 +0,0 @@ -package ru.bclib.world.generator; - -import java.util.Random; - -import ru.bclib.world.biomes.BCLBiome; - -public class BiomeChunk { - protected static final int WIDTH = 16; - private static final int SM_WIDTH = WIDTH >> 1; - private static final int MASK_OFFSET = SM_WIDTH - 1; - protected static final int MASK_WIDTH = WIDTH - 1; - - private final BCLBiome[][] biomes; - - public BiomeChunk(BiomeMap map, Random random, BiomePicker picker) { - BCLBiome[][] PreBio = new BCLBiome[SM_WIDTH][SM_WIDTH]; - biomes = new BCLBiome[WIDTH][WIDTH]; - - for (int x = 0; x < SM_WIDTH; x++) - for (int z = 0; z < SM_WIDTH; z++) - PreBio[x][z] = picker.getBiome(random); - - for (int x = 0; x < WIDTH; x++) - for (int z = 0; z < WIDTH; z++) - biomes[x][z] = PreBio[offsetXZ(x, random)][offsetXZ(z, random)].getSubBiome(random); - } - - public BCLBiome getBiome(int x, int z) { - return biomes[x & MASK_WIDTH][z & MASK_WIDTH]; - } - - private int offsetXZ(int x, Random random) { - return ((x + random.nextInt(2)) >> 1) & MASK_OFFSET; - } -} diff --git a/src/main/java/ru/bclib/world/generator/BiomeMap.java b/src/main/java/ru/bclib/world/generator/BiomeMap.java deleted file mode 100644 index 088fda63..00000000 --- a/src/main/java/ru/bclib/world/generator/BiomeMap.java +++ /dev/null @@ -1,113 +0,0 @@ -package ru.bclib.world.generator; - -import java.util.Map; - -import com.google.common.collect.Maps; - -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.levelgen.WorldgenRandom; -import ru.bclib.noise.OpenSimplexNoise; -import ru.bclib.util.MHelper; -import ru.bclib.world.biomes.BCLBiome; - -public class BiomeMap { - private static final WorldgenRandom RANDOM = new WorldgenRandom(); - - private final Map maps = Maps.newHashMap(); - private final int size; - private final int sizeXZ; - private final int depth; - private final OpenSimplexNoise noiseX; - private final OpenSimplexNoise noiseZ; - private final BiomePicker picker; - private final long seed; - - public BiomeMap(long seed, int size, BiomePicker picker) { - maps.clear(); - RANDOM.setSeed(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; - this.seed = seed; - } - - public long getSeed() { - return seed; - } - - public void clearCache() { - if (maps.size() > 32) { - maps.clear(); - } - } - - private BCLBiome getRawBiome(int bx, int bz) { - double x = (double) bx * size / sizeXZ; - double z = (double) bz * size / sizeXZ; - double nx = x; - double nz = z; - - double px = bx * 0.2; - double pz = bz * 0.2; - - for (int i = 0; i < depth; i++) { - nx = (x + noiseX.eval(px, pz)) / 2F; - nz = (z + noiseZ.eval(px, pz)) / 2F; - - x = nx; - z = nz; - - px = px / 2 + i; - pz = pz / 2 + i; - } - - bx = MHelper.floor(x); - bz = MHelper.floor(z); - if ((bx & BiomeChunk.MASK_WIDTH) == BiomeChunk.MASK_WIDTH) { - x += (bz / 2) & 1; - } - if ((bz & BiomeChunk.MASK_WIDTH) == BiomeChunk.MASK_WIDTH) { - z += (bx / 2) & 1; - } - - ChunkPos cpos = new ChunkPos(MHelper.floor(x / BiomeChunk.WIDTH), MHelper.floor(z / BiomeChunk.WIDTH)); - BiomeChunk chunk = maps.get(cpos); - if (chunk == null) { - RANDOM.setBaseChunkSeed(cpos.x, cpos.z); - chunk = new BiomeChunk(this, RANDOM, picker); - maps.put(cpos, chunk); - } - - return chunk.getBiome(MHelper.floor(x), MHelper.floor(z)); - } - - public BCLBiome getBiome(int x, int z) { - BCLBiome biome = getRawBiome(x, z); - - if (biome.hasEdge() || (biome.hasParentBiome() && biome.getParentBiome().hasEdge())) { - BCLBiome search = biome; - if (biome.hasParentBiome()) { - search = biome.getParentBiome(); - } - int d = (int) Math.ceil(search.getEdgeSize() / 4F) << 2; - - boolean edge = !search.isSame(getRawBiome(x + d, z)); - edge = edge || !search.isSame(getRawBiome(x - d, z)); - edge = edge || !search.isSame(getRawBiome(x, z + d)); - edge = edge || !search.isSame(getRawBiome(x, z - d)); - 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; - } -} diff --git a/src/main/java/ru/bclib/world/generator/BiomePicker.java b/src/main/java/ru/bclib/world/generator/BiomePicker.java deleted file mode 100644 index 57eb36dd..00000000 --- a/src/main/java/ru/bclib/world/generator/BiomePicker.java +++ /dev/null @@ -1,69 +0,0 @@ -package ru.bclib.world.generator; - -import java.util.List; -import java.util.Random; -import java.util.Set; - -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; - -import net.minecraft.resources.ResourceLocation; -import ru.bclib.util.WeighTree; -import ru.bclib.util.WeightedList; -import ru.bclib.world.biomes.BCLBiome; - -public class BiomePicker { - private final Set immutableIDs = Sets.newHashSet(); - private final List biomes = Lists.newArrayList(); - private int biomeCount = 0; - private WeighTree tree; - - public void addBiome(BCLBiome biome) { - immutableIDs.add(biome.getID()); - biomes.add(biome); - biomeCount ++; - } - - public void addBiomeMutable(BCLBiome biome) { - biomes.add(biome); - } - - public void clearMutables() { - for (int i = biomes.size() - 1; i >= biomeCount; i--) { - biomes.remove(i); - } - } - - public BCLBiome getBiome(Random random) { - return biomes.isEmpty() ? null : tree.get(random); - } - - public List getBiomes() { - return biomes; - } - - public boolean containsImmutable(ResourceLocation id) { - return immutableIDs.contains(id); - } - - public void removeMutableBiome(ResourceLocation id) { - for (int i = biomeCount; i < biomes.size(); i++) { - BCLBiome biome = biomes.get(i); - if (biome.getID().equals(id)) { - biomes.remove(i); - break; - } - } - } - - public void rebuild() { - if (biomes.isEmpty()) { - return; - } - WeightedList list = new WeightedList(); - biomes.forEach((biome) -> { - list.add(biome, biome.getGenChance()); - }); - tree = new WeighTree(list); - } -} diff --git a/src/main/java/ru/bclib/world/generator/BiomeType.java b/src/main/java/ru/bclib/world/generator/BiomeType.java deleted file mode 100644 index 6e40eb2c..00000000 --- a/src/main/java/ru/bclib/world/generator/BiomeType.java +++ /dev/null @@ -1,6 +0,0 @@ -package ru.bclib.world.generator; - -public enum BiomeType { - LAND, - VOID; -} diff --git a/src/main/java/ru/bclib/world/processors/DestructionStructureProcessor.java b/src/main/java/ru/bclib/world/processors/DestructionStructureProcessor.java deleted file mode 100644 index 3d12057a..00000000 --- a/src/main/java/ru/bclib/world/processors/DestructionStructureProcessor.java +++ /dev/null @@ -1,31 +0,0 @@ -package ru.bclib.world.processors; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorType; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo; -import ru.bclib.util.BlocksHelper; -import ru.bclib.util.MHelper; - -public class DestructionStructureProcessor extends StructureProcessor { - private int chance = 4; - - public void setChance(int chance) { - this.chance = chance; - } - - @Override - public StructureBlockInfo processBlock(LevelReader worldView, BlockPos pos, BlockPos blockPos, StructureBlockInfo structureBlockInfo, StructureBlockInfo structureBlockInfo2, StructurePlaceSettings structurePlacementData) { - if (!BlocksHelper.isInvulnerable(structureBlockInfo2.state, worldView, structureBlockInfo2.pos) && MHelper.RANDOM.nextInt(chance) == 0) { - return null; - } - return structureBlockInfo2; - } - - @Override - protected StructureProcessorType getType() { - return StructureProcessorType.RULE; - } -} diff --git a/src/main/java/ru/bclib/world/processors/TerrainStructureProcessor.java b/src/main/java/ru/bclib/world/processors/TerrainStructureProcessor.java deleted file mode 100644 index dceb0ee1..00000000 --- a/src/main/java/ru/bclib/world/processors/TerrainStructureProcessor.java +++ /dev/null @@ -1,27 +0,0 @@ -package ru.bclib.world.processors; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorType; -import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo; - -public class TerrainStructureProcessor extends StructureProcessor { - @Override - public StructureBlockInfo processBlock(LevelReader worldView, BlockPos pos, BlockPos blockPos, StructureBlockInfo structureBlockInfo, StructureBlockInfo structureBlockInfo2, StructurePlaceSettings structurePlacementData) { - BlockPos bpos = structureBlockInfo2.pos; - if (structureBlockInfo2.state.is(Blocks.END_STONE) && worldView.isEmptyBlock(bpos.above())) { - BlockState top = worldView.getBiome(structureBlockInfo2.pos).getGenerationSettings().getSurfaceBuilderConfig().getTopMaterial(); - return new StructureBlockInfo(bpos, top, structureBlockInfo2.nbt); - } - return structureBlockInfo2; - } - - @Override - protected StructureProcessorType getType() { - return StructureProcessorType.RULE; - } -} diff --git a/src/main/java/ru/bclib/world/structures/BCLStructureFeature.java b/src/main/java/ru/bclib/world/structures/BCLStructureFeature.java deleted file mode 100644 index 581f0b60..00000000 --- a/src/main/java/ru/bclib/world/structures/BCLStructureFeature.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.bclib.world.structures; - -import java.util.Random; - -import net.fabricmc.fabric.api.structure.v1.FabricStructureBuilder; -import net.minecraft.data.BuiltinRegistries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.levelgen.GenerationStep; -import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature; -import net.minecraft.world.level.levelgen.feature.StructureFeature; -import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; - -public class BCLStructureFeature { - private static final Random RANDOM = new Random(354); - private final StructureFeature structure; - private final ConfiguredStructureFeature featureConfigured; - private final GenerationStep.Decoration featureStep; - - public BCLStructureFeature(ResourceLocation id, StructureFeature structure, GenerationStep.Decoration step, int spacing, int separation) { - this.featureStep = step; - this.structure = FabricStructureBuilder.create(id, structure) - .step(step) - .defaultConfig(spacing, separation, RANDOM.nextInt(8192)) - .register(); - - this.featureConfigured = this.structure.configured(NoneFeatureConfiguration.NONE); - - BuiltinRegistries.register(BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE, id, this.featureConfigured); - } - - public StructureFeature getStructure() { - return structure; - } - - public ConfiguredStructureFeature getFeatureConfigured() { - return featureConfigured; - } - - public GenerationStep.Decoration getFeatureStep() { - return featureStep; - } -} diff --git a/src/main/java/ru/bclib/world/structures/StructureWorld.java b/src/main/java/ru/bclib/world/structures/StructureWorld.java deleted file mode 100644 index af49651b..00000000 --- a/src/main/java/ru/bclib/world/structures/StructureWorld.java +++ /dev/null @@ -1,170 +0,0 @@ -package ru.bclib.world.structures; - -import java.util.Map; - -import com.google.common.collect.Maps; - -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtUtils; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.levelgen.structure.BoundingBox; - -public class StructureWorld { - private Map parts = Maps.newHashMap(); - private ChunkPos lastPos; - private Part lastPart; - private int minX = Integer.MAX_VALUE; - private int minY = Integer.MAX_VALUE; - private int minZ = Integer.MAX_VALUE; - private int maxX = Integer.MIN_VALUE; - private int maxY = Integer.MIN_VALUE; - private int maxZ = Integer.MIN_VALUE; - - public StructureWorld() {} - - public StructureWorld(CompoundTag tag) { - minX = tag.getInt("minX"); - maxX = tag.getInt("maxX"); - minY = tag.getInt("minY"); - maxY = tag.getInt("maxY"); - minZ = tag.getInt("minZ"); - maxZ = tag.getInt("maxZ"); - - ListTag map = tag.getList("parts", 10); - map.forEach((element) -> { - CompoundTag compound = (CompoundTag) element; - Part part = new Part(compound); - int x = compound.getInt("x"); - int z = compound.getInt("z"); - parts.put(new ChunkPos(x, z), part); - }); - } - - public void setBlock(BlockPos pos, BlockState state) { - ChunkPos cPos = new ChunkPos(pos); - - if (cPos.equals(lastPos)) { - lastPart.addBlock(pos, state); - return; - } - - Part part = parts.get(cPos); - if (part == null) { - part = new Part(); - parts.put(cPos, part); - - if (cPos.x < minX) minX = cPos.x; - if (cPos.x > maxX) maxX = cPos.x; - if (cPos.z < minZ) minZ = cPos.z; - if (cPos.z > maxZ) maxZ = cPos.z; - } - if (pos.getY() < minY) minY = pos.getY(); - if (pos.getY() > maxY) maxY = pos.getY(); - part.addBlock(pos, state); - - lastPos = cPos; - lastPart = part; - } - - public boolean placeChunk(WorldGenLevel world, ChunkPos chunkPos) { - Part part = parts.get(chunkPos); - if (part != null) { - ChunkAccess chunk = world.getChunk(chunkPos.x, chunkPos.z); - part.placeChunk(chunk); - return true; - } - return false; - } - - public CompoundTag toBNT() { - CompoundTag tag = new CompoundTag(); - tag.putInt("minX", minX); - tag.putInt("maxX", maxX); - tag.putInt("minY", minY); - tag.putInt("maxY", maxY); - tag.putInt("minZ", minZ); - tag.putInt("maxZ", maxZ); - ListTag map = new ListTag(); - tag.put("parts", map); - parts.forEach((pos, part) -> { - map.add(part.toNBT(pos.x, pos.z)); - }); - return tag; - } - - public BoundingBox getBounds() { - if (minX == Integer.MAX_VALUE || maxX == Integer.MIN_VALUE || minZ == Integer.MAX_VALUE || maxZ == Integer.MIN_VALUE) { - return BoundingBox.getUnknownBox(); - } - return new BoundingBox(minX << 4, minY, minZ << 4, (maxX << 4) | 15, maxY, (maxZ << 4) | 15); - } - - private static final class Part { - Map blocks = Maps.newHashMap(); - - public Part() {} - - public Part(CompoundTag tag) { - ListTag map = tag.getList("blocks", 10); - ListTag map2 = tag.getList("states", 10); - BlockState[] states = new BlockState[map2.size()]; - for (int i = 0; i < states.length; i++) { - states[i] = NbtUtils.readBlockState((CompoundTag) map2.get(i)); - } - - map.forEach((element) -> { - CompoundTag block = (CompoundTag) element; - BlockPos pos = NbtUtils.readBlockPos(block.getCompound("pos")); - int stateID = block.getInt("state"); - BlockState state = stateID < states.length ? states[stateID] : Block.stateById(stateID); - blocks.put(pos, state); - }); - } - - void addBlock(BlockPos pos, BlockState state) { - BlockPos inner = new BlockPos(pos.getX() & 15, pos.getY(), pos.getZ() & 15); - blocks.put(inner, state); - } - - void placeChunk(ChunkAccess chunk) { - blocks.forEach((pos, state) -> { - chunk.setBlockState(pos, state, false); - }); - } - - CompoundTag toNBT(int x, int z) { - CompoundTag tag = new CompoundTag(); - tag.putInt("x", x); - tag.putInt("z", z); - ListTag map = new ListTag(); - tag.put("blocks", map); - ListTag stateMap = new ListTag(); - tag.put("states", stateMap); - - int[] id = new int[1]; - Map states = Maps.newHashMap(); - - blocks.forEach((pos, state) -> { - int stateID = states.getOrDefault(states, -1); - if (stateID < 0) { - stateID = id[0] ++; - states.put(state, stateID); - stateMap.add(NbtUtils.writeBlockState(state)); - } - - CompoundTag block = new CompoundTag(); - block.put("pos", NbtUtils.writeBlockPos(pos)); - block.putInt("state", stateID); - map.add(block); - }); - - return tag; - } - } -} diff --git a/src/main/java/ru/bclib/world/surface/BCLSurfaceBuilders.java b/src/main/java/ru/bclib/world/surface/BCLSurfaceBuilders.java deleted file mode 100644 index 641eeace..00000000 --- a/src/main/java/ru/bclib/world/surface/BCLSurfaceBuilders.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.bclib.world.surface; - -import net.minecraft.core.Registry; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilder; -import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilderBaseConfiguration; - -public class BCLSurfaceBuilders { - public static SurfaceBuilder register(String name, SurfaceBuilder builder) { - return Registry.register(Registry.SURFACE_BUILDER, name, builder); - } - - public static SurfaceBuilderBaseConfiguration makeSimpleConfig(Block block) { - BlockState state = block.defaultBlockState(); - return new SurfaceBuilderBaseConfiguration(state, state, state); - } - - public static void register() {} -} diff --git a/src/main/java/ru/bclib/world/surface/DoubleBlockSurfaceBuilder.java b/src/main/java/ru/bclib/world/surface/DoubleBlockSurfaceBuilder.java deleted file mode 100644 index 1e19d33e..00000000 --- a/src/main/java/ru/bclib/world/surface/DoubleBlockSurfaceBuilder.java +++ /dev/null @@ -1,52 +0,0 @@ -package ru.bclib.world.surface; - -import java.util.Random; - -import net.minecraft.core.Registry; -import net.minecraft.world.level.biome.Biome; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.levelgen.surfacebuilders.ConfiguredSurfaceBuilder; -import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilder; -import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilderBaseConfiguration; -import ru.bclib.noise.OpenSimplexNoise; -import ru.bclib.util.MHelper; - -public class DoubleBlockSurfaceBuilder extends SurfaceBuilder { - private static final OpenSimplexNoise NOISE = new OpenSimplexNoise(4141); - private SurfaceBuilderBaseConfiguration config1; - private SurfaceBuilderBaseConfiguration config2; - - private DoubleBlockSurfaceBuilder() { - super(SurfaceBuilderBaseConfiguration.CODEC); - } - - public DoubleBlockSurfaceBuilder setBlock1(Block block) { - BlockState stone = Blocks.END_STONE.defaultBlockState(); - config1 = new SurfaceBuilderBaseConfiguration(block.defaultBlockState(), stone, stone); - return this; - } - - public DoubleBlockSurfaceBuilder setBlock2(Block block) { - BlockState stone = Blocks.END_STONE.defaultBlockState(); - config2 = new SurfaceBuilderBaseConfiguration(block.defaultBlockState(), stone, stone); - return this; - } - - @Override - public void apply(Random random, ChunkAccess chunk, Biome biome, int x, int z, int height, double noise, BlockState defaultBlock, BlockState defaultFluid, int seaLevel, long seed, SurfaceBuilderBaseConfiguration surfaceBlocks) { - noise = NOISE.eval(x * 0.1, z * 0.1) + MHelper.randRange(-0.4, 0.4, random); - SurfaceBuilder.DEFAULT.apply(random, chunk, biome, x, z, height, noise, defaultBlock, defaultFluid, seaLevel, seed, noise > 0 ? config1 : config2); - } - - public static DoubleBlockSurfaceBuilder register(String name) { - return Registry.register(Registry.SURFACE_BUILDER, name, new DoubleBlockSurfaceBuilder()); - } - - public ConfiguredSurfaceBuilder configured() { - BlockState stone = Blocks.END_STONE.defaultBlockState(); - return this.configured(new SurfaceBuilderBaseConfiguration(config1.getTopMaterial(), stone, stone)); - } -} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/betterx.png b/src/main/resources/assets/bclib/betterx.png new file mode 100644 index 00000000..5223d467 Binary files /dev/null and b/src/main/resources/assets/bclib/betterx.png differ diff --git a/src/main/resources/assets/bclib/blockstates/test_hanging_sign.json b/src/main/resources/assets/bclib/blockstates/test_hanging_sign.json new file mode 100644 index 00000000..6fbb51b8 --- /dev/null +++ b/src/main/resources/assets/bclib/blockstates/test_hanging_sign.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "bclib:block/empty_hanging_test" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/blockstates/test_sign.json b/src/main/resources/assets/bclib/blockstates/test_sign.json new file mode 100644 index 00000000..0a4d46e3 --- /dev/null +++ b/src/main/resources/assets/bclib/blockstates/test_sign.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "bclib:block/empty_test" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/blockstates/test_wall_hanging_sign.json b/src/main/resources/assets/bclib/blockstates/test_wall_hanging_sign.json new file mode 100644 index 00000000..6fbb51b8 --- /dev/null +++ b/src/main/resources/assets/bclib/blockstates/test_wall_hanging_sign.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "bclib:block/empty_hanging_test" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/blockstates/test_wall_sign.json b/src/main/resources/assets/bclib/blockstates/test_wall_sign.json new file mode 100644 index 00000000..0a4d46e3 --- /dev/null +++ b/src/main/resources/assets/bclib/blockstates/test_wall_sign.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "bclib:block/empty_test" + } + } +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/header.jpg b/src/main/resources/assets/bclib/header.jpg new file mode 100644 index 00000000..14f30a77 Binary files /dev/null and b/src/main/resources/assets/bclib/header.jpg differ diff --git a/src/main/resources/assets/bclib/icon.png b/src/main/resources/assets/bclib/icon.png index 95281739..368a2a60 100644 Binary files a/src/main/resources/assets/bclib/icon.png and b/src/main/resources/assets/bclib/icon.png differ diff --git a/src/main/resources/assets/bclib/icon_betterend.png b/src/main/resources/assets/bclib/icon_betterend.png new file mode 100644 index 00000000..8f696087 Binary files /dev/null and b/src/main/resources/assets/bclib/icon_betterend.png differ diff --git a/src/main/resources/assets/bclib/icon_betternether.png b/src/main/resources/assets/bclib/icon_betternether.png new file mode 100644 index 00000000..215bc42f Binary files /dev/null and b/src/main/resources/assets/bclib/icon_betternether.png differ diff --git a/src/main/resources/assets/bclib/icon_bright.png b/src/main/resources/assets/bclib/icon_bright.png new file mode 100644 index 00000000..d094242f Binary files /dev/null and b/src/main/resources/assets/bclib/icon_bright.png differ diff --git a/src/main/resources/assets/bclib/icon_updater.png b/src/main/resources/assets/bclib/icon_updater.png new file mode 100644 index 00000000..bdd375b0 Binary files /dev/null and b/src/main/resources/assets/bclib/icon_updater.png differ diff --git a/src/main/resources/assets/bclib/iconpixelated.png b/src/main/resources/assets/bclib/iconpixelated.png new file mode 100644 index 00000000..056ed30a Binary files /dev/null and b/src/main/resources/assets/bclib/iconpixelated.png differ diff --git a/src/main/resources/assets/bclib/lang/de_de.json b/src/main/resources/assets/bclib/lang/de_de.json new file mode 100644 index 00000000..f1881eb1 --- /dev/null +++ b/src/main/resources/assets/bclib/lang/de_de.json @@ -0,0 +1,117 @@ +{ + "bclib.datafixer.backupWarning.backup": "Backup erstellen bevor die Reperaturen angewendet werden", + "bclib.datafixer.backupWarning.fix": "Reperaturen Anwenden", + "bclib.datafixer.backupWarning.message": "Der Wächter hat festgestellt, dass einige Mod Änderungen an der Welt durchführen müssen.\n\nDiese Änderungen können automatische angewendet werden. Wenn Du fortfährst ohne die Änderungen anzuwenden, kann dies zu Fehlern oder Abstürzen führen. Bevor du fortfährst sollte auf jedenfall ein Backup angelegt werden.", + "bclib.datafixer.backupWarning.nofixes": "Weiter ohne Reperaturen", + "bclib.datafixer.backupWarning.title": "Der Wächter hat eine inkompatible Welt entdeckt", + "bclib.updates.curseforge_link": "[CurseForge]", + "bclib.updates.description": "Einige der installierten Mods sind veraltet. Wir verbessern und erweitern ständig unseren Inhalt und stellen wichtige Fehlerbehebungen zur Verfügung.\nBitte aktualisiere Deine Mods über die unten angegebenen Links.", + "bclib.updates.disable_check": "Nicht Prüfen", + "bclib.updates.donate": "Spenden", + "bclib.updates.donate_pre": "Gefallen Dir unsere Inhalte?\nDann erwäge eine kleine Spende :)", + "bclib.updates.download_link": "Herunterladen", + "bclib.updates.modrinth_link": "[Modrinth]", + "bclib.updates.title": "Mod Aktualisierungen", + "bclib.welcome.description": "... und ein riesiges herzliches **Dankeschön** für das Herunterladen und Spielen unserer Mods. Wir hoffen, dass sie euch genauso viel Spaß machen wie uns.\n\nBevor wir anfangen, gibt es ein paar Dinge, die wir einrichten müssen, also lest bitte den folgenden langweiligen Teil weiter.", + "bclib.welcome.donation": "Wenn Dir unsere Mods so gut gefallen, wie wir hoffen, dann denke bitte über eine kleine Spende nach :)", + "bclib.welcome.title": "Wilkommen bei ", + "description.config.bclib.client.ui.forceBetterXPreset": "Der Welt-Typ bestimmt, wie eine Welt generiert wird. In den meisten Fällen solltet BetterX als Standardeinstellung beibehalten werden (Du kannst den Typ jederzeit im Welt-Erstellen-Bildschirm ändern). Dieser Typ ist für maximale Kompatibilität zwischen DataPacks, unserer Mod und anderen Fabric-Mods optimiert. Außerdem bietet er einige einzigartige Funktionen für BetterNether und BetterEnd. Du solltest diese Option nur deaktivieren, wenn Du einen Grund dazu hast!", + "description.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Der Warnbildschirm wird immer dann angezeigt, wenn Deine Welt nicht die Einstellungen der Vanilla-Version verwendet. Dies kann passieren, wenn Du eine Welt in einer Vorschauversion startest, aber auch wenn Du Mods verwendest, um die Welterzeugung zu verändern. Wenn diese Option aktiviert ist, wird die Warnung übersprungen, wenn Du eine bestehende Welt lädst. Sie wird weiterhin angezeigt (als Erinnerung), wenn eine neue Welt erstellt wird.", + "description.config.bclib.client.version.check": "BCLib enthält eine einfache Versionsüberprüfung, die Dich benachrichtigen kann, wenn neue Updates unserer Mods verfügbar sind. Dazu müssen wir eine Ressource von einem unserer Webserver abrufen. Um die Anfrage zu bearbeiten versenden wir zusammen mit Deiner IP-Address (wir müssen ja wissen wohin die Antwort gehen soll) auch Deine Minecraft-Version (damit wir die richtige Mod-Version auswählen können). Die übertragenen Daten werden von uns niemals für andere Zwecke verwendet, verarbeitet oder weitergegeben, müssen aber aus rechtlichen Gründen für 4 Wochen in unseren Log-Dateien gespeichert werden.", + "emi.category.bclib.alloying": "Legierungen", + "emi.category.bclib.anvil_0": "Amboss (Stufe 0)", + "emi.category.bclib.anvil_1": "Amboss (Stufe I)", + "emi.category.bclib.anvil_2": "Amboss (Stufe II)", + "emi.category.bclib.anvil_3": "Amboss (Stufe III)", + "emi.category.bclib.anvil_4": "Amboss (Stufe IV)", + "emi.category.bclib.anvil_5": "Amboss (Stufe V)", + "generator.bclib.amplified": "Ampl. & BetterX", + "generator.bclib.large": "Groß & BetterX", + "generator.bclib.normal": "BetterX", + "message.bclib.anvil_damage": "§cSchaden", + "message.bclib.bclibmissmatch": "Die Version von BCLib auf dem Server und dem Client sind unterschiedlich. Dies kann Probleme verursachen.\n\nSoll die passende Version von BCLib vom Server auf diese Maschine kopiert werden? \n\nDazu wird die aktuell installierte Version von BCLib im Mods-Verzeichnis in einen Unterordner verschoben und die Version vom Server installiert.", + "message.bclib.confirmrestart": "Die angeforderten Inhalte wurden erfolgreich übertragen. Minecraft muss nun neu gestartet werden.", + "message.bclib.datafixer.error": "Es gab Fehler beim Reparieren der Welt. Das bedeutet, dass dieser Level wahrscheinlich in einem inkonsistenten Zustand ist und Sie ihn nicht spielen sollten. Bitte stellen Sie Ihr Backup wieder her und beheben Sie die unten aufgeführten Fehler, bevor Sie es erneut versuchen.", + "message.bclib.datafixer.progress": "Anwenden aller Änderungen", + "message.bclib.datafixer.progress.level": "Patches auf level.dat anwenden", + "message.bclib.datafixer.progress.players": "Repariere Spieler", + "message.bclib.datafixer.progress.reading": "Lese Daten", + "message.bclib.datafixer.progress.regions": "Alle Regionen reparieren", + "message.bclib.datafixer.progress.saving": "Patch-Status speichern", + "message.bclib.datafixer.progress.waitbackup": "Ich warte auf das Ende der Sicherung. Dies kann eine Weile dauern!", + "message.bclib.datafixer.progress.worlddata": "Benutzerdefinierte Weltdaten patchen", + "message.bclib.filesync.progress": "Snychronisiere Dateien und Verzeichnise vom Server", + "message.bclib.filesync.progress.stage.empty": "", + "message.bclib.modmissmatch": "Einige Mods auf diesem Rechner stimmen nicht mit der Version auf dem Server überein.\n\nNicht übereinstimmende Mods können zu merkwürdigem Spielverhalten oder Abstürzen führen. Bitte stellen Sie sicher, dass Sie die gleichen Mods wie auf dem Server verwenden.", + "message.bclib.syncfiles": "Einige Daten (Konfigurationen, Mods, ...) sind unterschiedlich.\nSollen die unten ausgewählten Inhalte vom Server auf diese Maschine kopieren?", + "message.bclib.syncfiles.configs": "Einstellungen synchronisieren", + "message.bclib.syncfiles.delete": "Unnötige löschen", + "message.bclib.syncfiles.folders": "Dateien und Ordner synchronisieren", + "message.bclib.syncfiles.modlist": "Im Folgenden wird der Status deiner installierten Mods angezeigt.\n\nAlle Mods, die lokal nicht vorhanden sind oder eine andere Version auf dem Server haben, werden synchronisiert.", + "message.bclib.syncfiles.mods": "Mods synchronisieren", + "tag.c.barrel": "Fässer", + "tag.c.furnaces": "Brennöfen", + "tag.c.hammers": "Hämmer", + "tag.c.leaves": "Blätter", + "tag.c.saplings": "Setzlinge", + "tag.c.soul_ground": "Seelenböden", + "tag.c.wooden_barrels": "Hölzerne Fässer Barrels", + "tag.c.workbench": "Werkbänke", + "title.bclib.bclibmissmatch": "Versionsunterschied", + "title.bclib.confirmrestart": "Neustart erforderlich", + "title.bclib.datafixer.error": "Fehler beim Reparieren der Welt", + "title.bclib.datafixer.error.continue": "Continue and Mark as Fixed", + "title.bclib.datafixer.progress": "Welt in Ordnung bringen", + "title.bclib.filesync.progress": "Datenübertragung", + "title.bclib.modmenu.main": "BCLib Einstellungen", + "title.bclib.modmissmatch": "Mod-Konflikt", + "title.bclib.progress": "Fortschritt", + "title.bclib.syncfiles": "Inkonsistente Daten", + "title.bclib.syncfiles.modInfo": "Mod Info", + "title.bclib.syncfiles.modlist": "Mod Information", + "title.bclib.the_end": "Das Ende", + "title.bclib.the_nether": "Nether", + "title.config.bclib.client.auto_sync.acceptConfigs": "Konfiguration von Server annehmen", + "title.config.bclib.client.auto_sync.acceptFiles": "Dateien von Server annehmen", + "title.config.bclib.client.auto_sync.acceptMods": "Mods von Server annehmen", + "title.config.bclib.client.auto_sync.debugHashes": "Erweiterete Logausgabe für Auto-Sync", + "title.config.bclib.client.auto_sync.displayModInfo": "Warnung anzeigen, wenn Mods auf Server/Client unterschiedlich", + "title.config.bclib.client.auto_sync.enabled": "Auto-Sync Aktivieren", + "title.config.bclib.client.infos.survives_on_hint": "Zeige 'Überlebt auf' Tooltip", + "title.config.bclib.client.rendering.FogDensity": "Nebeldichte", + "title.config.bclib.client.rendering.customFogRendering": "Angepasster Nebel", + "title.config.bclib.client.rendering.netherThickFog": "Dicker Nether-Nebel", + "title.config.bclib.client.ui.forceBetterXPreset": "BetterX als Standard-Welt-Typ verwenden", + "title.config.bclib.client.ui.preferModrinthForUpdates": "Bevorzuge Updates über Modrinth", + "title.config.bclib.client.ui.showUpdateInfo": "Anzeigen wenn neue Updates verfügabr sind", + "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Experimenteller Warnbildschirm beim Laden deaktivieren", + "title.config.bclib.client.version.check": "Versionsprüfung erlauben", + "title.config.bclib.generator.options.useOldBiomeGenerator": "Alten 1.17 Biome Generator verwenden", + "title.config.bclib.main.infos.verbose": "Ausführliche Logs", + "title.config.bclib.main.patches.applyPatches": "Automatisches Anwenden von Patches beim Laden eines Levels", + "title.config.bclib.main.patches.repairBiomesOnLoad": "Biomesource beim Laden eines Levels reparieren", + "title.link.bclib.discord": "Discord", + "title.screen.bclib.worldgen.avg_biome_size": "Durchschnittl. Biome Größe (in Chunks)", + "title.screen.bclib.worldgen.barrens_biome_size": "Ödniss", + "title.screen.bclib.worldgen.center_biome_size": "Zentralbiome", + "title.screen.bclib.worldgen.central_radius": "Innerer Radius (in Chunks)", + "title.screen.bclib.worldgen.custom_end_biome_source": "Benutzerdefinierte End-Biomquelle", + "title.screen.bclib.worldgen.custom_end_terrain": "Angepasster End-Terrain-Generator", + "title.screen.bclib.worldgen.custom_nether_biome_source": "Benutzerdefinierte Nether-Biomquelle", + "title.screen.bclib.worldgen.end_void": "Kleine End-Inseln erzeugen", + "title.screen.bclib.worldgen.land_biome_size": "Land-Biome", + "title.screen.bclib.worldgen.legacy_square": "Legacy-Verteilung (1.17)", + "title.screen.bclib.worldgen.main": "Welt-Generator Eigenschaften", + "title.screen.bclib.worldgen.nether_amplified": "Doppelte Netherhöhe (Amplified)", + "title.screen.bclib.worldgen.nether_biome_size": "Größe", + "title.screen.bclib.worldgen.nether_vertical": "Biome auch vertikal verteilen", + "title.screen.bclib.worldgen.nether_vertical_biome_size": "Biomhöhe", + "title.screen.bclib.worldgen.other": "Sonstiges", + "title.screen.bclib.worldgen.void_biome_size": "Kleine Inseln", + "tooltip.bclib.place_on": "Lebt auf: %s", + "tooltip.bclib.place_underwater_on": "Lebt unter Wasser auf: %s", + "tooltip.bclib.place_underwater": "Lebt unter Wasser", + "tooltip.bclib.place_underwater_depth": "min. Tiefe: %d", + "tooltip.bclib.pottable_on": "Eintopfen auf: %s", + "warning.config.bclib.client.ui.forceBetterXPreset": "IN DEN MEISTEN FÄLLEN SOLLTE DIESE OPTION AKTIVIERT BLEIBEN.\n" +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/lang/en_us.json b/src/main/resources/assets/bclib/lang/en_us.json new file mode 100644 index 00000000..ae61e536 --- /dev/null +++ b/src/main/resources/assets/bclib/lang/en_us.json @@ -0,0 +1,117 @@ +{ + "bclib.datafixer.backupWarning.backup": "Create Backup before applying Fixes", + "bclib.datafixer.backupWarning.fix": "Apply Fixes", + "bclib.datafixer.backupWarning.message": "The Guardian detected, that the internals some installed Mods did change since this world was last played.\n\nWe can automatically change the world for you. If you continue without applying the changes the world may not load correct. Before you continue, you should create a Backup.", + "bclib.datafixer.backupWarning.nofixes": "Continue Without Fixes", + "bclib.datafixer.backupWarning.title": "Guardian detected an incompatible World", + "bclib.updates.curseforge_link": "[CurseForge]", + "bclib.updates.description": "Some of the installed mods are outdated. We continually improve and extend our content as well as provide important bug-fixes.\nPlease consider updating your mods using the provided links below.", + "bclib.updates.disable_check": "Disable Check", + "bclib.updates.donate": "Donate", + "bclib.updates.donate_pre": "Like our Content?\nPlease consider a small Donation :)", + "bclib.updates.download_link": "Download", + "bclib.updates.modrinth_link": "[Modrinth]", + "bclib.updates.title": "Mod Updates", + "bclib.welcome.description": "... and a huge hearty **thank you** for downloading and playing our mods. We hope you enjoy them as much as we do.\n\nBefore we start, there are a few things we need to set up, so please continue reading the following boring part.", + "bclib.welcome.donation": "If you enjoy our Mods as much as we hope you do, please consider a small Donation :)", + "bclib.welcome.title": "Welcome to ", + "description.config.bclib.client.ui.forceBetterXPreset": "The world type determines how a world is generated. In most cases, BetterX should be kept as the default (you can change the type at any time in the world creation screen). This type is optimized for maximum compatibility between DataPacks, our mod, and other Fabric mods. It also provides some unique features for BetterNether and BetterEnd. You should only disable this option if you have a reason to do so!", + "description.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "The warning screen appears whenever your world does not use the vanilla version settings. This can happen when you start a world in a preview version, but also when you use mods to change the world creation. If this option is enabled, the warning will be skipped when you load an existing world. It will still be displayed (as a reminder) when a new world is created.", + "description.config.bclib.client.version.check": "BCLib includes a simple version checker that can notify you when new updates of our mods are available. To do this, we need to read a resource from one of our web servers. To process the request, together with your IP address (we need to know where to send the response) we also send your Minecraft version (so we can choose the right mod version). The transmitted data will never be used or processed by us for other purposes, but must be stored in our log files for 4 weeks for legal reasons.", + "emi.category.bclib.alloying": "Alloying", + "emi.category.bclib.anvil_0": "Anvil (Level 0)", + "emi.category.bclib.anvil_1": "Anvil (Level I)", + "emi.category.bclib.anvil_2": "Anvil (Level II)", + "emi.category.bclib.anvil_3": "Anvil (Level III)", + "emi.category.bclib.anvil_4": "Anvil (Level IV)", + "emi.category.bclib.anvil_5": "Anvil (Level V)", + "generator.bclib.amplified": "Ampl. & BetterX", + "generator.bclib.large": "Large & BetterX", + "generator.bclib.normal": "BetterX", + "message.bclib.anvil_damage": "§cDamage", + "message.bclib.bclibmissmatch": "The Version of BCLib on the server and this client do not match. This will cause problems when playing.\n\nDo you want to automatically download the BCLib-Version from the server? \n\nBCLib will move the old version into a subdirectory of your Mods-Folder and before installing the new one.", + "message.bclib.confirmrestart": "The requested content was synchronized. You need to restart Minecraft now.", + "message.bclib.datafixer.error": "There were errors while repairing the world. This means that this level is probably in an inconsistent state and you should not play it. Please restore your backup and fix the errors below before trying again.", + "message.bclib.datafixer.progress": "Applying all Patches to your World.", + "message.bclib.datafixer.progress.level": "Applying Patches to level.dat", + "message.bclib.datafixer.progress.players": "Fixing Players", + "message.bclib.datafixer.progress.reading": "Reading Data", + "message.bclib.datafixer.progress.regions": "Repairing all Regions", + "message.bclib.datafixer.progress.saving": "Saving Patch State", + "message.bclib.datafixer.progress.waitbackup": "Waiting for Backup to finish. This may take a while!", + "message.bclib.datafixer.progress.worlddata": "Patching Custom World-Data", + "message.bclib.filesync.progress": "Syncing File-Content with Server", + "message.bclib.filesync.progress.stage.empty": "", + "message.bclib.modmissmatch": "Some Mods on this client do not match the version of Mods on the Server.\n\nMismatching Mods can result in odd game behavior or crashes. Please make sue that you use the same mods as the server.", + "message.bclib.syncfiles": "Some Content on the Server does not match the versions on the client.\nDo you want to replace the selected content with the data from the server?", + "message.bclib.syncfiles.configs": "Synchronize Configs", + "message.bclib.syncfiles.delete": "Delete unneeded", + "message.bclib.syncfiles.folders": "Synchronize Folders and Files", + "message.bclib.syncfiles.modlist": "The following shows the state of your installed Mods.\n\nAll Mods that do not exist locally, or have a different version on the Server will be synchronized.", + "message.bclib.syncfiles.mods": "Synchronize Mods", + "tag.c.barrel": "Barrels", + "tag.c.furnaces": "Furnaces", + "tag.c.hammers": "Hammers", + "tag.c.leaves": "Leaves", + "tag.c.saplings": "Saplings", + "tag.c.soul_ground": "Soul Ground", + "tag.c.wooden_barrels": "Wooden Barrels", + "tag.c.workbench": "Crafting Tables", + "title.bclib.bclibmissmatch": "Version Mismatch", + "title.bclib.confirmrestart": "Restart Required", + "title.bclib.datafixer.error": "Errors while fixing World", + "title.bclib.datafixer.error.continue": "Proceed and mark as fixed", + "title.bclib.datafixer.progress": "Fixing World", + "title.bclib.filesync.progress": "File Transfer", + "title.bclib.modmenu.main": "BCLib Settings", + "title.bclib.modmissmatch": "Mod Version Conflict", + "title.bclib.progress": "Progress", + "title.bclib.syncfiles": "Mismatching Data", + "title.bclib.syncfiles.modInfo": "Mod Info", + "title.bclib.syncfiles.modlist": "Mod Information", + "title.bclib.the_end": "The End", + "title.bclib.the_nether": "The Nether", + "title.config.bclib.client.auto_sync.acceptConfigs": "Accept incoming Config Files", + "title.config.bclib.client.auto_sync.acceptFiles": "Accept incoming Files", + "title.config.bclib.client.auto_sync.acceptMods": "Accept incoming Mods", + "title.config.bclib.client.auto_sync.debugHashes": "Print Auto-Sync Debug-Hashes to Log", + "title.config.bclib.client.auto_sync.displayModInfo": "Display warning when Serverside Mods differ from Client", + "title.config.bclib.client.auto_sync.enabled": "Enable Auto-Sync", + "title.config.bclib.client.infos.survives_on_hint": "Show 'Survives On' hint as Tooltip", + "title.config.bclib.client.rendering.FogDensity": "Fog Density", + "title.config.bclib.client.rendering.customFogRendering": "Custom Fog Rendering", + "title.config.bclib.client.rendering.netherThickFog": "Nether Thick Fog", + "title.config.bclib.client.ui.forceBetterXPreset": "Use BetterX as Default World-Type", + "title.config.bclib.client.ui.preferModrinthForUpdates": "Prefere Modrinth for Updates", + "title.config.bclib.client.ui.showUpdateInfo": "Allow Update Reminder Screen", + "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Disable Experimental Warning Screen on Load", + "title.config.bclib.client.version.check": "Enable Version Check", + "title.config.bclib.generator.options.useOldBiomeGenerator": "Use legacy 1.17 Biome Generator", + "title.config.bclib.main.infos.verbose": "Verbose Logging", + "title.config.bclib.main.patches.applyPatches": "Automatically apply patches when loading level", + "title.config.bclib.main.patches.repairBiomesOnLoad": "Fix Biomesource on level load", + "title.link.bclib.discord": "Discord", + "title.screen.bclib.worldgen.avg_biome_size": "Average Biome Size (in Chunks)", + "title.screen.bclib.worldgen.barrens_biome_size": "Barrens", + "title.screen.bclib.worldgen.center_biome_size": "Central Biomes", + "title.screen.bclib.worldgen.central_radius": "Central Void Radius (in Chunks)", + "title.screen.bclib.worldgen.custom_end_biome_source": "Use Custom End Biome Source", + "title.screen.bclib.worldgen.custom_end_terrain": "Custom End Terrain Generator", + "title.screen.bclib.worldgen.custom_nether_biome_source": "Use Custom Nether Biome Source", + "title.screen.bclib.worldgen.end_void": "Generate Small Islands", + "title.screen.bclib.worldgen.land_biome_size": "Land Biomes", + "title.screen.bclib.worldgen.legacy_square": "Use Legacy Map (1.17)", + "title.screen.bclib.worldgen.main": "World Generator Settings", + "title.screen.bclib.worldgen.nether_amplified": "Double the Nether Height (Amplified)", + "title.screen.bclib.worldgen.nether_biome_size": "Biome Size", + "title.screen.bclib.worldgen.nether_vertical": "Generate vertical Biomes", + "title.screen.bclib.worldgen.nether_vertical_biome_size": "Biome Height", + "title.screen.bclib.worldgen.other": "Other Settings", + "title.screen.bclib.worldgen.void_biome_size": "Small Island Biomes", + "tooltip.bclib.place_on": "Survives on: %s", + "tooltip.bclib.place_underwater_on": "Survives under water on: %s", + "tooltip.bclib.place_underwater": "Survives under water", + "tooltip.bclib.place_underwater_depth": "min. depth: %d", + "tooltip.bclib.pottable_on": "Pottable on: %s", + "warning.config.bclib.client.ui.forceBetterXPreset": "MOST LIKELY YOU WILL WANT TO KEEP THIS OPTION ENABLED.\n" +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/lang/ko_kr.json b/src/main/resources/assets/bclib/lang/ko_kr.json new file mode 100644 index 00000000..8f5c3d70 --- /dev/null +++ b/src/main/resources/assets/bclib/lang/ko_kr.json @@ -0,0 +1,51 @@ +{ + "message.bclib.anvil_damage": "§c위험", + "bclib.datafixer.backupWarning.title": "가디언에서 호환되지 않는 월드를 감지", + "bclib.datafixer.backupWarning.message": "가디언에서 감지한 바에 따르면 설치된 모드들의 내부가 이 월드가 마지막으로 게임한 이후로 바뀌었습니다.\n\n우리는 당신을 위해 자동으로 월드를 바꿀 수 있습니다. 변경 내용을 적용하지 않고 계속하면 월드 로드가 올바르게 수행되지 않을 수 있습니다. 계속하기 전에 백업을 만들어야 합니다.", + "bclib.datafixer.backupWarning.backup": "수정 사항을 적용하기 전에 백업 생성", + "bclib.datafixer.backupWarning.nofixes": "수정 없이 계속", + "bclib.datafixer.backupWarning.fix": "수정 사항 적용", + "title.bclib.bclibmissmatch": "버전 불일치", + "message.bclib.bclibmissmatch": "서버와 클라이언트의 BCLib 버전이 일치하지 않습니다. 이것은 게임을 할 때 문제를 일으킬 것입니다.\n\n서버에서 BCLib-Version을 자동으로 다운로드하시겠습니까? \n\nBCLib은 새 버전을 설치하기 전에 이전 버전을 모드 폴더의 하위 디렉터리로 이동합니다.", + "title.bclib.syncfiles": "데이터 불일치", + "message.bclib.syncfiles": "서버의 일부 내용이 클라이언트의 버전과 일치하지 않습니다.\n선택한 내용을 서버의 데이터로 바꾸시겠습니까?", + "message.bclib.syncfiles.mods": "모드 동기화", + "message.bclib.syncfiles.configs": "구성 동기화", + "message.bclib.syncfiles.folders": "폴더 및 파일 동기화", + "message.bclib.syncfiles.delete": "필요 없는 삭제", + "title.bclib.confirmrestart": "재시작 필요", + "message.bclib.confirmrestart": "요청한 콘텐츠가 동기화되었습니다. 지금 마인크래프트를 다시 시작해야 합니다.", + "title.link.bclib.discord": "디스코드", + "title.bclib.modmenu.main": "BCLib 환경설정", + "title.bclib.progress": "진행", + "title.bclib.filesync.progress": "파일 전송", + "message.bclib.filesync.progress": "파일 내용을 서버와 동기화하는 중", + "message.bclib.filesync.progress.stage.empty": "", + "title.config.bclib.client.auto_sync.enabled": "자동 동기화 사용", + "title.config.bclib.client.auto_sync.acceptConfigs": "들어오는 구성 파일 수락", + "title.config.bclib.client.auto_sync.acceptFiles": "수신 파일 수락", + "title.config.bclib.client.auto_sync.acceptMods": "수신 모드 수락", + "title.config.bclib.client.auto_sync.displayModInfo": "서버사이드 모드가 클라이언트와 다를 때 경고 표시", + "title.config.bclib.client.auto_sync.debugHashes": "로그에 자동 동기화 디버그 해시 인쇄", + "title.config.bclib.generator.options.useOldBiomeGenerator": "유산 1.17 생물군계 생성기를 사용합니다.", + "title.config.bclib.main.patches.applyPatches": "레벨을 로드할 때 패치를 자동으로 적용합니다.", + "title.config.bclib.main.patches.repairBiomesOnLoad": "레벨 로드할 때 바이옴소스를 수정합니다.", + "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "로드 시 실험 경고 화면을 사용하지 않도록 설정합니다.", + "title.bclib.syncfiles.modInfo": "모드 정보", + "title.bclib.syncfiles.modlist": "모드 목록", + "message.bclib.syncfiles.modlist": "설치된 모드의 상태는 다음과 같습니다.\n\n로컬에 없거나 서버에 다른 버전이 있는 모든 모드는 동기화됩니다.", + "title.bclib.modmissmatch": "모드 버전 충돌", + "message.bclib.modmissmatch": "이 클라이언트의 일부 모드는 서버의 모드 버전과 일치하지 않습니다.\n\n모드가 일치하지 않으면 이상한 게임 동작이나 충돌이 발생할 수 있습니다. 서버와 같은 모드를 사용하는 것으로 해주세요.", + "message.bclib.datafixer.progress.waitbackup": "백업이 완료될 때까지 기다리는 중입니다. 시간이 걸릴 수 있습니다!", + "message.bclib.datafixer.progress.reading": "데이터 읽기", + "message.bclib.datafixer.progress.players": "플레이어 수정", + "message.bclib.datafixer.progress.level": "level.dat에 패치 적용", + "message.bclib.datafixer.progress.worlddata": "사용자 지정 월드-데이터 패치 적용", + "message.bclib.datafixer.progress.regions": "모든 영역 복구", + "message.bclib.datafixer.progress.saving": "패치 상태 저장 중", + "title.bclib.datafixer.progress": "월드 수정", + "message.bclib.datafixer.progress": "사용자 환경에 모든 패치를 적용하고 있습니다.", + "title.bclib.datafixer.error": "월드를 수정하는 중 오류 발생", + "message.bclib.datafixer.error": "월드를 수리하는 동안 오류가 발생했습니다. 즉, 이 수준은 일치하지 않는 상태이므로 재생하지 않아야 합니다. 백업을 복구하고 아래 오류를 수정한 후 다시 시도하십시오.", + "title.bclib.datafixer.error.continue": "계속 진행 및 수정됨" +} diff --git a/src/main/resources/assets/bclib/lang/pt_br.json b/src/main/resources/assets/bclib/lang/pt_br.json new file mode 100644 index 00000000..0cb2e1ad --- /dev/null +++ b/src/main/resources/assets/bclib/lang/pt_br.json @@ -0,0 +1,103 @@ +{ + "message.bclib.anvil_damage": "§cDano", + "bclib.datafixer.backupWarning.title": "Guardian detectou um mundo incompatível", + "bclib.datafixer.backupWarning.message": "O Guardian detectou, que os internos alguns mods instalados mudaram desde que este mundo foi jogado pela última vez.\n\nPodemos mudar automaticamente o mundo para você.Se você continuar sem aplicar as alterações, o mundo pode não carregar correto.Antes de continuar, você deve criar um backup.", + "bclib.datafixer.backupWarning.backup": "Crie backup antes de aplicar correções", + "bclib.datafixer.backupWarning.nofixes": "Continue sem correções", + "bclib.datafixer.backupWarning.fix": "Aplique correções", + "title.bclib.bclibmissmatch": "Incompatibilidade de versão", + "message.bclib.bclibmissmatch": "A versão do BCLIB no servidor e esse cliente não corresponde.Isso causará problemas ao jogar.\n\nVocê deseja baixar automaticamente a versão bclib do servidor? \n\nO BCLIB moverá a versão antiga para um subdiretório do seu mods-dobrador e antes de instalar o novo.", + "title.bclib.syncfiles": "Dados incompatíveis", + "message.bclib.syncfiles": "Algum conteúdo no servidor não corresponde às versões do cliente.\nDeseja substituir o conteúdo selecionado pelos dados do servidor?", + "message.bclib.syncfiles.mods": "Sincronize mods", + "message.bclib.syncfiles.configs": "Sincronize as configurações", + "message.bclib.syncfiles.folders": "Sincronize pastas e arquivos", + "message.bclib.syncfiles.delete": "Exclua desnecessário", + "title.bclib.confirmrestart": "É necessário reiniciar", + "message.bclib.confirmrestart": "O conteúdo solicitado foi sincronizado.Você precisa reiniciar o minecraft agora.", + "title.link.bclib.discord": "Discórdia", + "title.bclib.modmenu.main": "Configurações do BCLIB", + "title.bclib.progress": "Progresso", + "title.bclib.filesync.progress": "Transferência de arquivo", + "message.bclib.filesync.progress": "Sincronizar o conteúdo do arquivo com o servidor", + "message.bclib.filesync.progress.stage.empty": "", + "title.config.bclib.client.auto_sync.enabled": "Ativar sincronização automática", + "title.config.bclib.client.auto_sync.acceptConfigs": "Accept incoming Config Files", + "title.config.bclib.client.auto_sync.acceptFiles": "Aceitar arquivos de entrada", + "title.config.bclib.client.auto_sync.acceptMods": "Aceitar mods de entrada", + "title.config.bclib.client.auto_sync.displayModInfo": "Exibir aviso quando os mods do servidor diferem do cliente", + "title.config.bclib.client.auto_sync.debugHashes": "Imprima-se-hashes de depuração de sincronização automática para registrar", + "title.config.bclib.generator.options.useOldBiomeGenerator": "Use Legacy 1.17 Biome Gerator", + "title.config.bclib.main.patches.applyPatches": "Aplique automaticamente patches ao carregar o nível", + "title.config.bclib.main.patches.repairBiomesOnLoad": "Corrija o Biomesource na carga nivelada", + "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Desative a tela de aviso experimental na carga", + "title.bclib.syncfiles.modInfo": "modInfo", + "title.bclib.syncfiles.modlist": "Informação mod", + "message.bclib.syncfiles.modlist": "A seguir, mostra o estado de seus mods instalados.\n\nTodos os mods que não existem localmente ou têm uma versão diferente no servidor serão sincronizados.", + "title.bclib.modmissmatch": "Conflito da versão mod", + "message.bclib.modmissmatch": "Alguns mods neste cliente não correspondem à versão dos mods no servidor.\n\nMods incompatíveis podem resultar em comportamento ou falha de jogo estranho.Por favor, faça Sue que você use os mesmos mods que o servidor.", + "message.bclib.datafixer.progress.waitbackup": "Esperando o backup terminar.Isso pode demorar um pouco!", + "message.bclib.datafixer.progress.reading": "Reading Data", + "message.bclib.datafixer.progress.players": "Consertando jogadores", + "message.bclib.datafixer.progress.level": "Aplicando patches ao nível.dat", + "message.bclib.datafixer.progress.worlddata": "Patching de dados mundiais personalizados", + "message.bclib.datafixer.progress.regions": "Reparando todas as regiões", + "message.bclib.datafixer.progress.saving": "Salvando o estado do patch", + "title.bclib.datafixer.progress": "fixingWorld", + "message.bclib.datafixer.progress": "Aplicando todos os patches ao seu mundo.", + "title.bclib.datafixer.error": "Erros enquanto corrige o mundo", + "message.bclib.datafixer.error": "Houve erros enquanto reparava o mundo.Isso significa que esse nível provavelmente está em um estado inconsistente e você não deve jogar.Restaure seu backup e corrija os erros abaixo antes de tentar novamente.", + "title.bclib.datafixer.error.continue": "Prossiga e marque como fixo", + "title.config.bclib.client.rendering.customFogRendering": "Renderização de nevoeiro personalizada", + "title.config.bclib.client.rendering.netherThickFog": "Nether de névoa espessa", + "title.config.bclib.main.infos.verbose": "Registro detalhado", + "title.config.bclib.client.infos.survives_on_hint": "Mostrar dica 'Sobrevive em' como dica de ferramenta", + "tooltip.bclib.place_on": "Sobrevive: %s", + "generator.bclib.normal": "BetterX", + "title.screen.bclib.worldgen.main": "Configurações mundiais do gerador", + "title.bclib.the_nether": "The Nether", + "title.bclib.the_end": "The End", + "title.screen.bclib.worldgen.custom_biome_source": "Use fonte de bioma personalizado", + "title.screen.bclib.worldgen.legacy_square": "Use mapa herdado (1.17)", + "title.screen.bclib.worldgen.custom_end_terrain": "Gerador de terreno final personalizado", + "title.screen.bclib.worldgen.avg_biome_size": "Tamanho médio do bioma (em pedaços)", + "title.screen.bclib.worldgen.other": "Other Settings", + "title.screen.bclib.worldgen.land_biome_size": "Biomas terrestres", + "title.screen.bclib.worldgen.void_biome_size": "Pequenos biomas da ilha", + "title.screen.bclib.worldgen.center_biome_size": "Biomas Centrais", + "title.screen.bclib.worldgen.barrens_biome_size": "barrens", + "title.screen.bclib.worldgen.central_radius": "Raio do vazio central (em pedaços)", + "title.screen.bclib.worldgen.end_void": "Gerar pequenas ilhas", + "bclib.updates.curseforge_link": "[CurseForge]", + "bclib.updates.title": "Moda atualizações", + "bclib.updates.disable_check": "Desative a verificação", + "bclib.updates.donate_pre": "Como nosso conteúdo?\nPor favor, considere uma pequena doação :)", + "bclib.updates.donate": "Doar", + "bclib.updates.description": "Alguns dos mods instalados estão desatualizados.Melhoramos e estendemos continuamente nosso conteúdo, bem como fornecemos importantes fixos de insetos.\nConsidere atualizar seus mods usando os links fornecidos abaixo.", + "bclib.welcome.title": "Bem-vindo ao ", + "bclib.welcome.description": "... E um enorme saudável ** Obrigado ** por baixar e reproduzir nossos mods.Esperamos que você os aprecie tanto quanto nós.\n\nAntes de começarmos, há algumas coisas que precisamos configurar; portanto, continue lendo a seguinte parte chata.", + "bclib.welcome.donation": "Se você gosta de nossos mods tanto quanto esperamos que faça, considere uma pequena doação :)", + "description.config.bclib.client.version.check": "O BCLIB inclui um verificador de versão simples que pode notificá -lo quando novas atualizações de nossos mods estiverem disponíveis.Para fazer isso, precisamos ler um recurso de um de nossos servidores da Web.Para processar a solicitação, juntamente com seu endereço IP (precisamos saber para onde enviar a resposta), também enviamos sua versão do Minecraft (para que possamos escolher a versão Mod certa).Os dados transmitidos nunca serão usados ou processados por nós para outros fins, mas devem ser armazenados em nossos arquivos de log por 4 semanas por razões legais.", + "title.config.bclib.client.version.check": "Habilitar Verificação da versão", + "title.config.bclib.client.ui.showUpdateInfo": "Permitir a tela de lembrete de atualização", + "title.config.bclib.client.rendering.FogDensity": "Densidade de nevoeiro", + "description.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "A tela de aviso aparece sempre que seu mundo não usa as configurações da versão de baunilha.Isso pode acontecer quando você inicia um mundo em uma versão de visualização, mas também quando você usa mods para mudar a criação do mundo.Se essa opção estiver ativada, o aviso será ignorado quando você carregar um mundo existente.Ele ainda será exibido (como um lembrete) quando um novo mundo for criado.", + "title.config.bclib.client.ui.forceBetterXPreset": "Use melhorx como o tipo mundial padrão", + "warning.config.bclib.client.ui.forceBetterXPreset": "Provavelmente, você desejará manter esta opção ativada.\n", + "description.config.bclib.client.ui.forceBetterXPreset": "O tipo de mundo determina como um mundo é gerado.Na maioria dos casos, o melhorx deve ser mantido como padrão (você pode alterar o tipo a qualquer momento na tela de criação mundial).Esse tipo é otimizado para máxima compatibilidade entre os dados, nosso mod e outros mods de tecido.Ele também fornece alguns recursos exclusivos para melhor e melhor.Você só deve desativar essa opção se tiver um motivo para fazê -lo! ", + "emi.category.bclib.alloying": "Liga", + "emi.category.bclib.anvil_0": "Bigorna (Nível 0)", + "emi.category.bclib.anvil_1": "Bigorna (Nível I)", + "emi.category.bclib.anvil_2": "Bigorna (Nível II)", + "emi.category.bclib.anvil_3": "Bigorna (Nível III)", + "emi.category.bclib.anvil_4": "Bigorna (Nível IV)", + "emi.category.bclib.anvil_5": "Bigorna (Nível V)", + "tag.c.barrel": "Barris", + "tag.c.wooden_barrels": "Barris de madeira", + "tag.c.workbench": "Tabelas de criação", + "tag.c.furnaces": "Fornos", + "tag.c.saplings": "Mudas", + "tag.c.hammers": "Martelos", + "tag.c.leaves": "Folhas", + "tag.c.soul_ground": "Solo da Alma" + } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/lang/ru_ru.json b/src/main/resources/assets/bclib/lang/ru_ru.json new file mode 100644 index 00000000..e2606334 --- /dev/null +++ b/src/main/resources/assets/bclib/lang/ru_ru.json @@ -0,0 +1,53 @@ +{ + "message.bclib.anvil_damage": "§cУрон", + "bclib.datafixer.backupWarning.title": "Страж обнаружил несовместимый мир", + "bclib.datafixer.backupWarning.message": "Страж обнаружил, что внутренности некоторых установленных модов изменились с тех пор, как в этом мире последний раз играли.\n\nМы можем автоматически изменить мир для вас. Если вы продолжите без применения изменений, мир может загрузиться некорректно. Прежде чем продолжить, вам следует создать резервную копию.", + "bclib.datafixer.backupWarning.backup": "Создать резервную копию перед применением исправлений", + "bclib.datafixer.backupWarning.nofixes": "Продолжить без исправлений", + "bclib.datafixer.backupWarning.fix": "Применить исправления", + "title.bclib.bclibmissmatch": "Несоответствие версий", + "message.bclib.bclibmissmatch": "Версия BCLib на сервере и на этом клиенте не совпадают. Это вызовет проблемы при игре.\n\nВы хотите, чтобы версия BCLib автоматически загружалась с сервера? \n\nBCLib переместит старую версию в подкаталог вашей папки mods и перед установкой новой.", + "title.bclib.syncfiles": "Несоответствие данных", + "message.bclib.syncfiles": "Некоторый контент на сервере не соответствует версиям на клиенте.\nВы хотите заменить выбранный контент данными с сервера?", + "message.bclib.syncfiles.mods": "Синхронизировать моды", + "message.bclib.syncfiles.configs": "Синхронизировать конфигурации", + "message.bclib.syncfiles.folders": "Синхронизировать папки и файлы", + "message.bclib.syncfiles.delete": "Удалить ненужное", + "title.bclib.confirmrestart": "Требуется перезагрузка", + "message.bclib.confirmrestart": "Запрошенный контент был синхронизирован. Вам нужно сейчас перезапустить Minecraft.", + "title.link.bclib.discord": "Discord", + "title.bclib.modmenu.main": "Настройки BCLib", + "title.bclib.progress": "Прогресс", + "title.bclib.filesync.progress": "Передача файлов", + "message.bclib.filesync.progress": "Синхронизация файлового содержимого с сервером", + "message.bclib.filesync.progress.stage.empty": "", + "title.config.bclib.client.auto_sync.enabled": "Включить автосинхронизацию", + "title.config.bclib.client.auto_sync.acceptConfigs": "Принять входящие файлы конфигурации", + "title.config.bclib.client.auto_sync.acceptFiles": "Принять входящие файлы", + "title.config.bclib.client.auto_sync.acceptMods": "Принять входящие моды", + "title.config.bclib.client.auto_sync.displayModInfo": "Отображать предупреждение, когда серверные моды отличаются от клиентских", + "title.config.bclib.client.auto_sync.debugHashes": "Печатать хэши отладки автоматической синхронизации в журнал", + "title.config.bclib.generator.options.useOldBiomeGenerator": "Использовать устаревший Генератор биомов 1.17", + "title.config.bclib.main.patches.applyPatches": "Автоматически применять патчи при загрузке мира", + "title.config.bclib.main.patches.repairBiomesOnLoad": "Исправить источник биомов при загрузке мира", + "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Отключить экспериментальный экран предупреждения при загрузке", + "title.bclib.syncfiles.modInfo": "Информация о моде", + "title.bclib.syncfiles.modlist": "Информация о модах", + "message.bclib.syncfiles.modlist": "Ниже показано состояние ваших установленных модов.\n\nВсе моды, не существующие локально, или имеющие другую версию на сервере, будут синхронизированы.", + "title.bclib.modmissmatch": "Конфликт версий модов", + "message.bclib.modmissmatch": "Некоторые моды на этом клиенте не соответствуют версии модов на сервере.\n\nНесоответствие модов может привести к странному поведению игры или вылетам. Пожалуйста, убедитесь, что вы используете те же моды, что и сервер.", + "message.bclib.datafixer.progress.waitbackup": "Ожидание завершения резервного копирования. Это может занять некоторое время!", + "message.bclib.datafixer.progress.reading": "Чтение данных", + "message.bclib.datafixer.progress.players": "Исправление игроков", + "message.bclib.datafixer.progress.level": "Применение патчей к level.dat", + "message.bclib.datafixer.progress.worlddata": "Исправление пользовательских мировых данных", + "message.bclib.datafixer.progress.regions": "Восстановление всех регионов", + "message.bclib.datafixer.progress.saving": "Сохранение состояния патча", + "title.bclib.datafixer.progress": "Исправление мира", + "message.bclib.datafixer.progress": "Применение всех патчей к вашему миру.", + "title.bclib.datafixer.error": "Ошибки при исправлении мира", + "message.bclib.datafixer.error": "Были ошибки при восстановлении мира. Это означает, что этот мир, вероятно, находится в несогласованном состоянии, и вам не следует играть в него. Прежде чем повторить попытку, восстановите резервную копию и исправьте ошибки, указанные ниже.", + "title.bclib.datafixer.error.continue": "Продолжить и отметить как исправленное", + "title.config.bclib.client.rendering.customFogRendering": "Пользовательский рендеринг тумана", + "title.config.bclib.client.rendering.netherThickFog": "Плотный туман в Нижнем мире" +} diff --git a/src/main/resources/assets/bclib/lang/tr_tr.json b/src/main/resources/assets/bclib/lang/tr_tr.json new file mode 100644 index 00000000..68618cbd --- /dev/null +++ b/src/main/resources/assets/bclib/lang/tr_tr.json @@ -0,0 +1,112 @@ +{ + "__comment__": "@alpeerkaraca - BCLib Turkish Language File", + "message.bclib.anvil_damage": "§cHasar", + "bclib.datafixer.backupWarning.title": "Dünyayla Uyumsuz Bir Gardiyan Bulundu", + "bclib.datafixer.backupWarning.message": "The Guardian detected, that the internals some installed Mods did change since this world was last played.\n\nWe can automatically change the world for you. If you continue without applying the changes the world may not load correct. Before you continue, you should create a Backup.", + "bclib.datafixer.backupWarning.backup": "Fix uyglamadan önce yedekle", + "bclib.datafixer.backupWarning.nofixes": "Fix yapmadan devam et", + "bclib.datafixer.backupWarning.fix": "Fix Uygula", + "title.bclib.bclibmissmatch": "BCLib Sürümü Uyuşmazlığı", + "message.bclib.bclibmissmatch": "Sunucudaki BCLib'in sürümü ve bu istemcinin sürümü eşleşmiyor. Bu oynarken sorunlara neden olabilir.\n\nSunucudan BCLib sürümünü otomatik olarak indirmek istiyor musunuz?\n\nBCLib, eski sürümü Modlar Klasörünüzün bir alt dizinine taşıyacak ve ardından yeni sürümü kuracak.", + "title.bclib.syncfiles": "Veri Uyuşmazlığı", + "message.bclib.syncfiles": "Sunucudaki içerikler ile istemcideki içeriklerin sürümü aynı değil.\nSeçilen içerikleri sunucudakiyle senkronize etmek ister misin (Sunucu'dan indirme yapılacak)?", + "message.bclib.syncfiles.mods": "Modları Senkronize Et", + "message.bclib.syncfiles.configs": "Configi Senkronize Et", + "message.bclib.syncfiles.folders": "Dosya ve Klasörleri Senkronize Et", + "message.bclib.syncfiles.delete": "Gereksizleri Sil", + "title.bclib.confirmrestart": "Yeniden Başlatma Gerekli", + "message.bclib.confirmrestart": "İstenen içerik senkronize edildi. Şimdi Minecraft'ı yeniden başlatmalısınız.", + "title.link.bclib.discord": "Discord", + "title.bclib.modmenu.main": "BCLib Ayarları", + "title.bclib.progress": "İşlem", + "title.bclib.filesync.progress": "Dosya Transferi", + "message.bclib.filesync.progress": "Dosya İçeriği Sunucu ile Senkronize Ediliyor", + "message.bclib.filesync.progress.stage.empty": "", + "title.config.bclib.client.auto_sync.enabled": "Oto-Senkronizasyonu Etkinleştir", + "title.config.bclib.client.auto_sync.acceptConfigs": "Gelen Configleri Kabul Et", + "title.config.bclib.client.auto_sync.acceptFiles": "Gelen Dosyaları Kabul Et", + "title.config.bclib.client.auto_sync.acceptMods": "Gelen Modları Kabul Et", + "title.config.bclib.client.auto_sync.displayModInfo": "Sunucu Tarafındaki Modlar İstemciden Farklıysa Uyarı Göster", + "title.config.bclib.client.auto_sync.debugHashes": "Oto-Senkronizasyon Hashlerini LOG Dosyasına Yaz", + "title.config.bclib.generator.options.useOldBiomeGenerator": "Eski 1.17 Biyom Üreticisini Kullan", + "title.config.bclib.main.patches.applyPatches": "Dünya yüklenirken otomatik yama uygula", + "title.config.bclib.main.patches.repairBiomesOnLoad": "Dünya yüklenirken biyomları onar", + "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Dünya yüklenirken deneysel uyarıyı gösterme", + "title.config.bclib.client.ui.preferModrinthForUpdates": "Güncellemeleri Modrinth'ten kontrol et", + "title.bclib.syncfiles.modInfo": "Mod Bilgisi", + "title.bclib.syncfiles.modlist": "Mod Detayı", + "message.bclib.syncfiles.modlist": "Burası kurulu modları gösterir\n\nLocal cihazınızda olmayan ve sürüm uyuşmazlığı olan modlar sunucuyla senkronize edilecek.", + "title.bclib.modmissmatch": "Mod Sürüm Uyuşmazlığı", + "message.bclib.modmissmatch": "İstemcideki mod sürümleriyle sunucudaki sürümler aynı değil\n\n Sürüm uyuşmazlığı istenmeyen hatalara yol açabilir. Sunucuyla aynı modları kullandığınıza emin olun.", + "message.bclib.datafixer.progress.waitbackup": "Yedeklemenin Bitmesi Bekleniyor. Bu Biraz Sürebilir.!", + "message.bclib.datafixer.progress.reading": "Veri Okunuyor", + "message.bclib.datafixer.progress.players": "Oyuncular Onarılıyor", + "message.bclib.datafixer.progress.level": "level.dat Onarılıyor", + "message.bclib.datafixer.progress.worlddata": "Custom World Data Onarılıyor", + "message.bclib.datafixer.progress.regions": "Region Dosyaları Onarılıyor", + "message.bclib.datafixer.progress.saving": "Onarım Durumu Kaydediliyor", + "title.bclib.datafixer.progress": "Dünya Onarılıyor", + "message.bclib.datafixer.progress": "Dünyanıza tüm onarımlar uygulanıyor.", + "title.bclib.datafixer.error": "Dünyayı onarırken oluşan hatalar", + "message.bclib.datafixer.error": "Dünyayı onarırken hatalar oluştu. Bu dünyanın büyük ihtimalle kararsız halde oluğunu ve oynamamınızı belirtir. Yedeğinizi geri yükleyin ve onarımı tekrar başlatın.", + "title.bclib.datafixer.error.continue": "Onarıldı olarak işaretle ve devam et", + "title.config.bclib.client.rendering.customFogRendering": "Custom Sis Renderleme", + "title.config.bclib.client.rendering.netherThickFog": "Nether Kalın Sis", + "title.config.bclib.main.infos.verbose": "Detaylı Loglama", + "title.config.bclib.client.infos.survives_on_hint": "'Survives On' ipucunu Eşya İpucu olarak göster", + "tooltip.bclib.place_on": "Sağlık: %s", + "generator.bclib.normal": "BetterX", + "generator.bclib.large": "Geniş & BetterX", + "generator.bclib.amplified": "Sağlam & BetterX", + "title.screen.bclib.worldgen.main": "Dünya Üretici Ayarları", + "title.bclib.the_nether": "The Nether", + "title.bclib.the_end": "The End", + "title.screen.bclib.worldgen.custom_biome_source": "Custom Biyom Kaynağı Kullan", + "title.screen.bclib.worldgen.legacy_square": "Eski Haritayı Kullan(1.17)", + "title.screen.bclib.worldgen.custom_end_terrain": "Custom End Terrain Generator", + "title.screen.bclib.worldgen.avg_biome_size": "Ortalama Biyom Boyutu (Chunklardaki)", + "title.screen.bclib.worldgen.other": "Diğer Ayarlar", + "title.screen.bclib.worldgen.nether_biome_size": "Biyom Boyutu", + "title.screen.bclib.worldgen.nether_vertical_biome_size": "Biyom Boyutu (Yükseklik)", + "title.screen.bclib.worldgen.nether_vertical": "Dikey Biyomlar Oluştur", + "title.screen.bclib.worldgen.nether_amplified": "Nether Yüksekliğini İkiye Katla (Sağlamlaştırılmış)", + "title.screen.bclib.worldgen.land_biome_size": "Yeryüzü Biyom Boyutu", + "title.screen.bclib.worldgen.void_biome_size": "Küçük Ada Biyomları", + "title.screen.bclib.worldgen.center_biome_size": "Merkezi Biyomlar", + "title.screen.bclib.worldgen.barrens_biome_size": "Çorak Arazi", + "title.screen.bclib.worldgen.central_radius": "Merkezi Boşluk Yarıçapı (Chunk Olarka)", + "title.screen.bclib.worldgen.end_void": "Küçük Adaları Oluştur", + "bclib.updates.curseforge_link": "[CurseForge]", + "bclib.updates.modrinth_link": "[Modrinth]", + "bclib.updates.download_link": "Download", + "bclib.updates.title": "Mod Güncellemeleri", + "bclib.updates.disable_check": "Kontrolü Devre Dışı Bırak", + "bclib.updates.donate_pre": "İçeriği Beğendiniz mi?\n O zaman belki elinizi pamuk cebinize atabilirsiniz :)", + "bclib.updates.donate": "Bağış Yap", + "bclib.updates.description": "Bazı yüklenmiş modlar güncel değil. Sürekli olarak içeriğimizi geliştiriyor, genişletiyor ve önemli hata düzeltmeleri sağlıyoruz.Lütfen aşağıdaki bağlantıları kullanarak modlarınızı güncelleyiniz.", + "bclib.welcome.title": "Hoş Geldiniz! ", + "bclib.welcome.description": "... Modumuzu kullandığınız için kocaman bir kalp ile **teşekkür ederiz**. Umarız ki bizim kadar eğlenirsiniz.\n\nBaşlamadan önce, ayarlamamız gereken birtakım şeyler var, bu sebeple devamındaki sıkıcı kısmı okumaya devam edin.", + "bclib.welcome.donation": "Eğer modlarımızı düşündüğümüz kadar sevdiyseniz, ufak bir bağış yapabilirsiniz :)", + "description.config.bclib.client.version.check": "BCLib modların yeni bir sürümü çıktığında seni bilgilendirecek bir özellik ekler. İsteğinizi yerine getirmek ve web sunucunuzdan gerekli kaynağı almak için, kullanıcının IP adresi ve Minecraft sürümü gibi belirli bilgilerin toplanması gerektiği anlaşılabilir. Aktarılan verilerin, gizlilik ve veri koruma düzenlemelerine uygun bir şekilde işlenmesi önemlidir.Verilerin yasal nedenlerle belirli bir süre boyunca log dosyalarında saklanması kabul edilebilir, ancak verilerin depolama ve erişim sırasında uygun güvenlik önlemlerinin alındığından emin olunmalıdır. Bu verilerin toplanma amacı, nasıl kullanılacağı ve uygulanabilir saklama süreleri konusunda kullanıcıları bilgilendirmek önemlidir.", + "title.config.bclib.client.version.check": "Sürüm Kontrolünü Etkinleştir", + "title.config.bclib.client.ui.showUpdateInfo": "Güncelleme Hatırlatma Ekranını Göster", + "title.config.bclib.client.rendering.FogDensity": "Sis Yoğunluğu", + "description.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Bu seçenek etkinleştirildiğinde, mevcut bir dünya yüklediğinizde uyarı ekranı görüntülenmez. Bu uyarı ekranı, dünyanızın vanilla sürüm ayarlarını kullanmadığı durumlarda ortaya çıkar. Bu durum, önizleme sürümünde bir dünya başlattığınızda veya dünya oluşturmak için modları kullandığınızda gerçekleşebilir. Yeni bir dünya oluşturulduğunda hatırlatma olarak yine gösterilir.", "title.config.bclib.client.ui.forceBetterXPreset": "Use BetterX as Default World-Type", + "warning.config.bclib.client.ui.forceBetterXPreset": "HER ZAMANKİ GİBİ BU AYARI AÇIK BIRAKMAYACAKSIN.\n", + "description.config.bclib.client.ui.forceBetterXPreset": "Dünya türü, bir dünyanın nasıl oluşturulduğunu belirler. Çoğu durumda, BetterX varsayılan olarak kalması gereken bir seçenektir (dünya oluşturma ekranında her zaman türü değiştirebilirsiniz). Bu tür, DataPack'ler, kendi modumuz ve diğer Fabric modları arasında maksimum uyumluluk için optimize edilmiştir. Ayrıca BetterNether ve BetterEnd için bazı benzersiz özellikler sunar. Bu seçeneği yalnızca bir nedeniniz varsa devre dışı bırakmalısınız!", + "emi.category.bclib.alloying": "Alaşım Hazırlama", + "emi.category.bclib.anvil_0": "Örs (Seviye 0)", + "emi.category.bclib.anvil_1": "Örs (Seviye I)", + "emi.category.bclib.anvil_2": "Örs (Seviye II)", + "emi.category.bclib.anvil_3": "Örs (Seviye III)", + "emi.category.bclib.anvil_4": "Örs (Seviye IV)", + "emi.category.bclib.anvil_5": "Örs (Seviye V)", + "tag.c.barrel": "Varil", + "tag.c.wooden_barrels": "Tahta Varil", + "tag.c.workbench": "Çalışma Masası", + "tag.c.furnaces": "Ocaklar", + "tag.c.saplings": "Fidanlar", + "tag.c.hammers": "Çekiçler", + "tag.c.leaves": "Yapraklar", + "tag.c.soul_ground": "Ruh Zemini" +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/lang/uk_ua.json b/src/main/resources/assets/bclib/lang/uk_ua.json new file mode 100644 index 00000000..e4460930 --- /dev/null +++ b/src/main/resources/assets/bclib/lang/uk_ua.json @@ -0,0 +1,112 @@ +{ + "message.bclib.anvil_damage": "§cШкоди", + "bclib.datafixer.backupWarning.title": "Вартовий виявив несумісний світ", + "bclib.datafixer.backupWarning.message": "Вартовий виявив, що нутрощі деяких встановлених модів змінилися відтоді, як у цьому світі востаннє грали.\n\nМи можемо автоматично змінити світ для вас. Якщо ви продовжите без застосування змін, світ може завантажитись некоректно. Перш ніж продовжити, вам слід створити резервну копію.", + "bclib.datafixer.backupWarning.backup": "Створити резервну копію перед застосуванням виправлень", + "bclib.datafixer.backupWarning.nofixes": "Продовжити без виправлень", + "bclib.datafixer.backupWarning.fix": "Застосувати виправлення", + "title.bclib.bclibmissmatch": "Невідповідність версій", + "message.bclib.bclibmissmatch": "Версія BCLib на сервері та на цьому клієнті не збігаються. Це спричинить проблеми під час гри.\n\nВи хочете, щоб версія BCLib автоматично завантажувалась із сервера? \n\nBCLib перемістить стару версію до підкаталогу вашої папки з модами та перед установленням нової.", + "title.bclib.syncfiles": "Невідповідність даних", + "message.bclib.syncfiles": "Деякий контент на сервері не відповідає версіям на клієнті.\nВи бажаєте замінити вибраний контент даними із сервера?", + "message.bclib.syncfiles.mods": "Синхронізувати моди", + "message.bclib.syncfiles.configs": "Синхронізувати конфігурації", + "message.bclib.syncfiles.folders": "Синхронізувати папки та файли", + "message.bclib.syncfiles.delete": "Видалити непотрібне", + "title.bclib.confirmrestart": "Потрібне перезавантаження", + "message.bclib.confirmrestart": "Запрошений контент був синхронізований. Вам потрібно перезапустити Minecraft.", + "title.link.bclib.discord": "Discord", + "title.bclib.modmenu.main": "Налаштування BCLib", + "title.bclib.progress": "Прогрес", + "title.bclib.filesync.progress": "Передача файлів", + "message.bclib.filesync.progress": "Синхронізація файлового вмісту із сервером", + "message.bclib.filesync.progress.stage.empty": "", + "title.config.bclib.client.auto_sync.enabled": "Увімкнути автосинхронізацію", + "title.config.bclib.client.auto_sync.acceptConfigs": "Прийняти вхідні файли конфігурації", + "title.config.bclib.client.auto_sync.acceptFiles": "Прийняти вхідні файли", + "title.config.bclib.client.auto_sync.acceptMods": "Прийняти вхідні моди", + "title.config.bclib.client.auto_sync.displayModInfo": "Відображати попередження, коли серверні моди відрізняються від клієнтських", + "title.config.bclib.client.auto_sync.debugHashes": "Друкувати хеші налагодження автосинхронізації в журнал", + "title.config.bclib.generator.options.useOldBiomeGenerator": "Використовувати застарілий генератор біомів 1.17", + "title.config.bclib.main.patches.applyPatches": "Автоматично застосовувати патчі під час завантаження світу", + "title.config.bclib.main.patches.repairBiomesOnLoad": "Виправити джерело біомів під час завантаження світу", + "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Вимкнути експериментальний екран попередження при завантаженні", + "title.config.bclib.client.ui.preferModrinthForUpdates": "Надати перевагу Modrinth для оновлень", + "title.bclib.syncfiles.modInfo": "Інформація про мод", + "title.bclib.syncfiles.modlist": "Інформація про моди", + "message.bclib.syncfiles.modlist": "Нижче показано стан ваших установлених модів.\n\nУсі моди, що не існують локально, або мають іншу версію на сервері, будуть синхронізовані.", + "title.bclib.modmissmatch": "Конфлікт версій мода", + "message.bclib.modmissmatch": "Деякі моди на цьому клієнті не відповідають версіям модів на сервері.\n\nНевідповідність модів може призвести до дивної поведінки гри або збоїв. Будь ласка, переконайтеся, що ви використовуєте ті ж моди, що й сервер.", + "message.bclib.datafixer.progress.waitbackup": "Очікування завершення резервного копіювання. Це може зайняти деякий час!", + "message.bclib.datafixer.progress.reading": "Читання даних", + "message.bclib.datafixer.progress.players": "Виправлення гравців", + "message.bclib.datafixer.progress.level": "Застосування патчів до level.dat", + "message.bclib.datafixer.progress.worlddata": "Виправлення користувацьких даних світу", + "message.bclib.datafixer.progress.regions": "Відновлення всіх регіонів", + "message.bclib.datafixer.progress.saving": "Збереження стану патчу", + "title.bclib.datafixer.progress": "Виправлення світу", + "message.bclib.datafixer.progress": "Застосування всіх патчів до вашого світу.", + "title.bclib.datafixer.error": "Помилки під час виправлення світу", + "message.bclib.datafixer.error": "Були помилки під час відновлення світу. Це означає, що цей світ, ймовірно, перебуває в неузгодженому стані, і вам не слід грати в ньому. Перш ніж повторити спробу, відновіть резервну копію та виправте помилки, наведені нижче.", + "title.bclib.datafixer.error.continue": "Продовжити та позначити як виправлене", + "title.config.bclib.client.rendering.customFogRendering": "Візуалізація користувацького туману", + "title.config.bclib.client.rendering.netherThickFog": "Сильний туман Незеру", + "title.config.bclib.main.infos.verbose": "Докладне ведення журналу", + "title.config.bclib.client.infos.survives_on_hint": "Показати пораду «Виживає на» як підказку", + "tooltip.bclib.place_on": "Виживає на: %s", + "generator.bclib.normal": "BetterX", + "generator.bclib.large": "Великий та BetterX", + "generator.bclib.amplified": "Розш. та BetterX", + "title.screen.bclib.worldgen.main": "Налаштування генератора світу", + "title.bclib.the_nether": "Незер", + "title.bclib.the_end": "Енд", + "title.screen.bclib.worldgen.custom_biome_source": "Використовувати користувацьке джерело біому", + "title.screen.bclib.worldgen.legacy_square": "Використовувати стару карту (1.17)", + "title.screen.bclib.worldgen.custom_end_terrain": "Користувацький генератор місцевості Енду", + "title.screen.bclib.worldgen.avg_biome_size": "Середній розмір біому (у чанках)", + "title.screen.bclib.worldgen.other": "Інші налаштування", + "title.screen.bclib.worldgen.nether_biome_size": "Розмір біому", + "title.screen.bclib.worldgen.nether_vertical_biome_size": "Висота біому", + "title.screen.bclib.worldgen.nether_vertical": "Генерувати вертикальні біоми", + "title.screen.bclib.worldgen.nether_amplified": "Подвоїти висоту Незеру (Розширений)", + "title.screen.bclib.worldgen.land_biome_size": "Наземні біоми", + "title.screen.bclib.worldgen.void_biome_size": "Острівкові біоми", + "title.screen.bclib.worldgen.center_biome_size": "Центральні біоми", + "title.screen.bclib.worldgen.barrens_biome_size": "Околиця", + "title.screen.bclib.worldgen.central_radius": "Центральний радіус порожнечі (у чанках)", + "title.screen.bclib.worldgen.end_void": "Генерувати острівки", + "bclib.updates.curseforge_link": "[CurseForge]", + "bclib.updates.modrinth_link": "[Modrinth]", + "bclib.updates.download_link": "Завантажити", + "bclib.updates.title": "Оновлення мода", + "bclib.updates.disable_check": "Вимкнути перевірку", + "bclib.updates.donate_pre": "Подобається наш контент?\nБудь ласка, подумайте про невеличку пожертву :)", + "bclib.updates.donate": "Пожертвувати", + "bclib.updates.description": "Деякі з встановлених модів застаріли. Ми постійно вдосконалюємо та розширюємо наш контент, а також виправляємо важливі баги.\nБудь ласка, оновіть свої моди за допомогою наданих нижче посилань.", + "bclib.welcome.title": "Ласкаво просимо до ", + "bclib.welcome.description": "... і величезне сердечне **дякую** вам за те, що завантажили та зіграли з нашими модами. Сподіваємось, вони вам сподобаються так само, як і нам.\n\nПерш ніж ми почнемо, є декілька речей, які ми повинні налаштувати, тому, будь ласка, продовжуйте читати наступну нудну частину.", + "bclib.welcome.donation": "Якщо вам подобаються наші моди так сильно, як ми сподіваємось, подумайте про невеличку пожертву :)", + "description.config.bclib.client.version.check": "BCLib містить простий засіб перевірки версій, який сповіщає вас про наявність нових оновлень наших модів. Для цього нам потрібно прочитати ресурс з одного з наших вебсерверів. Щоб обробити запит, разом із вашою IP-адресою (нам потрібно знати, куди надіслати відповідь) ми надсилаємо вашу версію Minecraft (щоб ми могли вибрати правильну версію мода). Передані дані ніколи не використовуватимуться чи оброблятимуться нами для інших цілей, але повинні зберігатися в наших файлах журналу протягом 4 тижнів з юридичних причин.", + "title.config.bclib.client.version.check": "Увімкнути перевірку версій", + "title.config.bclib.client.ui.showUpdateInfo": "Дозволити екран нагадування про оновлення", + "title.config.bclib.client.rendering.FogDensity": "Густота туману", + "description.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "Екран попередження з'являється щоразу, коли у вашому світі не використовуються налаштування ванільної версії. Це може статися, коли ви запускаєте світ у попередній версії, а також коли ви використовуєте моди для зміни створення світу. Якщо цей параметр увімкнено, попередження буде пропущено під час завантаження існуючого світу. Воно все ще відображатиметься (як нагадування), коли буде створено новий світ.", + "title.config.bclib.client.ui.forceBetterXPreset": "Використовувати BetterX як тип світу за замовчуванням", + "warning.config.bclib.client.ui.forceBetterXPreset": "НАЙІМОВІРНІШЕ, ВИ ЗАХОЧЕТЕ ЗАЛИШИТИ ЦЕЙ ПАРАМЕТР УВІМКНЕНИМ.\n", + "description.config.bclib.client.ui.forceBetterXPreset": "Тип світу визначає спосіб генерування світу. Здебільшого слід залишити BetterX за замовчуванням (ви можете будь-коли змінити тип на екрані створення світу). Цей тип оптимізовано для максимальної сумісності між пакетами даних, нашим модом та іншими Fabric модами. Він також надає деякі унікальні функції для BetterNether та BetterEnd. Вимкніть цей параметр, лише якщо у вас на те є причина!", + "emi.category.bclib.alloying": "Легування", + "emi.category.bclib.anvil_0": "Ковадло (Рівень 0)", + "emi.category.bclib.anvil_1": "Ковадло (Рівень I)", + "emi.category.bclib.anvil_2": "Ковадло (Рівень II)", + "emi.category.bclib.anvil_3": "Ковадло (Рівень III)", + "emi.category.bclib.anvil_4": "Ковадло (Рівень IV)", + "emi.category.bclib.anvil_5": "Ковадло (Рівень V)", + "tag.c.barrel": "Діжки", + "tag.c.wooden_barrels": "Дерев'яні діжки", + "tag.c.workbench": "Верстаки", + "tag.c.furnaces": "Печі", + "tag.c.saplings": "Паростки", + "tag.c.hammers": "Молоти", + "tag.c.leaves": "Листя", + "tag.c.soul_ground": "Земля душ" +} diff --git a/src/main/resources/assets/bclib/lang/zh_cn.json b/src/main/resources/assets/bclib/lang/zh_cn.json new file mode 100644 index 00000000..8b8100f4 --- /dev/null +++ b/src/main/resources/assets/bclib/lang/zh_cn.json @@ -0,0 +1,101 @@ +{ + "message.bclib.anvil_damage": "§c损坏", + "bclib.datafixer.backupWarning.title": "发现了一个不兼容的世界", + "bclib.datafixer.backupWarning.message": "自从上次加载这个世界以来,一些安装的MOD的内部结构确实发生了变化。\n\n我们可以自动为您更改世界。如果继续而不应用更改,则世界可能无法正确加载,在着之前,您应该创建一个备份.", + "bclib.datafixer.backupWarning.backup": "创建备份", + "bclib.datafixer.backupWarning.nofixes": "忽略错误", + "bclib.datafixer.backupWarning.fix": "应用修复", + "title.bclib.bclibmissmatch": "版本不匹配", + "message.bclib.bclibmissmatch": "服务器上的BCLib版本与此客户端不匹配。这将导致非常严重的问题。\n\n是否要从服务器自动下载BCLib版本?\n\nBCLib将在安装新版本之前,将旧版本移动到Mods文件夹的子目录中.", + "title.bclib.syncfiles": "不匹配数据", + "message.bclib.syncfiles": "服务器上的某些内容与客户端上的版本不匹配。\n是否要用服务器上的数据替换所选内容?", + "message.bclib.syncfiles.mods": "同步Mods", + "message.bclib.syncfiles.configs": "同步配置", + "message.bclib.syncfiles.folders": "同步文件夹和文件", + "message.bclib.syncfiles.delete": "删除不需要的文件", + "title.bclib.confirmrestart": "需要重新启动", + "message.bclib.confirmrestart": "请求的内容已同步。你现在需要重新启动Minecraft.", + "title.link.bclib.discord": "Discord", + "title.bclib.modmenu.main": "BCLib设置", + "title.bclib.progress": "改进", + "title.bclib.filesync.progress": "文件传输", + "message.bclib.filesync.progress": "正在将文件内容与服务器同步", + "message.bclib.filesync.progress.stage.empty": "", + "title.config.bclib.client.auto_sync.enabled": "启用自动同步", + "title.config.bclib.client.auto_sync.acceptConfigs": "接受下载的配置文件", + "title.config.bclib.client.auto_sync.acceptFiles": "接受下载的文件", + "title.config.bclib.client.auto_sync.acceptMods": "接受下载的MOD", + "title.config.bclib.client.auto_sync.displayModInfo": "当服务器端MOD与客户端不同时显示警告", + "title.config.bclib.client.auto_sync.debugHashes": "将自动同步调试哈希打印到日志", + "title.config.bclib.generator.options.useOldBiomeGenerator": "使用旧版1.17生物群落生成器", + "title.config.bclib.main.patches.applyPatches": "加载时自动应用修复补丁", + "title.config.bclib.main.patches.repairBiomesOnLoad": "将生物群系固定在水平荷载上", + "title.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "禁用加载时的实验警告", + "title.bclib.syncfiles.modInfo": "模组信息", + "title.bclib.syncfiles.modlist": "模组列表", + "message.bclib.syncfiles.modlist": "以下显示已安装的MOD的状态 \n\n将同步本地不存在或服务器上版本不同的所有MOD", + "title.bclib.modmissmatch": "Mod版本冲突", + "message.bclib.modmissmatch": "此客户端上的某些MOD与服务器上的MOD版本不匹配。\n可能导致奇怪的游戏行为或崩溃。请确保您使用与服务器相同的mod版本", + "message.bclib.datafixer.progress.waitbackup": "正在等待备份完成。这可能需要一段时间!", + "message.bclib.datafixer.progress.reading": "读取数据", + "message.bclib.datafixer.progress.players": "固定玩家", + "message.bclib.datafixer.progress.level": "将修复补丁应用于level.dat", + "message.bclib.datafixer.progress.worlddata": "修补自定义世界数据", + "message.bclib.datafixer.progress.regions": "修复所有区域", + "message.bclib.datafixer.progress.saving": "保存补丁状态", + "title.bclib.datafixer.progress": "修复世界", + "message.bclib.datafixer.progress": "将所有修复补丁应用于您的世界", + "title.bclib.datafixer.error": "修复世界时的错误", + "message.bclib.datafixer.error": "修复世界时出现了错误,请还原备份并修复以下错误,然后重试", + "title.bclib.datafixer.error.continue": "继续并标记为已修复", + "title.config.bclib.client.rendering.customFogRendering": "自定义雾渲染", + "title.config.bclib.client.rendering.netherThickFog": "下界浓雾", + "tooltip.bclib.place_on": "在 %s 上存活", + "generator.bclib.normal": "BetterX", + "title.screen.bclib.worldgen.main": "世界生成器设置", + "title.bclib.the_nether": "下界", + "title.bclib.the_end": "末地", + "title.screen.bclib.worldgen.custom_biome_source": "使用自定义生物群系", + "title.screen.bclib.worldgen.legacy_square": "使用传统地图(1.17)", + "title.screen.bclib.worldgen.custom_end_terrain": "自定义末地地形生成器", + "title.screen.bclib.worldgen.avg_biome_size": "平均生物群系大小(以区块为单位)", + "title.screen.bclib.worldgen.other": "其他设置", + "title.screen.bclib.worldgen.land_biome_size": "末地生物群系", + "title.screen.bclib.worldgen.void_biome_size": "末地小型岛屿生物群系", + "title.screen.bclib.worldgen.center_biome_size": "末地主岛生物群系", + "title.screen.bclib.worldgen.barrens_biome_size": "末地荒地", + "title.screen.bclib.worldgen.central_radius": "末地主岛虚空半径(以区块为单位)", + "title.screen.bclib.worldgen.end_void": "生成末地小型岛屿", + "bclib.updates.curseforge_link": "[CurseForge]", + "bclib.updates.title": "模组更新", + "bclib.updates.disable_check": "关闭更新检查", + "bclib.updates.donate_pre": "喜欢我们的模组吗?\n考虑考虑捐点小钱呗 :)", + "bclib.updates.donate": "捐赠", + "bclib.updates.description": "一些已安装的模组已过时。 我们不断改进和扩展我们的内容,并提供重要的错误修复。\n请考虑使用下面提供的链接更新您的模组。", + "bclib.welcome.title": "欢迎来到", + "bclib.welcome.description": "...非常衷心地**感谢您**下载并游玩我们的模组。我们希望您和我们一样喜欢它们。\n\n在开始之前,我们需要设置一些东西,所以请继续阅读下面无聊的部分。", + "bclib.welcome.donation": "如果您像我们希望的那样喜欢我们的模组,考虑捐点小钱呗 :)", + "description.config.bclib.client.version.check": "BCLib包含一个简易的版本更新检测器,它可以在我们的模组有新的更新时通知您。为此,我们需要从我们的其中一个Web服务器中读取资源。为了处理这样的请求,您的IP地址(我们需要知道把响应发送到哪里)和Minecraft版本(以便我们可以选择正确的Mod版本)会发送到我们的服务器上。我们绝不会出于其他目的使用或处理传输的数据,但出于法律原因,这些数据必须在我们的日志文件中存储4周。", + "title.config.bclib.client.version.check": "启用版本检测", + "title.config.bclib.client.ui.showUpdateInfo": "允许出现更新提醒屏幕", + "title.config.bclib.client.rendering.FogDensity": "雾密度", + "description.config.bclib.client.ui.suppressExperimentalDialogOnLoad": "只要您的世界不使用原版设置,就会出现此警告屏幕。这也可能在当您在预览版中启动世界或使用模组更改世界创建的方式时发生。如果启用此选项,则在加载现有世界时将跳过此警告。创建新世界时,它仍会(作为提醒)显示。", + "title.config.bclib.client.ui.forceBetterXPreset": "使用BetterX作为默认世界类型", + "warning.config.bclib.client.ui.forceBetterXPreset": "您很可能希望启用此选项。\n", + "description.config.bclib.client.ui.forceBetterXPreset": "世界类型决定了世界的生成方式。在大多数情况下,BetterX应该保持为默认值(您可以在世界创建屏幕中随时更改类型)。此类型针对数据包、我们的模组和其他Fabric模组之间进行了兼容性方面的最大优化。它还为更好的下界和更好的末地提供了一些独特的功能。如果您有理由这样做,您应该只禁用此选项!", + "emi.category.bclib.alloying": "合金化", + "emi.category.bclib.anvil_0": "铁砧(等级 0 )", + "emi.category.bclib.anvil_1": "铁砧(等级 I )", + "emi.category.bclib.anvil_2": "铁砧(等级 II )", + "emi.category.bclib.anvil_3": "铁砧(等级 III )", + "emi.category.bclib.anvil_4": "铁砧(等级 IV )", + "emi.category.bclib.anvil_5": "铁砧(等级 V )", + "tag.c.barrel": "桶", + "tag.c.wooden_barrels": "木桶", + "tag.c.workbench": "工作台", + "tag.c.furnaces": "熔炉", + "tag.c.saplings": "树苗", + "tag.c.hammers": "锤子", + "tag.c.leaves": "树叶", + "tag.c.soul_ground": "灵魂之地" +} diff --git a/src/main/resources/assets/bclib/materialmaps/block/alpha_emission.json b/src/main/resources/assets/bclib/materialmaps/block/alpha_emission.json new file mode 100644 index 00000000..871b495d --- /dev/null +++ b/src/main/resources/assets/bclib/materialmaps/block/alpha_emission.json @@ -0,0 +1,3 @@ +{ + "defaultMaterial": "bclib:alpha_emission" +} diff --git a/src/main/resources/assets/bclib/materials/alpha_emission.json b/src/main/resources/assets/bclib/materials/alpha_emission.json new file mode 100644 index 00000000..25fd0641 --- /dev/null +++ b/src/main/resources/assets/bclib/materials/alpha_emission.json @@ -0,0 +1,8 @@ +{ + "layers": [ + { + "vertexSource": "canvas:shaders/material/default.vert", + "fragmentSource": "bclib:shaders/material/alpha_emission.frag" + } + ] +} diff --git a/src/main/resources/assets/bclib/models/block/bar_stool.json b/src/main/resources/assets/bclib/models/block/bar_stool.json new file mode 100644 index 00000000..38d88ead --- /dev/null +++ b/src/main/resources/assets/bclib/models/block/bar_stool.json @@ -0,0 +1,162 @@ +{ + "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", + "parent": "block/block", + "textures": { + "particle": "#texture" + }, + "elements": [ + { + "__comment": "Box1", + "from": [ 4, 13, 5 ], + "to": [ 12, 15, 11 ], + "faces": { + "down": { "uv": [ 4, 5, 12, 11 ], "texture": "#texture" }, + "up": { "uv": [ 4, 5, 12, 11 ], "texture": "#texture" }, + "north": { "uv": [ 4, 1, 12, 3 ], "texture": "#texture" }, + "south": { "uv": [ 4, 1, 12, 3 ], "texture": "#texture" }, + "west": { "uv": [ 5, 1, 11, 3 ], "texture": "#texture" }, + "east": { "uv": [ 5, 1, 11, 3 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 5, 13, 4 ], + "to": [ 11, 15, 5 ], + "faces": { + "down": { "uv": [ 5, 11, 11, 12 ], "texture": "#texture" }, + "up": { "uv": [ 5, 4, 11, 5 ], "texture": "#texture" }, + "north": { "uv": [ 5, 1, 11, 3 ], "texture": "#texture" }, + "west": { "uv": [ 4, 1, 5, 3 ], "texture": "#texture" }, + "east": { "uv": [ 11, 1, 12, 3 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 5, 13, 11 ], + "to": [ 11, 15, 12 ], + "faces": { + "down": { "uv": [ 5, 4, 11, 5 ], "texture": "#texture" }, + "up": { "uv": [ 5, 11, 11, 12 ], "texture": "#texture" }, + "south": { "uv": [ 5, 1, 11, 3 ], "texture": "#texture" }, + "west": { "uv": [ 11, 1, 12, 3 ], "texture": "#texture" }, + "east": { "uv": [ 4, 1, 5, 3 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 5, 15, 5 ], + "to": [ 11, 16, 11 ], + "faces": { + "up": { "uv": [ 5, 5, 11, 11 ], "texture": "#cloth" }, + "north": { "uv": [ 5, 0, 11, 1 ], "texture": "#cloth" }, + "south": { "uv": [ 5, 0, 11, 1 ], "texture": "#cloth" }, + "west": { "uv": [ 5, 0, 11, 1 ], "texture": "#cloth" }, + "east": { "uv": [ 5, 0, 11, 1 ], "texture": "#cloth" } + } + }, + { + "__comment": "Box5", + "from": [ 5.5, 12, 5.5 ], + "to": [ 10.5, 13, 10.5 ], + "faces": { + "down": { "uv": [ 5.5, 5.5, 10.5, 10.5 ], "texture": "#texture" }, + "north": { "uv": [ 5.5, 3, 10.5, 4 ], "texture": "#texture" }, + "south": { "uv": [ 5.5, 3, 10.5, 4 ], "texture": "#texture" }, + "west": { "uv": [ 5.5, 3, 10.5, 4 ], "texture": "#texture" }, + "east": { "uv": [ 5.5, 3, 10.5, 4 ], "texture": "#texture" } + } + }, + { + "__comment": "Box5", + "from": [ 5, 0, 5 ], + "to": [ 6, 13, 6 ], + "faces": { + "down": { "uv": [ 5, 10, 6, 11 ], "texture": "#texture", "cullface": "down" }, + "north": { "uv": [ 1, 5, 14, 6 ], "texture": "#texture", "rotation": 90 }, + "south": { "uv": [ 2, 9, 15, 10 ], "texture": "#texture", "rotation": 90 }, + "west": { "uv": [ 1, 13, 14, 14 ], "texture": "#texture", "rotation": 90 }, + "east": { "uv": [ 2, 9, 15, 10 ], "texture": "#texture", "rotation": 90 } + } + }, + { + "__comment": "Box5", + "from": [ 5, 0, 10 ], + "to": [ 6, 13, 11 ], + "faces": { + "down": { "uv": [ 5, 5, 6, 6 ], "texture": "#texture", "cullface": "down" }, + "north": { "uv": [ 3, 9, 16, 10 ], "texture": "#texture", "rotation": 90 }, + "south": { "uv": [ 3, 13, 16, 14 ], "texture": "#texture", "rotation": 90 }, + "west": { "uv": [ 3, 13, 16, 14 ], "texture": "#texture", "rotation": 90 }, + "east": { "uv": [ 3, 1, 16, 2 ], "texture": "#texture", "rotation": 90 } + } + }, + { + "__comment": "Box5", + "from": [ 10, 0, 10 ], + "to": [ 11, 13, 11 ], + "faces": { + "down": { "uv": [ 10, 5, 11, 6 ], "texture": "#texture", "cullface": "down" }, + "north": { "uv": [ 3, 1, 16, 2 ], "texture": "#texture", "rotation": 90 }, + "south": { "uv": [ 3, 5, 16, 6 ], "texture": "#texture", "rotation": 90 }, + "west": { "uv": [ 3, 13, 16, 14 ], "texture": "#texture", "rotation": 90 }, + "east": { "uv": [ 3, 9, 16, 10 ], "texture": "#texture", "rotation": 90 } + } + }, + { + "__comment": "Box5", + "from": [ 10, 0, 5 ], + "to": [ 11, 13, 6 ], + "faces": { + "down": { "uv": [ 10, 10, 11, 11 ], "texture": "#texture", "cullface": "down" }, + "north": { "uv": [ 3, 13, 16, 14 ], "texture": "#texture", "rotation": 90 }, + "south": { "uv": [ 0, 9, 13, 10 ], "texture": "#texture", "rotation": 90 }, + "west": { "uv": [ 3, 5, 16, 6 ], "texture": "#texture", "rotation": 90 }, + "east": { "uv": [ 0, 1, 13, 2 ], "texture": "#texture", "rotation": 90 } + } + }, + { + "__comment": "Box5", + "from": [ 10, 5, 6 ], + "to": [ 11, 6, 10 ], + "faces": { + "down": { "uv": [ 7, 9, 11, 10 ], "texture": "#texture", "rotation": 90 }, + "up": { "uv": [ 3, 9, 7, 10 ], "texture": "#texture", "rotation": 90 }, + "west": { "uv": [ 3, 5, 7, 6 ], "texture": "#texture" }, + "east": { "uv": [ 3, 5, 7, 6 ], "texture": "#texture" } + } + }, + { + "__comment": "Box5", + "from": [ 6, 7, 5 ], + "to": [ 10, 8, 6 ], + "faces": { + "down": { "uv": [ 6.5, 9, 10.5, 10 ], "texture": "#texture" }, + "up": { "uv": [ 4, 9, 8, 10 ], "texture": "#texture" }, + "north": { "uv": [ 2, 9, 6, 10 ], "texture": "#texture" }, + "south": { "uv": [ 7, 9, 11, 10 ], "texture": "#texture" } + } + }, + { + "__comment": "Box5", + "from": [ 6, 7, 10 ], + "to": [ 10, 8, 11 ], + "faces": { + "down": { "uv": [ 4, 5, 8, 6 ], "texture": "#texture" }, + "up": { "uv": [ 6, 9, 10, 10 ], "texture": "#texture" }, + "north": { "uv": [ 4, 9, 8, 10 ], "texture": "#texture" }, + "south": { "uv": [ 5, 9, 9, 10 ], "texture": "#texture" } + } + }, + { + "__comment": "Box5", + "from": [ 5, 5, 6 ], + "to": [ 6, 6, 10 ], + "faces": { + "down": { "uv": [ 7, 9, 11, 10 ], "texture": "#texture", "rotation": 90 }, + "up": { "uv": [ 3, 9, 7, 10 ], "texture": "#texture", "rotation": 90 }, + "west": { "uv": [ 3, 5, 7, 6 ], "texture": "#texture" }, + "east": { "uv": [ 3, 5, 7, 6 ], "texture": "#texture" } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/models/block/chair.json b/src/main/resources/assets/bclib/models/block/chair.json new file mode 100644 index 00000000..3f71e294 --- /dev/null +++ b/src/main/resources/assets/bclib/models/block/chair.json @@ -0,0 +1,200 @@ +{ + "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", + "parent": "block/block", + "textures": { + "particle": "#texture" + }, + "elements": [ + { + "__comment": "Box1", + "from": [ 3, 9, 3 ], + "to": [ 13, 10, 13 ], + "faces": { + "down": { "uv": [ 3, 3, 13, 13 ], "texture": "#texture" }, + "up": { "uv": [ 3, 3, 13, 13 ], "texture": "#texture" }, + "north": { "uv": [ 3, 6, 13, 7 ], "texture": "#texture" }, + "south": { "uv": [ 3, 6, 13, 7 ], "texture": "#texture" }, + "west": { "uv": [ 3, 6, 13, 7 ], "texture": "#texture" }, + "east": { "uv": [ 3, 6, 13, 7 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 3, 0, 3 ], + "to": [ 5, 9, 5 ], + "faces": { + "down": { "uv": [ 3, 11, 5, 13 ], "texture": "#texture" }, + "north": { "uv": [ 11, 7, 13, 16 ], "texture": "#texture" }, + "south": { "uv": [ 3, 7, 5, 16 ], "texture": "#texture" }, + "west": { "uv": [ 3, 7, 5, 16 ], "texture": "#texture" }, + "east": { "uv": [ 11, 7, 13, 16 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 3, 16, 3 ], + "to": [ 5, 21, 5 ], + "faces": { + "up": { "uv": [ 3, 3, 5, 5 ], "texture": "#texture" }, + "north": { "uv": [ 11, 11, 13, 16 ], "texture": "#texture" }, + "west": { "uv": [ 3, 11, 5, 16 ], "texture": "#texture" }, + "east": { "uv": [ 11, 11, 13, 16 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 11, 0, 3 ], + "to": [ 13, 9, 5 ], + "faces": { + "down": { "uv": [ 11, 11, 13, 13 ], "texture": "#texture" }, + "north": { "uv": [ 3, 7, 5, 16 ], "texture": "#texture" }, + "south": { "uv": [ 11, 7, 13, 16 ], "texture": "#texture" }, + "west": { "uv": [ 3, 7, 5, 16 ], "texture": "#texture" }, + "east": { "uv": [ 11, 7, 13, 16 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 11, 16, 3 ], + "to": [ 13, 21, 5 ], + "faces": { + "up": { "uv": [ 11, 3, 13, 5 ], "texture": "#texture" }, + "north": { "uv": [ 3, 11, 5, 16 ], "texture": "#texture" }, + "west": { "uv": [ 3, 11, 5, 16 ], "texture": "#texture" }, + "east": { "uv": [ 11, 11, 13, 16 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 11, 10, 3 ], + "to": [ 13, 16, 5 ], + "faces": { + "north": { "uv": [ 3, 0, 5, 6 ], "texture": "#texture" }, + "south": { "uv": [ 11, 0, 13, 6 ], "texture": "#texture" }, + "west": { "uv": [ 3, 0, 5, 6 ], "texture": "#texture" }, + "east": { "uv": [ 11, 0, 13, 6 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 3, 10, 3 ], + "to": [ 5, 16, 5 ], + "faces": { + "north": { "uv": [ 11, 0, 13, 6 ], "texture": "#texture" }, + "south": { "uv": [ 3, 0, 5, 6 ], "texture": "#texture" }, + "west": { "uv": [ 3, 0, 5, 6 ], "texture": "#texture" }, + "east": { "uv": [ 11, 0, 13, 6 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 3, 0, 11 ], + "to": [ 5, 9, 13 ], + "faces": { + "down": { "uv": [ 3, 3, 5, 5 ], "texture": "#texture" }, + "north": { "uv": [ 11, 7, 13, 16 ], "texture": "#texture" }, + "south": { "uv": [ 3, 7, 5, 16 ], "texture": "#texture" }, + "west": { "uv": [ 11, 7, 13, 16 ], "texture": "#texture" }, + "east": { "uv": [ 3, 7, 5, 16 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 11, 0, 11 ], + "to": [ 13, 9, 13 ], + "faces": { + "down": { "uv": [ 11, 3, 13, 5 ], "texture": "#texture" }, + "north": { "uv": [ 3, 7, 5, 16 ], "texture": "#texture" }, + "south": { "uv": [ 11, 7, 13, 16 ], "texture": "#texture" }, + "west": { "uv": [ 11, 7, 13, 16 ], "texture": "#texture" }, + "east": { "uv": [ 3, 7, 5, 16 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 3, 14, 4 ], + "to": [ 13, 16, 5 ], + "faces": { + "down": { "uv": [ 3, 11, 13, 12 ], "texture": "#texture" }, + "north": { "uv": [ 3, 0, 13, 2 ], "texture": "#texture" }, + "south": { "uv": [ 3, 0, 13, 2 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 4, 21, 3 ], + "to": [ 12, 22, 5 ], + "faces": { + "down": { "uv": [ 4, 11, 12, 13 ], "texture": "#texture" }, + "up": { "uv": [ 4, 3, 12, 5 ], "texture": "#texture" }, + "north": { "uv": [ 4, 10, 12, 11 ], "texture": "#texture" }, + "south": { "uv": [ 4, 10, 12, 11 ], "texture": "#texture" }, + "west": { "uv": [ 3, 10, 5, 11 ], "texture": "#texture" }, + "east": { "uv": [ 11, 10, 13, 11 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 3, 16, 4 ], + "to": [ 13, 21, 5 ], + "faces": { + "up": { "uv": [ 3, 4, 13, 5 ], "texture": "#texture" }, + "north": { "uv": [ 3, 11, 13, 16 ], "texture": "#texture" }, + "south": { "uv": [ 3, 11, 13, 16 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 4, 8, 4 ], + "to": [ 12, 9, 12 ], + "faces": { + "down": { "uv": [ 4, 4, 12, 12 ], "texture": "#texture" }, + "north": { "uv": [ 4, 7, 12, 8 ], "texture": "#texture" }, + "south": { "uv": [ 4, 7, 12, 8 ], "texture": "#texture" }, + "west": { "uv": [ 4, 7, 12, 8 ], "texture": "#texture" }, + "east": { "uv": [ 4, 7, 12, 8 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 5, 20, 3 ], + "to": [ 11, 21, 4 ], + "faces": { + "down": { "uv": [ 5, 12, 11, 13 ], "texture": "#texture" }, + "north": { "uv": [ 5, 11, 11, 12 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 5, 14, 3 ], + "to": [ 11, 16, 4 ], + "faces": { + "down": { "uv": [ 5, 12, 11, 13 ], "texture": "#texture" }, + "up": { "uv": [ 5, 3, 11, 4 ], "texture": "#texture" }, + "north": { "uv": [ 5, 0, 11, 2 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 4, 4, 5 ], + "to": [ 5, 5, 11 ], + "faces": { + "down": { "uv": [ 4, 5, 5, 11 ], "texture": "#texture" }, + "up": { "uv": [ 4, 5, 5, 11 ], "texture": "#texture" }, + "west": { "uv": [ 5, 11, 11, 12 ], "texture": "#texture" }, + "east": { "uv": [ 5, 11, 11, 12 ], "texture": "#texture" } + } + }, + { + "__comment": "Box1", + "from": [ 11, 4, 5 ], + "to": [ 12, 5, 11 ], + "faces": { + "down": { "uv": [ 11, 5, 12, 11 ], "texture": "#texture" }, + "up": { "uv": [ 11, 5, 12, 11 ], "texture": "#texture" }, + "west": { "uv": [ 5, 11, 11, 12 ], "texture": "#texture" }, + "east": { "uv": [ 5, 11, 11, 12 ], "texture": "#texture" } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/models/block/chest_item.json b/src/main/resources/assets/bclib/models/block/chest_item.json index c0a3265f..97ce0cbf 100644 --- a/src/main/resources/assets/bclib/models/block/chest_item.json +++ b/src/main/resources/assets/bclib/models/block/chest_item.json @@ -1,190 +1,190 @@ { - "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", - "parent": "block/block", - "elements": [ - { - "__comment": "Box1", - "faces": { - "down": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 3.5, - 4.75, - 7, - 8.25 - ] - }, - "east": { - "texture": "#texture", - "uv": [ - 3.5, - 10.75, - 7, - 8.25 - ] - }, - "north": { - "texture": "#texture", - "uv": [ - 10.5, - 10.75, - 14, - 8.25 - ] - }, - "south": { - "texture": "#texture", - "uv": [ - 0, - 10.75, - 3.5, - 8.25 - ] - }, - "west": { - "texture": "#texture", - "uv": [ - 7, - 10.75, - 10.5, - 8.25 - ] - } - }, - "from": [ - 1, - 0, - 1 - ], - "to": [ - 15, - 10, - 15 - ] - }, - { - "__comment": "Box1", - "faces": { - "east": { - "texture": "#texture", - "uv": [ - 3.5, - 4.75, - 7, - 3.75 - ] - }, - "north": { - "texture": "#texture", - "uv": [ - 10.5, - 4.75, - 14, - 3.75 - ] - }, - "south": { - "texture": "#texture", - "uv": [ - 0, - 4.75, - 3.5, - 3.75 - ] - }, - "up": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 7, - 0, - 10.5, - 3.5 - ] - }, - "west": { - "texture": "#texture", - "uv": [ - 7, - 4.75, - 10.5, - 3.75 - ] - } - }, - "from": [ - 1, - 10, - 1 - ], - "to": [ - 15, - 14, - 15 - ] - }, - { - "__comment": "Box1", - "faces": { - "down": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 0.25, - 0, - 0.75, - 0.25 - ] - }, - "east": { - "texture": "#texture", - "uv": [ - 0, - 1.25, - 0.25, - 0.25 - ] - }, - "north": { - "texture": "#texture", - "uv": [ - 0.5, - 1.25, - 1, - 0.25 - ] - }, - "up": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 0.75, - 0, - 1.25, - 0.25 - ] - }, - "west": { - "texture": "#texture", - "uv": [ - 0, - 1.25, - 0.25, - 0.25 - ] - } - }, - "from": [ - 7, - 7, - 0 - ], - "to": [ - 9, - 11, - 1 - ] - } - ] + "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", + "parent": "block/block", + "elements": [ + { + "__comment": "Box1", + "faces": { + "down": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 3.5, + 4.75, + 7, + 8.25 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 3.5, + 10.75, + 7, + 8.25 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 10.5, + 10.75, + 14, + 8.25 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 0, + 10.75, + 3.5, + 8.25 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 7, + 10.75, + 10.5, + 8.25 + ] + } + }, + "from": [ + 1, + 0, + 1 + ], + "to": [ + 15, + 10, + 15 + ] + }, + { + "__comment": "Box1", + "faces": { + "east": { + "texture": "#texture", + "uv": [ + 3.5, + 4.75, + 7, + 3.75 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 10.5, + 4.75, + 14, + 3.75 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 0, + 4.75, + 3.5, + 3.75 + ] + }, + "up": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 7, + 0, + 10.5, + 3.5 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 7, + 4.75, + 10.5, + 3.75 + ] + } + }, + "from": [ + 1, + 10, + 1 + ], + "to": [ + 15, + 14, + 15 + ] + }, + { + "__comment": "Box1", + "faces": { + "down": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 0.25, + 0, + 0.75, + 0.25 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 0, + 1.25, + 0.25, + 0.25 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 0.5, + 1.25, + 1, + 0.25 + ] + }, + "up": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 0.75, + 0, + 1.25, + 0.25 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 0, + 1.25, + 0.25, + 0.25 + ] + } + }, + "from": [ + 7, + 7, + 0 + ], + "to": [ + 9, + 11, + 1 + ] + } + ] } diff --git a/src/main/resources/assets/bclib/models/block/empty_hanging_test.json b/src/main/resources/assets/bclib/models/block/empty_hanging_test.json new file mode 100644 index 00000000..d7f6983a --- /dev/null +++ b/src/main/resources/assets/bclib/models/block/empty_hanging_test.json @@ -0,0 +1,5 @@ +{ + "textures": { + "particle": "bclib:block/test_planks" + } +} diff --git a/src/main/resources/assets/bclib/models/block/empty_test.json b/src/main/resources/assets/bclib/models/block/empty_test.json new file mode 100644 index 00000000..d7f6983a --- /dev/null +++ b/src/main/resources/assets/bclib/models/block/empty_test.json @@ -0,0 +1,5 @@ +{ + "textures": { + "particle": "bclib:block/test_planks" + } +} diff --git a/src/main/resources/assets/bclib/models/block/ladder.json b/src/main/resources/assets/bclib/models/block/ladder.json index 65ea537b..2ce23d45 100644 --- a/src/main/resources/assets/bclib/models/block/ladder.json +++ b/src/main/resources/assets/bclib/models/block/ladder.json @@ -1,441 +1,441 @@ { - "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", - "elements": [ - { - "__comment": "Box1", - "faces": { - "down": { - "cullface": "down", - "rotation": 180, - "texture": "#texture", - "uv": [ - 2, - 15, - 4, - 16 - ] - }, - "east": { - "texture": "#texture", - "uv": [ - 2, - 0, - 3, - 16 - ] - }, - "north": { - "texture": "#texture", - "uv": [ - 2, - 0, - 4, - 16 - ] - }, - "south": { - "cullface": "north", - "texture": "#texture", - "uv": [ - 2, - 0, - 4, - 16 - ] - }, - "up": { - "cullface": "up", - "rotation": 180, - "texture": "#texture", - "uv": [ - 2, - 0, - 4, - 1 - ] - }, - "west": { - "texture": "#texture", - "uv": [ - 3, - 0, - 4, - 16 - ] - } - }, - "from": [ - 12, - 0, - 15 - ], - "to": [ - 14, - 16, - 16 - ] - }, - { - "__comment": "Box1", - "faces": { - "down": { - "cullface": "down", - "rotation": 180, - "texture": "#texture", - "uv": [ - 12, - 15, - 14, - 16 - ] - }, - "east": { - "texture": "#texture", - "uv": [ - 2, - 0, - 3, - 16 - ] - }, - "north": { - "texture": "#texture", - "uv": [ - 12, - 0, - 14, - 16 - ] - }, - "south": { - "cullface": "north", - "texture": "#texture", - "uv": [ - 12, - 0, - 14, - 16 - ] - }, - "up": { - "cullface": "up", - "rotation": 180, - "texture": "#texture", - "uv": [ - 12, - 0, - 14, - 1 - ] - }, - "west": { - "texture": "#texture", - "uv": [ - 3, - 0, - 4, - 16 - ] - } - }, - "from": [ - 2, - 0, - 15 - ], - "to": [ - 4, - 16, - 16 - ] - }, - { - "__comment": "Box3", - "faces": { - "down": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 1, - 14, - 15, - 15 - ] - }, - "east": { - "texture": "#texture", - "uv": [ - 1, - 13, - 2, - 15 - ] - }, - "north": { - "texture": "#texture", - "uv": [ - 1, - 13, - 15, - 15 - ] - }, - "south": { - "texture": "#texture", - "uv": [ - 1, - 13, - 15, - 15 - ] - }, - "up": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 1, - 13, - 15, - 14 - ] - }, - "west": { - "texture": "#texture", - "uv": [ - 14, - 13, - 15, - 15 - ] - } - }, - "from": [ - 1, - 1, - 14.5 - ], - "to": [ - 15, - 3, - 15.5 - ] - }, - { - "__comment": "Box3", - "faces": { - "down": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 1, - 10, - 15, - 11 - ] - }, - "east": { - "texture": "#texture", - "uv": [ - 1, - 9, - 2, - 11 - ] - }, - "north": { - "texture": "#texture", - "uv": [ - 1, - 9, - 15, - 11 - ] - }, - "south": { - "texture": "#texture", - "uv": [ - 1, - 9, - 15, - 11 - ] - }, - "up": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 1, - 9, - 15, - 10 - ] - }, - "west": { - "texture": "#texture", - "uv": [ - 14, - 9, - 15, - 11 - ] - } - }, - "from": [ - 1, - 5, - 14.5 - ], - "to": [ - 15, - 7, - 15.5 - ] - }, - { - "__comment": "Box3", - "faces": { - "down": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 1, - 6, - 15, - 7 - ] - }, - "east": { - "texture": "#texture", - "uv": [ - 1, - 5, - 2, - 7 - ] - }, - "north": { - "texture": "#texture", - "uv": [ - 1, - 5, - 15, - 7 - ] - }, - "south": { - "texture": "#texture", - "uv": [ - 1, - 5, - 15, - 7 - ] - }, - "up": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 1, - 5, - 15, - 6 - ] - }, - "west": { - "texture": "#texture", - "uv": [ - 14, - 5, - 15, - 7 - ] - } - }, - "from": [ - 1, - 9, - 14.5 - ], - "to": [ - 15, - 11, - 15.5 - ] - }, - { - "__comment": "Box3", - "faces": { - "down": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 1, - 2, - 15, - 3 - ] - }, - "east": { - "texture": "#texture", - "uv": [ - 1, - 1, - 2, - 3 - ] - }, - "north": { - "texture": "#texture", - "uv": [ - 1, - 1, - 15, - 3 - ] - }, - "south": { - "texture": "#texture", - "uv": [ - 1, - 1, - 15, - 3 - ] - }, - "up": { - "rotation": 180, - "texture": "#texture", - "uv": [ - 1, - 1, - 15, - 2 - ] - }, - "west": { - "texture": "#texture", - "uv": [ - 14, - 1, - 15, - 3 - ] - } - }, - "from": [ - 1, - 13, - 14.5 - ], - "to": [ - 15, - 15, - 15.5 - ] - } - ], - "parent": "block/ladder", - "textures": { - "particle": "#texture" - } + "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", + "elements": [ + { + "__comment": "Box1", + "faces": { + "down": { + "cullface": "down", + "rotation": 180, + "texture": "#texture", + "uv": [ + 2, + 15, + 4, + 16 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 2, + 0, + 3, + 16 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 2, + 0, + 4, + 16 + ] + }, + "south": { + "cullface": "north", + "texture": "#texture", + "uv": [ + 2, + 0, + 4, + 16 + ] + }, + "up": { + "cullface": "up", + "rotation": 180, + "texture": "#texture", + "uv": [ + 2, + 0, + 4, + 1 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 3, + 0, + 4, + 16 + ] + } + }, + "from": [ + 12, + 0, + 15 + ], + "to": [ + 14, + 16, + 16 + ] + }, + { + "__comment": "Box1", + "faces": { + "down": { + "cullface": "down", + "rotation": 180, + "texture": "#texture", + "uv": [ + 12, + 15, + 14, + 16 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 2, + 0, + 3, + 16 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 12, + 0, + 14, + 16 + ] + }, + "south": { + "cullface": "north", + "texture": "#texture", + "uv": [ + 12, + 0, + 14, + 16 + ] + }, + "up": { + "cullface": "up", + "rotation": 180, + "texture": "#texture", + "uv": [ + 12, + 0, + 14, + 1 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 3, + 0, + 4, + 16 + ] + } + }, + "from": [ + 2, + 0, + 15 + ], + "to": [ + 4, + 16, + 16 + ] + }, + { + "__comment": "Box3", + "faces": { + "down": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 1, + 14, + 15, + 15 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 1, + 13, + 2, + 15 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1, + 13, + 15, + 15 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 1, + 13, + 15, + 15 + ] + }, + "up": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 1, + 13, + 15, + 14 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 14, + 13, + 15, + 15 + ] + } + }, + "from": [ + 1, + 1, + 14.5 + ], + "to": [ + 15, + 3, + 15.5 + ] + }, + { + "__comment": "Box3", + "faces": { + "down": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 1, + 10, + 15, + 11 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 1, + 9, + 2, + 11 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1, + 9, + 15, + 11 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 1, + 9, + 15, + 11 + ] + }, + "up": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 1, + 9, + 15, + 10 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 14, + 9, + 15, + 11 + ] + } + }, + "from": [ + 1, + 5, + 14.5 + ], + "to": [ + 15, + 7, + 15.5 + ] + }, + { + "__comment": "Box3", + "faces": { + "down": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 1, + 6, + 15, + 7 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 1, + 5, + 2, + 7 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1, + 5, + 15, + 7 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 1, + 5, + 15, + 7 + ] + }, + "up": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 1, + 5, + 15, + 6 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 14, + 5, + 15, + 7 + ] + } + }, + "from": [ + 1, + 9, + 14.5 + ], + "to": [ + 15, + 11, + 15.5 + ] + }, + { + "__comment": "Box3", + "faces": { + "down": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 1, + 2, + 15, + 3 + ] + }, + "east": { + "texture": "#texture", + "uv": [ + 1, + 1, + 2, + 3 + ] + }, + "north": { + "texture": "#texture", + "uv": [ + 1, + 1, + 15, + 3 + ] + }, + "south": { + "texture": "#texture", + "uv": [ + 1, + 1, + 15, + 3 + ] + }, + "up": { + "rotation": 180, + "texture": "#texture", + "uv": [ + 1, + 1, + 15, + 2 + ] + }, + "west": { + "texture": "#texture", + "uv": [ + 14, + 1, + 15, + 3 + ] + } + }, + "from": [ + 1, + 13, + 14.5 + ], + "to": [ + 15, + 15, + 15.5 + ] + } + ], + "parent": "block/ladder", + "textures": { + "particle": "#texture" + } } diff --git a/src/main/resources/assets/bclib/models/block/path.json b/src/main/resources/assets/bclib/models/block/path.json index 04fed330..d7f0b84b 100644 --- a/src/main/resources/assets/bclib/models/block/path.json +++ b/src/main/resources/assets/bclib/models/block/path.json @@ -1,18 +1,81 @@ -{ "parent": "block/block", - "textures": { - "particle": "#bottom" - }, - "elements": [ - { "from": [ 0, 0, 0 ], - "to": [ 16, 15, 16 ], - "faces": { - "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, - "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#top" }, - "north": { "uv": [ 0, 1, 16, 16 ], "texture": "#side", "cullface": "north" }, - "south": { "uv": [ 0, 1, 16, 16 ], "texture": "#side", "cullface": "south" }, - "west": { "uv": [ 0, 1, 16, 16 ], "texture": "#side", "cullface": "west" }, - "east": { "uv": [ 0, 1, 16, 16 ], "texture": "#side", "cullface": "east" } - } +{ + "parent": "block/block", + "textures": { + "particle": "#bottom" + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 15, + 16 + ], + "faces": { + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#bottom", + "cullface": "down" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#top" + }, + "north": { + "uv": [ + 0, + 1, + 16, + 16 + ], + "texture": "#side", + "cullface": "north" + }, + "south": { + "uv": [ + 0, + 1, + 16, + 16 + ], + "texture": "#side", + "cullface": "south" + }, + "west": { + "uv": [ + 0, + 1, + 16, + 16 + ], + "texture": "#side", + "cullface": "west" + }, + "east": { + "uv": [ + 0, + 1, + 16, + 16 + ], + "texture": "#side", + "cullface": "east" } - ] + } + } + ] } diff --git a/src/main/resources/assets/bclib/models/block/sided_door_bottom.json b/src/main/resources/assets/bclib/models/block/sided_door_bottom.json index 6fddf595..d23fe970 100644 --- a/src/main/resources/assets/bclib/models/block/sided_door_bottom.json +++ b/src/main/resources/assets/bclib/models/block/sided_door_bottom.json @@ -1,18 +1,71 @@ { - "ambientocclusion": false, - "textures": { - "particle": "#facade" - }, - "elements": [ - { "from": [ 0, 0, 0 ], - "to": [ 3, 16, 16 ], - "faces": { - "down": { "uv": [ 13, 0, 16, 16 ], "texture": "#side", "cullface": "down" }, - "north": { "uv": [ 3, 0, 0, 16 ], "texture": "#side", "cullface": "north" }, - "south": { "uv": [ 0, 0, 3, 16 ], "texture": "#side", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#facade", "cullface": "west" }, - "east": { "uv": [ 16, 0, 0, 16 ], "texture": "#facade" } - } + "ambientocclusion": false, + "textures": { + "particle": "#facade" + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 3, + 16, + 16 + ], + "faces": { + "down": { + "uv": [ + 13, + 0, + 16, + 16 + ], + "texture": "#side", + "cullface": "down" + }, + "north": { + "uv": [ + 3, + 0, + 0, + 16 + ], + "texture": "#side", + "cullface": "north" + }, + "south": { + "uv": [ + 0, + 0, + 3, + 16 + ], + "texture": "#side", + "cullface": "south" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#facade", + "cullface": "west" + }, + "east": { + "uv": [ + 16, + 0, + 0, + 16 + ], + "texture": "#facade" } - ] + } + } + ] } diff --git a/src/main/resources/assets/bclib/models/block/sided_door_bottom_rh.json b/src/main/resources/assets/bclib/models/block/sided_door_bottom_rh.json index e6294d19..119f91de 100644 --- a/src/main/resources/assets/bclib/models/block/sided_door_bottom_rh.json +++ b/src/main/resources/assets/bclib/models/block/sided_door_bottom_rh.json @@ -1,18 +1,71 @@ { - "ambientocclusion": false, - "textures": { - "particle": "#facade" - }, - "elements": [ - { "from": [ 0, 0, 0 ], - "to": [ 3, 16, 16 ], - "faces": { - "down": { "uv": [ 13, 0, 16, 16 ], "texture": "#side", "cullface": "down" }, - "north": { "uv": [ 3, 0, 0, 16 ], "texture": "#side", "cullface": "north" }, - "south": { "uv": [ 0, 0, 3, 16 ], "texture": "#side", "cullface": "south" }, - "west": { "uv": [ 16, 0, 0, 16 ], "texture": "#facade", "cullface": "west" }, - "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#facade" } - } + "ambientocclusion": false, + "textures": { + "particle": "#facade" + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 3, + 16, + 16 + ], + "faces": { + "down": { + "uv": [ + 13, + 0, + 16, + 16 + ], + "texture": "#side", + "cullface": "down" + }, + "north": { + "uv": [ + 3, + 0, + 0, + 16 + ], + "texture": "#side", + "cullface": "north" + }, + "south": { + "uv": [ + 0, + 0, + 3, + 16 + ], + "texture": "#side", + "cullface": "south" + }, + "west": { + "uv": [ + 16, + 0, + 0, + 16 + ], + "texture": "#facade", + "cullface": "west" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#facade" } - ] + } + } + ] } diff --git a/src/main/resources/assets/bclib/models/block/sided_door_top.json b/src/main/resources/assets/bclib/models/block/sided_door_top.json index 4a646108..9f4a6599 100644 --- a/src/main/resources/assets/bclib/models/block/sided_door_top.json +++ b/src/main/resources/assets/bclib/models/block/sided_door_top.json @@ -1,18 +1,71 @@ { - "ambientocclusion": false, - "textures": { - "particle": "#facade" - }, - "elements": [ - { "from": [ 0, 0, 0 ], - "to": [ 3, 16, 16 ], - "faces": { - "up": { "uv": [ 13, 0, 16, 16 ], "texture": "#side", "cullface": "up" }, - "north": { "uv": [ 3, 0, 0, 16 ], "texture": "#side", "cullface": "north" }, - "south": { "uv": [ 0, 0, 3, 16 ], "texture": "#side", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#facade", "cullface": "west" }, - "east": { "uv": [ 16, 0, 0, 16 ], "texture": "#facade" } - } + "ambientocclusion": false, + "textures": { + "particle": "#facade" + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 3, + 16, + 16 + ], + "faces": { + "up": { + "uv": [ + 13, + 0, + 16, + 16 + ], + "texture": "#side", + "cullface": "up" + }, + "north": { + "uv": [ + 3, + 0, + 0, + 16 + ], + "texture": "#side", + "cullface": "north" + }, + "south": { + "uv": [ + 0, + 0, + 3, + 16 + ], + "texture": "#side", + "cullface": "south" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#facade", + "cullface": "west" + }, + "east": { + "uv": [ + 16, + 0, + 0, + 16 + ], + "texture": "#facade" } - ] + } + } + ] } diff --git a/src/main/resources/assets/bclib/models/block/sided_door_top_rh.json b/src/main/resources/assets/bclib/models/block/sided_door_top_rh.json index c706fe16..8bec5ee1 100644 --- a/src/main/resources/assets/bclib/models/block/sided_door_top_rh.json +++ b/src/main/resources/assets/bclib/models/block/sided_door_top_rh.json @@ -1,18 +1,71 @@ { - "ambientocclusion": false, - "textures": { - "particle": "#facade" - }, - "elements": [ - { "from": [ 0, 0, 0 ], - "to": [ 3, 16, 16 ], - "faces": { - "up": { "uv": [ 13, 0, 16, 16 ], "texture": "#side", "cullface": "up" }, - "north": { "uv": [ 3, 0, 0, 16 ], "texture": "#side", "cullface": "north" }, - "south": { "uv": [ 0, 0, 3, 16 ], "texture": "#side", "cullface": "south" }, - "west": { "uv": [ 16, 0, 0, 16 ], "texture": "#facade", "cullface": "west" }, - "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#facade" } - } + "ambientocclusion": false, + "textures": { + "particle": "#facade" + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 3, + 16, + 16 + ], + "faces": { + "up": { + "uv": [ + 13, + 0, + 16, + 16 + ], + "texture": "#side", + "cullface": "up" + }, + "north": { + "uv": [ + 3, + 0, + 0, + 16 + ], + "texture": "#side", + "cullface": "north" + }, + "south": { + "uv": [ + 0, + 0, + 3, + 16 + ], + "texture": "#side", + "cullface": "south" + }, + "west": { + "uv": [ + 16, + 0, + 0, + 16 + ], + "texture": "#facade", + "cullface": "west" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#facade" } - ] + } + } + ] } diff --git a/src/main/resources/assets/bclib/models/block/sided_trapdoor.json b/src/main/resources/assets/bclib/models/block/sided_trapdoor.json index 9f776427..84ff2e52 100644 --- a/src/main/resources/assets/bclib/models/block/sided_trapdoor.json +++ b/src/main/resources/assets/bclib/models/block/sided_trapdoor.json @@ -1,18 +1,85 @@ -{ "parent": "block/thin_block", - "textures": { - "particle": "#texture" - }, - "elements": [ - { "from": [ 0, 0, 0 ], - "to": [ 16, 3, 16 ], - "faces": { - "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "cullface": "down" }, - "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture" }, - "north": { "uv": [ 16, 0, 13, 16 ], "texture": "#side", "cullface": "north", "rotation": 90 }, - "south": { "uv": [ 16, 0, 13, 16 ], "texture": "#side", "cullface": "south", "rotation": 90 }, - "west": { "uv": [ 16, 0, 13, 16 ], "texture": "#side", "cullface": "west", "rotation": 90 }, - "east": { "uv": [ 16, 0, 13, 16 ], "texture": "#side", "cullface": "east", "rotation": 90 } - } +{ + "parent": "block/thin_block", + "textures": { + "particle": "#texture" + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 3, + 16 + ], + "faces": { + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#texture", + "cullface": "down" + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#texture" + }, + "north": { + "uv": [ + 16, + 0, + 13, + 16 + ], + "texture": "#side", + "cullface": "north", + "rotation": 90 + }, + "south": { + "uv": [ + 16, + 0, + 13, + 16 + ], + "texture": "#side", + "cullface": "south", + "rotation": 90 + }, + "west": { + "uv": [ + 16, + 0, + 13, + 16 + ], + "texture": "#side", + "cullface": "west", + "rotation": 90 + }, + "east": { + "uv": [ + 16, + 0, + 13, + 16 + ], + "texture": "#side", + "cullface": "east", + "rotation": 90 } - ] + } + } + ] } diff --git a/src/main/resources/assets/bclib/models/block/taburet.json b/src/main/resources/assets/bclib/models/block/taburet.json new file mode 100644 index 00000000..6cf0818d --- /dev/null +++ b/src/main/resources/assets/bclib/models/block/taburet.json @@ -0,0 +1,433 @@ +{ + "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", + "parent": "block/block", + "textures": { + "particle": "#texture", + "top": "#texture" + }, + "elements": [ + { + "__comment": "Box1", + "from": [ + 2, + 8, + 2 + ], + "to": [ + 14, + 10, + 14 + ], + "faces": { + "down": { + "uv": [ + 2, + 2, + 14, + 14 + ], + "texture": "#top" + }, + "up": { + "uv": [ + 2, + 2, + 14, + 14 + ], + "texture": "#top" + }, + "north": { + "uv": [ + 2, + 6, + 14, + 8 + ], + "texture": "#texture" + }, + "south": { + "uv": [ + 2, + 6, + 14, + 8 + ], + "texture": "#texture" + }, + "west": { + "uv": [ + 2, + 6, + 14, + 8 + ], + "texture": "#texture" + }, + "east": { + "uv": [ + 2, + 6, + 14, + 8 + ], + "texture": "#texture" + } + } + }, + { + "__comment": "Box1", + "from": [ + 3, + 0, + 3 + ], + "to": [ + 5, + 8, + 5 + ], + "faces": { + "down": { + "uv": [ + 3, + 11, + 5, + 13 + ], + "texture": "#texture", + "cullface": "down" + }, + "north": { + "uv": [ + 11, + 8, + 13, + 16 + ], + "texture": "#texture" + }, + "south": { + "uv": [ + 3, + 8, + 5, + 16 + ], + "texture": "#texture" + }, + "west": { + "uv": [ + 3, + 8, + 5, + 16 + ], + "texture": "#texture" + }, + "east": { + "uv": [ + 11, + 8, + 13, + 16 + ], + "texture": "#texture" + } + } + }, + { + "__comment": "Box1", + "from": [ + 11, + 0, + 3 + ], + "to": [ + 13, + 8, + 5 + ], + "faces": { + "down": { + "uv": [ + 11, + 11, + 13, + 13 + ], + "texture": "#texture", + "cullface": "down" + }, + "north": { + "uv": [ + 3, + 8, + 5, + 16 + ], + "texture": "#texture" + }, + "south": { + "uv": [ + 11, + 8, + 13, + 16 + ], + "texture": "#texture" + }, + "west": { + "uv": [ + 3, + 8, + 5, + 16 + ], + "texture": "#texture" + }, + "east": { + "uv": [ + 11, + 8, + 13, + 16 + ], + "texture": "#texture" + } + } + }, + { + "__comment": "Box1", + "from": [ + 11, + 0, + 11 + ], + "to": [ + 13, + 8, + 13 + ], + "faces": { + "down": { + "uv": [ + 11, + 3, + 13, + 5 + ], + "texture": "#texture", + "cullface": "down" + }, + "north": { + "uv": [ + 3, + 8, + 5, + 16 + ], + "texture": "#texture" + }, + "south": { + "uv": [ + 11, + 8, + 13, + 16 + ], + "texture": "#texture" + }, + "west": { + "uv": [ + 11, + 8, + 13, + 16 + ], + "texture": "#texture" + }, + "east": { + "uv": [ + 3, + 8, + 5, + 16 + ], + "texture": "#texture" + } + } + }, + { + "__comment": "Box1", + "from": [ + 3, + 0, + 11 + ], + "to": [ + 5, + 8, + 13 + ], + "faces": { + "down": { + "uv": [ + 3, + 3, + 5, + 5 + ], + "texture": "#texture", + "cullface": "down" + }, + "north": { + "uv": [ + 11, + 8, + 13, + 16 + ], + "texture": "#texture" + }, + "south": { + "uv": [ + 3, + 8, + 5, + 16 + ], + "texture": "#texture" + }, + "west": { + "uv": [ + 11, + 8, + 13, + 16 + ], + "texture": "#texture" + }, + "east": { + "uv": [ + 3, + 8, + 5, + 16 + ], + "texture": "#texture" + } + } + }, + { + "__comment": "Box6", + "from": [ + 5, + 2, + 4 + ], + "to": [ + 11, + 3, + 5 + ], + "faces": { + "down": { + "uv": [ + 2, + 4, + 3, + 10 + ], + "texture": "#texture", + "rotation": 90 + }, + "up": { + "uv": [ + 2, + 4, + 3, + 10 + ], + "texture": "#texture", + "rotation": 90 + }, + "north": { + "uv": [ + 2, + 4, + 3, + 10 + ], + "texture": "#texture", + "rotation": 90 + }, + "south": { + "uv": [ + 2, + 4, + 3, + 10 + ], + "texture": "#texture", + "rotation": 90 + } + } + }, + { + "__comment": "Box6", + "from": [ + 5, + 2, + 11 + ], + "to": [ + 11, + 3, + 12 + ], + "faces": { + "down": { + "uv": [ + 5, + 4, + 6, + 10 + ], + "texture": "#texture", + "rotation": 90 + }, + "up": { + "uv": [ + 5, + 4, + 6, + 10 + ], + "texture": "#texture", + "rotation": 90 + }, + "north": { + "uv": [ + 5, + 4, + 6, + 10 + ], + "texture": "#texture", + "rotation": 90 + }, + "south": { + "uv": [ + 5, + 4, + 6, + 10 + ], + "texture": "#texture", + "rotation": 90 + } + } + } + ] +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/models/block/tint_cube.json b/src/main/resources/assets/bclib/models/block/tint_cube.json index f65b5c05..74bc9a32 100644 --- a/src/main/resources/assets/bclib/models/block/tint_cube.json +++ b/src/main/resources/assets/bclib/models/block/tint_cube.json @@ -1,21 +1,89 @@ { - "parent": "block/block", - "textures": { - "particle": "#texture" - }, - "elements": [ - { - "__comment": "Box1", - "from": [ 0, 0, 0 ], - "to": [ 16, 16, 16 ], - "faces": { - "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "cullface": "down", "tintindex": 0 }, - "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "cullface": "up", "tintindex": 0 }, - "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "cullface": "north", "tintindex": 0 }, - "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "cullface": "south", "tintindex": 0 }, - "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "cullface": "west", "tintindex": 0 }, - "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#texture", "cullface": "east", "tintindex": 0 } - } - } - ] + "parent": "block/block", + "textures": { + "particle": "#texture" + }, + "elements": [ + { + "__comment": "Box1", + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "faces": { + "down": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#texture", + "cullface": "down", + "tintindex": 0 + }, + "up": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#texture", + "cullface": "up", + "tintindex": 0 + }, + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#texture", + "cullface": "north", + "tintindex": 0 + }, + "south": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#texture", + "cullface": "south", + "tintindex": 0 + }, + "west": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#texture", + "cullface": "west", + "tintindex": 0 + }, + "east": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#texture", + "cullface": "east", + "tintindex": 0 + } + } + } + ] } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/models/item/test_hanging_sign.json b/src/main/resources/assets/bclib/models/item/test_hanging_sign.json new file mode 100644 index 00000000..862f84e6 --- /dev/null +++ b/src/main/resources/assets/bclib/models/item/test_hanging_sign.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "bclib:item/test_hanging_sign" + } +} diff --git a/src/main/resources/assets/bclib/models/item/test_sign.json b/src/main/resources/assets/bclib/models/item/test_sign.json new file mode 100644 index 00000000..ffe4af35 --- /dev/null +++ b/src/main/resources/assets/bclib/models/item/test_sign.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "bclib:item/test_sign" + } +} diff --git a/src/main/resources/assets/bclib/patterns/block/anvil.json b/src/main/resources/assets/bclib/patterns/block/anvil.json index 7710c9a1..a3356a02 100644 --- a/src/main/resources/assets/bclib/patterns/block/anvil.json +++ b/src/main/resources/assets/bclib/patterns/block/anvil.json @@ -1,68 +1,277 @@ { - "parent": "block/block", - "textures": { - "particle": "%modid%:block/%anvil%_front", - "front": "%modid%:block/%anvil%_front", - "back": "%modid%:block/%anvil%_back", - "top": "%modid%:block/%top%", - "panel": "%modid%:block/%anvil%_panel", - "bottom": "%modid%:block/%anvil%_bottom" - }, - "elements": [ - { - "__comment": "Bottom", - "from": [ 2, 0, 2 ], - "to": [ 14, 4, 14 ], - "faces": { - "down": { "uv": [ 2, 2, 14, 14 ], "texture": "#bottom", "cullface": "down" }, - "up": { "uv": [ 2, 2, 14, 14 ], "texture": "#panel" }, - "north": { "uv": [ 2, 12, 14, 16 ], "texture": "#back" }, - "south": { "uv": [ 2, 12, 14, 16 ], "texture": "#back" }, - "west": { "uv": [ 2, 12, 14, 16 ], "texture": "#front" }, - "east": { "uv": [ 2, 12, 14, 16 ], "texture": "#front" } - } - }, - { - "__comment": "Plate", - "from": [ 4, 4, 3 ], - "to": [ 12, 5, 13 ], - "faces": { - "up": { "uv": [ 4, 3, 12, 13 ], "texture": "#panel" }, - "north": { "uv": [ 4, 11, 12, 12 ], "texture": "#back" }, - "south": { "uv": [ 4, 11, 12, 12 ], "texture": "#back" }, - "west": { "uv": [ 3, 11, 13, 12 ], "texture": "#front" }, - "east": { "uv": [ 3, 11, 13, 12 ], "texture": "#front" } - } - }, - { - "__comment": "Support", - "from": [ 6, 5, 4 ], - "to": [ 10, 10, 12 ], - "faces": { - "north": { "uv": [ 6, 6, 10, 11 ], "texture": "#back" }, - "south": { "uv": [ 6, 6, 10, 11 ], "texture": "#back" }, - "west": { "uv": [ 4, 6, 12, 11 ], "texture": "#front" }, - "east": { "uv": [ 4, 6, 12, 11 ], "texture": "#front" } - } - }, - { - "__comment": "Top", - "from": [ 3, 10, 0 ], - "to": [ 13, 16, 16 ], - "faces": { - "down": { "uv": [ 3, 0, 13, 16 ], "texture": "#top" }, - "up": { "uv": [ 3, 0, 13, 16 ], "texture": "#top" }, - "north": { "uv": [ 3, 0, 13, 6 ], "texture": "#back" }, - "south": { "uv": [ 3, 0, 13, 6 ], "texture": "#back" }, - "west": { "uv": [ 0, 0, 16, 6 ], "texture": "#front" }, - "east": { "uv": [ 0, 0, 16, 6 ], "texture": "#front" } - } - } - ], - "display": { - "fixed": { - "rotation": [ 0, 90, 0 ], - "scale": [ 0.5, 0.5, 0.5 ] - } - } + "parent": "block/block", + "textures": { + "particle": "%modid%:block/%anvil%_front", + "front": "%modid%:block/%anvil%_front", + "back": "%modid%:block/%anvil%_back", + "top": "%modid%:block/%top%", + "panel": "%modid%:block/%anvil%_panel", + "bottom": "%modid%:block/%anvil%_bottom" + }, + "elements": [ + { + "__comment": "Bottom", + "from": [ + 2, + 0, + 2 + ], + "to": [ + 14, + 4, + 14 + ], + "faces": { + "down": { + "uv": [ + 2, + 2, + 14, + 14 + ], + "texture": "#bottom", + "cullface": "down" + }, + "up": { + "uv": [ + 2, + 2, + 14, + 14 + ], + "texture": "#panel" + }, + "north": { + "uv": [ + 2, + 12, + 14, + 16 + ], + "texture": "#back" + }, + "south": { + "uv": [ + 2, + 12, + 14, + 16 + ], + "texture": "#back" + }, + "west": { + "uv": [ + 2, + 12, + 14, + 16 + ], + "texture": "#front" + }, + "east": { + "uv": [ + 2, + 12, + 14, + 16 + ], + "texture": "#front" + } + } + }, + { + "__comment": "Plate", + "from": [ + 4, + 4, + 3 + ], + "to": [ + 12, + 5, + 13 + ], + "faces": { + "up": { + "uv": [ + 4, + 3, + 12, + 13 + ], + "texture": "#panel" + }, + "north": { + "uv": [ + 4, + 11, + 12, + 12 + ], + "texture": "#back" + }, + "south": { + "uv": [ + 4, + 11, + 12, + 12 + ], + "texture": "#back" + }, + "west": { + "uv": [ + 3, + 11, + 13, + 12 + ], + "texture": "#front" + }, + "east": { + "uv": [ + 3, + 11, + 13, + 12 + ], + "texture": "#front" + } + } + }, + { + "__comment": "Support", + "from": [ + 6, + 5, + 4 + ], + "to": [ + 10, + 10, + 12 + ], + "faces": { + "north": { + "uv": [ + 6, + 6, + 10, + 11 + ], + "texture": "#back" + }, + "south": { + "uv": [ + 6, + 6, + 10, + 11 + ], + "texture": "#back" + }, + "west": { + "uv": [ + 4, + 6, + 12, + 11 + ], + "texture": "#front" + }, + "east": { + "uv": [ + 4, + 6, + 12, + 11 + ], + "texture": "#front" + } + } + }, + { + "__comment": "Top", + "from": [ + 3, + 10, + 0 + ], + "to": [ + 13, + 16, + 16 + ], + "faces": { + "down": { + "uv": [ + 3, + 0, + 13, + 16 + ], + "texture": "#top" + }, + "up": { + "uv": [ + 3, + 0, + 13, + 16 + ], + "texture": "#top" + }, + "north": { + "uv": [ + 3, + 0, + 13, + 6 + ], + "texture": "#back" + }, + "south": { + "uv": [ + 3, + 0, + 13, + 6 + ], + "texture": "#back" + }, + "west": { + "uv": [ + 0, + 0, + 16, + 6 + ], + "texture": "#front" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 6 + ], + "texture": "#front" + } + } + } + ], + "display": { + "fixed": { + "rotation": [ + 0, + 90, + 0 + ], + "scale": [ + 0.5, + 0.5, + 0.5 + ] + } + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/barrel_open.json b/src/main/resources/assets/bclib/patterns/block/barrel_open.json index b81ad7da..65997fa5 100644 --- a/src/main/resources/assets/bclib/patterns/block/barrel_open.json +++ b/src/main/resources/assets/bclib/patterns/block/barrel_open.json @@ -1,8 +1,8 @@ { - "parent": "block/cube_bottom_top", - "textures": { - "bottom": "%modid%:block/%texture%_bottom", - "side": "%modid%:block/%texture%_side", - "top": "%modid%:block/%texture%_top_open" - } + "parent": "block/cube_bottom_top", + "textures": { + "bottom": "%modid%:block/%texture%_bottom", + "side": "%modid%:block/%texture%_side", + "top": "%modid%:block/%texture%_top_open" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/bars_post.json b/src/main/resources/assets/bclib/patterns/block/bars_post.json index 0dee124c..d3054451 100644 --- a/src/main/resources/assets/bclib/patterns/block/bars_post.json +++ b/src/main/resources/assets/bclib/patterns/block/bars_post.json @@ -1,22 +1,80 @@ { - "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", - "textures": { - "top": "%modid%:block/%texture%_top", - "particle": "#top" - }, - "elements": [ - { - "__comment": "Box1", - "from": [ 7, 0, 7 ], - "to": [ 9, 16, 9 ], - "faces": { - "down": { "uv": [ 7, 7, 9, 9 ], "texture": "#top", "cullface": "down" }, - "up": { "uv": [ 7, 7, 9, 9 ], "texture": "#top", "cullface": "up" }, - "north": { "uv": [ 7, 0, 9, 16 ], "texture": "#top" }, - "south": { "uv": [ 7, 0, 9, 16 ], "texture": "#top" }, - "west": { "uv": [ 7, 0, 9, 16 ], "texture": "#top" }, - "east": { "uv": [ 7, 0, 9, 16 ], "texture": "#top" } - } - } - ] + "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", + "textures": { + "top": "%modid%:block/%texture%_top", + "particle": "#top" + }, + "elements": [ + { + "__comment": "Box1", + "from": [ + 7, + 0, + 7 + ], + "to": [ + 9, + 16, + 9 + ], + "faces": { + "down": { + "uv": [ + 7, + 7, + 9, + 9 + ], + "texture": "#top", + "cullface": "down" + }, + "up": { + "uv": [ + 7, + 7, + 9, + 9 + ], + "texture": "#top", + "cullface": "up" + }, + "north": { + "uv": [ + 7, + 0, + 9, + 16 + ], + "texture": "#top" + }, + "south": { + "uv": [ + 7, + 0, + 9, + 16 + ], + "texture": "#top" + }, + "west": { + "uv": [ + 7, + 0, + 9, + 16 + ], + "texture": "#top" + }, + "east": { + "uv": [ + 7, + 0, + 9, + 16 + ], + "texture": "#top" + } + } + } + ] } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/bars_side.json b/src/main/resources/assets/bclib/patterns/block/bars_side.json index 44794f8e..4491dde2 100644 --- a/src/main/resources/assets/bclib/patterns/block/bars_side.json +++ b/src/main/resources/assets/bclib/patterns/block/bars_side.json @@ -1,23 +1,82 @@ { - "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", - "textures": { - "side": "%modid%:block/%texture%", - "top": "%modid%:block/%texture%_top", - "particle": "#side" - }, - "elements": [ - { - "__comment": "Box1", - "from": [ 7, 0, 0 ], - "to": [ 9, 16, 9 ], - "faces": { - "down": { "uv": [ 7, 7, 9, 16 ], "texture": "#top", "cullface": "down" }, - "up": { "uv": [ 7, 0, 9, 9 ], "texture": "#top", "cullface": "up" }, - "north": { "uv": [ 7, 0, 9, 16 ], "texture": "#side", "cullface": "north" }, - "south": { "uv": [ 7, 0, 9, 16 ], "texture": "#side" }, - "west": { "uv": [ 0, 0, 9, 16 ], "texture": "#side" }, - "east": { "uv": [ 7, 0, 16, 16 ], "texture": "#side" } - } - } - ] + "__comment": "Designed by Paulevs with Cubik Studio - https://cubik.studio", + "textures": { + "side": "%modid%:block/%texture%", + "top": "%modid%:block/%texture%_top", + "particle": "#side" + }, + "elements": [ + { + "__comment": "Box1", + "from": [ + 7, + 0, + 0 + ], + "to": [ + 9, + 16, + 9 + ], + "faces": { + "down": { + "uv": [ + 7, + 7, + 9, + 16 + ], + "texture": "#top", + "cullface": "down" + }, + "up": { + "uv": [ + 7, + 0, + 9, + 9 + ], + "texture": "#top", + "cullface": "up" + }, + "north": { + "uv": [ + 7, + 0, + 9, + 16 + ], + "texture": "#side", + "cullface": "north" + }, + "south": { + "uv": [ + 7, + 0, + 9, + 16 + ], + "texture": "#side" + }, + "west": { + "uv": [ + 0, + 0, + 9, + 16 + ], + "texture": "#side" + }, + "east": { + "uv": [ + 7, + 0, + 16, + 16 + ], + "texture": "#side" + } + } + } + ] } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/block.json b/src/main/resources/assets/bclib/patterns/block/block.json index 431bc5cf..928fa249 100644 --- a/src/main/resources/assets/bclib/patterns/block/block.json +++ b/src/main/resources/assets/bclib/patterns/block/block.json @@ -1,6 +1,6 @@ { - "parent": "block/cube_all", - "textures": { - "all": "%modid%:block/%texture%" - } + "parent": "block/cube_all", + "textures": { + "all": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/block_bottom_top.json b/src/main/resources/assets/bclib/patterns/block/block_bottom_top.json index 713f190b..2fb382c6 100644 --- a/src/main/resources/assets/bclib/patterns/block/block_bottom_top.json +++ b/src/main/resources/assets/bclib/patterns/block/block_bottom_top.json @@ -1,8 +1,8 @@ { - "parent": "block/cube_bottom_top", - "textures": { - "bottom": "%modid%:block/%texture%_bottom", - "side": "%modid%:block/%texture%_side", - "top": "%modid%:block/%texture%_top" - } + "parent": "block/cube_bottom_top", + "textures": { + "bottom": "%modid%:block/%texture%_bottom", + "side": "%modid%:block/%texture%_side", + "top": "%modid%:block/%texture%_top" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/block_colored.json b/src/main/resources/assets/bclib/patterns/block/block_colored.json index 7020afb1..3c625fe2 100644 --- a/src/main/resources/assets/bclib/patterns/block/block_colored.json +++ b/src/main/resources/assets/bclib/patterns/block/block_colored.json @@ -1,6 +1,6 @@ { - "parent": "bclib:block/tint_cube", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "bclib:block/tint_cube", + "textures": { + "texture": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/block_sided.json b/src/main/resources/assets/bclib/patterns/block/block_sided.json index 4dd385ac..e0f10712 100644 --- a/src/main/resources/assets/bclib/patterns/block/block_sided.json +++ b/src/main/resources/assets/bclib/patterns/block/block_sided.json @@ -1,12 +1,12 @@ { - "parent": "block/cube", - "textures": { - "particle": "%modid%:block/%particle%", - "down": "%modid%:block/%down%", - "up": "%modid%:block/%up%", - "north": "%modid%:block/%north%", - "south": "%modid%:block/%south%", - "west": "%modid%:block/%west%", - "east": "%modid%:block/%east%" - } + "parent": "block/cube", + "textures": { + "particle": "%modid%:block/%particle%", + "down": "%modid%:block/%down%", + "up": "%modid%:block/%up%", + "north": "%modid%:block/%north%", + "south": "%modid%:block/%south%", + "west": "%modid%:block/%west%", + "east": "%modid%:block/%east%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/bookshelf.json b/src/main/resources/assets/bclib/patterns/block/bookshelf.json index d8821c3d..f49dde6a 100644 --- a/src/main/resources/assets/bclib/patterns/block/bookshelf.json +++ b/src/main/resources/assets/bclib/patterns/block/bookshelf.json @@ -1,7 +1,7 @@ { - "parent": "block/cube_column", - "textures": { - "end": "%modid%:block/%texture%_planks", - "side": "%modid%:block/%texture%_bookshelf" - } + "parent": "block/cube_column", + "textures": { + "end": "%modid%:block/%texture%_planks", + "side": "%modid%:block/%texture%_bookshelf" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/button.json b/src/main/resources/assets/bclib/patterns/block/button.json index e09625cd..8160f1ff 100644 --- a/src/main/resources/assets/bclib/patterns/block/button.json +++ b/src/main/resources/assets/bclib/patterns/block/button.json @@ -1,6 +1,6 @@ { - "parent": "block/button", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/button", + "textures": { + "texture": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/button_pressed.json b/src/main/resources/assets/bclib/patterns/block/button_pressed.json index dfe04d40..04077fe6 100644 --- a/src/main/resources/assets/bclib/patterns/block/button_pressed.json +++ b/src/main/resources/assets/bclib/patterns/block/button_pressed.json @@ -1,6 +1,6 @@ { - "parent": "block/button_pressed", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/button_pressed", + "textures": { + "texture": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/chain.json b/src/main/resources/assets/bclib/patterns/block/chain.json index c108d99c..0c363f26 100644 --- a/src/main/resources/assets/bclib/patterns/block/chain.json +++ b/src/main/resources/assets/bclib/patterns/block/chain.json @@ -1,7 +1,7 @@ { - "parent": "block/chain", - "textures": { - "particle": "%modid%:block/%texture%", - "all": "%modid%:block/%texture%" - } + "parent": "block/chain", + "textures": { + "particle": "%modid%:block/%texture%", + "all": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/composter.json b/src/main/resources/assets/bclib/patterns/block/composter.json index cddf7792..f36ceec8 100644 --- a/src/main/resources/assets/bclib/patterns/block/composter.json +++ b/src/main/resources/assets/bclib/patterns/block/composter.json @@ -1,60 +1,154 @@ { - "parent": "block/block", - "textures": { - "particle": "%modid%:block/%texture%_side", - "top": "%modid%:block/%texture%_top", - "bottom": "%modid%:block/%texture%_bottom", - "side": "%modid%:block/%texture%_side", - "inside": "%modid%:block/%texture%_bottom" - }, - "elements": [ - { - "from": [ 0, 0, 0 ], - "to": [ 16, 2, 16 ], - "faces": { - "up": { "texture": "#inside", "cullface": "up" }, - "down": { "texture": "#bottom", "cullface": "down" } - } - }, - { - "from": [ 0, 0, 0 ], - "to": [ 2, 16, 16 ], - "faces": { - "up": { "texture": "#top", "cullface": "up" }, - "north": { "texture": "#side", "cullface": "north" }, - "south": { "texture": "#side", "cullface": "south" }, - "west": { "texture": "#side", "cullface": "west" }, - "east": { "texture": "#side", "cullface": "up" } - } - }, - { - "from": [ 14, 0, 0 ], - "to": [ 16, 16, 16 ], - "faces": { - "up": { "texture": "#top", "cullface": "up" }, - "north": { "texture": "#side", "cullface": "north" }, - "south": { "texture": "#side", "cullface": "south" }, - "west": { "texture": "#side", "cullface": "up" }, - "east": { "texture": "#side", "cullface": "east" } - } - }, - { - "from": [ 2, 0, 0 ], - "to": [ 14, 16, 2 ], - "faces": { - "up": { "texture": "#top", "cullface": "up" }, - "north": { "texture": "#side", "cullface": "north" }, - "south": { "texture": "#side", "cullface": "up" } - } - }, - { - "from": [ 2, 0, 14 ], - "to": [ 14, 16, 16 ], - "faces": { - "up": { "texture": "#top", "cullface": "up" }, - "north": { "texture": "#side", "cullface": "up" }, - "south": { "texture": "#side", "cullface": "south" } - } - } - ] + "parent": "block/block", + "textures": { + "particle": "%modid%:block/%texture%_side", + "top": "%modid%:block/%texture%_top", + "bottom": "%modid%:block/%texture%_bottom", + "side": "%modid%:block/%texture%_side", + "inside": "%modid%:block/%texture%_bottom" + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 2, + 16 + ], + "faces": { + "up": { + "texture": "#inside", + "cullface": "up" + }, + "down": { + "texture": "#bottom", + "cullface": "down" + } + } + }, + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 2, + 16, + 16 + ], + "faces": { + "up": { + "texture": "#top", + "cullface": "up" + }, + "north": { + "texture": "#side", + "cullface": "north" + }, + "south": { + "texture": "#side", + "cullface": "south" + }, + "west": { + "texture": "#side", + "cullface": "west" + }, + "east": { + "texture": "#side", + "cullface": "up" + } + } + }, + { + "from": [ + 14, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "faces": { + "up": { + "texture": "#top", + "cullface": "up" + }, + "north": { + "texture": "#side", + "cullface": "north" + }, + "south": { + "texture": "#side", + "cullface": "south" + }, + "west": { + "texture": "#side", + "cullface": "up" + }, + "east": { + "texture": "#side", + "cullface": "east" + } + } + }, + { + "from": [ + 2, + 0, + 0 + ], + "to": [ + 14, + 16, + 2 + ], + "faces": { + "up": { + "texture": "#top", + "cullface": "up" + }, + "north": { + "texture": "#side", + "cullface": "north" + }, + "south": { + "texture": "#side", + "cullface": "up" + } + } + }, + { + "from": [ + 2, + 0, + 14 + ], + "to": [ + 14, + 16, + 16 + ], + "faces": { + "up": { + "texture": "#top", + "cullface": "up" + }, + "north": { + "texture": "#side", + "cullface": "up" + }, + "south": { + "texture": "#side", + "cullface": "south" + } + } + } + ] } diff --git a/src/main/resources/assets/bclib/patterns/block/cross.json b/src/main/resources/assets/bclib/patterns/block/cross.json index deb3d1a1..279ec518 100644 --- a/src/main/resources/assets/bclib/patterns/block/cross.json +++ b/src/main/resources/assets/bclib/patterns/block/cross.json @@ -1,6 +1,6 @@ { - "parent": "block/cross", - "textures": { - "cross": "%modid%:block/%texture%" - } + "parent": "block/cross", + "textures": { + "cross": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/cross_shaded.json b/src/main/resources/assets/bclib/patterns/block/cross_shaded.json index 1db00d86..de2d9cd8 100644 --- a/src/main/resources/assets/bclib/patterns/block/cross_shaded.json +++ b/src/main/resources/assets/bclib/patterns/block/cross_shaded.json @@ -1,25 +1,93 @@ { - "ambientocclusion": false, - "textures": { - "cross": "%modid%:block/%texture%", - "particle": "#cross" - }, - "elements": [ - { "from": [ 0.8, 0, 8 ], - "to": [ 15.2, 16, 8 ], - "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": true }, - "faces": { - "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#cross" }, - "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#cross" } - } - }, - { "from": [ 8, 0, 0.8 ], - "to": [ 8, 16, 15.2 ], - "rotation": { "origin": [ 8, 8, 8 ], "axis": "y", "angle": 45, "rescale": true }, - "faces": { - "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#cross" }, - "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#cross" } - } - } - ] + "ambientocclusion": false, + "textures": { + "cross": "%modid%:block/%texture%", + "particle": "#cross" + }, + "elements": [ + { + "from": [ + 0.8, + 0, + 8 + ], + "to": [ + 15.2, + 16, + 8 + ], + "rotation": { + "origin": [ + 8, + 8, + 8 + ], + "axis": "y", + "angle": 45, + "rescale": true + }, + "faces": { + "north": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#cross" + }, + "south": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#cross" + } + } + }, + { + "from": [ + 8, + 0, + 0.8 + ], + "to": [ + 8, + 16, + 15.2 + ], + "rotation": { + "origin": [ + 8, + 8, + 8 + ], + "axis": "y", + "angle": 45, + "rescale": true + }, + "faces": { + "west": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#cross" + }, + "east": { + "uv": [ + 0, + 0, + 16, + 16 + ], + "texture": "#cross" + } + } + } + ] } diff --git a/src/main/resources/assets/bclib/patterns/block/door_bottom.json b/src/main/resources/assets/bclib/patterns/block/door_bottom.json index 0af8ff69..42857bbc 100644 --- a/src/main/resources/assets/bclib/patterns/block/door_bottom.json +++ b/src/main/resources/assets/bclib/patterns/block/door_bottom.json @@ -1,7 +1,7 @@ { - "parent": "bclib:block/sided_door_bottom", - "textures": { - "facade": "%modid%:block/%texture%_bottom", - "side": "%modid%:block/%texture%_side" - } + "parent": "bclib:block/sided_door_bottom", + "textures": { + "facade": "%modid%:block/%texture%_bottom", + "side": "%modid%:block/%texture%_side" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/door_bottom_hinge.json b/src/main/resources/assets/bclib/patterns/block/door_bottom_hinge.json index 7921c6b1..19ed9802 100644 --- a/src/main/resources/assets/bclib/patterns/block/door_bottom_hinge.json +++ b/src/main/resources/assets/bclib/patterns/block/door_bottom_hinge.json @@ -1,7 +1,7 @@ { - "parent": "bclib:block/sided_door_bottom_rh", - "textures": { - "facade": "%modid%:block/%texture%_bottom", - "side": "%modid%:block/%texture%_side" - } + "parent": "bclib:block/sided_door_bottom_rh", + "textures": { + "facade": "%modid%:block/%texture%_bottom", + "side": "%modid%:block/%texture%_side" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/door_top.json b/src/main/resources/assets/bclib/patterns/block/door_top.json index 1ca0cc7a..7e69ba35 100644 --- a/src/main/resources/assets/bclib/patterns/block/door_top.json +++ b/src/main/resources/assets/bclib/patterns/block/door_top.json @@ -1,7 +1,7 @@ { - "parent": "bclib:block/sided_door_top", - "textures": { - "facade": "%modid%:block/%texture%_top", - "side": "%modid%:block/%texture%_side" - } + "parent": "bclib:block/sided_door_top", + "textures": { + "facade": "%modid%:block/%texture%_top", + "side": "%modid%:block/%texture%_side" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/door_top_hinge.json b/src/main/resources/assets/bclib/patterns/block/door_top_hinge.json index 1dcbb7f7..172339a4 100644 --- a/src/main/resources/assets/bclib/patterns/block/door_top_hinge.json +++ b/src/main/resources/assets/bclib/patterns/block/door_top_hinge.json @@ -1,7 +1,7 @@ { - "parent": "bclib:block/sided_door_top_rh", - "textures": { - "facade": "%modid%:block/%texture%_top", - "side": "%modid%:block/%texture%_side" - } + "parent": "bclib:block/sided_door_top_rh", + "textures": { + "facade": "%modid%:block/%texture%_top", + "side": "%modid%:block/%texture%_side" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/empty.json b/src/main/resources/assets/bclib/patterns/block/empty.json index 618890a2..2a4d813b 100644 --- a/src/main/resources/assets/bclib/patterns/block/empty.json +++ b/src/main/resources/assets/bclib/patterns/block/empty.json @@ -1,5 +1,5 @@ { - "textures": { - "particle": "%modid%:block/%texture%" - } + "textures": { + "particle": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/fence_gate_closed.json b/src/main/resources/assets/bclib/patterns/block/fence_gate_closed.json index f3a97640..c68234e9 100644 --- a/src/main/resources/assets/bclib/patterns/block/fence_gate_closed.json +++ b/src/main/resources/assets/bclib/patterns/block/fence_gate_closed.json @@ -1,6 +1,6 @@ { - "parent": "block/template_fence_gate", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/template_fence_gate", + "textures": { + "texture": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/fence_gate_open.json b/src/main/resources/assets/bclib/patterns/block/fence_gate_open.json index 52396ef6..c3f9e51d 100644 --- a/src/main/resources/assets/bclib/patterns/block/fence_gate_open.json +++ b/src/main/resources/assets/bclib/patterns/block/fence_gate_open.json @@ -1,6 +1,6 @@ { - "parent": "block/template_fence_gate_open", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/template_fence_gate_open", + "textures": { + "texture": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/fence_post.json b/src/main/resources/assets/bclib/patterns/block/fence_post.json index 90719373..747eebc5 100644 --- a/src/main/resources/assets/bclib/patterns/block/fence_post.json +++ b/src/main/resources/assets/bclib/patterns/block/fence_post.json @@ -1,6 +1,6 @@ { - "parent": "block/fence_post", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/fence_post", + "textures": { + "texture": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/fence_side.json b/src/main/resources/assets/bclib/patterns/block/fence_side.json index 2569375e..dee5b275 100644 --- a/src/main/resources/assets/bclib/patterns/block/fence_side.json +++ b/src/main/resources/assets/bclib/patterns/block/fence_side.json @@ -1,6 +1,6 @@ { - "parent": "block/fence_side", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/fence_side", + "textures": { + "texture": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/furnace.json b/src/main/resources/assets/bclib/patterns/block/furnace.json index ee2fc463..a93f613b 100644 --- a/src/main/resources/assets/bclib/patterns/block/furnace.json +++ b/src/main/resources/assets/bclib/patterns/block/furnace.json @@ -1,9 +1,9 @@ { - "parent": "block/orientable_with_bottom", - "textures": { - "top": "%modid%:block/%top%", - "front": "%modid%:block/%front%", - "side": "%modid%:block/%side%", - "bottom": "%modid%:block/%top%" - } + "parent": "block/orientable_with_bottom", + "textures": { + "top": "%modid%:block/%top%", + "front": "%modid%:block/%front%", + "side": "%modid%:block/%side%", + "bottom": "%modid%:block/%top%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/furnace_glow.json b/src/main/resources/assets/bclib/patterns/block/furnace_glow.json index caa5ffdc..9f5ccc40 100644 --- a/src/main/resources/assets/bclib/patterns/block/furnace_glow.json +++ b/src/main/resources/assets/bclib/patterns/block/furnace_glow.json @@ -1,38 +1,87 @@ { - "parent": "block/block", - "textures": { - "top": "%modid%:block/%top%", - "front": "%modid%:block/%front%", - "side": "%modid%:block/%side%", - "glow": "%modid%:block/%glow%" - }, - "display": { - "firstperson_righthand": { - "rotation": [ 0, 135, 0 ], - "translation": [ 0, 0, 0 ], - "scale": [ 0.40, 0.40, 0.40 ] - } - }, - "elements": [ - { - "from": [ 0, 0, 0 ], - "to": [ 16, 16, 16 ], - "faces": { - "down": { "texture": "#top", "cullface": "down" }, - "up": { "texture": "#top", "cullface": "up" }, - "north": { "texture": "#front", "cullface": "north" }, - "south": { "texture": "#side", "cullface": "south" }, - "west": { "texture": "#side", "cullface": "west" }, - "east": { "texture": "#side", "cullface": "east" } - } - }, - { - "from": [ 0, 0, 0 ], - "to": [ 16, 16, 16 ], - "shade": false, - "faces": { - "north": { "texture": "#glow", "cullface": "north" } - } - } - ] + "parent": "block/block", + "textures": { + "top": "%modid%:block/%top%", + "front": "%modid%:block/%front%", + "side": "%modid%:block/%side%", + "glow": "%modid%:block/%glow%" + }, + "display": { + "firstperson_righthand": { + "rotation": [ + 0, + 135, + 0 + ], + "translation": [ + 0, + 0, + 0 + ], + "scale": [ + 0.40, + 0.40, + 0.40 + ] + } + }, + "elements": [ + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "faces": { + "down": { + "texture": "#top", + "cullface": "down" + }, + "up": { + "texture": "#top", + "cullface": "up" + }, + "north": { + "texture": "#front", + "cullface": "north" + }, + "south": { + "texture": "#side", + "cullface": "south" + }, + "west": { + "texture": "#side", + "cullface": "west" + }, + "east": { + "texture": "#side", + "cullface": "east" + } + } + }, + { + "from": [ + 0, + 0, + 0 + ], + "to": [ + 16, + 16, + 16 + ], + "shade": false, + "faces": { + "north": { + "texture": "#glow", + "cullface": "north" + } + } + } + ] } diff --git a/src/main/resources/assets/bclib/patterns/block/ladder.json b/src/main/resources/assets/bclib/patterns/block/ladder.json index 791c7a59..00b4c7cf 100644 --- a/src/main/resources/assets/bclib/patterns/block/ladder.json +++ b/src/main/resources/assets/bclib/patterns/block/ladder.json @@ -1,6 +1,6 @@ { - "parent": "bclib:block/ladder", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "bclib:block/ladder", + "textures": { + "texture": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/path.json b/src/main/resources/assets/bclib/patterns/block/path.json index 347121bb..2d09bd02 100644 --- a/src/main/resources/assets/bclib/patterns/block/path.json +++ b/src/main/resources/assets/bclib/patterns/block/path.json @@ -1,7 +1,8 @@ -{ "parent": "bclib:block/path", - "textures": { - "top": "%modid%:block/%top%", - "side": "%modid%:block/%side%", - "bottom": "%bottom%" - } +{ + "parent": "bclib:block/path", + "textures": { + "top": "%modid%:block/%top%", + "side": "%modid%:block/%side%", + "bottom": "%bottom%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/pillar.json b/src/main/resources/assets/bclib/patterns/block/pillar.json index 3585815c..5f86d018 100644 --- a/src/main/resources/assets/bclib/patterns/block/pillar.json +++ b/src/main/resources/assets/bclib/patterns/block/pillar.json @@ -1,7 +1,7 @@ { - "parent": "block/cube_column", - "textures": { - "end": "%modid%:block/%texture%_top", - "side": "%modid%:block/%texture%_side" - } + "parent": "block/cube_column", + "textures": { + "end": "%modid%:block/%texture%_top", + "side": "%modid%:block/%texture%_side" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/pressure_plate_down.json b/src/main/resources/assets/bclib/patterns/block/pressure_plate_down.json index 60147e84..3aa80561 100644 --- a/src/main/resources/assets/bclib/patterns/block/pressure_plate_down.json +++ b/src/main/resources/assets/bclib/patterns/block/pressure_plate_down.json @@ -1,6 +1,6 @@ { - "parent": "block/pressure_plate_down", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/pressure_plate_down", + "textures": { + "texture": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/pressure_plate_up.json b/src/main/resources/assets/bclib/patterns/block/pressure_plate_up.json index 66c028a3..b2c5c4c4 100644 --- a/src/main/resources/assets/bclib/patterns/block/pressure_plate_up.json +++ b/src/main/resources/assets/bclib/patterns/block/pressure_plate_up.json @@ -1,6 +1,6 @@ { - "parent": "block/pressure_plate_up", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/pressure_plate_up", + "textures": { + "texture": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/slab.json b/src/main/resources/assets/bclib/patterns/block/slab.json index 7012e909..16f1c32e 100644 --- a/src/main/resources/assets/bclib/patterns/block/slab.json +++ b/src/main/resources/assets/bclib/patterns/block/slab.json @@ -1,8 +1,8 @@ { - "parent": "block/slab", - "textures": { - "bottom": "%modid%:block/%texture%", - "side": "%modid%:block/%texture%", - "top": "%modid%:block/%texture%" - } + "parent": "block/slab", + "textures": { + "bottom": "%modid%:block/%texture%", + "side": "%modid%:block/%texture%", + "top": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/stairs.json b/src/main/resources/assets/bclib/patterns/block/stairs.json index af694555..c2922c02 100644 --- a/src/main/resources/assets/bclib/patterns/block/stairs.json +++ b/src/main/resources/assets/bclib/patterns/block/stairs.json @@ -1,8 +1,8 @@ { - "parent": "block/stairs", - "textures": { - "bottom": "%modid%:block/%texture%", - "side": "%modid%:block/%texture%", - "top": "%modid%:block/%texture%" - } + "parent": "block/stairs", + "textures": { + "bottom": "%modid%:block/%texture%", + "side": "%modid%:block/%texture%", + "top": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/stairs_inner.json b/src/main/resources/assets/bclib/patterns/block/stairs_inner.json index 30b7d9bf..c9a9a9c8 100644 --- a/src/main/resources/assets/bclib/patterns/block/stairs_inner.json +++ b/src/main/resources/assets/bclib/patterns/block/stairs_inner.json @@ -1,8 +1,8 @@ { - "parent": "block/inner_stairs", - "textures": { - "bottom": "%modid%:block/%texture%", - "side": "%modid%:block/%texture%", - "top": "%modid%:block/%texture%" - } + "parent": "block/inner_stairs", + "textures": { + "bottom": "%modid%:block/%texture%", + "side": "%modid%:block/%texture%", + "top": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/stairs_outer.json b/src/main/resources/assets/bclib/patterns/block/stairs_outer.json index 3544d10f..fc003d14 100644 --- a/src/main/resources/assets/bclib/patterns/block/stairs_outer.json +++ b/src/main/resources/assets/bclib/patterns/block/stairs_outer.json @@ -1,8 +1,8 @@ { - "parent": "block/outer_stairs", - "textures": { - "bottom": "%modid%:block/%texture%", - "side": "%modid%:block/%texture%", - "top": "%modid%:block/%texture%" - } + "parent": "block/outer_stairs", + "textures": { + "bottom": "%modid%:block/%texture%", + "side": "%modid%:block/%texture%", + "top": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/top_side_bottom.json b/src/main/resources/assets/bclib/patterns/block/top_side_bottom.json index 9fc15bd3..6d0f78fb 100644 --- a/src/main/resources/assets/bclib/patterns/block/top_side_bottom.json +++ b/src/main/resources/assets/bclib/patterns/block/top_side_bottom.json @@ -1,8 +1,8 @@ { - "parent": "block/cube_bottom_top", - "textures": { - "bottom": "%bottom%", - "side": "%side%", - "top": "%top%" - } + "parent": "block/cube_bottom_top", + "textures": { + "bottom": "%bottom%", + "side": "%side%", + "top": "%top%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/trapdoor.json b/src/main/resources/assets/bclib/patterns/block/trapdoor.json index b6309aa2..d3790068 100644 --- a/src/main/resources/assets/bclib/patterns/block/trapdoor.json +++ b/src/main/resources/assets/bclib/patterns/block/trapdoor.json @@ -1,7 +1,7 @@ { - "parent": "bclib:block/sided_trapdoor", - "textures": { - "texture": "%modid%:block/%texture%", - "side": "%modid%:block/%side%" - } + "parent": "bclib:block/sided_trapdoor", + "textures": { + "texture": "%modid%:block/%texture%", + "side": "%modid%:block/%side%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/vanilla_wood_bookshelf.json b/src/main/resources/assets/bclib/patterns/block/vanilla_wood_bookshelf.json new file mode 100644 index 00000000..f2305f1a --- /dev/null +++ b/src/main/resources/assets/bclib/patterns/block/vanilla_wood_bookshelf.json @@ -0,0 +1,7 @@ +{ + "parent": "block/cube_column", + "textures": { + "end": "minecraft:block/%texture%_planks", + "side": "%modid%:block/%texture%_bookshelf" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/block/wall_gate_closed.json b/src/main/resources/assets/bclib/patterns/block/wall_gate_closed.json index 1fa4886b..3d79dadd 100644 --- a/src/main/resources/assets/bclib/patterns/block/wall_gate_closed.json +++ b/src/main/resources/assets/bclib/patterns/block/wall_gate_closed.json @@ -1,6 +1,6 @@ { - "parent": "block/template_fence_gate_wall", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/template_fence_gate_wall", + "textures": { + "texture": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/block/wall_gate_open.json b/src/main/resources/assets/bclib/patterns/block/wall_gate_open.json index eac542cb..21ea0be4 100644 --- a/src/main/resources/assets/bclib/patterns/block/wall_gate_open.json +++ b/src/main/resources/assets/bclib/patterns/block/wall_gate_open.json @@ -1,6 +1,6 @@ { - "parent": "block/template_fence_gate_wall_open", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/template_fence_gate_wall_open", + "textures": { + "texture": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/item/pattern_block_item.json b/src/main/resources/assets/bclib/patterns/item/pattern_block_item.json index 81948061..b0e72110 100644 --- a/src/main/resources/assets/bclib/patterns/item/pattern_block_item.json +++ b/src/main/resources/assets/bclib/patterns/item/pattern_block_item.json @@ -1,6 +1,6 @@ { - "parent": "item/generated", - "textures": { - "layer0": "%modid%:block/%texture%" - } + "parent": "item/generated", + "textures": { + "layer0": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/item/pattern_button.json b/src/main/resources/assets/bclib/patterns/item/pattern_button.json index 7cc6b6e0..45928137 100644 --- a/src/main/resources/assets/bclib/patterns/item/pattern_button.json +++ b/src/main/resources/assets/bclib/patterns/item/pattern_button.json @@ -1,6 +1,6 @@ { - "parent": "block/button_inventory", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/button_inventory", + "textures": { + "texture": "%modid%:block/%texture%" + } } \ No newline at end of file diff --git a/src/main/resources/assets/bclib/patterns/item/pattern_chest.json b/src/main/resources/assets/bclib/patterns/item/pattern_chest.json index c80649b4..9372dc69 100644 --- a/src/main/resources/assets/bclib/patterns/item/pattern_chest.json +++ b/src/main/resources/assets/bclib/patterns/item/pattern_chest.json @@ -1,6 +1,6 @@ { - "parent": "bclib:block/chest_item", - "textures": { - "texture": "%modid%:entity/chest/%texture%" - } + "parent": "bclib:block/chest_item", + "textures": { + "texture": "%modid%:entity/chest/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/item/pattern_fence.json b/src/main/resources/assets/bclib/patterns/item/pattern_fence.json index 809d8540..4e191245 100644 --- a/src/main/resources/assets/bclib/patterns/item/pattern_fence.json +++ b/src/main/resources/assets/bclib/patterns/item/pattern_fence.json @@ -1,6 +1,6 @@ { - "parent": "block/fence_inventory", - "textures": { - "texture": "%modid%:block/%texture%" - } + "parent": "block/fence_inventory", + "textures": { + "texture": "%modid%:block/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/item/pattern_item_generated.json b/src/main/resources/assets/bclib/patterns/item/pattern_item_generated.json index 17ffcba3..07717627 100644 --- a/src/main/resources/assets/bclib/patterns/item/pattern_item_generated.json +++ b/src/main/resources/assets/bclib/patterns/item/pattern_item_generated.json @@ -1,6 +1,6 @@ { - "parent": "item/generated", - "textures": { - "layer0": "%modid%:item/%texture%" - } + "parent": "item/generated", + "textures": { + "layer0": "%modid%:item/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/item/pattern_item_handheld.json b/src/main/resources/assets/bclib/patterns/item/pattern_item_handheld.json index b2c71ca2..6742a9f3 100644 --- a/src/main/resources/assets/bclib/patterns/item/pattern_item_handheld.json +++ b/src/main/resources/assets/bclib/patterns/item/pattern_item_handheld.json @@ -1,6 +1,6 @@ { - "parent": "item/handheld", - "textures": { - "layer0": "%modid%:item/%texture%" - } + "parent": "item/handheld", + "textures": { + "layer0": "%modid%:item/%texture%" + } } diff --git a/src/main/resources/assets/bclib/patterns/item/pattern_item_spawn_egg.json b/src/main/resources/assets/bclib/patterns/item/pattern_item_spawn_egg.json index 765225c9..fb8b11e6 100644 --- a/src/main/resources/assets/bclib/patterns/item/pattern_item_spawn_egg.json +++ b/src/main/resources/assets/bclib/patterns/item/pattern_item_spawn_egg.json @@ -1,3 +1,3 @@ { - "parent": "item/template_spawn_egg" + "parent": "item/template_spawn_egg" } diff --git a/src/main/resources/assets/bclib/textures/block/test_planks.png b/src/main/resources/assets/bclib/textures/block/test_planks.png new file mode 100644 index 00000000..74eb6c9e Binary files /dev/null and b/src/main/resources/assets/bclib/textures/block/test_planks.png differ diff --git a/src/main/resources/assets/bclib/textures/entity/signs/hanging/test_wood.png b/src/main/resources/assets/bclib/textures/entity/signs/hanging/test_wood.png new file mode 100644 index 00000000..0e6815c2 Binary files /dev/null and b/src/main/resources/assets/bclib/textures/entity/signs/hanging/test_wood.png differ diff --git a/src/main/resources/assets/bclib/textures/entity/signs/test_wood.png b/src/main/resources/assets/bclib/textures/entity/signs/test_wood.png new file mode 100644 index 00000000..3b422fb4 Binary files /dev/null and b/src/main/resources/assets/bclib/textures/entity/signs/test_wood.png differ diff --git a/src/main/resources/assets/bclib/textures/gui/widgets.png b/src/main/resources/assets/bclib/textures/gui/widgets.png new file mode 100644 index 00000000..d7fa67c5 Binary files /dev/null and b/src/main/resources/assets/bclib/textures/gui/widgets.png differ diff --git a/src/main/resources/assets/bclib/textures/item/test_hanging_sign.png b/src/main/resources/assets/bclib/textures/item/test_hanging_sign.png new file mode 100644 index 00000000..62ce7875 Binary files /dev/null and b/src/main/resources/assets/bclib/textures/item/test_hanging_sign.png differ diff --git a/src/main/resources/assets/bclib/textures/item/test_sign.png b/src/main/resources/assets/bclib/textures/item/test_sign.png new file mode 100644 index 00000000..d40a5662 Binary files /dev/null and b/src/main/resources/assets/bclib/textures/item/test_sign.png differ diff --git a/src/main/resources/bclib.accesswidener b/src/main/resources/bclib.accesswidener new file mode 100644 index 00000000..013c5ba3 --- /dev/null +++ b/src/main/resources/bclib.accesswidener @@ -0,0 +1,42 @@ +accessWidener v1 named + +# Classes +accessible class net/minecraft/server/MinecraftServer$ReloadableResources +accessible class net/minecraft/world/level/levelgen/SurfaceRules$Context +accessible class net/minecraft/world/level/levelgen/SurfaceRules$Condition +accessible class net/minecraft/world/level/levelgen/SurfaceRules$SurfaceRule +accessible class net/minecraft/world/level/levelgen/SurfaceRules$LazyXZCondition +accessible class net/minecraft/world/level/levelgen/SurfaceRules$LazyCondition +accessible class net/minecraft/world/level/levelgen/SurfaceRules$SequenceRuleSource +accessible class net/minecraft/world/level/levelgen/presets/WorldPresets$Bootstrap +extendable class net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator +accessible class net/minecraft/core/registries/BuiltInRegistries$RegistryBootstrap +accessible class net/minecraft/world/level/levelgen/SurfaceRules$SequenceRuleSource +accessible class net/minecraft/server/dedicated/DedicatedServerProperties$WorldDimensionData +accessible class net/minecraft/client/resources/model/AtlasSet$AtlasEntry +extendable class net/minecraft/world/level/block/state/properties/WoodType +accessible class net/minecraft/world/level/levelgen/SurfaceRules$BiomeConditionSource +accessible class net/minecraft/world/level/levelgen/SurfaceRules$TestRuleSource + +#Methods +accessible method net/minecraft/world/level/storage/loot/LootPool ([Lnet/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer;[Lnet/minecraft/world/level/storage/loot/predicates/LootItemCondition;[Lnet/minecraft/world/level/storage/loot/functions/LootItemFunction;Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;)V +accessible method net/minecraft/world/entity/ai/village/poi/PoiTypes register (Lnet/minecraft/core/Registry;Lnet/minecraft/resources/ResourceKey;Ljava/util/Set;II)Lnet/minecraft/world/entity/ai/village/poi/PoiType; +accessible method net/minecraft/world/level/levelgen/SurfaceRules$SequenceRuleSource (Ljava/util/List;)V +accessible method net/minecraft/core/registries/BuiltInRegistries registerSimple (Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/core/registries/BuiltInRegistries$RegistryBootstrap;)Lnet/minecraft/core/Registry; +accessible method net/minecraft/world/entity/SpawnPlacements register (Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/entity/SpawnPlacements$Type;Lnet/minecraft/world/level/levelgen/Heightmap$Types;Lnet/minecraft/world/entity/SpawnPlacements$SpawnPredicate;)V +accessible method net/minecraft/world/level/block/state/properties/WoodType register (Lnet/minecraft/world/level/block/state/properties/WoodType;)Lnet/minecraft/world/level/block/state/properties/WoodType; +accessible method net/minecraft/world/level/levelgen/NoiseRouterData nether (Lnet/minecraft/core/HolderGetter;Lnet/minecraft/core/HolderGetter;)Lnet/minecraft/world/level/levelgen/NoiseRouter; +accessible method net/minecraft/world/level/levelgen/NoiseRouterData noNewCaves (Lnet/minecraft/core/HolderGetter;Lnet/minecraft/core/HolderGetter;Lnet/minecraft/world/level/levelgen/DensityFunction;)Lnet/minecraft/world/level/levelgen/NoiseRouter; +accessible method net/minecraft/world/level/levelgen/NoiseRouterData slideNetherLike (Lnet/minecraft/core/HolderGetter;II)Lnet/minecraft/world/level/levelgen/DensityFunction; +accessible method net/minecraft/tags/TagEntry elementOrTag ()Lnet/minecraft/util/ExtraCodecs$TagOrElementLocation; +accessible method net/minecraft/data/worldgen/biome/OverworldBiomes calculateSkyColor (F)I +accessible method net/minecraft/advancements/Advancement$Builder (Z)V +accessible method net/minecraft/world/level/block/Blocks ocelotOrParrot (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntityType;)Ljava/lang/Boolean; +accessible method net/minecraft/world/level/block/Blocks never (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/entity/EntityType;)Ljava/lang/Boolean; +accessible method net/minecraft/world/level/block/Blocks never (Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/BlockGetter;Lnet/minecraft/core/BlockPos;)Z +accessible method net/minecraft/world/level/levelgen/structure/pools/SinglePoolElement (Lcom/mojang/datafixers/util/Either;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool$Projection;)V +accessible method net/minecraft/world/level/levelgen/structure/pools/LegacySinglePoolElement (Lcom/mojang/datafixers/util/Either;Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/levelgen/structure/pools/StructureTemplatePool$Projection;)V + +#Fields +accessible field net/minecraft/world/entity/ai/village/poi/PoiTypes TYPE_BY_STATE Ljava/util/Map; +accessible field net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity lootTable Lnet/minecraft/resources/ResourceLocation; \ No newline at end of file diff --git a/src/main/resources/bclib.mixins.client.json b/src/main/resources/bclib.mixins.client.json index f36a59e9..cd8df283 100644 --- a/src/main/resources/bclib.mixins.client.json +++ b/src/main/resources/bclib.mixins.client.json @@ -1,14 +1,22 @@ { - "required": true, - "minVersion": "0.8", - "package": "ru.bclib.mixin.client", - "compatibilityLevel": "JAVA_8", - "client": [ - "EnchantingTableBlockMixin", - "BackgroundRendererMixin", - "ModelBakeryMixin" - ], - "injectors": { - "defaultRequire": 1 - } + "required": true, + "minVersion": "0.8", + "package": "org.betterx.bclib.mixin.client", + "compatibilityLevel": "JAVA_17", + "client": [ + "AnvilScreenMixin", + "AtlasSetMixin", + "BlockMixin", + "ClientLevelMixin", + "ClientPacketListenerMixin", + "ClientRecipeBookMixin", + "FogRendererMixin", + "LevelRendererMixin", + "MinecraftMixin", + "ModelManagerMixin", + "boat.BoatRendererMixin" + ], + "injectors": { + "defaultRequire": 1 + } } diff --git a/src/main/resources/bclib.mixins.common.json b/src/main/resources/bclib.mixins.common.json index 20e03d8d..420564a1 100644 --- a/src/main/resources/bclib.mixins.common.json +++ b/src/main/resources/bclib.mixins.common.json @@ -1,20 +1,49 @@ { - "required": true, - "minVersion": "0.8", - "package": "ru.bclib.mixin.common", - "compatibilityLevel": "JAVA_8", - "mixins": [ - "ComposterBlockAccessor", - "PotionBrewingAccessor", - "RecipeManagerAccessor", - "EnchantmentMenuMixin", - "MinecraftServerMixin", - "RecipeManagerMixin", - "BoneMealItemMixin", - "ServerLevelMixin", - "TagLoaderMixin" - ], - "injectors": { - "defaultRequire": 1 - } + "required": true, + "minVersion": "0.8", + "package": "org.betterx.bclib.mixin.common", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "AnvilBlockMixin", + "AnvilMenuMixin", + "BiomeGenerationSettingsAccessor", + "BiomeMixin", + "BoneMealItemMixin", + "ChunkGeneratorMixin", + "ComposterBlockAccessor", + "CraftingMenuMixin", + "DiggerItemMixin", + "EnchantingTableBlockMixin", + "ItemStackMixin", + "LayerLightSectionStorageMixin", + "LootPoolMixin", + "MobSpawnSettingsAccessor", + "PistonBaseBlockMixin", + "PoiTypeMixin", + "PortalShapeMixin", + "PotionBrewingAccessor", + "RecipeManagerMixin", + "RecipeMixin", + "RegistryDataLoaderMixin", + "ServerLevelMixin", + "ShovelItemAccessor", + "SurfaceRulesContextAccessor", + "WorldGenRegionMixin", + "boat.BoatItemMixin", + "boat.BoatMixin", + "boat.ChestBoatMixin", + "elytra.LivingEntityMixin", + "shears.BeehiveBlockMixin", + "shears.DiggingEnchantmentMixin", + "shears.ItemPredicateBuilderMixin", + "shears.MushroomCowMixin", + "shears.PumpkinBlockMixin", + "shears.SheepMixin", + "shears.SnowGolemMixin", + "shears.TripWireBlockMixin", + "signs.BlockEntityTypeMixin" + ], + "injectors": { + "defaultRequire": 1 + } } diff --git a/src/main/resources/data/bclib/config/recipes.json b/src/main/resources/data/bclib/config/recipes.json new file mode 100644 index 00000000..cad00c11 --- /dev/null +++ b/src/main/resources/data/bclib/config/recipes.json @@ -0,0 +1,5 @@ +{ + "disable": [ + "bclib:test_star" + ] +} \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index d4c9f8f1..35dc8cc9 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,43 +1,75 @@ { - "schemaVersion": 1, - "id": "bclib", - "version": "${version}", - - "name": "BCLib", - "description": "A library for BetterX team mods", - "authors": [ - "paulevs", - "Bulldog83" - ], - "contact": { - "homepage": "https://www.curseforge.com/minecraft/mc-mods/bclib", - "issues": "https://github.com/paulevsGitch/bclib/issues", - "sources": "https://github.com/paulevsGitch/bclib" - }, - - "license": "MIT", - "icon": "assets/bclib/icon.png", - - "environment": "*", - "entrypoints": { - "main": [ - "ru.bclib.BCLib" - ], - "client": [ - "ru.bclib.client.BCLibClient" - ], - "server": [ - "ru.bclib.server.BCLibServer" - ] - }, - "mixins": [ - "bclib.mixins.common.json", - "bclib.mixins.client.json" - ], - - "depends": { - "fabricloader": ">=0.11.0", - "fabric": ">=0.32.0", - "minecraft": ">=1.16.4" - } + "schemaVersion": 1, + "id": "bclib", + "version": "3.0.14", + "name": "BCLib", + "description": "A library for BetterX team mods", + "authors": [ + "Quiqueck", + "paulevs", + "Bulldog83" + ], + "contact": { + "homepage": "https://modrinth.com/mod/bclib", + "issues": "https://github.com/quiqueck/bclib/issues", + "sources": "https://github.com/quiqueck/bclib" + }, + "license": "MIT (CC BY-NC-SA 4.0 for some Assets)", + "icon": "assets/bclib/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "org.betterx.bclib.BCLib" + ], + "client": [ + "org.betterx.bclib.client.BCLibClient" + ], + "server": [ + "org.betterx.bclib.server.BCLibServer" + ], + "modmenu": [ + "org.betterx.bclib.integration.modmenu.ModMenuEntryPoint" + ], + "emi": [ + "org.betterx.bclib.integration.emi.EMIPlugin" + ], + "fabric-datagen": [ + "org.betterx.datagen.bclib.BCLibDatagen" + ] + }, + "accessWidener": "bclib.accesswidener", + "mixins": [ + "together.mixins.common.json", + "together.mixins.client.json", + "ui.mixins.client.json", + "bclib.mixins.common.json", + "bclib.mixins.client.json" + ], + "depends": { + "fabricloader": ">=0.14.21", + "fabric": ">=0.83.0", + "minecraft": [ + "1.20", + "1.20.1" + ], + "wunderlib": "1.2.x" + }, + "breaks": { + "wunderlib": "<1.1.2", + "emi": "<1.0.0", + "fabric-api": "0.86.0" + }, + "custom": { + "bclib": { + "downloads": { + "modrinth": "https://modrinth.com/mod/bclib", + "curseforge": "https://www.curseforge.com/minecraft/mc-mods/bclib" + } + }, + "modmenu": { + "links": { + "title.link.bclib.discord": "https://discord.gg/kYuATbYbKW" + } + } + } } diff --git a/src/main/resources/together.mixins.client.json b/src/main/resources/together.mixins.client.json new file mode 100644 index 00000000..1242053e --- /dev/null +++ b/src/main/resources/together.mixins.client.json @@ -0,0 +1,13 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.betterx.worlds.together.mixin.client", + "compatibilityLevel": "JAVA_17", + "client": [ + "CreateWorldScreen_Mixin", + "WorldOpenFlowsMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/resources/together.mixins.common.json b/src/main/resources/together.mixins.common.json new file mode 100644 index 00000000..ab8facaf --- /dev/null +++ b/src/main/resources/together.mixins.common.json @@ -0,0 +1,17 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.betterx.worlds.together.mixin.common", + "compatibilityLevel": "JAVA_17", + "mixins": [ + "DiggerItemAccessor", + "MainMixin", + "MinecraftServerMixin", + "TagLoaderMixin", + "WorldLoaderMixin", + "WorldStem_Mixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/resources/ui.mixins.client.json b/src/main/resources/ui.mixins.client.json new file mode 100644 index 00000000..264f5a58 --- /dev/null +++ b/src/main/resources/ui.mixins.client.json @@ -0,0 +1,10 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.betterx.ui.mixin.client", + "compatibilityLevel": "JAVA_17", + "client": [], + "injectors": { + "defaultRequire": 1 + } +}